第3章 判断语句进阶

3.1 do-while循环

事实上,单用while循环就可以写出很复杂的程序,但是也有一个很明显的缺点:无论结果如何都会先循环一次,这样就可以使用do-while来弥补这个缺点。
语法:
do
{
多个语句;
}
while (条件);

例3.1:加法机

// adding.cpp
//用户输入一系列数字,直到输入一个特殊码退出(可选择0)。程序反复提示输入,但运行程序时至少要提示一次。
# include <iostream>
using namespace std;

int main()
{
	int sum = 0;
	int n = 0;

	do
	{
		cout << "输入一个数(0表示退出):";
		cin >> n;
		sum += n;
	}
	while (n > 0);
	cout << "总和是:" << sum <<endl;

	return 0;
}

当然,这个也可以用只用while 来写。

# include <iostream>
using namespace std;

int main()
{
	int sum = 0, n = 0;

	while (true)
	{
		cout << "输入一个数(0表示退出):";
		cin >> n;
		sum += n;
		if (n <= 0)
		{
			break;
		}
	}
}

练习3.1.1

修改程序来接受浮点输入并打印浮点结果。常量要正确表示。

答案:

# include <iostream>
using namespace std;

int main()
{
	int n = 0;
	float sum = 0;

	do
	{
		cout << "输入一个数(0表示退出):";
		cin >> n;
		sum += n;
	}
	while (n > 0);
	cout << "总和是:" << sum << endl;
	
	return 0;
}

练习3.1.2

修改例子使用while循环但不要用do或break关键字。要求程序行为和例3.1完全一样。

答案:

# include <iosream>
using namespace std;

int main()
{
	int sum = 0, n = 1;

	while (n > 0)
	{
		cout << "输入一个数(0表示退出):";
		cin >> n;
		sum += n;
	}
	cout << "总和是:" << sum << endl;

	return 0;
}

3.2 随机数入门

随机数是游戏和模拟经常用到的东西,听起来容易,但生成随机数并不是一件简单的事情。不妨想一想:从哪里获得这种数?用什么当“虚拟骰子”?

幸好,C++程序员可通过两个步骤生成随机数,这是我们的“虚拟骰子”。

  • 设置随机数种子。
  • 通过复杂的数学运算生成序列中的下一个数(细节不重要)。
    有一个问题:虽然序列几乎不可能预测,但它仍是确定性的,数字计算机中的一切都是确定性的。不设置随机数种子,程序每次运行都获得相同的随机数序列。所以,第二次生成的数就不是真正的随机数了。为防止这种情况的发生,必须设置种子,而且每次都要不同。这就回到了原来的问题:从哪里获得这样的一个数?解决方案很简单:系统时间。

从C++程序员的角度来看,获取随机数实际是一个分三步走的过程。
首先用include指令引入对C++库的两个部分的支持:

# include <cstdlib> // 支持srand和rand函数
# include <ctime> //支持ctime函数

包含cstdlib后,就获得了随机函数的声明。包含ctime是因为要用到事件函数。
下一步是设置种子。记住不管程序要获得随机数多少次,都只需设置该种子值一次:

srand (time (nullptr));

注意:从C++11 开始支持nullptr关键字,即 空指针(null pointer)。如编译器较老,可改为NULL或0。

设置号随机数种子后,就可调用rand函数来生成随机数了。再次提醒,种子值无需多次设置。

cout << rand () << endl; //打印一个随机数
cout << rand () << endl; // 打印另一个

以下程序模拟掷10次骰子,每次生成1到6的随机数。

# include <iostream>
# include <cstdlib> //支持随机函数
# include <ctime> // 支持ctime
using namespace std;

int main ()
{
	srand (time(nullptr));
	for (int i = 0; i < 10; ++i)
	{
		cout << (rand() % 6) + 1 << endl;
	}

	return 0;
}

例3.2:猜数游戏

玩法很简单。首先生成一个随机数,然后让用户猜,输入猜的数之后,程序必须要做出判断。

  • 如果猜的数大于目标数,报告这一事实,提示再猜。
  • 如果猜的数小于目标数,报告这一事实,提示再猜。
  • 猜的数正确,报告这一事实,退出循环。游戏结束。

除非猜对,程序就必须继续。

// guessing.cpp
# include <iostream>
# include <cstdlib> //支持rand和srand
# include <ctime> // 支持时间函数
using namespace std;

int main()
{
	int n = 0;
	bool do_more = true;
	srand (time(nullptr));	//设置随机数种子
	int target = rand() % 50 + 1;	// 获取1-50的随机数

	do
	{
		cout << "1-50, 你猜是哪个:";
		cin >> n;
		if (n == 0)
		{
			cout << "再见!";
			do_more = false;
		}
		else
			if (n > target)
			{
				cout << "太大" << endl;
			}
			else
				if (n < target)
				{
					cout << “太小” << endl;
				}
				else
				{
					cout << "你赢了!";
					cout << "答案是:" << n << endl;
					do_more = false;
				}
	}
	while (do_more);

	return 0;
}

注意:C++14规范在标准模版库中提供了新的、增强的随机函数。虽然还是需要设置种子值,但在范围、随机数生成引擎和概率分布方面更灵活。不过,对于这种最简单的应用程序,标准的、老式的随机函数功能已经足够。

在这里插入图片描述

优化

如果使用Microsoft Visual Studio,你可能会注意到调用srand会造成开发环境显示一条警告。因为将time_t类型(一个长整数)的数据赋给获取unsigned int 的函数后,可能造成数据丢失。
两个都是整数,正负号也不是问题,所以可安全忽略该警告。但许多老程序员不喜欢警告。解决方案是执行强制类型转换(cast)。最简单的事老式C风格的强制类型转换。

srand ((unsigned int) time (nullptr));

然而,目前的首选方案是使用新的强制类型转换操作符static_cast。语法有点复杂,也不好看,但成为首选是有原因的。

srand (static_cast <unsigned int> (time(nullptr)));

static_cast常规语法如下所示:
static_cast <类型>(表达式)
它获取指定表达式,并将其转换成指定类型。即使编译器已知如何转换(比如将有符号整型赋给无符号整型),不显示执行强制类型转换还是消除不了警告。

练习3.2.1

修改程序来使用while循环。可继续使用do_more变量,也可去掉该变量,使用break关键字在必要时退出。

答案:

# include <iostream>
# include <cstdlib>
# include <ctime>
using namespace std;

int main()
{
	int n = 0;
	boo do_more = true;

	srand (time (nullptr));
	int target = rand() % 50 + 1;

	while (do_more)
	{
		cout << "1-50,你猜哪个:";
		cin >> n;
		
		if (n == 0)
		{
			cout << "再见!";
			do_more = false;
		}
		else
			if (n > target)
			{
				cout << "猜大了!" << endl;else
				if (n < target)
				{
					cout << "猜小了!" << endl;
				}
				else
				{
					cout << "你赢了!";
					cout << "答案是:" << n << endl;
					do_more = false;
				}
	}

	return 0;
}
练习3.2.2

玩过几次游戏后,应该对最优的玩家策略有所体会。可以用伪代码总结该策略吗?

答案:

  • 找出最小值和最大值的平均数。
  • 猜这个值。
  • 根据结果提示,猜这个值与最小值或最大值的平均数。
  • 依次类推。
练习3.2.3

完成上一题,你应该能写一个程序来实现最优策略。你心里记住一个数,让程序去猜这个数,每次都询问是太大、太小还是刚刚好。然后程序根据你的回答来调整所猜的数,前提是你要诚实回答。
提示:为方便写程序,用一套记号系统来表示你的回答:1=太大,2=太小,3=答对了。应该在提示中告知用户:告诉我猜得怎么样(1=太大,2=太小,3=答对了)。

答案:

# include <iostream>
using namespace std;

int main()
{
	cout << "在1到50间猜一个数,并且记住这个数" << endl;

	int lo = 1, hi = 50, cmd = 0;

	while (true)
	{
		int guess = (lo + hi) / 2;
		cout << "我猜的数是:" << guess << "。" << endl;
		cout << "请告诉我,我猜的怎么样?" << endl;
		cout << "1表示太大了,2表示太小了,3表示猜对了。" << endl;
		cin >> cmd;

		while (cmd < 1 || cmd > 3)
		{
			cout << "输入有误。请重新输入:";
			cin >> cmd;
		}
		if (cmd == 3)
		{
			cout << "我猜对了!真高兴跟你一起玩。" << endl;
			break;
		}
		else
			if (cmd == 1)
			{
				hi = guess -1;
			}
			else
			{
				lo = guess + 1;
			}
	}
	return 0;
}
例3.2.4

这个练习更像是数学问题而不是编程问题,但仍然有趣。一个包含N个数的有序数列,最多猜几次就能猜到目标数?提示:用二分法,大难是最多猜log2(N+1)次,向上取整。

答案:

1 move is sufficient to guarantee success for range size 1.

2 moves are sufficient to guarantee success for range size 3.
(Because there’s one spot on either side of the mid point.)

3 moves are sufficient to guarantee success for range size 7.
(Because there are two areas of 3 on either side of mid point.)

4 moves are sufficient to guarantee success for range size 15.
(Because there are two areas of 7 on either side of mid point.)

Note that if you add 1 to the sizes 1, 3, 7, 15, etc., you get powers of 2:
2, 4, 8, 16… Therefore, the solution is to add 1 to the size of the range,
and take the log-base-2 of this number, always rounding up:

 Ceiling(log-base-2(N + 1))

In C++ code, we can write this as:

 #include <cmath>  // Brings in support for ceil and log.

 int answer = ceil(log(n + 1)/log(2))

3.3 switch-case语句

和do-while一样,switch-case并非严格需要。
switch-case之所以有用,是因为许多程序的许多小节只是测试一系列值。
例如,根据n等于多少(1,2,3)打印对应单词。

switch (n)
{
	case 1:
		cout << "one" << endl;
		break;
	case 2:
		cout << "two" << endl;
		break;
	case 3:
		cout << "three" << endl;
}

switch 语句常规语法如下所示:
switch(值)
{
语句(s)
}

某个case不写break语句会发生什么?答案是“直通”到下个case。少数情况才需要这样做,但一般都应该习惯性地添加break。
还要注意,加了标签的语句具有以下形式:
标签:语句

case和default属于特殊标签。由于加了标签的语句本身就是语句,所以一个语句可以有多个标签。例如:

case 'a':
case 'e':
case 'i':
case 'o':
	cout << "是元音字母。";
	break;

例3.3:打印数字

# include <iostream>
using namespace std;

int main()
{
	int n = 0;
	cout << "输入20到99的数:";
	cin >> n;
	int tens_digits = n / 10;
	int units_digits = n % 10;

	switch (tens_digits)
	{
		case 2: cout << "贰拾"; break;
		case 3: cout << "叁拾"; break;
		case 4: cout << "肆拾"; break;
		case 5: cout << "伍拾"; break;
		case 6: cout << "陆拾"; break;
		case 7: cout << "柒拾"; break;
		case 8: cout << "捌拾"; break;
		case 9: cout << "玖拾"; break;
	}

	switch (units_digits)
	{
		case 1: cout << "壹"; break;
		case 2: cout << "貳"; break;
		case 3: cout << "叁"; break;
		case 4: cout << "肆"; break;
		case 5: cout << "伍"; break;
		case 6: cout << "陆"; break;
		case 7: cout << "柒"; break;
		case 8: cout << "捌"; break;
		case 9: cout << "玖"; break;
	}

	return 0;
}
练习 3.3.1

例3.3 的程序能重复执行就好了。(事实上,大多数程序都应如此,用户退出才退出。)所以,将程序放到do-while循环中,用户输入0就退出循环。

答案:

# include <iostream>
using namespace std;

int main()
{
	int n = 0;
	while (true)
	{
		cout << "输入20到99之间的数:";
		cin >> n;
		int tens_digits = n / 10;
		int units_digits = n % 10;
		switch (tens_digits)
		{
			case 2: cout << "贰拾"; break;
			case 3: cout << "叁拾"; break;
			case 4: cout << "肆拾"; break;
			case 5: cout << "伍拾"; break;
			case 6: cout << "陆拾"; break;
			case 7: cout << "柒拾"; break;
			case 8: cout << "捌拾"; break;
			case 9: cout << "玖拾"; break;
		}
		switch (units_digits)
		{
			case 1: cout << "壹" << endl; break;
			case 2: cout << "貳" << endl; break;
			case 3: cout << "叁" << endl; break;
			case 4: cout << "肆" << endl; break;
			case 5: cout << "伍" << endl; break;
			case 6: cout << "陆" << endl; break;
			case 7: cout << "柒" << endl; break;
			case 8: cout << "捌" << endl; break;
			case 9: cout << "玖" << endl; break;
		}

		cout << "继续吗?(1表示是,0表示否):";
		cin >> n;
		if (n != 1)
		{
			break;
		}
	}

	return 0;
}
练习3.3.2

修改程序使之能处理0到9的数。这应该很容易,或许不用修改?

答案:

# include <iostream>
using namespace std;

int main()
{
	int n = 0;

	cout << "请输入0到9或20到99之间的数:";
	cin >> n;
	int tens_digits = n / 10;
	int units_digits = n % 10;

	if (n >= 20)
	{
		switch (tens_digits)
		{
			case 2: cout << "贰拾"; break;
			case 3: cout << "叁拾"; break;
			case 4: cout << "肆拾"; break;
			case 5: cout << "伍拾"; break;
			case 6: cout << "陆拾"; break;
			case 7: cout << "柒拾"; break;
			case 8: cout << "捌拾"; break;
			case 9: cout << "玖拾"; break;
		}
	}
	switch (units_digits)
	{
		case 1: cout << "壹" << endl; break;
		case 2: cout << "貳" << endl; break;
		case 3: cout << "叁" << endl; break;
		case 4: cout << "肆" << endl; break;
		case 5: cout << "伍" << endl; break;
		case 6: cout << "陆" << endl; break;
		case 7: cout << "柒" << endl; break;
		case 8: cout << "捌" << endl; break;
		case 9: cout << "玖" << endl; break;
	}

	return 0;
}
练习3.3.3

修改使之能处理11到19的数。提示:添加十位数为1时的case。

答案:

# include <iostream>
using namespace std;

int main()
{
	int n = 0;

	cout << "请输入1到99之间的数:";
	cin >> n;
	int tens_digits = n / 10;
	int units_digits = n % 10;

	if (n >= 20)
	{
		switch (tens_digits)
		{
			case 2: cout << "贰拾"; break;
			case 3: cout << "叁拾"; break;
			case 4: cout << "肆拾"; break;
			case 5: cout << "伍拾"; break;
			case 6: cout << "陆拾"; break;
			case 7: cout << "柒拾"; break;
			case 8: cout << "捌拾"; break;
			case 9: cout << "玖拾"; break;
		}
	}
	if (n > 10 && n < 20)
	{
		switch (units_digits)
		{
			case 1: cout << "拾壹" << endl; break;
			case 2: cout << "拾贰" << endl; break;
			case 3: cout << "拾叁" << endl; break;
			case 4: cout << "拾肆" << endl; break;
			case 5: cout << "拾伍" << endl; break;
			case 6: cout << "拾陆" << endl; break;
			case 7: cout << "拾柒" << endl; break;
			case 8: cout << "拾捌" << endl; break;
			case 9: cout << "拾玖" << endl; break;
		}
	}
	else
	{
		switch (units_digits)
		{
			case 1: cout << "壹" << endl; break;
			case 2: cout << "貳" << endl; break;
			case 3: cout << "叁" << endl; break;
			case 4: cout << "肆" << endl; break;
			case 5: cout << "伍" << endl; break;
			case 6: cout << "陆" << endl; break;
			case 7: cout << "柒" << endl; break;
			case 8: cout << "捌" << endl; break;
			case 9: cout << "玖" << endl; break;
		}
	}

	return 0;
}
练习3.3.4

扩展程序使之能处理最大为999的数。提示:先完成练习3.3.3,否则随着值范围增大,会出现许多“漏洞”。

答案:

//
# include <iostream>
using namespace std;

int main()
{
	int n = 0;

	cout << "输入一个数(1-999):";
	cin >> n;
	int hundreds_digits = n / 100;
	int remainder_from_100 = n % 100;
	int tens_digits = remainder_from_100 / 10;
	int units_digits = remainder_from_100 % 10;

	if (hundreds_digits > 0)
	{
		switch (hundreds_digits)
		{
			case 1: cout << "壹佰" ; break;
			case 2: cout << "貳佰" ; break;
			case 3: cout << "叁佰" ; break;
			case 4: cout << "肆佰" ; break;
			case 5: cout << "伍佰" ; break;
			case 6: cout << "陆佰" ; break;
			case 7: cout << "柒佰" ; break;
			case 8: cout << "捌佰" ; break;
			case 9: cout << "玖佰" ; break;
		}
	}
	if (remainder_from_100 > 0)
	{
		cout << " , ";
	}
	if (remainder_from_100 >= 20)
	{
		switch (tens_digits)
		{
			case 2: cout << "贰拾"; break;
			case 3: cout << "叁拾"; break;
			case 4: cout << "肆拾"; break;
			case 5: cout << "伍拾"; break;
			case 6: cout << "陆拾"; break;
			case 7: cout << "柒拾"; break;
			case 8: cout << "捌拾"; break;
			case 9: cout << "玖拾"; break;
		}
	}
	if (remainder_from_100 > 10 && remainder_from_100 < 20)
	{
		switch (units_digits)
		{
			case 1: cout << "拾壹"; break;
			case 2: cout << "拾贰"; break;
			case 3: cout << "拾叁"; break;
			case 4: cout << "拾肆"; break;
			case 5: cout << "拾伍"; break;
			case 6: cout << "拾陆"; break;
			case 7: cout << "拾柒"; break;
			case 8: cout << "拾捌"; break;
			case 9: cout << "拾玖"; break;
		}
	}
	else
	{
		switch (units_digits)
		{
			case 1: cout << "壹" << endl; break;
			case 2: cout << "貳" << endl; break;
			case 3: cout << "叁" << endl; break;
			case 4: cout << "肆" << endl; break;
			case 5: cout << "伍" << endl; break;
			case 6: cout << "陆" << endl; break;
			case 7: cout << "柒" << endl; break;
			case 8: cout << "捌" << endl; break;
			case 9: cout << "玖" << endl; break;
		}
	}

	cout << endl;
	return 0;
}

小结

  • do-while 循环和 while 循环相似,只是循环主体至少执行一次.
do 语句
while(条件);
  • 和其他控制结构一样,语句可替换为复合语句(用大括号封闭的代码块).
do
{
	多个语句
}while(条件);
  • 一般用bool变量控制循环。注意,该变量不需要和 true 比较,可以直接作为条件使用。
  • 这种 bool 变量可以使用逻辑NOT操作符(!)和false比较来反转其真假.
  • 生成随机数要包含 来使用srand和rand函数,还要包含 来使用时间函数。
#include <cstdlib>
#include <ctime>
  • 生成随机数的下一步是设置随机数种子,确保程序每次运行都获得一组不同的“随机”(实际是伪随机)数。
srand(time(nullptr));
  • 然后调用 rand 生成序列中的下个随机数。结果是无符号 int 范围中的任意数字。应用取余操作符(%)可获得从 0 到 n-1 的随机数。
cout << rand()%n;  //打印0到n-1的随机数
  • 使用 if 和 else if 子句反复测试单个值时,适合用 switch-case 语句替代。
if(n == 1){
	cout <<  "1";
}else if(n == 2){
	cout << "2";
	}else if(n*3){
		cout <<"3";
		}
  • 上述代码可替换成以下形式:
switch(n){
	case 1:cout  <<  "1";break;
	case 2:cout  << "2";break;
	case 3:cout  << "3";break;
  • switch-case的语法如下所示:
switch(值){
语句(s)
}
  • 这个块做下面这些事情:首先求值,执行和该值匹配的case.如果没有匹配项,就执行 default(如果有的话)语句。
  • 一旦执行语句,就按照正常流程一直执行到break语句,此时会跳出 switch-case 块
  • 加后赋值操作符(+=)是在变量上加一个值的简写形式。也有对应的减后赋值操作符。
n += 50; // n = n + 50
n -= 25; // n=n-25
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值