### 函数的定义 C语言程序中可以用分组的方式管理语句。 每个语句分组叫做一个函数。 多函数程序执行模式的特点: * 整个程序的执行时间被分解成几段,每段时间被分给一个函数使用。 * 任何两个时间段不能互相重叠,并且所有时间段必须连续。 * 如果函数A在工作过程中把一段时间分配给函数B使用,则函数B在使用完所有时间之后必须把后面的时候还给函数A。 函数调用关系是有时间范围的,只有被调用函数工作期间函数调用关系才存在。 在程序中使用函数调用语句产生调用关系。 C语言中不可以跨函数使用变量。 不同函数里的变量是可以重名的。 如果一个函数多次运行,则每次运行的时候内部变量对应的存储区有可能不同。 volatile关键字可以用来声明变量,使用这个关键字声明的变量每次拿数的时候都可以得到存储区里真正的数字。 函数调用过程中通常伴随着两个函数之间的数据传递。 数据传递存在两个完全不同的方向,有可能从调用函数向被调用函数传递,也有可能从被调用函数向调用函数传递。 任何一个方向的数据传递都需要使用被调用函数提供的存储区。 /* 函数演示 */ #include <stdio.h> void print(void) { printf("1\n"); } int main() { print(); return 0; } ### 函数的返回值 只能从被调用函数向调用函数传递一个数据,并且只能在被调用函数结束的时候才能传递这个数据,这个数据叫被调用函数的返回值。 函数的返回值必须记录在被调用函数提供的一个专门的存储区里,应该把这个存储区的类型名称写在函数名称前。 /* 函数返回值演示 */ #include <stdio.h> int read(void) { int num = 0; printf("请输入一个数字:"); scanf("%d", &num); return num; } int main() { printf("read() = %d\n", read()); return 0; } 调用函数直接把函数调用语句当成数字使用就可以得到被调用函数的返回值。 如果被调用函数里没有使用`return`关键字指定返回值,则存放返回值的存储区里的内容是随机的,并且这个存储区不能用来长期存放数字,调用函数得到返回值以后或者立刻使用或者转存到其它存储区里。 函数名称前些void表示函数没有提供用来存放返回值的存储区。 函数名称前不能什么都不写。 被调用函数不能使用数组存放返回值。 ### 函数的参数 调用函数可以一次向被调用函数传递多个不同类型的数据,被调用函数需要提供多个存储区存放这些数据。 **形式参数** 可以用一组变量表示这些存储区,这组变量必须声明在函数名称后面的小括号里,这些变量叫做形式参数,小括号里的内容统称为形式参数列表。 形式参数列表是用逗号连接的多个变量的声明语句,每个形式参数的类型名称都不可以省略。 **实际参数** 函数调用语句里需要为每个形式参数提供一个对应的数据,这些数据叫实际参数。 函数调用的时候计算机把实际参数记录到对应的形式参数里,被调用函数可以从形式参数里获得实际参数的数值。 /* 函数演示 */ #include <stdio.h> void print(int num, int num1) { printf("%d %d\n", num, num1); } int main() { int num = 0, num1 = 0; printf("请输入两个整数:"); scanf("%d%d", &num, &num1); print(num, num1); return 0; } 所有能当数字使用的内容,都可以当做实际参数使用。 函数名称后面的小括号里写void表示函数不需要从调用函数得到任何数字,就不会给参数分配存储区。 如果小括号里什么都没写表示函数可以提供任意多个任意类型的形式参数。 **数组形式参数** 数组可以作为形式参数使用。 /* 数组形式参数演示 */ #include <stdio.h> void print(int arr[]) { int num = 0; for (num = 0;num <= 4;num++){ printf("%d ", arr[num]); } printf("\n"); } int main() { int arr[] = {1, 2, 3, 4, 5}; print(arr); return 0; } 数组做形式参数的时候只是把形式参数写成数组的样子,但真正的形式参数不是数组。 数组形式参数包含的所有存储区都不是被调用函数提供的。 数组形式参数声明的时候可以省略中括号里面的数字。 使用数组做形式参数的时候必须使用一个整数类型参数表示数组里的存储区个数。 /* 数组形式参数演示 */ #include <stdio.h> void print(int arr[], int size) { int num = 0; for (num = 0;num <= size - 1;num++){ printf("%d ", arr[num]); } printf("\n"); } int main() { int arr[] = {1, 2, 3, 4, 5}; print(arr, 5); return 0; } 使用数组作为形式参数可以让被调用函数使用其他函数提供的存储区。 **输入输出参数** 可以利用数组形式参数实现双向数据传递,这种参数叫输入输出参数。 **变长参数** C语言里允许函数的参数个数不固定,这种参数叫变长参数。 变长参数不能再编写函数的时候命名,被调用函数里需要使用特殊方法才能获得这些参数里的内容。 如果编译器在编译的时候首先遇到函数调用语句就会猜测函数的格式,这个猜测结果里函数用一个整数类型的存储区记录返回值,函数可以有任意多个不确定类型的形式参数。 ### 函数的声明 **隐式声明** 这个猜测的结果叫函数的隐式声明。 隐式声明里函数的参数类型只能是整数类型或者双精度浮点类型。 如果隐式声明和函数的实际格式不一致,则会编译失败。 **显示声明** 函数大括号前面的部分可以单独协程一条语句,这种语句叫函数声明语句。 函数声明语句里可以省略形式参数名称。 把函数声明语句单独写在文件的开头叫做函数的显示声明。 显示声明可以避免隐式声明出现的问题。 除了main主函数,其他的函数都应该使用显示声明的方式,声明函数。 /* 显示声明演示 */ #include <stdio.h> double add(double, double); int main() { int num = add(5, 8); printf("num = %d\n", num); return 0; } double add(double num, double num1) { return num + num1; } ### 递归函数 C语言里允许自己调用自己。 这种可以调用自己的函数叫做递归函数。 如果一个问题可以分解成多个小问题,至少一个小问题和原来的问题本质上一样,只是稍微简单一点,具有这种特征的问题就适合采用递归函数。 递归函数编写步骤: * 编写语句解决分解后的每个小问题。 * 在函数的开头编写分支处理不可分解的情况。(这个分支必须保证函数可以结束) 递归函数例子: /* 递归函数演示 */ #include <stdio.h> void print(int num) { if (num == 1){ printf("1 "); return ; } print(num - 1); printf("%d ", num); } int main(){ print(10); printf("\n"); return 0; } 检验递归函数的时候首先用最简单的参数测试,然后逐渐把参数变复杂,如果这个过程可以持续下去就说明函数是正确的。 采用递归函数解决问题的思路叫递归。 采用循环解决类似问题的思路叫递推。