文章目录
5. 函数的嵌套调用和链式访问
函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。
5.1嵌套调用
函数是可以嵌套调用的,比如这段代码,three_line调用的new_line这个函数进行使用,main函数调用three_line函数进行使用,就像拼乐高一样把代码串起来。
注意:函数可以嵌套调用,但是不能嵌套定义。
比如这段代码就是错误的,要注意,每一段都是平等的,他们可以嵌套调用,但是不难嵌套定义!
更改如图所示。
5.2 链式访问
把一个函数的返回值作为另外一个函数的参数。
1.
2.
大家有没有发现这两种代码结果是一样的,这里就是把strlen的返回值直接作为一个参数(整形)打印了出来。
我们再来看一个例子:
大家认为这段代码的结果是什么?在这之前,我想提示大家:printf返回的是打印在屏幕上字符的个数。
来看结果:
结果是:4321,不懂得小伙伴听我来给大家解释:
printf返回的值又作为printf的参数进行了打印,这就是一个链式访问。
6. 函数的声明和定义
6.1 函数声明:
- 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
- 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
- 函数的声明一般要放在头文件中的。
6.2 函数定义:
函数的定义是指函数的具体实现,交待函数的功能实现。
举个例子:
大家看这段代码,当我们编译的时候,编译器告诉我们“Add”未定义,这是因为编译器扫描代码的时候是从上往下扫描的,所以当定义到下面的时候就会报错。
当我们在前面进行函数的声明,编译器知道了有这个函数,往下扫描到Add函数的时候就不会再报错了。
这种是也符合的。
7. 函数递归
7.1 什么是递归?
程序调用自身的编程技巧称为递归( recursion)。
递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。
递归的主要思考方式在于:把大事化小
7.2 递归的两个必要条件
1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。
2.每次递归调用之后越来越接近这个限制条件。
在此之前我们先来看一个史上最简单的递归:
这段代码就是自己调用自己的过程;
它会无限的打印haha,直到崩溃才停下来,崩溃的原因就是栈溢出:
简单解释一下栈溢出:
所以到最后打印的“haha”停止了,就是因为栈溢出了,但是这段代码是错误的,仅用来介绍什么是递归。
7.2.1 练习1:(画图讲解)
接受一个整型值(无符号),按照顺序打印它的每一位。
例如:
输入:1234,输出 1 2 3 4
#include<stdio.h>
void print(unsigned int n)
{
if (n > 9)
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
unsigned int num = 0;
scanf("%u", &num);
print(num);
return 0;
}
那么这段代码是如何实现的呢?
标注:先红色递推,再蓝色回归。
红色:在1.我们输入:1234,进入print函数到2.中,这时n=1234>9,进入print函数到3.中,这时n=123>9,接着进入print函数到4.中,这时n=12>9,继续进入print函数5.中,这时n=1<9,所以开始蓝色回归,先打印1%10=1,依次回归4、3、2、1,打印出1 2 3 4,程序结束。看懂的小伙伴们再来看一道题。
7.2.2 练习2:(画图讲解)
编写函数不允许创建临时变量,求字符串的长度。
#include<stdio.h>
int my_strlen(char* str)//所以这里用指针来接收
{
if (*str != '\0')
return 1 + my_strlen(str + 1);
else
return 0;
}
int main()
{
char arr[] = "bit";
//[b i t \0]
//数组名其实是数组首元素的地址
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
那这段代码要怎么理解呢?
这段代码关键理解部分是这一部分:
要怎么理解呢?
我们arr[]里假设放的是“b i t”,红色递推:1.里str为“b i t \0",*str=b!=‘\0’,那么到2.中,2.里str为“i t \0",*str=i!=‘\0’,接着到3.中,3.里str为“t \0",*str=t!=‘\0’,继续到4.中,4.里str为“\0",*str=‘\0’=‘\0’,蓝色回归:到这里走else开始回归,3.中回归1+0, 2.中回归2+0,1.中回归3+0,所以最终打印结果为:3。
7.3 递归与迭代
所谓迭代,就是用非递归的方式去解决问题
7.3.1 练习3:
求n的阶乘。(不考虑溢出)
先看迭代的方式:
#include<stdio.h>
int qjc(int n)
{
int ret = 1;
for (int a = 1; a <= n; a++)
{
ret = ret * a;
}
return ret;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret=qjc(n);
printf("%d\n", ret);
return 0;
}
再来看递归的方式:
#include<stdio.h>
int qjc(int n)
{
if (n == 1)
{
return 1;
}
else
{
return n * qjc(n - 1);
}
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret=qjc(n);
printf("%d\n", ret);
return 0;
}
这里递归的方式与上文代码思维相同,就不多做解释了。
7.3.2 练习4:
求第n个斐波那契数。(不考虑溢出)
#include<stdio.h>
int Fib(int n)
{
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);
return 0;
}
但当我们去执行这段代码是,我们发现如果我们要求第50个斐波那契数,效率会非常的慢,解释一下:
用递归的方式求第50个斐波那契数,它要从50往下一层一层剥,会计算很多重复的数,效率非常的低,甚至会出现栈溢出的现象,那么这时候就要用非递归的方式去优化代码:
#include<stdio.h>
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret= Fib(n);
printf("%d\n", ret);
return 0;
}
当我们去执行这段代码的时候,我们就会发现效率大大提高了。
7.3.3递归与迭代的总结:
- 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
- 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
- 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
好了,今天就到此结束了,我们下期再见!