1. 函数的概念
在C语言中,函数是一段可以重复使用的代码块。它有一个名字,用于完成特定的任务,比如计算一个数学表达式的值或者实现某种数据处理。
函数可以接收数据(通过参数传递),也可以返回一个值。例如, int add(int a, int b) 是一个函数声明,它的名字是 add ,接收两个 int 类型的参数 a 和 b ,并且返回一个 int 类型的值。当你在程序中需要计算两个整数相加的结果时,就可以调用这个函数,而不用每次都写一遍相加的代码。
2. 库函数
在C语言中,函数是由C语言标准库提供的函数。
这些函数预先编写好并经过测试,涵盖多种功能。比如 stdio.h 头文件里的 printf 函数,它用于格式化输出数据到控制台, scanf 函数用于从标准输入设备读取数据; math.h 头文件中有 sqrt 函数,能计算一个数的平方根。
库函数可以极大地提高编程效率,程序员不用自己编写代码去实现复杂的底层操作,只需要调用这些函数并按照要求传递参数就能实现对应的功能。
如何使用库函数?↓链接
3. ⾃定义函数
在C语言中,自定义函数是由程序员自己编写的函数,用于完成特定的任务。
定义一个自定义函数的基本形式是:返回值类型 函数名(参数列表) {函数体}。例如,定义一个计算两个整数之和的函数:
int add(int a, int b) {
return a + b;
}
在这个例子中, add 是函数名, int 是返回值类型,表示这个函数会返回一个整数。 (int a, int b) 是参数列表, a 和 b 是函数的参数,它们用于接收传入的数据。 {return a + b;} 是函数体,里面是实现函数功能的代码,这里是计算 a 和 b 的和并返回结果。
调用自定义函数和调用库函数类似,在需要的地方写函数名和传入相应的参数即可。例如
#include <stdio.h>
int main() {
int num1 = 3, num2 = 5;
int sum = add(num1, num2);
printf("两数之和为 %d", sum);
return 0;
}
在 main 函数中, add(num1, num2) 就是调用自定义的 add 函数,并把 num1 和 num2 的值传递进去,将函数返回的结果存储在 sum 变量中,再通过 printf 函数输出。
4. 形参和实参
例如,定义一个函数来计算两个数的乘积
int multiply(int a, int b) {
return a * b;
}
在这个函数定义中, a 和 b 就是形参。它们就像是函数内部定义的变量,用来接收外部传入的数据。当调用这个函数时:
int main() {
int x = 3;
int y = 4;
int result = multiply(x, y);
// 这里x和y是实参
return 0;
}
在 main 函数中调用 multiply 函数, x 和 y 就是实参。实参 x 的值 3 会被传递给形参 a ,实参 y 的值 4 会被传递给形参 b ,函数根据这些传入的值(也就是形参接收的值)进行计算,最后返回结果。
5. 函数的调用
1. 传值调用
定义:在传值调用中,函数被调用时,实参的值被复制一份传递给形参。在函数内部,形参是实参的一份临时拷贝,修改形参不会影响到实参。
例子:
#include <stdio.h>
void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
printf("在swap函数中,a = %d, b = %d\n", a, b);
}
int main() {
int num1 = 3, num2 = 5;
printf("在main函数中,交换前num1 = %d, num2 = %d\n", num1, num2);
swap(num1, num2);
printf("在main函数中,交换后num1 = %d, num2 = %d\n", num1, num2);
return 0;
}
在这个例子中, swap 函数试图交换两个数的值。在 main 函数中调用 swap 函数时, num1 和 num2 的值被复制给 swap 函数中的 a 和 b 。在 swap 函数内部, a 和 b 的值被交换了,但是这种交换不会影响 main 函数中的 num1 和 num2 ,因为 a 和 b 是 num1 和 num2 的副本。
2. 传址调用
定义:在传址调用中,实参是一个地址(如变量的地址),函数被调用时,这个地址被传递给形参。这样形参和实参指向同一块内存空间,在函数内部对形参所指向的内存空间进行修改,就会影响到实参的值。
例子:
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
printf("在swap函数中,*a = %d, *b = %d\n", *a, *b);
}
int main() {
int num1 = 3, num2 = 5;
printf("在main函数中,交换前num1 = %d, num2 = %d\n", num1, num2);
swap(&num1, &num2);
printf("在main函数中,交换后num1 = %d, num2 = %d\n", num1, num2);
return 0;
}
这里 swap 函数的参数是指针类型( int *a 和 int *b )。在 main 函数中调用 swap 函数时,传递的是 num1 和 num2 的地址( &num1 和 &num2 )。这样, swap 函数中的 *a 和 *b 就分别指向 main 函数中的 num1 和 num2 所占据的内存空间,对 *a 和 *b 的修改会直接影响 num1 和 num2 的值。
6.函数的嵌套调用和链式访问
(函数可以嵌套调用,但是不能嵌套定义。)
6.1嵌套调用
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
函数嵌套调用的例子:
#include <stdio.h>
// 函数add用于计算两个数的和
int add(int a, int b) {
return a + b;
}
// 函数calculate用于先计算两个数的和,然后再加10
int calculate(int x, int y) {
int sum = add(x, y);
return sum + 10;
}
int main() {
int num1 = 5;
int num2 = 3;
int result = calculate(num1, num2);
printf("结果是: %d\n", result);
return 0;
}
↑代码 解释:
- 首先定义了一个函数 add ,它接受两个整数参数 a 和 b ,并返回它们的和。
- 然后定义了函数 calculate ,这个函数接受两个整数参数 x 和 y 。在 calculate 函数内部,它调用了 add 函数来计算 x 和 y 的和,并将结果存储在 sum 变量中。接着, calculate 函数将 sum 的值再加10,并返回这个结果。
- 在 main 函数中,定义了 num1 和 num2 两个变量,然后调用 calculate 函数,将 num1 和 num2 作为参数传递进去。最后,将 calculate 函数返回的结果存储在 result 变量中,并通过 printf 函数输出结果。
6.2链式访问
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//结果是啥?
//printf函数的返回值是打印在屏幕上字符的个数
return 0;
}
//结果是4321
7.函数的声明和定义
7.1.1函数声明
函数声明是一种告诉编译器函数的基本信息(返回值类型、函数名和参数类型)的语句。它的形式一般为“返回值类型 函数名(参数类型列表);”。例如,有一个函数用于计算两个整数相加,函数声明可以写成 int add(int a, int b); 。
7.1.2 为什么要声明函数?
假设没有函数声明,看下面的代码:
#include <stdio.h>
int main() {
int result = add(3, 5);
printf("结果是 %d", result);
return 0;
}
int add(int a, int b) {
return a + b;
}
当编译器编译 main 函数中的 add(3, 5) 时,它还没看到 add 函数的定义,不知道 add 函数需要什么样的参数和返回什么类型的值。这可能会导致编译器产生警告或者错误。
加上函数声明后:
#include <stdio.h>
int add(int a, int b); //函数声明
int main() {
int result = add(3, 5);
printf("结果是 %d", result);
return 0;
}
int add(int a, int b) {
return a + b;
}
有了函数声明,编译器就能找得到add函数,这样在编译 main 函数中的 add 函数调用部分时,就可以检查参数类型和数量是否正确,避免一些潜在的错误,并且也让代码结构更加清晰,方便阅读者理解函数的输入和输出情况。
7.2.1 函数的定义
函数定义是对函数功能的具体实现,包括函数头和函数体。函数头说明了函数的返回值类型、函数名和参数列表;函数体则是用花括号括起来的一组语句,用于完成特定的操作。
7.2.2 举例
以下是一个简单的函数定义,用于计算两个整数的和:
int add(int a, int b) {
return a + b;
}
在这个例子中:
int 是返回值类型,表示这个函数执行完后会返回一个整数。
{return a + b;} 是函数体,这里只有一条语句,它计算 a 和 b 的和并返回结果。当在程序的其他地方调用 add 函数时,就会执行这个函数体中的语句来得到两个数的和。
8. 数组做函数参数
1. 值传递(传递数组名)
当把数组名作为参数传递给函数时,实际上传递的是数组的首地址,这属于传址调用。例如:
#include <stdio.h>
void printArray(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
int main() {
int myArray[] = {1, 2, 3, 4, 5};
int size = sizeof(myArray)/sizeof(myArray[0]);
printArray(myArray, size);
return 0;
}
这里 printArray 函数的参数 arr 接收了 myArray 的首地址,函数通过这个地址访问数组元素,所以在函数内部可以操作并输出数组的内容。这种方式使得函数能够对原始数组进行操作,而不是副本。
2. 数组大小的不确定性
在函数参数中, int arr[] 这样的声明方式,编译器不会检查数组的实际大小。例如:
void wrongFunction(int arr[]) {
// 编译器不会检查数组大小,可能导致越界访问
printf("%d", arr[10]);
}
所以在写函数时,通常需要另外传递一个表示数组大小的参数,像前面 printArray 函数中的 size 参数,以避免数组越界访问等错误。