【研0自学100天 —— Day 9 函数调用和递归函数 】

大病一场

病来如山倒,病去如抽丝。
从感到不适,到去医院检查,再开始治疗,中间耗时颇长。现在继续学习进程。


C语言入门(9/100)

回顾前8次的学习中,我们从C语言的基础知识开始着手,并逐渐接触了部分基本语法知识。
Day 7和 Day 8 的学习中,主要学习的 函数 这样稍有难度的部分,今天我们将继续补充 函数 的知识点。

  • 函数
    • 函数调用
    • 递归函数

一、函数调用

1.1 概念

我们需要用到自定义的函数的时候,就得调用它,那么在调用的时候就称之为函数调用

在C语言中,函数调用的一般形式为:

函数名([参数]; 

注意

  • 对无参函数调用的时候可以将[]包含的部分省略。
  • []中可以是常数,变量或其它构造类型数据及表达式,多个参数之间用分隔。

1.2 传值调用

函数的形参和实参分别占有不同内存块,对形参的修改不会影响实参

1.3 传址调用

传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式。
这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

1.4 示例

我们以交换两个变量的值的代码示例来给大家进行举例讲解传值调用传址调用区别。

传值调用:

#include 
void swap(int x, int y) //形参
{
	int temp = x;
	x = y;
	y = temp;
}
int main()
{
	int num1 = 10;
	int num2 = 20;
	swap(num1, num2); //实参
	printf("num1 = %d\nnum2 = %d", num1,num2);
	return 0;
}

代码运行结果:
在这里插入图片描述

可见在传值调用并未影响实参

  • main调用swap时候,只是把num1和num2的值拷贝给x和y,然后num1和num2就不再和swap有关了,swap交换的也是x和y的值
  • 但是x, y的作用域只在swap中,他们确实完成了交换,可是swap结束之后,x, y的值也就随之销毁了,所以根本不会对形参有任何影响,当然就不会实现实参的交换。

因为在传值调用的过程中实参和形参有不同的内存块,修改形参不会影响实参;即形参是实参的临时拷贝

而要想将num1和num2交换则需要引用调用。

传址调用:

#include 
void swap(int *x, int *y) //形参
{
	int temp = *x;
	*x = *y;
	*y = temp;
}
int main()
{
	int num1 = 10; 
	int num2 = 20; 
	swap(&num1, &num2); //实参
	printf("num1 = %d\nnum2 = %d", num1,num2);
	return 0;
}

函数参数完成了交换,原因是

  • 传址调用实际上还是实参到形参的拷贝
  • 不过这次实参是要交换的两个数字的指针(即地址),而不是要交换的两个数本身,虽然形参在swap结束后被销毁,但是形参是根据要交换的两个数的地址完成交换的,所以对这两个数字产生影响,也就完成交换。

1.4 嵌套调用

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

示例,

#include 
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;
}

在上述代码中,在main()函数中调用了three()函数,而在three()函数中又调用了new_line()函数,这就是嵌套调用,即在一个函数中调用另一个函数

特别注意:C语言中,函数可以嵌套调用,但是不能嵌套定义!

1.5 链式访问

什么是链式访问

通俗来讲,就是把一个函数的返回值作为另外一个函数的参数

貌似与嵌套调用挺像的,但两者是存在区别的:

  • 嵌套调用是在函数中调用函数
  • 而链式访问则是将一个函数的返回值作为另一个函数的参数。
#include 
int main()
{
    printf("%d", printf("%d", printf("%d", 43)));
    //结果是啥?
    //注:printf函数的返回值是打印在屏幕上字符的个数
    return 0;
}

打印结果是什么呢?

最后的打印结果是4321

为什么呢?此处给大家普及一个知识点:

printf()函数的返回值是正确输出在屏幕上的字符数
例如,输出在屏幕上的数字是43,此时函数的返回值就是2,如果打印在屏幕上的数字是2的话,返回值就是1,那么此处输出的结果也就不难理解了。

另外一个给大家补充的点是:在上述函数中,是怎样的执行流程呢?

  • 首先执行的是最外层的printf()函数,
    • 而最外层函数的返回值依赖于次外层函数的返回值,
    • 而次外层函数的返回值又依赖于内层函数的返回值,
  • 就是层层向内调用然后层层返回
  • printf(“%d”,43)的返回值是2,而printf(“%d”,2)的返回值是1,所以最终输出在屏幕上的就是4321
  • 因为最内层函数先执行进行输出的,所以也可以理解成是由内向外进行执行的,但调用的顺序却是由外向内的。

二、递归函数

2.1 什么是递归函数?

  • 一个函数在它的函数体内调用它自身称为递归( recursion)
  • 执行递归函数将反复调用其自身,每调用一次就进入新的一层。

示例:

void function(int x)
{
    function(x);
}

注意:递归函数必须有结束条件

2.2 递归函数的目的是?

递归的主要思考方式在于:把大事化小

递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解

递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

2.3 示例

5的阶乘这个例子进行一下剖析,看一看他的运算过程:
【研0自学100天 —— Day 9 函数调用和递归函数 】_第1张图片
程序在计算5的阶乘的时候,先执行递推,当n=1或者n=0的时候返回1,再回推将计算并返回。

由此可以看出,递归函数必须有结束条件

递归函数特点

  1. 每一级函数调用时都有自己的变量,但是函数代码并不会得到复制,如计算5的阶乘时每递推一次变量都不同
  2. 每次调用都会有一次返回,如计算5的阶乘时每递推一次都返回进行下一次;
  3. 递归函数中,位于递归调用前的语句和各级被调用函数具有相同的执行顺序;
  4. 递归函数中,位于递归调用后的语句的执行顺序和各个被调用函数的顺序相反;
  5. 递归函数中必须有终止语句。

一句话总结递归:自我调用且有完成状态

任务
猴子第一天摘下N个桃子,当时就吃了一半,还不过瘾,就又多吃了一个。第二天又将剩下的桃子吃掉一半,又多吃了一个。以后每天都吃前一天剩下的一半零一个。到第10天在想吃的时候就剩一个桃子了,问第一天共摘下来多少个桃子?并反向打印每天所剩桃子数。

#include 
int getPeachNumber(int n)
{
	int num;
	if(n==10)
	{
		return 1;
	}
	else
	{
		num = (getPeachNumber(n+1)+1)*2;
		printf("第%d天所剩桃子%d个",n,num);
	}
	return num;
}
int main()
{
	int num = getPeachNumber(1);
	printf("猴子第一天摘了%d个桃子。\n",num);
	return 0;
}

程序分析: 利用递归的方法,递归分为回推递推两个阶段。

要想知道第1天摘得桃子数,就需知道第2天的,依次类推,推到第10天(1个),再往回推。

2.4 递归和循环区别

  • 能用循环实现的功能,用递归都可以实现
  • 递归常用于"回溯", “树的遍历”,"图的搜索"等问题
  • 但代码理解难度大,内存消耗大(易导致栈溢出),所以考虑到代码理解难度和内存消耗问题, 在企业开发中一般能用循环都不会使用递归

学习参考对象

  1. c语言入门这一篇就够了-学习笔记(一万字)

  2. 13 万字 C 语言从入门到精通保姆级教程2021 年版

  3. 史上最强C语言教程----函数(1)

  4. C语言函数的定义和声明

  5. C语言程序设计 —— 中国大学mooc

  6. C语言初阶——手把手教零基础/新手入门(万字心得笔记)

  7. 【2023年官方C语言】9小时快速精通C语言,动画讲解C语言视频教程 —— Bilibili

你可能感兴趣的:(研0学习,c语言,笔记,学习,经验分享)