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

函数的递归

在这里插入图片描述

#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.mfbz.cn/a/85056.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AP9235 dc-dc升压恒流电源驱动IC 2000ma SOT23-6

概述 AP9235B 系列是一款固定振荡频率、恒流输出的升压型DC/DC转换器&#xff0c;非常适合于移动电话、PDA、数码相机等电子产品的背光驱动。输出电压可达30V &#xff0c;3.2V输入电压可以驱动六个串联LED&#xff0c; 2.5V输入电压可以驱动两路并联LED&#xff08;每路串联…

Android Studio调试出现错误时,无法定位错误信息解决办法

做项目时运行项目会出现问题&#xff0c;但是找不到具体位置&#xff0c;如下图所示&#xff1a;感觉是不是很懵逼~&#xff0c;Log也没有显示是哪里的问题 解决方案&#xff0c;在右侧导航栏中选择Gradle——app——build&#xff0c;然后点击运行 运行结果如下&#xff0c;很…

YOLO目标检测算法调试过程学习记录

先前已经完成过YOLO系列目标检测算法的调试过程&#xff0c;今天主要是将所有的调试加以总结 这里的conda环境就不再赘述了&#xff0c;直接使用requirement.txt文件的即可&#xff0c;也可以参考YOLOX的配置过程5 数据集处理 YOLOv5有自己的数据集格式&#xff0c;博主的数据…

live555在Windows WSL2中编译、运行,搭建RTSP流服务器

文章目录 1. 背景2. 实施步骤2.1 下载live555安装包2.2 解压压缩包2.3 编译源码2.3 安装ffmpeg2.4 安装opencv-python2.5 视频文件格式转换2.6 启动推流2.6 安装VLC&#xff0c;验证 3. 用opencv-python接口接收视频流参考 1. 背景 想要通过RTSP往opencv的接口中推流&#xff…

选择靠谱商城系统的重要性

电子商务的蓬勃发展&#xff0c;越来越多的企业和商家开始进入电商领域&#xff0c;希望通过搭建自己的网上商城来实现业务增长和利润提升。然而&#xff0c;在选择合适的商城系统时&#xff0c;很多人往往会忽视靠谱性这一关键因素。下面就选择靠谱商城系统的重要性作一些简单…

DataSecurity Plus:守护企业数据安全的坚实屏障

在数字化时代&#xff0c;数据被誉为企业最重要的资产之一。然而&#xff0c;随着大数据的兴起和信息的日益增长&#xff0c;企业面临着前所未有的数据安全挑战。为了应对这些挑战&#xff0c;数据安全管理变得至关重要。在这个领域&#xff0c;ManageEngine的DataSecurity Plu…

一篇文章教你自动化测试如何解析excel文件?

前言 自动化测试中我们存放数据无非是使用文件或者数据库&#xff0c;那么文件可以是csv&#xff0c;xlsx&#xff0c;xml&#xff0c;甚至是txt文件&#xff0c;通常excel文件往往是我们的首选&#xff0c;无论是编写测试用例还是存放测试数据&#xff0c;excel都是很方便的。…

【Linux】进程间通信之信号机制2

文章目录 信号阻塞代码验证验证信号的阻塞验证信号的阻塞不影响信号注册验证可靠信号不会丢信号&#xff0c;不可靠信号会丢信号验证9号和19号信号不能被阻塞 用信号解决僵尸进程volatile关键字 信号阻塞代码验证 在上篇详解信号机制的博文中&#xff0c;我们提到了设置阻塞位…

Vue-9.集成(.editorconfig、.eslintrc.js、.prettierrc)

介绍 同时使用 .editorconfig、.prettierrc 和 .eslintrc.js 是很常见的做法&#xff0c;因为它们可以在不同层面上帮助确保代码的格式一致性和质量。这种组合可以在开发过程中提供全面的代码维护和质量保证。然而&#xff0c;这也可能增加一些复杂性&#xff0c;需要谨慎配置…

一文详解4种聚类算法及可视化(Python)

在这篇文章中&#xff0c;基于20家公司的股票价格时间序列数据。根据股票价格之间的相关性&#xff0c;看一下对这些公司进行聚类的四种不同方式。 苹果&#xff08;AAPL&#xff09;&#xff0c;亚马逊&#xff08;AMZN&#xff09;&#xff0c;Facebook&#xff08;META&…

【Java】Java如何生成随机数?

文章目录 前言一、Random类介绍二、Random类生成随机数1.生成随机数2.nextInt()方法 三、使用场景四、官方提示总结 前言 我们在学习 Java 基础时就知道可以生成随机数&#xff0c;可以为我们枯燥的学习增加那么一丢丢的乐趣。本文就来介绍 Java 随机数。 一、Random类介绍 …

docker的资源控制及docker数据管理

文章目录 docker的资源控制及docker数据管理一.docker的资源控制1.CPU 资源控制1.1 资源控制工具1.2 cgroups有四大功能1.3 设置CPU使用率上限1.4 进行CPU压力测试1.5 设置50%的比例分配CPU使用时间上限1.6 设置CPU资源占用比&#xff08;设置多个容器时才有效&#xff09;1.6.…

合宙Air724UG LuatOS-Air LVGL API--简介

为何是 LVGL LVGL 是一个开源的图形库&#xff0c;它提供了创建嵌入式 GUI 所需的一切&#xff0c;具有易于使用的图形元素、漂亮的视觉效果和低内存占用的特点。 LVGL特点&#xff1a; 强大的 控件 &#xff1a;按钮、图表、列表、滑动条、图像等 高级图形引擎&#xff1a;动…

【Visual Studio】生成.i文件

环境 VS版本&#xff1a;VS2013 问题 如何生成.i预编译文件&#xff1f; 步骤 1、打开VS项目属性&#xff0c;打开C/C\预处理器页面&#xff0c;【预处理到文件】选择是&#xff0c;开启。 2、生成文件如下。 3、正常编译需要关闭此选项。

ORB-SLAM2学习笔记9之图像帧Frame

文章目录 0 引言1 Frame类1.1 构造和重载函数1.1.1 双目相机1.1.2 RGBD相机1.1.3 单目相机 1.2 成员函数1.2.1 特征点去畸变1.2.2 特征点网格分配1.2.3 双目匹配1.2.4 RGBD相机深度计算 1.3 成员变量 2 Frame类的用途 0 引言 ORB-SLAM2学习笔记7详细了解了System主类和多线程和…

安卓图形显示系统

Android图形显示系统 Android图形显示系统是Android比较重要的一个子系统&#xff0c;和很多其他子系统的关联紧密。 Android图形系统比较复杂&#xff0c;这里我们从整体上理一遍&#xff0c;细节留待后期再去深入。Android图形系统主要包括以下几个方面&#xff1a; - 渲染…

Shell编程及自动化运维实现

Linux Shell编程及自动化运维实现 变量 Linux Shell编程及自动化运维实现 判断 Linux Shell编程及自动化运维实现 循环 Linux Shell编程及自动化运维实现 数组和函数 Linux Shell编程及自动化运维实现 三剑客 Linux Shell编程及自动化运维实现 综合实战 什么是…

API 接口选择那个?RESTful、GraphQL、gRPC、WebSocket、Webhook

大家好&#xff0c;我是比特桃。目前我们的生活紧紧地被大量互联网服务所包围&#xff0c;互联网上每天都有数百亿次API调用。API 是两个设备相互通讯的一种方式&#xff0c;人们在手机上每次指尖的悦动&#xff0c;背后都是 API 接口的调用。 本文将列举常见的一些 API 接口&…

code论坛系统测试

目录 一 项目介绍**项目名称****项目介绍****项目功能****项目展示** 二 测试用例设计和功能测试1.测试用例设计**①登录页面****②注册页面****③首页****④发布帖子页面****⑤修改个人信息页面** 2.功能测试环境3.实际执行功能测试的部分操作**①登录页面****②注册页面****③…

ps怎么布尔运算多个图层合并?

我们经常使用Photoshop制作大型海报类&#xff0c;也可以用ps进行一些简单icon小图标的制作&#xff0c;这些icon图标多数应用在工具按钮上&#xff0c;比较小巧美观。但是对于ps对图形的操作经常会用到布尔运算的使用&#xff0c;今天小编就给大家详细讲解下ps布尔运算多个图层…
最新文章