C++新经典01--函数递归

news2024/9/27 19:20:58

函数的递归

在这里插入图片描述

#include <stdio.h>
void diguifunc()
{
	printf("diguifunc()函数执行\n");
	diguifunc();//自己调用自己
}

void main(){
    diguifunc();
}

把程序执行起来,等几秒钟,可以看到,屏幕不断滚动并输出如下内容:

diguifunc()函数执行
diguifunc()函数执行

继续等待一会儿(几秒)后,有的Visual Studio编译环境版本直接出现程序报错,弹出异常对话框,有的Visual Studio编译环境版本直接退出了整个程序的执行,等等,各种不正常的现象都会发生,但总归就是一句话,程序执行不正常,出现了各种问题。报错也好,执行崩溃或者程序退出也罢,根本原因是系统的资源(内存)耗尽了,这是因为不断无限次地调用函数自身所导致。调用函数是要占内存的,每多调用一次函数,系统的内存就要多占用一些,当函数调用完成,从函数中返回时,调用该函数时所占用的内存才能被系统释放掉。
以图7.2为例,如果是在第3步定义一个变量,那么这个变量所占用的内存需要到第11步才能被释放,这也说明了,函数嵌套调用的层次越深,所需要占用的系统内存就越大。

对于函数嵌套调用来讲,系统会给函数调用分配一些内存来保存这些信息(局部变量、函数参数、函数调用关系等),但分配的内存大小是固定和有限的,一旦超过这个内存大小,程序执行就会出现上述崩溃或者异常退出的情况。

本节开头做了一个函数递归调用(自己调用自己)的代码演示,针对这个调用,可以绘制一个比较形象的图看看函数调用关系,如图7.3所示。
在这里插入图片描述

这个例子导致函数自己不断地调用自己(递归调用),造成了调用的死循环。所以递归调用这种自己调用自己的方式必须要有一个出口,这个出口也叫作递归结束条件,有了这个递归结束条件,就能够让这种函数调用结束,可以用图7.4做一个形象一点的说明。
在这里插入图片描述
总结一下图7.4:递归调用就是一个函数在它的函数体内部调用它自身。执行递归函数将反复调用其自身,每调用一次就进入新的一层,递归函数必须有结束条件(递归调用的出口),从而引出下一个话题:递归调用的出口。

用递归实现阶乘:

#include <stdio.h>
int dg_jiecheng(int n)
{
	int result; //保存阶乘结果
	if(n==1)
	{
		result=1; //这里就是该递归调用函数的出口
	}
	else
	{
		result=dg_jiecheng(n-1)*n;//递推关系,这个数与上一个数之间的关系
		return result;
	}
}

void main(){
    printf("5的阶乘结果是%d",dg_jiecheng(5));  //5的阶乘结果是120
}

递归的缺点,可以用三条来总结。
(1)虽然代码简洁,代码也精妙,但代码理解起来比较有难度。
(2)如果调用层次太深,调用栈(保存函数调用关系等需要用到的内存)可能会溢出,如果真出现这种情况,那么说明不能用递归调用解决该问题。例如,可以自己演示一下计算50000的阶乘,堆栈会溢出,结果也会溢出,但结果溢出与否不重要,关注的重点是堆栈的溢出,也就是内存装不下这么多层调用了,此时,程序执行并等待几秒钟后,程序要么报告异常,要么无征兆地退出。总之一句话:程序运行不正常。
(3)效率和性能都不算高。这么深层次的函数调用,调用中间要保存的内容也很多,所以效率和性能肯定高不起来。

局部变量和全局变量

在函数内定义的变量叫局部变量。那么在函数外定义的变量就称为全局变量(也叫外部变量)。全局变量可以为本源程序文件中其他函数所共用(全局变量还可以被其他源程序文件所用,后续会讲解),它的有效范围从定义该变量的位置开始到本源程序文件结束为止,如果在整个源程序文件开头定义该变量,则整个文件范围内都可以使用该全局变量。

全局变量说明

(1)如果某个函数想引用在它后面定义的全局变量,可以使用关键字extern做一个“外部变量说明”,表示该变量在函数的外部定义,这样在函数内就能使用,否则编译就会出错,但有一点要注意,全局变量在定义的时候是可以给初值的,但是在做外部变量说明时,是不可以给变量初值的。看看如下范例:


#include <stdio.h>

extern int c1,c2; //外部变量说明,不可以写成extern int c1=0,c2=1这样
void lookvalue()	//一个自定义函数
{
	c1=5; //因为前面用了extern作外部变量说明,所以这里可以用c1和c2
	c2=8;
	return;
}
int c1,c2;  //这里才是全局变量定义的地方
int main()	//主函数
{
	lookvalue();
	printf("c1 = %d\n",c1);	//c1=5
	printf("c2 = %d\n",c2);	//c2=8
	printf("断点停在这");	//可以在这里设置断点跟踪调试查看全局变量的值
	return 0;
}

所以,容易想到,如果全局变量的定义放在引用它的所有函数之前,就可以避免使用关键字extern做外部变量说明了。

(2)严格区分全局变量(外部变量)定义和外部变量说明。全局变量定义只能有一次,位置是在所有函数之外,定义时会分配内存,定义时可以初始化该全局变量的值。
而在同一个文件中,外部变量说明是可以有很多次的(不分配内存)。在上面的范例中,是把外部变量说明放在文件最上面,所有函数之外。其实在每个函数内部做外部变量说明也是可以的(不过一般极少看到有人这样做),所以这更进一步加深了对外部变量说明的理解:所声明的变量是已在外部定义过的变量,仅仅是引用该变量而做的“声明”。看看如下范例:


#include <stdio.h>

void lookvalue(int a)
{
	extern int c1,c2;	//外部变量说明
	c1=5;
	c2=8;
	return;
}
void lookvalue2(){
	extern int c1,c2;	//外部变量说明
	c1=51;
	c2=81;
	return;
}
int c1,c2;	//全局变量(外部变量)定义
int main()	//主函数
{
    int q = 1;
	lookvalue(q);
	printf("c1 = %d\n",c1);	//c1=5
	printf("c2 = %d\n",c2);	//c2=8

    lookvalue2();
	printf("c1 = %d\n",c1);	//c1=51
	printf("c2 = %d\n",c2);	//c2=81

	printf("断点停在这");	
	return 0;
}

(3)在同一个源文件中,如果全局变量和局部变量同名,则在局部变量作用范围(作用域)内,全局变量不起作用,如果给局部变量赋值,当然也不会影响全局变量的值。看看如下范例:


#include <stdio.h>
int a=4,b=5;	//全局变量定义
void lookvalue(int a,int b)
{
	a=123;
	b=456;	//局部变量b作用范围内,全局变量b不起作用
}
int main()	//主函数
{
	int i=2, j=5;
	lookvalue(i,j);
	printf("a =%d\n",a);	//a=4
	printf("b= %d\n",b);	//b=5
	return 0;
}

(4)针对一个项目中包含多个源程序文件的情形(后面会详细讲解如何在一个项目中包含多个源程序文件),如果在一个源程序文件中定义的全局变量想在该项目的其他源程序文件中使用,则只需要在其他的源程序文件中使用上面介绍的extern关键字做外部变量说明,就可以在其他源程序文件中使用该全局变量了。
例如,在MyProject.cpp中定义了一个全局变量:
在这里插入图片描述

假设要在MyProject2.cpp(后面会讲一个新的.cpp源程序文件如何加入到当前项目中来)中的func1函数内使用MyProject.cpp中定义的全局变量a,则首先在MyProject2.cpp的开头使用关键字extern对全局变量a做外部变量说明(表示这个变量在其他文件中已经定义了),然后就可以开始使用了。MyProject2.cpp的代码如下:
在这里插入图片描述

局部变量的存储方式

1.传统情形函数中的局部变量,一般来说,都是动态分配存储空间,也就是说,存储在动态存储区中。对这些变量分配和释放存储空间由系统自动处理:函数被调用时分配存储空间,函数执行完成后自动释放其所占用的存储空间。

2.特殊情形有时希望函数中局部变量的值在函数调用结束后不消失(不被系统自动释放)而保留原值,也就是说,它占用的存储单元不释放,在下一次调用该函数时,该变量中保存的值就是上一次该函数调用结束时的值,这是可以做到的,只需指定该局部变量为“局部静态变量”,用static关键字加以说明即可。看看如下范例,先看看传统的函数内的局部变量输出求值结果:

#include <stdio.h>
void funcTest()	//自定义函数
{
	int c = 4; //如果把断点设置到这行,调试执行会注意到断点确实能够停留在该行
	printf("c=%d\n",c);
	c++;
	return;
}
int main() //主函数main中调用三次上述的自定义函数
{
	funcTest() ;
	funcTest() ;
	funcTest() ;
	return 0;
}

执行上面的代码可以看到,程序连续输出三次相同的结果如下:

c=4
c=4
c=4

现在,修改上述的自定义函数funcTest(),修改“intc=4;”这行的内容,其他内容不变。修改后的内容如下:

static int c = 4;

再次执行上面的代码可以看到,程序三次输出的结果发生了改变,如下:

c=4
c=5
c=6

通过分析执行的结果,不难发现,定义一个变量时,在前面加上static(局部静态变量说明),变量的表现就不同了。具体有如下几点不同:
· 在静态存储区(见图7.12)中分配存储单元,程序整个运行期间都不释放。· 局部静态变量是在编译时赋初值的,只赋初值一次,在程序运行的时候它已经有了初值,以后每次调用函数时不再重新赋初值,只是保留上次函数调用结束时的值,而普通变量的定义和赋值是在函数调用时才进行的。
· 定义局部静态变量时如果不赋初值,则编译时自动给其赋初值0,而常规变量,如果不赋初值,则它是一个不确定的值。
· 虽然局部静态变量在函数调用结束后仍然存在,但在其他函数中是不能引用的。
· 局部静态变量长期占用内存,降低了程序可读性(当多次调用该函数时往往弄不清当前该静态变量的值是多少)。

内部函数和外部函数

1.内部函数
只能被本文件中其他函数调用。定义内部函数时,在最前面加static关键字即可,内部函数又称为静态函数,使用内部函数,可以使函数只局限于其定义所在的源程序文件中,所以不同源程序文件中的同名函数彼此不受干扰,试想,如果分工不同的两个人编写两个不同的.cpp源程序文件,如果他们所写的函数同名,则在编译链接时会报错,如果用了static修饰这些函数,那么即使所起的函数名相同,也互相不受影响(因为这些函数被限制在当前定义所在的源文件中)。

2.外部函数
定义一个函数时,如果在其前面不使用static关键字修饰,它就是外部函数。
在需要调用此函数的其他源程序文件中,只需要增加该函数的函数声明即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/889516.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

通过cpolar分享本地电脑上有趣的照片:部署piwigo网页【无公网IP内网穿透】

在强者的眼中&#xff0c;没有最好&#xff0c;只有更好。我们是移动开发领域的优质创作者&#xff0c;同时也是阿里云专家博主。 ✨ 关注我们的主页&#xff0c;探索iOS开发的无限可能&#xff01; &#x1f525;我们与您分享最新的技术洞察和实战经验&#xff0c;助您在移动应…

如何在pycharm中指定GPU

如何在pycharm中指定GPU 作者:安静到无声 个人主页 目录 如何在pycharm中指定GPU打开编辑配置点击环境变量添加GPU配置信息推荐专栏在Pycharm运行程序的时候,有时候需要指定GPU,我们可以采用以下方式进行设置: 打开编辑配置 点击环境变量 添加GPU配置信息 添加名称:CU…

【C++学习手札】一文带你初识C++继承

食用指南&#xff1a;本文在有C基础的情况下食用更佳 &#x1f340;本文前置知识&#xff1a; C类 ♈️今日夜电波&#xff1a;napori—Vaundy 1:21 ━━━━━━️&#x1f49f;──────── 3:23 …

unity物体移动至指定位置

物体坐标与物体移动 世界坐标与局部坐标之间的转换物体移动至指定位置需求思路注意 世界坐标与局部坐标之间的转换 在Unity中&#xff0c;物体的坐标分为局部坐标和世界坐标。 局部坐标是相对于物体的父对象的坐标系&#xff0c;而世界坐标是相对于场景的整体坐标系。 使用tr…

一个DW的计算

一个DW的计算 1- 题目: 已知一个DW1.1 要求: 从DW中取出指定的位的值1.1.1 分析1.1.2 实现1.1.3 简化实现1.1.4 验证 2- 题目: 已知一个DW2.1 要求: 从DW中的指定的P和S,取出指定的位的值2.1.1 分析2.1.2 实现 1- 题目: 已知一个DW 有图中所示一行信息&#xff0c;表示一个DW(…

mktime有时会返回-1使用boost库没有问题

linux获得时间戳 #include <iostream> #include <boost/date_time/posix_time/posix_time.hpp> long long utc8_to_stamp(int date, float time) {struct tm stm;int itime (time);int iY date/10000,iM(date-iY*10000)/100,iDdate%100,iHitime/10000,iMin(itime…

学习笔记十七:node节点选择器,亲和性

node节点选择器&#xff0c;污点、容忍度、亲和性 node节点选择器nodeName&#xff0c;指定pod节点运行在哪个具体node上nodeSelector&#xff1a;指定pod调度到具有哪些标签的node节点上 亲和性node节点亲和性使用requiredDuringSchedulingIgnoredDuringExecution硬亲和性使用…

Linux编程库

1、Linux编程库介绍&#xff1a; 编程库就是指始终可以被多个Linux软件项目重复使用的代码集。 使用编程库有两个主要的优点&#xff1a; 可以简化编程&#xff0c;实现代码重复使用&#xff0c;进而减小应用程序的大小。可以直接使用比较稳定的代码。 Linux下的库文件分为共…

rabbitmq容器启动后修改连接密码

1、进入容器 docker exec -it rabbitmq bash 2、查看当前用户列表 rabbitmqctl list_users 3、修改密码 rabbitmqctl change_password [username] ‘[NewPassword]’ 4、修改后退出容器 ctrlpq 5、退出容器后即可生效&#xff0c;不需要重启容器

三分之一的英国大学生被欺诈

根据NatWest的一项新研究&#xff0c;去年英国大学三分之一的学生在网上遭遇欺诈。 今年5月&#xff0c;这家高街银行委托咨询公司RedBrick对来自63个城镇的3000多名英国大学生进行了调查。 尽管三分之一的受访者表示他们在过去的12个月里遇到过诈骗&#xff0c;但没有统计数…

动手学深度学习-pytorch版本(二):线性神经网络

参考引用 动手学深度学习 1. 线性神经网络 神经网络的整个训练过程&#xff0c;包括: 定义简单的神经网络架构、数据处理、指定损失函数和如何训练模型。经典统计学习技术中的线性回归和 softmax 回归可以视为线性神经网络 1.1 线性回归 回归 (regression) 是能为一个或多个…

【06 英语语法:时态、语态、虚拟语气】

时态、语态、虚拟语气 1. 时态和语态1.1 时态: 4个时间*4个状态 &#xff08;时间&#xff1a;现在、过去、将来、过去将来&#xff1b;状态&#xff1a;一般、进行、完成、完成进行&#xff09;⑴ 16 时态 详解表⑵ 主从句的 时态搭配⑶ 常用的 不规则动词变化 1.2 语态&#…

url下载地址含非法字符下载失败

示例下载链接&#xff1a;https://666666.shei.org.cn:2023/20230105/0ac280a3-498b-45d8-830c-a788475a8022/2023817-F2666666很六 &#xff08;改&#xff09;.doc java.lang.IllegalArgumentException: Illegal character in path at index 96: https://666666.shei.org.c…

android resoure资源图片颜色值错乱

最近androidstudio开发&#xff0c;添加一些颜色值或者drawable资源文件时&#xff0c;运行app,颜色值或者图片对应不上&#xff0c;暂时找不到原因&#xff0c;望告知。 暂时解决方法&#xff1a;

IT 运营管理中的根本原因分析(RCA)

全球数字化的兴起造成了一种情况&#xff0c;即组织在很大程度上依赖于其IT基础架构&#xff0c;就像我们依赖神经系统一样。我们可以将其等同于神经系统&#xff0c;因为IT基础架构可以实现有效控制&#xff0c;协调所有功能&#xff0c;并确保高效&#xff0c;顺利地完成每项…

代码随想录算法训练营第60天|动态规划part17| 647. 回文子串、516.最长回文子序列、动态规划总结篇

代码随想录算法训练营第60天&#xff5c;动态规划part17&#xff5c; 647. 回文子串、516.最长回文子序列、动态规划总结篇 647. 回文子串 647. 回文子串 思路&#xff1a; 暴力解法 两层for循环&#xff0c;遍历区间起始位置和终止位置&#xff0c;然后还需要一层遍历判断…

vue2.0/vue3.0学习笔记——2022.08.16

vue2&#xff08;查漏补缺&#xff09; 一、vue基础 内置指令&#xff08;查漏补缺&#xff09; 1、v-text 更新元素的textContent 2、v-html 更新元素的innerHtml 3、v-cloak 防止闪现&#xff0c;与css配合: [v-cloak] {dispaly: none} 4、v-once 在初次动态渲染厚&#x…

1N4007S 整流二极管 1A 1000V A-405

前两天二极管生产厂家东沃电子科普过1N4007和1N4007G这两种普通塑封整流二极管&#xff0c;查看“STD-1N4001 Thru 1N4007 (DO-41) Datasheet”和“STD-1N4001G Thru 1N4007G (DO-41) Datasheet”产品手册可知&#xff0c;1N4007和1N4007G参数除了结电容和芯片尺寸不一样以外&a…

每日一练 | mongo集群如何创建分片键

文章目录 MongoDB是什么什么是分片键环境如何设置分片键 MongoDB是什么 MongoDB 是一个基于分布式文件存储的数据库 什么是分片键 分片&#xff1a;每个分片包含分片数据的一部分。每个分片可以部署为副本集。 而分片键的作用就是把数据按一定的条件分布到各个分片中&#…

Linux 修改信号的响应方式

修改信号的响应方式 1.signal()方法介绍&#xff1a; 修改信号的响应方式要用到方法signal()。需要引用头文件signal.h。signal()的原型&#xff1a; typedef重命名了一个函数指针的类型&#xff0c;这个指针的类型为指向一个参数为int返回值为void的函数的指针。这个函数指针…