一 函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
1.1嵌套调用
即定义一个函数的时候,里面可以调用别的函数;但是定义一个函数的时候,不能在里面定义另一个函数。(即可以嵌套调用,不能嵌套定义)
1.2 链式访问
把一个函数的返回值作为一个函数的参数
上面这个图片,两个printf都可以打印出来 3 。第二个printf就是把strlen这个函数的返回值,作为printf的第二个参数。(上图中的②就是printf的第一个参数,①就是printf的第二个参数)
代码展示:
1. #include <stdio.h> 2. int main() 3. { 4. printf("%d", printf("%d", printf("%d", 43))); 5. return 0; 6. }
打印的结果是:4321
调用printf函数,显示的是字符的个数。
代码分析:
两次调用printf
最里面的那个printf首先在屏幕打印出来43,然后因为调用printf(调用printf函数,显示的是字符的个数),所以,变成了
printf("%d", printf("%d", 2),然后,再在屏幕上显示2,调用printf函数(调用printf函数,显示的是字符的个数),所以变成了printf("%d", 1),再在屏幕上显示1,所以最后的结果显示的是4321.
二 函数的声明和定义
2.1 函数的声明
(1)告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
(2)函数声明一般出现在函数使用之前。要满足,先声明后使用。
(3)函数的声明一般要放在头文件里。
2.2 函数的定义
函数的定义是指函数的具体实现,交代函数功能的实现。
定义函数放在前面 代码展示:
1. 2. #include <stdio.h> 3. int Add(int a, int b) 4. { 5. int c = 0; 6. c = a + b; 7. return c; 8. } 9. int main() 10. { 11. int a = 20; 12. int b = 30; 13. int ret = Add(a, b); 14. printf("%d ", ret); 15. return 0; 16. }
定义函数放在后面 代码展示:
1. #include <stdio.h> 2. int Add(int a, int b);//函数的声明 3. int main() 4. { 5. int a = 20; 6. int b = 30; 7. int ret = Add(a, b); 8. printf("%d ", ret); 9. return 0; 10. } 11. 12. int Add(int a, int b) 13. { 14. int c = 0; 15. c = a + b; 16. return c; 17. }
定义的函数如果放在前面,就不需要声明,如果放在后面,就需要提前声明,否则就会报错。
一般都是采用分文件的方法书写。函数声明放在头文件1.h里,定义的函数,自己一个文件2.c,在别的文件里使用时需要 #include "头文件1".
三 函数递归(这个非常重要)
3.1 什么是递归?
程序调用自身的编程技巧成为递归。(函数自己调用自己就是递归)
递归作为一种算法在程序设计语言中广泛使用。一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略,只需少量的程序就可描述出解决过程所需要的多次重复计算,大大的减少了程序的代码量。
递归的主要思考方式在于:把大事化小
3.2 递归的两个条件
(1)存在限制条件,当满足这个限制条件的时候,递归便不再继续。
(2)每次递归调用之后越来越接近这个限制条件。
3.2.1 练习1
接受一个整形值(无符号),按照顺序打印它的每一位。
例如:输入1234,输出1 2 3 4
代码展示:
1. #include <stdio.h> 2. void print(unsigned int n) 3. { 4. if (n > 9) 5. { 6. print(n / 10); 7. } 8. printf("%d ", n % 10); 9. } 10. int main() 11. { 12. unsigned int num = 0; 13. scanf("%d", &num); 14. print(num); 15. return 0; 16. }
知识点:
unsigned int 输出的时候用%u
3.2.2 练习2
编写函数不允许创建临时变量,求字符串的长度。
代码1展示:(这个代码不符合题意,因为含有临时变量count)
1. #include <stdio.h> 2. int my_strlen(char* s) 3. { 4. int count = 0; 5. while (*s != '\0') 6. { 7. count++; 8. s++; 9. } 10. return count; 11. } 12. int main() 13. { 14. char arr[] = "abc"; 15. int len = my_strlen(arr); 16. printf("%d", len); 17. return 0; 18. }
知识点:
如果是int类型,s+1,跳过4个字节。double也是+1,跳过8个字节。
代码2展示:
1. #include <stdio.h> 2. int my_strlen(char* s) 3. { 4. if (*s == '\0') 5. return 0; 6. else 7. return 1 + my_strlen(s + 1); 8. } 9. int main() 10. { 11. char arr[] = "abc"; 12. int len = my_strlen(arr); 13. printf("%d", len); 14. return 0; 15. }
3.3 递归与迭代
迭代就是不断的重复,就是不是递归的方法。
3.3.1 练习3
求n的阶乘 (不考虑溢出)
代码展示:
循环的方法 代码展示:
1. #include <stdio.h> 2. int main() 3. { 4. int i = 0; 5. int n = 0; 6. scanf("%d", &n); 7. int ret = 1; 8. for (i = 1; i <= n; i++) 9. { 10. ret = ret * i; 11. } 12. printf("%d", ret); 13. return 0; 14. }
递归的方法 代码展示:
1. #include <stdio.h> 2. int fac(int n) 3. { 4. if (n <= 1) 5. return 1; 6. else 7. return n * fac(n - 1); 8. } 9. int main() 10. { 11. int n = 0; 12. scanf("%d", &n); 13. int ret = fac(n); 14. printf("%d", ret); 15. return 0; 16. }
4!= 4 *3! 3! = 3*2!
3.3.2 练习4
求第n个斐波那契数列 (不考虑溢出)
代码1展示:(递归的方法)
1. #include <stdio.h> 2. int fib(int n) 3. { 4. if (n <= 2) 5. return 1; 6. else 7. return fib(n - 1) + fib(n - 2); 8. } 9. int main() 10. { 11. int n = 0; 12. scanf("%d", &n); 13. int ret = fib(n); 14. printf("%d", ret); 15. return 0; 16. }
这种方法,如果求第50个斐波那契数,需要花费大量的时间 说明用递归的方法不是简单的方法
在调用函数的时候,如果参数过大,那就会报错:stack overflow (栈溢出)这样的信息。
系统分配给程序的栈空间是有限的,当时出现了死循环,或者(死递归),这样可能导致一直开辟栈空间,最终导致栈空间耗尽的现象,这样的情况我们称之为栈溢出。
代码2展示:(用循环的方法)
1. #include <stdio.h> 2. int fib(int n) 3. { 4. int a = 1; 5. int b = 1; 6. int c = 0; 7. while (n > 2) 8. { 9. c = a + b; 10. a = b; 11. b = c; 12. n--; 13. } 14. return c; 15. } 16. int main() 17. { 18. int n = 0; 19. scanf("%d", &n); 20. int ret = fib(n); 21. prntf("%d", ret); 22. return 0; 23. }
许多问题是可以用递归的形式解释的,但只是因为它比非递归的形式更为清晰,但是这些问题的迭代实现往往比递归效率更高,虽然代码的可读性稍微差些。
当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可弥补它所带来的运行时开销。
函数就到此结束了,希望友友们可以提出宝贵的的意见。