文章目录
5.1 函数的概念
函数写好后可被调用任意多次。调用函数,相当于将程序执行权移交给函数定义代码。函数会运行至结束或遇到return语句。之后,执行权还给调用者。
5.2 函数的使用
建议用以下方式创建和调用用户自定义函数。
- 在程序开头 声明 函数
- 在程序某个地方 定义 函数。
- 其他函数 调用 该函数
步骤1:声明函数(创建函数原型)
虽然没有严格要求,但通常应该在程序开头创建函数(main除外)原型。C++要求函数先声明再使用;这种声明既可以是原型,也可以是定义(步骤2)。
函数原型只提供类型信息,语法如下:
返回类型 函数名(参数列表);
其中,返回类型描述函数返回什么类型的值。如函数不返回值,就使用特殊的void类型(即空类型)。
参数列表是包含零个或多个参数名称的列表,多个参数以逗号分隔。每个前面都要附加正确的类型名称。(技术上说不需要在原型中提供参数名称,但这是良好的编程实践。)
例如,以下语句声明avg函数,获取两个double参数,返回double值。
double avg(double x, double y);
参数列表可为空,表明函数不接搜任何参数。
步骤2:定义函数
函数定义描述函数要做的事情,语法如下:
返回类型 函数名(参数列表){
语句
}
看起来和原型差不多,唯一区别是分号被替换成两个大括号之间的零个或多个语句。不管包含多少语句,大括号都是必须的。例如:
double avg (double x, double y){
return (x + y) / 2;
}
return 语句导致立即退出,并返回(x + y) / 2的求值结果。如函数不返回值,直接写return;退出。
步骤3:调用函数
函数定义号之后就可以从任意函数中使用(调用)任意多次。例如:
n = avg(9.5, 11.5);
n = avg(5, 25);
n = avg(27, 154.3);
函数调用是表达式,只要它返回值(而不是void),就可以在更大的表达式中使用。例如:
z = x + y + avg(a, b) + 25.3;
调用函数时,函数调用中指定的值传给函数的参数。下图展示了avg函数调用的一个例子,使用6.5和11.5作为输入。它们作为实参传给函数。函数返回值赋给z。
例 5.1 :avg()函数
// avg.app
# include <iostream>
using namespace std;
// 函数使用前必须声明
double avg (double x, double y);
int mian()
{
double a = 0.0;
double b = 0.0;
cout << "输入第一个数并按Enter:";
cin >> a;
cout << "输入第二个数并按Enter:";
cin >> b;
// 调用avg函数
cout << "平均数是:" << avg(a, b) << endl;
return 0;
]
// 定义avg函数
double avg(double x, double y)
{
return (x + y) / 2;
}
工作原理:
代码很简单,但演示了前面描述的三个步骤。
- 在程序顶部声明函数(创建函数原型)。
- 在程序某个地方定义函数。
- 从另一个函数(本例是main)中调用该函数。
虽然函数声明(原型)应该总是放到程序开头。一般规则是函数必须在调用前声明,但不一定在调用前定义。
函数调用函数
程序可包含任意数量的函数。
// avg2.cpp
# include <iostream>
using namespace std;
// 函数使用前必须声明
void print_results(double a, double b);
double avg(double x, double y);
int main()
{
double a = 0.0;
double b = 0.0;
cout << "输入第一个数并按Enter:";
cin >> a;
cout << "输入第二个数并按Enter:";
cin >> b;
// 调用print_results函数
print_results(a, b);
return 0;
}
// 定义print_results 函数
void print_results (double a, double b)
{
void print_results(double a, double b)
{
cout << "平均数是:" << avg(a, b) << endl;
}
// 定义avg 函数
double avg(double x, double y)
{
return (x + y) / 2;
}
}
练习
练习 5.1.1
写程序定义并测试factorial(阶乘)函数,N的阶乘是1到N的所有整数的乘积。例如:5的阶乘是1 * 2 * 3 * 4 * 5 = 120.提示:使用第4章描述的for循环。
// 测试factorial(阶乘)函数
# include <iostream>
using namespace std;
int factorial(int a);
int main()
{
int n = 0;
cout << "输入一个数:";
cin >> n;
factorial(n);
return 0;
}
int factorial(int a)
{
int b = 1;
for (int i = 1; i <= a; ++i)
{
b = b * i;
}
cout << "结果为:" << b << endl;
return b;
}
练习 5.1.2
写print_out函数打印1到N的所有整数。将函数放到程序中测试,传递一个从键盘输入的数。print_out函数应具有void类型,因为不需要返回一个值。函数可用一个简单的语句来调用:
print_out(n);
// print_out 函数打印1到N的所有整数
# include <iostream>
using namespace std;
void print_out(int n);
int main()
{
int a = 0;
cout << "请输入一个整数:";
cin >> a;
print_out(a);
return 0;
}
void print_out(int n)
{
for (int i = 1; i <= n; ++i)
{
cout << i << " ";
}
}
例 5.2:质数函数
// prime2.cpp
# include <iostream>
# include <cmath> // 因为要调用sqrt
using namespace std;
// 函数使用前必须声明
bool prime(int n);
int main()
{
int n = 0;
// 设置无限循环,用户输入0中断:
// 否则测试n是否质数
while (true)
{
cout << "输入数字(0 = 退出)并按Enter:";
cin >> n;
if (n == 0)
{
break;
}
if (prime(n))
{
cout << n << "是质数" << endl;
}
else
{
cout << n << "不是质数" << endl;
}
}
return 0;
}
// 质数函数,测试2到n平方根的整除数
// 找到整除数就返回false,否则返回true
bool prime (int n)
{
for (int i = 2; i <= sqrt(n); ++i)
{
if (n % i == 0)
{
return false; //如果n被i整除,那么n不是质数
}
}
return true; //没有发现整除数,表明n是质数
}
工作原理:
和以前一样,程序遵循基本模式:
- 在程序开始处声明函数类型信息(定义原型);
- 在程序某个地方定义函数
- 调用函数
练习
练习5.2.1
优化质数测试函数,每次调用函数,都只计算n的平方根一次。声明double类型的局部变量sqrt_of_n。(提示:函数中声明的变量就是该函数的局部变量。)然后在循环条件中使用该变量。
# include <iostream>
# include <cmath>
using namespace std;
bool prime(int n);
int main()
{
int n = 0;
while (true)
{
cout << "输入数字(0 = 退出)并按Enter:";
cin >> n;
if (n == 0)
{
break;
}
if (prime(n))
{
cout << n << "是质数。" << endl;
}
else {
cout << n << "不是质数。" << endl;
}
}
return 0;
}
bool prime(int n)
{
double sqrt_of_n;
sqrt_of_n = sqrt(n);
for (int i = 2; i <= sqrt_of_n; ++i)
{
if (n % i == 0)
{
return false;
}
}
return true;
}
练习5.2.2
重写main测试2到20的所有数,打印是不是质数,一行打印一个。提示:用for循环,i从2到20。
// 重写main函数测试2到20的所有数
# include <iostream>
# include <cmath>
using namespace std;
bool prime(int n);
int main()
{
for (int i = 2; i <= 20; ++i)
{
if (prime(i))
{
cout << i << "是质数。" << endl;
}
}
return 0;
}
bool prime(int n)
{
double sqrt_of_n = sqrt(n);
for (int i = 2; i <= sqrt_of_n; ++i)
{
if (n % i == 0)
{
return false;
}
}
return true;
}
练习5.2.3
写程序找出大于10亿(1000000000)的第一个质数。
// 找出大于10亿的第一个质数
# include <iostream>
# include <cmath>
using namespace std;
bool prime(int n);
int main()
{
int n = 1000000000;
while (!prime(n))
{
++n;
}
cout << "这个比10亿大的第一个质数是:" << n << endl;
return 0;
}
bool prime (int n)
{
double sqrt_of_n = sqrt(n);
for (int i = 2; i <= sqrt_of_n; ++i)
{
if (n % i == 0)
{
return false;
}
return true;
}
}
练习5.2.4
写程序让用户输入n,找出大于n的第一个质数。
// 找出大于n的第一个质数
# include <iostream>
# include <cmath>
using namespace std;
bool prime (int n);
int main()
{
int n = 0;
int x = 0;
cout << "请输入一个数:";
cin >> n;
x = n;
while (!= prime(n))
{
++n;
}
cout << "第一个大于" << x << "的质数是:" << n << endl;
return 0;
}
bool prime (int n)
{
double sqrt_of_n = sqrt(n);
for (int i = 2; i <= sqrt_of_n; ++i)
{
if (n % i == 0)
{
return false;
}
return true;
}
}
5.3 局部和全局变量
在任何函数定义的外部声明的变量就是全局(非局部)变量。一般将所有全局声明放在接近程序开头的地方,在第一个函数之前。全部变量的作用域是从它声明的地方开始,直到文件结束。
例如,下例在main之前声明全局变量status:
# include <iostream>
# include <cmath>
using namespace std;
int status = 0;
void main()
{
//
}
现在,任何函数都能访问status变量。由于是全局的,所以只存在它的一个拷贝。一个函数更改了status,会在其他函数中反映出来。
C++默认以“传值”方式传递实参,即函数获得它自己的所传数据的拷贝。结果是在函数中对实参的修改不会影响到外面。这些实参是“仅输入”的数据。由于输出,就是函数的返回值。
这会造成一个比较尴尬的情况,如下图所示,函数只能通过它的一个返回值或修改全局变量的值向调用者提供输出。后者虽然能够接受,但缺点也很明显,因为太多全局变量会让人感到不爽。第7章会讲到指针,将解释如何克服这些限制,通过“传引用”的实参返回多个值。
全局变量存在的意义
基于上一节的描述,全局变量可能很危险。用上瘾了会有隐患,因为一个函数的修改可能对其他函数造成非预期的影响。既然危险,为什么还要用?
事实上,经常都需要全局变量。有的时候,全局变量是在多个函数之间通信的最佳方式:否则,只能用一个长长的参数列表来回传输所有程序信息。第11章开始讲解类,它是在密切相关的函数之间共享数据的一种备选的、而且通常更优的方式。同一个类的函数可以访问别的函数访问不到的私有数据。
5.4 递归函数
函数自己调用自己称为递归。和无限循环一样,如函数自己调用自己,那么何时终止?答案很简单:加一个终止机制。
以练习 5.1.1的阶乘函数为例,可重写为递归函数:
int factorial (int n)
{
if (n <= 1)
{
return 1;
}
else
{
return n * factorial (n - 1); // 递归!
}
}
对于大于1的任何数,阶乘函数都会发出对它自己的一个调用,只是传递一个小1的数。最后调用factorial(1),之后终止。
这形成了一个函数调用“栈”(stack),每个都为n传递不同的实参,最后依次返回。栈是计算机维护的一个特殊内存区域,采用“后入先出”(last-in-first-out,LIFO)机制跟踪所有未决函数调用的信息(包含实参和局部变量)。
用for语句实现的许多函数都可改为用递归实现。但不推荐无脑使用递归。本例就是一个例子。它造成程序将1到n的所有值都存储到栈上,而不是直接在一个循环中累加,所以反而影响了效率。下一节将更好地利用递归。
但本节确实演示了递归的两个重点。递归函数(调用自身的函数)必须做到以下两点。
- 为了解决层数为n的一个常规问题,假定已解决了n-1层。
- 指定至少一个终止条件,比如n == 1或n == 0.
例5.3:质因数分解
我们已拥有完成该任务所需的几乎全部代码。对以前的质数程序进行少量修改即可。要进行质因数分解,先获得最小的整除数,再继续分解剩余的商。
为了获得n的所有整除数:
For 2到“n的平方根”的所有整数
If n能被循环变量(i)整除
打印i,后跟一个逗号,然后
传递n / i 重新运行函数
退出当前函数
如果没有找到整除数,就打印n本身
这是典型的递归逻辑。我们决定让get_divisors函数调用它自己。
// prime3.cpp
# include <iostream>
# include <cmath>
using namespace std;
void get_divisors (int n);
int main()
{
int n = 0;
cout << "输入一个数并按Enter:";
cin >> n;
get_divisors (n);
cout << endl;
return 0;
}
// 质因数分解函数,打印n的所有质因数
// 查找最小质因数i,传递 n/i 来重新运行它自身
void get_divisiors (int n)
{
double sqrt_of_n = sqrt (n);
for (int i = 2; i <= sqrt_of_n; ++i)
{
if (n % i == 0) // 如果i整除n
{
cout << i << ","; // 打印i
get_divisors (n / i); //分解 n/i
return; // 并推出
}
}
// 没有找到整除数,则n为质数
// 打印n,不再继续调用
cout << n;
}
练习
练习5.3.1
重写例5.3的main函数,打印“输入一个数(0=退出)并按Enter:”。程序调用get_divisors来显示质因数分解,提醒用户再次输入,直到输入0。提示:参考例5.2.
# include <iostream>
# include <cmath>
using namespace std;
void get_divisors(int n);
int main()
{
int n = 0;
while (true)
{
cout << "请输入一个数(0表示退出):";
cin >> n;
if (n == 0)
{
return false;
}
get_divisors(n);
cout << endl;
}
return 0;
}
void get_divisors(int n)
{
double sqrt_of_n = sqrt(n);
for (int i = 2; i <= sqrt_of_n; ++i)
{
if (n % i == 0)
{
cout << i << ", ";
get_divisors(n / i);
return;
}
}
cout << n;
}
例5.3.2
写程序用递归函数计算三角形数。三角形数是1到n的所有整数之和,其中n需要指定。例如,triangle(5) = 5 + 4 + 3 + 2 + 1。
//
# include <iostream>
using namespace std;
int triangle(int n);
int main()
{
int n = 0;
cout << "请输入一个数:";
cin >> n;
cout << "三角形数之和是:" << triangle(n);
return 0;
}
int triangle(int n)
{
if (n <= 1)
{
return 1;
}
else {
return n + triangle(n - 1);
}
}
练习5.3.3
修改例5.3使用非递归方案。这样肯定要写更多的代码。提示:用两个函数get_all_divisors和get_lowest_divisor简化工作。在main中调用get_all_divisors,后者反复调用get_lowest_divisor,每次都将n替换成 n/i,其中i是找到的整除数。返回n,表明该数是质数,循环终止。
# include <iostream>
# include <cmath>
using namespace std;
void get_all_divisors(int n);
int get_lowest_divisor(int n);
int main()
{
while (true)
{
int n = 0;
cout << "请输入一个数(0表示退出): ";
cin >> n;
if (n == 0)
{
break;
}
get_all_divisors(n);
cout << endl;
}
return 0;
}
void get_all_divisors(int n)
{
int result = get_lowest_divisor(n);
while (result < n)
{
cout << result << ", ";
n = n / result;
result = get_lowest_divisor(n);
}
cout << n << endl;
}
int get_lowest_divisor(int n)
{
double sqrt_of_n = sqrt(n);
for (int i = 2; i <= sqrt_of_n; ++i)
{
if (n % i == 0)
{
return i;
}
}
return n;
}
例5.4:欧几里得最大公因数算法
最大公因数——Greatest Common Factor,GCF
著名希腊数学家欧几里得提出了最大公因数算法:
为了计算A和B两个整数的最大公因数:
If B等于0,
答案是A
Else
答案是GCF(B,A%B)
# include <cstdlib>
# include <iostream>
using namespace std;
int gcf(int a, int b);
int main()
{
int a = 0, b = 0; // 要计算最大公因数的两个数
cout << "输入a: ";
cin >> a;
cout << "输入b:";
cin >> b;
cout << "GCF = " << gcf(a, b) << endl;
return 0;
}
int gcf(int a, int b)
{
if (b == 0)
{
return a;
}
else
{
return gcf(b, a%b);
}
}
练习
练习 5.4.1
修改程序打印算法涉及的全部步骤,示例如下:
GCF(500, 300) =>
GCF(300, 200) =>
GCF(200, 100) =>
GCF(100, 0) =>
100
答案:
# include <cstdlib>
# include <iostream>
using namespace std;
int gcf(int a, int b);
int main()
{
int a = 0, b = 0; // 要计算最大公因数的两个数
cout << "输入a: ";
cin >> a;
cout << "输入b:";
cin >> b;
cout << "GCF = " << gcf(a, b) << endl;
return 0;
}
int gcf(int a, int b)
{
cout << " GCF(" << a << "," << b << ")" << endl;
if (b == 0)
{
return a;
}
else
{
return gcf(b, a%b);
}
}
练习 5.4.2
面向专家:修改gcf函数使用迭代(基于循环)而非递归。每次循环迭代都在B为零时终止;否则设置A和B的新值并进入下一次迭代。需设置临时变量temp来容纳B的旧值:
temp = b;
b = a%b;
a = temp;
答案:
# include <iostream>;
using namespace std;
int gcf(int a, int b);
int main()
{
int a = 0, b = 0;
cout << "输入a:";
cin >> a;
cout << "输入b:";
cin >> b;
cout << "GCF = " << gcf(a, b) << endl;
return 0;
}
int gcf(int a, int b)
{
while (b > 0)
{
int temp = a;
a = b;
b = temp % b;
}
return a;
}
证明过程
前面解释了部分欧几里得算法。没有解释的是为何(B,A%B)的最大公因数也是(A,B)的最大公因数。要使此结论成立,需证明以下两点。
- 如果一个数是A和B的公因数,那么也是A%B的因数。
- 如果一个数是B和A%B的公因数,那么也是A的公因数。
如以上假设成立,那么一个数对的所有公因数都是另一个数对的公因数。换言之,(A,B)的公因数集合和(B,A%B)的公因数集合完全一致,具有相同的最大公因数。
来看看取余操作符%,假定m是整数,那么:
A = mB + A%B
A%B等于或小于A,所以算法会获得越来越小的数。假定整数n是A和B的公因数(能整除两者),那么: A = cn b = dn
其中c和d是整数,所以: cn = m(dn) + A%B A%B = cn - mdn = n(c - md)
这证明如果n是A和B的公因数,那么也是A%B的因数。通过类似的推导,可证明如果n是B和A%B的公因数,那么也是A的因数。
由于(A,B)的因数和(B,A%B)的因数完全一致,所以具有相同的最大公因数。所以,GCF(A,B)等于GCF(B,A%B)。证明完毕!
例 5.5:汉诺塔
严格地说,前面的例子并非一定需要递归。稍加改动,就可用基于循环的迭代函数解决。但下例演示了某些问题只有用递归才能优美地结局。
这就是汉诺塔(Tower of Hanoi)问题。有三个塔,每个塔由一叠穿孔圆盘组成,圆盘由下到上依次变小。要求按规则将第一叠圆盘全部移至第三叠,规则如下:
- 一次只能移动一个盘
- 大盘不能叠在小盘上
假定一塔4盘。先将顶部的盘移走,但移到哪里,之后怎么办?为了解决问题,假定已知如何移动 n-1 个盘。那么,为了将n个盘从源塔移至目标塔,要有以下行动:
- 将 n-1 个盘从源塔移至(当前)未使用的(或其他)塔。
- 将一个盘从源塔移至目标塔。
- 将 n-1 个盘从“其他”塔移至目标塔。
//tower.cpp
# include <iostream>
using namespace std;
void move_rings(int n, int src, int dest, int other); //移动多个盘
void move_a_ring(int src, int dest); //移动一个盘
int main()
{
int n = 3; // 假定一塔3盘
move_rings(n, 1, 3, 2); // 塔1移至塔3,“其他塔”是塔2
return 0;
}
void move_rings(int n, int src, int dest, int other)
{
if (n == 1)
{
move_a_ring(src, dest);
}
else
{
move_rings(n - 1, src, other, dest); //步骤1
move_a_ring(src, dest); //步骤2
move_rings(n - 1, other, dest, src); // 步骤3
}
}
void move_a_ring(int src, int dest)
{
cout << "从塔" << src << "移至塔" << dest << endl;
}
练习
练习 5.5.1
修改程序让用户为n输入任意正整数。检查输入是否大于0更佳。
答案:
# include <iostream>
using namespace std;
void move_rings(int n, int src, int dest, int other);
void move_a_ring(int src, int dest);
int main()
{
int n = 0;
cout << "请输入盘的数量:";
cin >> n;
while (n <= 0)
{
cout << "对不起,n 必须大于0.请重新输入:";
cin >> n;
}
move_rings(n, 1, 3, 2);
cout << endl;
return 0;
}
void move_rings(int n, int src, int dest, int other)
{
if (n == 1)
{
move_a_ring(src, dest);
}
else
{
move_rings(n - 1, src, other, dest);
move_a_ring(src, dest);
move_rings(n - 1, other, dest, src);
}
}
void move_a_ring(int src, int dest)
{
cout << "从塔" << src << "移至塔" << dest << endl;
}
练习 5.5.2
不是直接在屏幕上打印“移至”消息,而是让 move_ring 函数调用另一个函数 exec_move。后者获取源塔和目标塔的编号作为实参。由于这是一个单独的函数,可以写任意多行代码来打印更完善的消息,例如:
将最顶部的盘从塔1移至塔3
答案:
# include <iostream>
using namespace std;
void move_rings(int n, int src, int dest, int other);
void exec_move(int src, int dest);
int main()
{
int n = 0;
cout << "请输入盘的数量:";
cin >> n;
while (n <= 0)
{
cout << "对不起,n必须是大于0的数。请重新输入:";
cin >> n;
}
move_rings(n, 1, 3, 2);
cout << endl;
return 0;
}
void move_rings(int n, int src, int dest, int other)
{
if (n == 1)
{
exec_move(src, dest);
}
else
{
move_rings(n - 1, src, other, dest);
exec_move(src, dest);
move_rings(n - 1, other, dest, src);
}
}
void exec_move(int src, int dest)
{
cout << "将最顶部的盘从塔" << src << "移至塔" << dest << endl;
}
例5.6:随机数生成器
本节的程序模拟掷骰子任意次数的结果。它调用 rand_0toN1函数,函数获取实参n,随机返回 0 到 n - 1的一个数。例如,假定用户输入6,程序模拟掷骰子,可能产生以下输出:
3 4 6 4 5 3 2 4 6
//dice.cpp
# include <iostream>
# include <cstdlib> //支持rand 和 srand
# include <ctime> //支持时间函数
using namespace std;
int rand_0toN1(int n);
int main()
{
int n = 0;
int r = 0;
srand(time(nullptr)); //设置随机数种子
cout << "掷多少次骰:";
cin >> n;
for (int i = 1; i <= n; ++i)
{
r = rand_0toN1(6) + 1; //获取1到6的随机数
cout << r << " "; //打印该数
}
return 0;
}
// 返回0到n-1的随机数
int rand_0toN1(int n)
{
return rand() % n;
}
记住从C++11起,编译器要求支持nullptr指针类型,即“空指针”。老的编译器可能需要将nullptr 替换为 NULL 或0.另外,用 static_cast 操作符可防止出现警告。
rand输出的随机数范围太大,可能是任何无符号整数(最大值由RAND_MAX定义)。%操作符的妙处在于,不管范围多大,只要 rand 函数输出大于或等于 n - 1的数,那么 rand_0toN1 函数必然返回0到 n-1 的结果。
本例n等于6,所以函数返回0到5的值。加1确保获得1到6的随机数,这正是我们想要的 。
练习
练习 5.6.1
重写 rand_0toN1函数,改名为 rand_1toN,返回1到N(而不是0到N-1)的随机数。N是传给它的整数实参。
答案:
# include <iostream>
# include <cstdlib> //支持rand 和 srand
# include <ctime> //支持时间函数
using namespace std;
int rand_0toN(int n);
int main()
{
int n = 0;
int r = 0;
srand(time(nullptr)); //设置随机数种子
cout << "掷多少次骰:";
cin >> n;
for (int i = 1; i <= n; ++i)
{
r = rand_0toN(6) + 1; //获取1到6的随机数
cout << r << " "; //打印该数
}
return 0;
}
// 返回0到n-1的随机数
int rand_1toN(int n)
{
return rand() % n + 1;
}
练习 5.6.2
写函数返回 0.0 到 1.0 的随机浮点数。提示:调用rand,使用 static_cast®将结果r强制转换为double类型,然后用 int 范围的最大值(RAND_MAX)来除。函数返回类型是double。
//dice.cpp
# include <iostream>
# include <cstdlib> //支持rand 和 srand
# include <ctime> //支持时间函数
using namespace std;
double rand_0to1();
int main()
{
int n = 0;
int r = 0;
srand(time(nullptr)); //设置随机数种子
cout << "掷多少次骰:";
cin >> n;
for (int i = 1; i <= n; ++i)
{
r = rand_0to1(); //获取1到6的随机数
cout << r << " "; //打印该数
}
return 0;
}
// 返回0到n-1的随机数
double rand_0to1()
{
double r = rand ();
return r / RAND_MAX;
}
5.5 继续游戏
// nim2.cpp
# include <iostream>
# include <ctime>
# include <cstdlib>
using namespace std;
int rand_0toN1(int n);
int main()
{
int total, n;
srand(time(nullptr)); // 设置随机数种子
cout << "欢迎进入NIM游戏,选一个数吧:";
cin >> total;
while (true)
{
// 选择最佳应对并打印结果
if ((total % 3) == 2)
{
total = total - 2;
cout << "我减2." << endl;
}
else
if ((total % 3) == 1)
{
--total;
cout << "我减1." << endl;
}
else
{
n = 1 + rand_0toN1(2); // n = 1 或 2.
total = total - n;
cout << "我减";
cout << n << "。" << endl;
}
cout << "现在的数是:" << total << endl;
if (total <= 0)
{
cout << "我赢了!" << endl;
break;
}
// 获取用户的应对:必须是1或2
cout << "输入要减多少(1或者2):";
cin >> n;
while (n < 1 || n > 2)
{
cout << "只能输入1或者2." << endl;
cout << "请重输:";
cin >> n;
}
total = total - n;
cout << "现在的数是:" << total << endl;
if (total <= 0)
{
cout << "你赢了!" << endl;
break;
}
}
return 0;
}
int rand_0toN1(int n)
{
return rand() % n;
}
小结
- C+函数定义特定任务,类似于其他语言中的子程序或过程。C++用“函数”一词统称所有这样的例程,无论它们是否返回值。
- 需在程序开头声明好所有函数(main不用),以提供所需的类型信息。函数声明也称为“函数原型”,语法如下:
返回类型函数名(参数列表);
还需要在程序某个地方定义函数,描述函数要做的事情,语法如下:
返回类型 函数名(参数列表)
语句
}
- 函数运行至结束或遇到return语句。return语句可将值传回调用者,语法如下:
return表达式;
- void函数(无返回值的函数)可用 reutrn 语句提早退出
return;
- 函数定义中声明的变量是局部变量,在所有函数定义外部(最好在main之前)声明的变量是全局变量。局部变量不与其他函数共享;两个函数可以使用同名局部变量,两者不冲突。
- 全局变量使不同函数能共享数据,但这种共享使一个函数有可能与另一个发生冲突。除非绝对必要,否则不要使一个变量成为全局变量。
- C+函数可以递归调用,也就是调用自身(一个变体是两个或更多函数相互调用),只要有一种情况能终止调用,递归就有效。例如:
// 阶乘函数
int factorial(int n)
{
if(n <= 1){
return 1;
}else{
return n*factorial(n-1);//递归!
}
}