第19课 函数
文章目录
前言
函数是完成某种功能的程序段,是程序模块化的体现。对于一个复杂的问题,可以将其分解成若干个子问题来解决,如果子问题依然很复杂,还可以将它继续分解。直到每个子问题都是一个具有独立任务的模块。以这种方式编写的程序结构清晰,逻辑关系明确,会给编写、阅读、调试以及修改带来很多好处。
一个程序可以有许多函数,包括主函数和非主函数,主函数只能有一个,非主函数可以有多个。主函数自动执行,非主函数只有被调用时才会执行。程序从主函数开始执行到主函数结束而结束,主函数可以调用任何非主函数,非主函数之间可以互相调用,但不能调用主函数。函数调用过程中,调用其他函数的函数叫作主调函数,相应的被调用的函数叫作被调函数。
提示
主函数和主调函数是不同的。主函数特指 main ()函数,主调函数是指调用其他函数的函数。主调函数可以是主函数,也可以是非主函数。
一、函数的定义与声明
二、函数的调用与返回
三、函数的嵌套与递归
1. 函数的嵌套
2. 函数的递归
函数的递归调用是指函数自身调用自身,这是一类特殊的函数嵌套。
递归函数的一般格式
数据类型 函数名(形参列表) {
if<递归结束条件> return 返回值;
else return 函数名(形参该变量)
}
例如
long long factor(int n) {
if(n==0) return 1;
else return n*factor(n-1);
}
构成递归需要具备两个条件:
- 必须有结束条件和结束值
- 参数该变量使参数向结束条件发展
1. 求n!
非递归的一般实现代码。
long long factorial(int n) {
long long s=1;
for(int i=1; i<=n; i++) {
s *= i;
}
return s;
}
递归方式的代码。
long long factorial(int n) {
if(n==0) {
return 1;
}
else {
return n*factorial(n-1);
}
}
下面是factorial(2),即当实参为2时的递归调用过程:
- main()调用factor(2)时,main()暂停,进入factor(2);
- 执行factor(2)中的return 2*factor(2-1)时, factor(2)暂停,调用factor(1);
- 执行factor(1)中的return 1*factor(1-1)时, factor(1)暂停,调用factor(0);
- 执行factor(0)中的return 1;语句, factor(0)返回值1,回退至factor(1)调用处;
- 执行factor(1)中的return 1*1时, factor(1)返回值1,回退至factor(2)调用处;
- 执行factor(2)中的return 2*1时, factor(2)返回值2,返回至main()调用处。
图1是当n==5时的递归过程。
图1 递归求5!
2. Hanoi(汗诺)塔问题
Hanoi (汉诺)塔问题。这是一个古典的数学问题,是一个用递归方法解题的典型例子。问题是这样的:古代有一个梵塔,塔内有3个座 A 、 B 、 C ,开始时 A 座上有64个盘子,盘子大小不等,大的在下,小的在上(图2)。有一个老和尚想把这64个盘子从 A 座移到 C 座,但每次只允许移动一个盘,且在移动过程中在3个座上都始终保持大盘在下,小盘在上。在移动过程中可以利用 B 座,要求编程序输出移动的步骤。
图2 Hanoi(汗诺)塔问题
为便于理解,先分析将 A 座上3个盘子移到 C 座上的过程:
1 将 A 座上2个盘子移到 B 座上(借助 C );
2 将 A 座上1个盘子移到 C 座上;
3 将 B 座上2个盘子移到 C 座上(借助 A )。
图3 将 A 座上2个盘子移到 B 座上(借助 C )
图4 将 B 座上2个盘子移到 C 座上(借助 A )
其中第2步可以直接实现。第1步又可用递归方法分解为:
1.1 将 A 上1个盘子从 A 移到 C ;
1.2 将 A 上1个盘子从 A 移到 B ;
1.3 将 C 上1个盘子从 C 移到 B 。
第3步可以分解为:
3.1 将 B 上1个盘子从 B 移到 A 上;
3.2 将 B 上1个盘子从 B 移到 C 上;
3.3 将 A 上1个盘子从 A 移到 C 上。
将以上综合起来,可得到移动3个盘子的步骤为:
A→C, A→B, C→B, A→C, B→A, B→C, A→C.
共经历7步。由此可推出:移动n个盘子要经历
2
n
?
1
2^n-1
2n?1步。如移4个盘子经历15步,移5个盘子经历31步,移64个盘子经历
2
64
?
1
2^{64}-1
264?1步。
由上面的分析可知:将n个盘子从A座移到C座可以分解为以下3个步骤:
(1) 将A上 n-1个盘借助C座先移到B座上;
(2) 把A座上剩下的一个盘移到C座上;
(3) 将n-1个盘从B座借助于A座移到C座上。
上面第(1)步和第(3)步,都是把n-1个盘从一个座移到另一个座上,采取的办法一样的,只是座的名字不同而已。为使之一般化,可以将第(1)步和第(3)步表示为:将“one”座上n-1个盘移到“two”座(借助“three”座)。只是在第(1)步和第(3)中,one、two、three 和A、B、C的对应关系不同。对第(1)步,对应关系是one对应A,two对应B,three对应C。对第(3)步,是:one对应B, two对应C, three对应A。
因此,可以把上面3个步骤分成两类操作:
(1)当n==1时,直接将1个盘子从一个座上移到另一座上。
(2)当n>1时,将n-1个盘从一个座移到另一个座上。它是一个递归的过程(又可以拆分成3大步骤)。
下面编写程序,分别用两个函数实现以上的两类操作。
- 函数调用hanoi(n, p1, p2, p3)表示将n个盘子从p1座移到p3座的过程(借助p2座);
- 函数调用move(n, from, to)表示将1个盘子从 from座移到to座的过程。 from、to是代表 A、B、C 座之一,根据每次不同情况分别取A、B、C 代入。
对应的函数定义如下。
void move(int no, char from, char to) {
cout << "将" << no << "号盘子从" << from << "移动到" << to << endl;
}
// 将n个碟子从p1挪动到p3, p2是中间的柱子
void hanoi(int n, char p1, char p2, char p3) {
if(n==1) {
move(n, p1, p3);
} else {
//将p3看作中间柱子,将n-1个碟子从p1移动到p2
hanoi(n-1, p1, p3, p2);
//将编号为n的盘子从p1移动到p3
move(n, p1, p3);
//将p1看作中间柱子,将n-1个碟子从p2移动到p3
hanoi(n-1, p2, p1, p3);
}
}
① 将n-1个盘子从A移动到B
图5 将n-1个盘子从A移动到B
② 将编号为n的盘子从A移动到C
图6 将编号为n的盘子从A移动到C
③ 将n-1个盘子从B移动到C
图7 将n-1个盘子从B移动到C
步骤①③ 明显是个递归调用
所以,Hanoi(汗诺)塔问题完整的递归实现代码如下。
#include<iostream>
using namespace std;
void move(int no, char from, char to) {
cout << "将" << no << "号盘子从" << from << "移动到" << to << endl;
}
//将n个碟子从p1挪动到p3, p2为中间的柱子
void hanoi(int n, char p1, char p2, char p3) {
if(n==1) {
move(n, p1, p3);
} else {
//将p3看作中间柱子,将n-1个碟子从p1移动到p2
hanoi(n-1, p1, p3, p2);
//将编号为n的盘子从p1移动到p3
move(n, p1, p3);
//将p1看作中间柱子,将n-1个碟子从p2移动到p3
hanoi(n-1, p2, p1, p3);
}
}
int main() {
hanoi(3, 'A', 'B', 'C');
return 0;
}
当n==3时,程序输出如下。
将1号盘子从A移动到C
将2号盘子从A移动到B
将1号盘子从C移动到B
将3号盘子从A移动到C
将1号盘子从B移动到A
将2号盘子从B移动到C
将1号盘子从A移动到C
四、局部变量与全局变量
1. 局部变量
- 局部变量是指在函数内部定义的变量。前面所有例题中的变量都是在函数内定义的,因此都是局部变量。
- 局部变量的作用域(即起作用的范围)是从定义点到函数结束,即局部变量只在定义它的函数内有效。
- 由于局部变量的作用域仅局限于本函数内,因此,在不同函数中局部变量名可以相同,它们分别代表不同的对象,互不干扰。
2. 全局变量
- 全局变量是指在函数外定义的变量。其作用域是从变量的定义点到程序结束,即从变量定义之后的所有函数都可以访问该全局变量。
- 全局变量和局部变量可以同名,但在局部变量的作用域内全局变量无效。
- 当全局变量没有初始化时默认初始值为0,局部变量没有初始化时默认初始值不确定。
- 由于全局变量的作用域可以覆盖整个程序,因此可以作为共享数据在不同函数中传递数据。在函数调用中,可以通过参数从主调函数向被调函数传递数据(实参传给形参),也可以通过返回值从被调函数向主调函数传递数据,但二者都是单向传递,即只能向一个方向传递数据。而全局变量既可以在主调函数中使用,又可以在被调函数中使用,实现了数据的双向传递。
阅读下面的代码,给出代码运行的结果。
#include<iostream>
using namespace std;
int x, y;
int fun1(int s) {
int x=10;
y=x*s;
return x+y;
}
int main() {
int n;
cin >> n;
cout << "fun1(" << n << ")=" << fun1(n) << endl;
cout << "x=" << x << ", y=" << y << endl;
return 0;
}
代码运行输出为
5
fun1(5)=60
x=0, y=50
五、课后练习
1. 求3个数的最小公倍数
#include<iostream>
using namespace std;
int max(int x1, int x2, int x3) {
if(x1>x2 && x1>x3) return x1;
else if(x2>x1 && x2>x3) return x2;
else return x3;
}
int main() {
int x, y, z;
int biggest, m, i=1;
cin >> x >> y >> z;
biggest = max(x, y, z);
while(true) {
m = biggest * i;
if(m%x==0 && m%y==0 && m%z==0) break;
i++;
}
cout << m << endl;
return 0;
}
运行程序,输入
2 4 8
会输出
8
2. 求第5个人的年龄是多少
有5个人坐在一起,问第5个人多少岁?他说比第4个人大2岁。问第4个人岁数,他说比第3个人大2岁。问第3个人,又说比第2个人大2岁。问第2个人,说比第1个人大2岁。最后问第1个人,他说是10岁。请问第5个人多大。
显然,这是一个递归问题。
要求第5个人的年龄,就必须先知道第4个人的年龄,而第4个人的年龄也不知道,要求第4个人的年龄必须先知道第3个人的年龄,而第3个人的年龄又取决于第2个人的年龄,第2个人的年龄取决于第1个人的年龄。而且每一个人的年龄都比其前1个人的年龄大2。即:
age(5)= age(4)+2
age(4)= age(3)+2
age(3)= age(2)+2
age(2)= age(1)+2
age(1)=10
可以用数学公式表述如下:
a
g
e
(
n
)
=
{
?
10
n
=
10
?
a
g
e
(
n
?
1
)
+
2
n
>
1
age(n)= \begin{cases} \ 10 & n=10\\ \ age(n-1)+2 & n>1 \end{cases}
age(n)={?10?age(n?1)+2?n=10n>1?
公式本身就是一个递归式,过程图示如图8所示:
图8 求第5个人的年龄
据此,可以直接给出C++的递归实现。
#include<iostream>
using namespace std;
int age(int n) {
if(n==1) return 10;
else return age(n-1)+2;
}
int main() {
int n=5;
cout << age(n); // 18
return 0;
}
2. Hanoi(汗诺)塔问题
见前面相关内容。
补充练习
1. 用自定义函数difference_abs(m,n)求两个数之差的绝对值
2. 用自定义函数matrix_max(m, n)求一个m*n阶矩阵中最大的元素值
3. 用函数递归的方式求斐波那契数列(Fibonacci sequence)的第20项
斐波那契数列数值为:1, 1, 2, 3, 5, 8, 13, 21, 34, …在数学上,这一数列以如下递推的方法定义:F(0)=1, F(1)=1, F(n)=F(n-1)+F(n-2)(n≥2,n∈N),即
f
(
n
)
=
{
?
0
n
=
0
?
1
n
=
1
?
f
(
n
?
1
)
+
f
(
n
?
2
)
n
>
1
f(n)= \begin{cases} \ 0 & n=0\\ \ 1 & n=1\\ \ f(n-1)+f(n-2) & n>1 \end{cases}
f(n)=?
?
???0?1?f(n?1)+f(n?2)?n=0n=1n>1?
4. 输出矩阵形式的杨辉三角——帕斯卡三角形
帕斯卡三角形,是一个三角形矩阵,其顶端是 1,视为(row0)。第1行(row1)(1&1)两个1,这两个1是由他们上头左右两数之和 (不在三角形内的数视为0)产生。依此类推产生第2行(row2): 0+1=1; 1+1=2; 1+0=1。第3行(row3): 0+1=1; 1+2=3; 2+1=3; 1+0=1。循此法可以产生其余行元素——即 ( a + b ) n (a+b)^n (a+b)n的展开式中的各项系数(n=0表示杨辉三角的第0行row0)。
自定义函数YanghuiTriangle(a[][7])输出行数N=7的杨辉三角,如图9所示。
图9 输出的行数N=7的杨辉三角
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!