文章目录
五、函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
1.嵌套调用
c
#include <stdio.h>
void new_line()
{
printf("hehe\n");
}
void three_line()
{
int i = 0;
for (i = 0; i < 3; i++)
{
new_line();
}
}
int main()
{
three_line();
return 0;
}
函数可以嵌套调用,但是不能嵌套定义
2.链式访问
把一个函数的返回值作为另外一个函数的参数。
c
#include <stdio.h>
#include <string.h>
int main()
{
char arr[20] = "hello";
int ret = strlen(strcat(arr, "bit"));//这里介绍一下strlen函数
printf("%d\n", ret);
return 0;
}
#include <stdio.h>
int main()
{
printf("%d", printf("%d", printf("%d", 43)));
//结果是啥?
//注:printf函数的返回值是打印在屏幕上字符的个数
return 0;
}
结果是4321
六、函数的生命和定义
1.函数声明
- 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数
声明决定不了。 - 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
- 函数的声明一般要放在头文件中的。
2.函数定义
函数的定义是指函数的具体实现,交待函数的功能实现。
无论是变量还是函数,都得满足先声明后使用。
一般情况下:
add.h文件 放函数的声明
add.c文件 放函数的定义
test.c文件 (#include "add.h")里面可以调用函数
这样写可以将代码进行封装和隐藏。
test.c中需要使用某个函数,某人卖给他使用,但不暴露函数实现的源代码,某人将add模块生成静态库add.lib,将add.h文件和静态库add.lib卖给别人使用。别人使用add.h文件和test.c文件,并在test.c文件中写上以下这样的代码:
#pragma comment(lib,"add.lib")
#incluide "add.h"
即可使用
七、函数递归
1.什么是递归?
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接
调用自身的
一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,
递归策略
只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
通俗来说,递归就是自己调用自己
每一次函数调用都要在栈区申请空间,当栈区空间耗干,就会出现栈溢出(Stack overflow)
2.递归的两个必要条件
存在限制条件,当满足这个限制条件的时候,递归便不再继续。
每次递归调用之后越来越接近这个限制条件。
3.练习
(1)
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4
c
#include <stdio.h>
void print(int n)
{
if (n > 9)
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
int num = 1234;
print(num);
return 0;
}
(2)
编写函数不允许创建临时变量,求字符串的长度
思路:当发现指向的第一个字符不是'\0'时,长度为1,字符串的长度是1+其余字符串的长度。
c
#include <stdio.h>
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;
}
注意点
因为++是后置,已经把a的地址传进去了,str再++,str++后的结果留给当前函数了。当调用my_strlen时,新的my_strlen函数2的str还是指向a的,之后再调用的其他my_strlen中的str都是指向a的。所以如果写成str++,直接死递归了。
🎗️所以,在递归中尽量不要写成前置++,或者后置++,会带有副作用
写成str+1,就是把下一个字符的地址传进去了,str没动
写成str++,把下一个字符地址传进去了,str也动了,如果递归回来,还想使用这个地址的话,而这个地址已经变了,就有问题了。
4.递归与迭代
练习
(1)
求n的阶乘。(不考虑溢出)
c
int factorial(int n)
{
if(n <= 1)
return 1;
else
return n * factorial(n-1);
}
(2)
求第n个斐波那契数。(不考虑溢出)
🎗️斐波那契数列:
1 1 2 3 5 8 13 21 34 55 ...
特点:当前位置的值是前两个数相加的和。
这样写可以解决问题,但是当我们输入的n值比较大时,比如50,在程序的运行窗口迟迟计算不出来结果,为什么呢?
探究
接下来我们可以统计一下Fib(3)的调用次数
代码如下:
c
#include<stdio.h>
int count = 0;
int Fib(int n) {
if (n == 3) {
count++; //统计的是第三个斐波那契数被重复计算的次数
}
if (n <= 2) {
return 1;
}
else {
return Fib(n - 1) + Fib(n - 2);
}
}
int main() {
int n = 0;
scanf("%d", &n);
int ret = Fib(n);
printf("%d\n", ret);
printf("count=%d\n", count);
return 0;
}
由此发现,递归虽然能解决问题,但是它的效率比较低。
换个思路
用迭代的思路,一直用a+b赋值给c,如果计算的结果c过大会溢出,所以范围有限。
提示:
- 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
- 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
- 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
5.推荐题目
函数递归的几个经典题目(自主研究):
- 汉诺塔问题
- 青蛙跳台阶问题