一篇文章带你吃透函数
函数简言之就是实现某种特定功能的一段代码。函数分为库函数和自定义函数。函数具有以下特点1.封装性。 2.复用性。一、 库函数库函数是一些已经配置好功能的一些函数根据自己的现实需要来选择库函数。因为库函数是在标准头文件中声明的所以在使用库函数时应该加上对应的头文件。否则程序运行时会不认得这函数。库函数的学习工具https://legacy.cplusplus.com/二、 自定义函数库函数的功能不能满足功能的实现所以就需要自己通过写代码来实现这个功能将它包装成函数这样的函数就叫做自定义函数。2.1 结构ret_type fun_name(形式参数)//函数头 { …………//(函数体) }函数头包括函数名参数返回类型。函数名尽量有意义。参数这里的参数叫做形式参数。在调用函数时传过去的参数叫做实参。返回类型根据函数返回的数据类型来确定函数返回类型。函数返回类型不写的话会默认为int 类型。函数体就是自己实现功能的代码部分。函数体里面有一个return语句用来退出函数并返回数据。return 语句后面可以是表达式也可以是函数值。如果函数的返回类型为void类型不返回任何参数则无需return 语句也可以直接写为 return ;在if-else语句中应保证每种情况都有返回。红框框圈住的是我们自定义函数那里的参数为形参。蓝框框圈住的是函数调用部分即我们要使用这个函数括号里的是实参就是我们传给数据然后形参收到数据之后用这些数据进行计算。要保证函数的形参和实参数值和个数要相对应。2.2 实参和形参的关系这是两形参的地址。这是两实参的地址。可以发现实参和形参虽然数值相同但是地址并不同。结论形参是实参的一份临时拷贝。为啥叫临时拷贝呢因为当函数调用结束后就会销毁形参形参的改变并不会影响是实参的改变。2.3、 数组作为函数参数数组作为参数定义数组是什么类型那么参数就是什么类型。参中可以不写数组大小只写框框。实参中传数组应该只写数组名数组传参实际上是地址传参因此形参不会创建新数组形参和实参使用的是同一个数组。二维数组类似只不过二维数组行可以省略但是列不可以省略。练习数组内容全部置为-1再打印数组。#include stdio.h void arr_value(int arr[10], int len) { int i 0; for (i 0; i len; i) { arr[i] -1; } } void print_arr(int arr[], int len) { for (int i 0; i len; i) { printf(%d , arr[i]); } } int main() { int arr1[10] {1}; arr_value(arr1, 10); print_arr(arr1, 10); return 0; }三、 函数的嵌套调用函数嵌套就是函数里又再次调用了另一个函数。练习计算某年某月有多少天#include stdio.h #include stdbool.h bool is_leap_year(int year) { if ((year % 4 0 year % 100 ! 0) || (year % 400 0)) { return true; } else { return false; } } int get_days(int year,int month) { int days[13] { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month 2) { if (is_leap_year(year)) { return 29; } else { return 28; } } else { return days[month]; } } int main() { int month 0; int year 0; scanf(%d %d,year, month); int daysget_days(year,month); printf(%d\n, days); return 0; }四、 函数的链式访问链式访问就是一个函数的返回值作为另一个函数的参数。#include stdio.h #include string.h int main() { char arr[] abcdef; printf(%zu\n, strlen(arr)); return 0; }五、 函数的声明和定义5.1 单个文件函数调用使用函数的过程相当于用函数的窗口。函数定义就是自己定义函数头函数名参数返回类型以及函数体。函数声明当函数调用时程序会从函数调用的地方向上寻找这个函数如果我们将函数写到下方那么就会找不到所以就需要函数声明告诉程序我有这个函数。函数声明中的参数可以只写数据类型省略变量。5.2 多个文件当我们在做一个项目时我们往往会使用多个文件一个是用来函数声明的头文件.h一个是用来函数定义的文件.c,另一个是用来函数调用的测试文件.c。但是在函数调用前要加上头文件如“Add.h”,这是包含自己创建的头文件。而 是库函数的头文件如stdio.h。5.3 static 与 extern5.3.1 作用域与生命周期作用域创建的变量可用的代码范围生命周期变量从创建到被销毁的时间段。局部变量作用域仅能在变量所被创建的代码块用{ }括住的代码中有效一旦超出程序将显示未定义。生命周期进入作用域局部变量被创建生命周期开始出去作用域生命周期结束。全局变量作用域整个工程但是要记住用extern声明一下不在同一个文件。生命周期程序开始生命周期开始程序结束生命周期结束。5.3 externextern是用来声明外部符号的。5.3.3 static1.修饰局部变量//代码1 #include stdio.h void test() { int i 0; i; printf(%d , i);//1 1 1 1 1 } int main() { int i 0; for(i0; i5; i) { test(); } return 0; } //代码2 #include stdio.h void test() { //static修饰局部变量 static int i 0; i; printf(%d , i); //1 2 3 4 5 } int main() { int i 0; for(i0; i5; i) { test(); } return 0; }内存中用栈区堆区以及静态区。栈区用来存放局部变量和函数参数。堆区用来动态内存申请。静态区用来存放全局变量和静态变量。当用static来修饰局部变量局部变量称为静态变量进入静态区调用函数函数销毁但是静态变量并不销毁仍保持不变并且在下一次调用时继续接着上一次的值进行计算。结论static修饰局部变量作用域不变延长生命周期存储类型改变栈区-静态区。2.修饰全局变量结论全局变量原本具有外部链接属性即可以用extern声明就可以在同工程的其他文件进行使用。但是全局变量一旦被static修饰外部链接属性就会变为内部即使有声明也不能使用。只能在本源文件中使用。3.修饰函数结论与全局变量类似函数也具有外部链接属性。函数一旦被static修饰就仅能在本源文件中使用。六、 函数递归函数递归是指函数自己调用自己。递归递推和回归。递归的思想就是大事化小的思想将大问题逐渐拆解成一个个小问题。6.1 递归的限制条件递归存在限制条件当满足限制条件时递推停止开始回归。每一次递归都在不断地接近限制条件。6.2 递归举例练习1求正整数n的阶乘。思路n!n*(n-1)!(n1); n!1(n0时)Factn)n*Fact(n-1);0!1。#include stdio.h int Fact(int n) { if (n 0) { return 1; } else { return n * Fact(n - 1); } } int main() { int input 0; scanf(%d, input); printf(%d\n,Fact(input)); return 0; }练习2顺序打印一个整数的每一位。比如输入1234输出1 2 3 4输入520 输出5 2 0分析我们如果是逆序打印的话只需要%10取到最后一位/10删去最后一位。运用大事化小的思想来解决我们最容易取出最后一位可是要求先打印第一位那么就不断/10直到成为个位数就可以%10打印。#include stdio.h void print(int n) { if (n / 10 ! 0) { print(n/10); } n n % 10; printf(%d , n); } int main() { int input 0; scanf(%d, input); print(input); return 0; }6.3 递归与迭代练习1中既可以用递归写也可以用迭代写。//迭代 unsigned int Fact(int n) { int sum 1; for (int i 1; i n; i) { sum sum * i; } return sum; } int main() { int input 0; scanf(%d, input); printf(%u\n ,Fact(input)); return 0; }上述例子中由于递归可能会出现栈溢出而迭代没有栈溢出的风险写起来逻辑并不复杂效率更高所以练习1优先选迭代。练习3斐波那契数列分析n1,2时Fib(n)1n2时FibnFib(n-1)Fib(n-2)。、//递归 #include stdio.h unsigned int Fib(int n) { if (n 2) { return 1; } return Fib(n - 1) Fib(n - 2); } int main() { int input 0; scanf(%d, input); printf(%u\n, Fib(input)); return 0; } //迭代 #include stdio.h unsigned int Fib(int n) { int a 1; int b 1; int c 1; while (n 2) { a b; b c; c a b; n--; } return c; } int main() { int input 0; scanf(%d, input); printf(%u\n, Fib(input)); return 0; }递归中有好多数都被重复算多次造成冗余运行效率较低所以这个练习迭代较为好。