C++第三章掷骰子游戏与汉诺塔解析
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
一、例3-6
掷骰子,第一轮掷两次,如果两次的和为7或者11,为胜。如果两次的和为2、3或12,为输。如果是其他数值,作为自己的点数,然后进行第二轮,如果第二轮的和等于自己的点数,则为胜,如果和为7,则为输。如果第二轮还是没有分出胜负,进行第三轮、第四轮…玩家在程序开始时输入一个无符号整数,作为产生随机数的种子。
C++中系统函数int rand()的功能是产生一个伪随机数,但是因为是伪随机数所以每次运行这个程序产生的随机数都是相同的,所以函数rand()需要一个种子,这样不同的种子就会产生不同的伪随机数。如果不设置种子,rand()总是默认种子为1。不过设置种子的方法比较特殊,不是通过函数的参数,而是在调用它之前,需要先调用另一个函数void srand(unsigned int seed)为其设置种子,其中的参数seed就是种子。
-
这里有一个数学知识点:任何整数除以
n产生的余数范围是0~n-1
比如:rand()%12结果范围是0~11 -
enum叫做 枚举类型,作用:用一些有意义的名字,来代表一组整数常量:
enum GameStatus {WIN, LOSE, PLAYING};
相当于定义了一个新的类型 GameStatus,它里面有两个可能的取值:WIN, LOSE, PLAYING
#include<iostream>
using namespace std;
int rollDice(){
int sum = 0;
int die1 = 1 + rand() % 6; //rand()会随机产生一个非负整数0~32767,任何整数除以6取余结果都是0~5
int die2 = 1 + rand() % 6;
sum = die1 + die2;
cout << "player rolled:" << die1 << "+" << die2 << "=" << sum << endl;
return sum;
}
//相当于定义了一个新的类型 GameStatus,它里面有两个可能的取值:WIN, LOSE, PLAYING
enum GameStatus {WIN, LOSE, PLAYING};//enum 叫做 枚举类型,用一些有意义的名字,来代表一组 整数常量
int main(){
int sum, myPoint;
GameStatus status; //这句话是在 定义一个变量status,它的类型是 GameStatus(刚才自己定义的枚举类型)
// 所以这个变量 status 只能取 WIN 或者 LOSE 或者 PLAYING
int seed;
cout << "请输入一个随机数种子:";
cin >> seed;
srand(seed);
sum = rollDice();
switch(sum){
case 7:
case 11:
status = WIN;
break;
case 2:
case 3:
case 12:
status = LOSE;
break;
default:
status = PLAYING;
myPoint = sum;
cout << "我的点数是:" << myPoint << endl;
break;
}
while(status == PLAYING){
sum = rollDice();
if(sum == 7){
status = LOSE;
}
else if(sum == myPoint){
status = WIN;
}
}
if(status == WIN){
cout <<"你赢了" << endl;
}
else{
cout << "你输了" << endl;
}
return 0;
}
二、例3-10汉诺塔问题(递归函数)
有三根柱子:A、B、C。
A 柱子上有 n 个盘子(从大到小叠放)。
目标:把所有盘子从 A 移到 C,过程中必须遵守规则:
一次只能移动一个盘子。
不能把大盘子放在小盘子上面。
还记得递归三部曲吗?
1.确定递归函数的参数和返回值
hanoi(n, A, B, C) 表示:把 n 个盘子从柱子 A 借助柱子 B 移动到柱子 C。
2.确定终止条件
如果只有 1 个盘子,直接从 A 移到 C。
3.确定单层递归逻辑
- 把前 n−1 个盘子从 A → B(借助 C)
- 把第 n 个盘子从 A → C
- 把 n−1 个盘子从 B → C(借助 A)
#include <iostream>
using namespace std;
// 递归函数:把 n 个盘子从 from 移到 to,借助 aux
void hanoi(int n, char from, char aux, char to) {
if (n == 1) {
// 只有一个盘子,直接移动
cout << "Move disk 1 from " << from << " to " << to << endl;
} else {
// 1. 先把 n-1 个盘子从 from 移到 aux
hanoi(n - 1, from, to, aux);
// 2. 把第 n 个盘子从 from 移到 to
cout << "Move disk " << n << " from " << from << " to " << to << endl;
// 3. 再把 n-1 个盘子从 aux 移到 to
hanoi(n - 1, aux, from, to);
}
}
int main() {
int n;
cout << "请输入盘子的数量 n: ";
cin >> n;
cout << "汉诺塔移动步骤如下:" << endl;
hanoi(n, 'A', 'B', 'C'); // 把 n 个盘子从 A 移到 C,借助 B
return 0;
}
三、例3-11 将两个整数交换次序后输出(值传递)
1.值传递:当发生函数调用时,给形参分配内存空间,并用实参来初始化形参,这一过程是参数值的单向传递过程,一旦形参获得了值便与实参脱离关系,此后无论形参发生了怎样的改变,都不会影响实参,下面用例子来表示一个这个过程:
#include<iostream>
using namespace std;
void swap(int a, int b){ // a和b就是形参
int t = a;
a = b;
b = t;
}
int main(){
int x = 5, y = 10; // x和y就是实参
cout << "x=" << x << "y=" << y << endl;
swap(x, y);
cout << "x=" << x << "y=" << y << endl;
return 0;
}
运行结果:
x=5 y=10
x=5 y=10
显然,x和y的值并没有实现交换,为什么呢?(其实交换了a和b的值)
因为,采用的是值传递,函数调用时传递的是实参的值,是单向传递,形参值的改变对实参不起作用。
那如何在子函数中对形参做的更改对主函数中的实参有效呢?用引用传递
#include<iostream>
using namespace std;
void swap(int& a, int& b){ // 注意这里的改变 这就是引用传递
int t = a;
a = b;
b = t;
}
int main(){
int x = 5, y = 10;
cout << "x=" << x << "y=" << y << endl;
swap(x, y);
cout << "x=" << x << "y=" << y << endl;
return 0;
}
运行结果:
x=5 y=10
x=10 y=5
引用就是别名,定义int& a是指a是别的变量的别名,在定义这个函数的时候还没有规定a是谁的别名,没有关联到任何变量,a和b不是独立的变量不占用空间,只是别人的别名。主函数中swap(x,y)是指用x关联到a,使a成为x的别名,用y关联到b,使b成为y的别名,所有在子函数中交换a和b其实就是在交换x和y。
插个题外话:其实C++库函数中有swap函数
四、可变数量形参
使用模板类initializer_list<T>(第九章介绍)可向函数传递同类型不定个数参数。T根据实际情况写,比如string、int、char等等
void log_info(initializer_list<string> lst){
for(auto& info : lst){
cout << info << ' ';
}
cout << endl;
}
log_info({"hello", "world", "!"});
五、带默认形参值的函数
函数在定义时可以预先声明默认的形参值。 在主函数中调用函数时,如果给出了实参,那么用实参初始化形参,如果没有给出则采用预先声明的默认形参值。(语言描述有点抽象,可以看下面的例子)
int add(int x = 5, int y = 6){
return x+y;
}
int main(){
add(10, 20); // 10+20
add(10); // 10+6
add(); // 5+6
}
有默认值的形参必须在形参列表的最后 比如下面的例子:
因为函数调用的时候,实参与形参是按从左到右的顺序建立对应关系的
int add(int x = 1, int y = 2, int z = 3); // 正确
int add(int x = 1, int y = 2, int z); // 错误
int add(int x = 1, int y, int z = 3); // 错误
默认参数值与函数的调用位置:
如果一个函数有原型声明,且原型声明在定义之前,则默认参数值必须在函数原型声明中给出;而如果只有函数的定义,或函数定义在前,则默认参数值需在函数定义中给出。(还是非常抽象的一段话,具体看例子吧)
int add(int x = 5, int y = 6) // 原型声明在前
int mian(){
add();
}
int add(int x, int y){ // 此处不能再指定默认值
return x+y;
}
int add(int x = 5, int y = 6){
// 只有定义,没有原型声明
return x+y;
}
int mian(){
add();
}
至于这个带默认形参值的函数在哪里用得到,后面会讲到的!!(其实我刷力扣题从来没用到过)
六、函数重载
两个以上的函数,具有相同的函数名,但是形参的个数或者类型不同,编译器根据实参和形参的类型及个数的最佳匹配,自动确定调用哪一个函数,这就是函数的重载。
比如:如果没有重载,你要定义两个函数一个是整数的加法,一个是浮点数的加法,那么就要起两个名字,这样多了你每次调用就很麻烦,要仔细确认每一个的函数名。
函数重载的语法规定:形参必须不同:个数不同或者类型不同
(1)形参类型不同
int add(int x, int y);
float add(float x, float y);
(2)形参个数不同
int add(int x, int y, int z);
int add(int x, int y);
更多推荐
所有评论(0)