C语言初阶函数(二)

5. 函数的嵌套调用和链式访问

函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。

5.1嵌套调用

在这里插入图片描述
函数是可以嵌套调用的,比如这段代码,three_line调用的new_line这个函数进行使用,main函数调用three_line函数进行使用,就像拼乐高一样把代码串起来。
注意:函数可以嵌套调用,但是不能嵌套定义。
在这里插入图片描述
比如这段代码就是错误的,要注意,每一段都是平等的,他们可以嵌套调用,但是不难嵌套定义!
在这里插入图片描述
更改如图所示。

5.2 链式访问

把一个函数的返回值作为另外一个函数的参数。
1.在这里插入图片描述
2.
在这里插入图片描述
大家有没有发现这两种代码结果是一样的,这里就是把strlen的返回值直接作为一个参数(整形)打印了出来。
我们再来看一个例子:
在这里插入图片描述
大家认为这段代码的结果是什么?在这之前,我想提示大家:printf返回的是打印在屏幕上字符的个数。
来看结果:
在这里插入图片描述
结果是:4321,不懂得小伙伴听我来给大家解释:
在这里插入图片描述
printf返回的值又作为printf的参数进行了打印,这就是一个链式访问。

6. 函数的声明和定义

6.1 函数声明:

  1. 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数声明决定不了。
  2. 函数的声明一般出现在函数的使用之前。要满足先声明后使用。
  3. 函数的声明一般要放在头文件中的。

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递归与迭代的总结:

  1. 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。
  2. 但是这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性稍微差些。
  3. 当一个问题相当复杂,难以用迭代实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。
    在这里插入图片描述
    好了,今天就到此结束了,我们下期再见!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CC小师弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值