函数的声明和定义
函数声明:
告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
函数的声明一般出现在函数的使用之前。要满足先声明后使用。
函数的声明一般要放在头文件中的。
#include <stdio.h> int Add(int x, int y);//提前声明 int main() {} int Add(int x, int y) //定义 {}
函数定义:
函数的定义是指函数的具体实现,交待函数的功能实现。
未来在工程中代码是比较多的 函数在add.h(头文件)中声明
在add.c(源文件)中定义
在test.c(源文件)中实现
在test.c前有写 #include "add.h" //自己写的一般用""就可以了 //库函数的写成<>
从编译器到链接器的过程是由 VS2022-集成开发环境实现的
为什么要将代码拆成.c/.h的文件?
1. 多人协作
最后将代码整合
2. 代码保护
可以把代码变成静态库 可以把代码卖给别人而不暴露代码(.lib 和 .h 传给别人)
函数递归
什么是递归?
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。
一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解, 递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
递推+回归
最后栈溢出 // 程序错误
递归的两个必要条件
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件
例子
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:输入:1234,输出 1 2 3 4.
#include <stdio.h> int main(){ //1234 //1234%10 = 4 //1234/10 = 123 //123%10 = 3 int num = 0; scanf("%d",&num); //非递归的方法 while(num){ printf("%d ", num%10); num = num / 10; } //递归的方法 //Print能将参数num的每一位打印在屏幕上 Print(num); return 0; } void Print(int n){ if(n > 9){ Print(n/10); } printf("%d",n%10); }
函数之所以能实现递归(调用)
都是因为,函数在调用的时候会维护一个函数栈帧
调用的时候创建 结束的时候销毁
如图 函数结束就直接销毁(图中已经销毁了 n = 12 的Print 栈区)
编写函数不允许创建临时变量,求字符串的长度。
不允许创建临时变量
利用递归来实现
#include <stdio.h> //模拟实现strlen int Strlen(const char*str)//从第一位的地址开始求 { if(*str == '\0') return 0; else return 1+Strlen(str+1);//从下一位的地址继续求 } int main() { char *p = "abcdef"; int len = Strlen(p); printf("%d\n", len); return 0; }
库函数strlen的返回值是size_t类型
size_t 是无符号整型 %zd 为了sizeof 设计的
函数调用数组的时候传递的是数组首字母的地址
strlen 统计的是\0 之前的字符个数
递归与迭代
例子
求n的阶乘。(不考虑溢出)
int factorial(int n) { if(n <= 1) return 1; else return n * factorial(n-1); }
求第n个斐波那契数。(不考虑溢出)
1 1 2 3 5 8 13 21 34 55 89
int fib(int n) { if (n <= 2) return 1; else return fib(n - 1) + fib(n - 2); }
但是我们发现有问题:
在使用 fib 这个函数的时候如果我们要计算第50个斐波那契数字的时候特别耗费时间。
使用函数求10000的阶乘(不考虑结果的正确性),程序会崩溃。
为什么呢?
我们发现 fib 函数在调用的过程中很多计算其实在一直重复。 如果我们把代码修改一下:
int count = 0;//全局变量 int fib(int n) { if(n == 3) count++; if (n <= 2) return 1; else return fib(n - 1) + fib(n - 2); }
最后我们输出看看count,是一个很大很大的值。 那我们如何改进呢?
在调试函数的时候,如果你的参数比较大,那就会报错: stack overflow (栈溢出)
这样的信息。
系统分配给程序的栈空间是有限的,但是如果出现了死循环,或者(死递归),这样有可能导致一 直开辟栈空间,最终产生栈空间耗尽的情况,这样的现象我们称为栈溢出。
那如何解决上述的问题:
- 将递归改写成非递归。
- 使用static对象替代nonstatic局部对象。在递归函数设计中,可以使用static对象替代nonstatic 局部对象(即栈对象),这不仅可以减少每次递归调用和返回时产生和释放对象的开销,而且存递归调用的中间状态,并且可以各个调用层所访问。 比如,下面代码就采用了,非递归的方式来实现:
//求n的阶乘 int factorial(int n) { } int result = 1; while (n > 1) { result *= n ; n -= 1; } return result; //求第n个斐波那契数 int fib(int n) { int result; int pre_result; int next_older_result; result = pre_result = 1; while (n > 2) { n -= 1; next_older_result = pre_result; pre_result = result; result = pre_result + next_older_result; } return result; }