科技 · 编程

C++ 控制结构

从顺序、分支到循环结构,系统介绍 C++ 程序流程控制的基础语法与典型问题。

C++ 控制结构
从顺序、分支到循环结构,系统介绍 C++ 程序流程控制的基础语法与典型问题。

本系列为 C++ 的入门学习者服务,旨在于为此前无 C++ 基础的学习者简单介绍关于 C++ 语言基础的部分知识,如果已入门 C++,则不需要阅读本系列。

2.1 算法与流程图(不展开讲)

2.2 C++ 中的语句

语句是 C++ 程序的最基本单位,以分号结束。

注:头文件包含属于编译预处理,不是 C++ 语句。

C++
#include <iostream> //头文件包含不是语句int a; //是语句

1. 声明语句

用来定义或声明变量和函数。

C++
int a, b;

2. 表达式语句

完成计算和操作。

C++
c = a + b;

3. 控制语句

用来完成对程序流程的控制。

如:if 语句、for 语句、while 语句等。

4. 函数调用语句

用来完成函数的调用。

C++
strcpy(a, b);

5. 空语句

单独分号构成的语句。

意义:用在无内容可写,却需要语句来进行填充的位置。常见于循环语句,注意和分号区分。

6. 复合语句

由一对{}构成,用来包括多个语句。

  • 形式:{语句1; 语句2;}
C++
if(i <= 100) //该段语句只是示例,无实际意义{    a = b + c;}
  • 意义:用在只需要一个语句,但单个语句无法满足需求。
  • 注:在语法上复合语句认为是一条语句。

2.3 分支结构

1. if 语句

(1) 单分支 if 语句

C++
if(表达式)	语句; 

解释:表达式一般为逻辑或关系表达式,如果结果为 true,则运行语句。而如果表达式是其他类型,则自动转换,非0 为 true,0 为 false。

文章配图
文章配图

单分支 if 语句运行图示

(2) 双分支 if 语句

C++
if(表达式) 	语句1;else	语句2;

解释:如果表达式结果为 true,则运行 语句1,反之运行 语句2。

文章配图
文章配图

双分支 if 语句运行图示

例:闰年判断(4 倍数一般是闰年,整百数必是 400 倍数才是闰年)

C++
int year;cin >> year;if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0)	cout << year << “是闰年” << endl;else	cout << year << “不是闰年” << endl;

注:if 语句的嵌套

if 语句的分支语句可以是一个新的 if 语句,叫做 if 语句的嵌套。

借助 if 的嵌套可以实现多分支的判断功能。

A. 嵌套在 else 语句:阶梯式 if 语句

C++
if(表达式)	语句1;else if(表达式2)	语句2;else if……else	语句n;

解释:依次对各个表达式进行判断,如果有满足 true 的则执行对应语句,并终止整个语句。

(注意和 hoi4 伪语言区分开)

文章配图
文章配图

嵌套在 else 的 if 语句运行图示

B. 嵌套在 if 语句:在 if 分支语句中增加新的 if 语句(不推荐)

  • C++ 规定了 if 和 else 的“就近配对”原则,即相距最近且还没有配对的一对 if 和 else 首先配对。

    C++
    if(表达式1)	if(表达式2)		语句1;	else		语句2;//else将与第二个if匹配。
  • 如果需要强行更改配对关系,则要将属于同一层的语句放在{}中。

    C++
    if(表达式1){	if(表达式2)		语句1;}else	语句2;//else将与第一个if匹配。

2. 条件运算符 ?:

三目运算符?:可以简化 if 语句表达。

C++
表达式1 ? 表达式2 : 表达式3;

解释:判定 表达式1,若成立则执行 表达式2,不成立则执行 表达式3。

注:

  • 条件运算符中后两个表达式的类型和第一个表达式完全无关。
  • 条件运算符等级仅高于赋值(15)。
  • 条件运算符是右结合性,自右向左结合。
    • 例:a ? b : c ? d : e 应该理解为 a ? b : (c ? d : e)

3. switch 语句

C++
switch(整型表达式){case 常量表达式1 :《语句序列1》《break;》// 《》是“可选”的意思……case 常量表达式n :《语句序列n》《break;》《default : 语句序列》}

解释:

  1. 计算 switch 后的整型表达式值。

  2. 逐个匹配 case 后表达式的值,如果相等则执行对应的语句序列。

  3. 如果语句后有 break 则终止整个 switch 语句,否则继续执行后续语句序列且不再进行判断。

  4. 若所有 case 都无法匹配则执行 default 语句。

例:计算学生的成绩等级

C++
int score;cout << "请输入学生的成绩:"; cin >> score;switch(score/10){case 10: case 9:	cout << "该生成绩为A" << endl; break;case 8:	cout << "该生成绩为B" << endl; break;case 7:	cout << "该生成绩为C" << endl; break;case 6:	cout << "该生成绩为D" << endl; break;default:	cout << "该生成绩为E" << endl; }

注:

  • switch 语句只能对整数和字符类型使用。
  • 只能判断表达式是否相等,不能判定不等大于小于。
  • case 后的表达式只能是常量表达式。
  • break 语句的使用请务必注意。

2.4 循环结构

1. while 语句

C++
while(表达式)	循环体语句;

解释:如果满足表达式则反复执行循环体语句直到不满足表达式为止。

文章配图
文章配图

while 语句运行图示

例:求解 1~100 的和

C++
const int n=100; //用常变量利于修改程序int i=1, sum=0; //循环初始条件while(i <= n) //循环入口条件{	sum+=;//循环体    ++i;//循环修正条件}cout << "sum=" << sum << endl;

解析:

  1. 代码结束后 i 的值为 101。

  2. sum=0 不能遗忘,否则会出现随机数。

  3. 做题看结果通常看头尾。

  4. 若循环体中有多个语句一定要用{}。

  5. 部分题目中会将循环修正条件放入循环入口条件中,即将循环体中的 i++ 放入 while(i++<=n) 中,但一般不推荐这样写。

2. do-while 语句

C++
do 循环体语句while(表达式);

解释:先做一次循环体语句,若表达式满足则继续循环,直到表达式不满足终止循环;若表达式不满足则不执行循环。

注:do-while 语句与 while 语句的区别

  • do-while 语句至少执行一次循环体后再判断循环条件是否满足。
  • while 语句先判断条件,然后才执行循环体,可能一次都不执行。

例:求解 1~100 的和

C++
const int n=100;//用常变量利于修改程序int i=1, sum=O;//循环初始条件do{	sum+=i;//循环体	i++;//循环修正条件}while(i<=n);//循环入口条件cout << "sum=" << sum << endl;

3. for 语句

C++
for (表达式1; 表达式2; 表达式3)	循环体语句;

解释:先执行式 1,然后判断式 2,真则执行循环体,后执行式 3,接着重复判断式 2,直到 2 为假结束整个循环。

注:

  • 相对于 while,式 1 相当于初始条件;式 2 相当于维持条件;式 3 相当于修正条件。
  • for 语句中只有式 2 是判定条件。
  • for 语句中三个表达式都可以为空,但是分号**;**一定要有。
  • 省去 1 可以将初始条件放在 for 之前,省去 3 可以将修正条件放在循环体中,而省去 2 则表达式 2 的值将默认为 true,必须用其他方法结束循环。

例:求解 1~100 的和

C++
const int n=100; //用常变量利于修改程序int i, sum=O;for(i=1;i<=n;++i)    sum += i;cout << sum << endl;

4. 三种循环的选择与注意

​ A.三种循环语句没有本质区别,可以互相代替。

​ B.当循环起点和终点值直接确定,推荐使用 for 语句。

​ C.若终止条件不明确(如等待某值输入),推荐 while 语句。

​ D.do-while 语句最典型场合是必须至少执行一次的循环。如展示菜单。

​ E.若循环外还要用某变量,则该变量不能在 for() 和 while() 语句中定义。

5. 循环语句的嵌套

当循环语句中的循环体中又有循环语句时,就构成了嵌套循环。

理解:以时钟的时针和分针为例:

时针每移动一格(大循环),分针就必须跑完一圈(小循环)。

解释:大循环每进行一次,小循环就完整循环一次。

注:大小循环的运行可能相关也可能不相关。

经典案例:打印问题

  • 主要思想:利用循环算法,打印各种有规律的数字或图形。
  • 基本思路:行是大循环,列是小循环。首先断定打印的行数,然后在每一行当中确定打印的列数以及每一列的空格和内容。行数、列数以及打印内容往往存在关联,需要分析清楚并用来控制循环的次数。

算法示例:

文章配图
文章配图

打印星号平行四边形

C++
for (int i = 1; i <= 4;++i){	for (int j = 1; j <= i; ++j)		cout << ' ';	for (int j = 1; j <= 7; ++j)		cout << '*';	cout << '\n';}

解析:

  1. 首先确定行数为 4 行。

  2. 每行包括前面空格后面*号。

  3. 空格数量和行号相等。

  4. *号的数量是固定为 7 个。

  5. 打印完每一行之后要换行。

6. 循环中的控制语句

(1) break 语句

  • 在循环语句中遇到 break,整个循环结束。
  • 如果有多层循环嵌套,则 break 只结束最近的循环。

例:素数判断

基本思路:i 从 2 到 根号n 之间进行循环,将每个 i 用 n 去除,如果可以整除则直接判断不是素数;如果 i 的整个循环走完仍不能整除才能判断 n 是素数。

C++
int n, k, i;cout << "输入整数n:" ;cin >> n;k = sqrt(1.0 * n);for (i = 2; i <= k; ++i)	if (n % i == 0)		break;if(i > k)	cout << "是素数\n";else	cout <<"不是素数\n";

(2) continue 语句

在循环语句中遇到 continue,当轮循环停止,立即开始下一轮循环。

例:输出 100 以内的偶数。

C++
for (i = 1; i <= 100; i++){if ( i % 2 != 0 ) continue;	cout<< i << endl;}

注:break 与 continue 的区别:

​ break 终止整个循环,而 continue 只是终止一次循环。

2.5 C++ 常用基本算法

1. 穷举法

  • 主要思想:列出问题的所有可能性,按所需条件进行逐一筛选。
  • 要求:在设计算法时,应尽可能减少循环的次数以提高效率。

(1) 素数问题

点击跳转

(2) 水仙花数问题

问题说明:如果一个三位数等于其各位数字的立方和,则该数为水仙花数。

问题思路:将所有的三位数用条件进行验证即可

代码:

C++
int i, a, b, c; //a,b,c分别为百位、十位、个位for(i=100; i<=999; i++){    a = i/100;    b = (i-a*100)/10;    c = i - a*100 - b*10;    if(i==a*a*a+b*b*b+c*c*c)        cout << i << '\t'}

(3) 完全数问题

问题说明:如果一个数等于其所有因子(自身除外)的和,则该数为完全数。

如:6=1+2+3,28=1+2+4+7+14

问题思路:遍历一个数的所有因子,并将其求和之后判断与自己是否相等即可。

问题要点:如何遍历一个数的所有因子?

A.单个数的判断:

C++
int i, n, s=0; //注意s必须初始化为0cin >> n;for(i=1; i<=n/2; i++)	if(n%i==0)		s += i;if(s==n)    cout << "该数是完全数\n";else    cout << "该数不是完全数\n";

B.遍历指定区间的完全数:(以 1000 以内为例)

  • 未优化代码:正确但运算次数大,判断需要进行 n/2 次循环。
C++
int i, n, s;for(i=1; n<=1000; n++){    s = 0; //注意s必须在此处初始化,对每一个n,s都应该重置为0开始    for(i=1; i<=n/2; i++)        if(n%i==0)            s += i;    if(s==n)    	cout << n << "\t";}
  • 代码优化:当区间过大的时候,将其从 2 到 根号n 进行循环,每次求和的时候同时加上成对因子,但如果有重复的因子需去除。
C++
int i, n, k, s=1; //因子必定有1,将s初始化为1减少一次循环cin >> n;k = sqrt(n);for(i=2; n<=k; i++)    if(n%i==0)        s += i + n/i;	if(k*k == n)        s -= k; //关键!!k为平方数时会出现一个重复因子i = n/i故要减一次    if(s==n)    	cout << "该数是完全数\n";	else    	cout << "该数不是完全数\n";

(4) 简单推理问题

例:某同学做了好事但不留名。校长问了 4 个人:A 说不是我,B 说是 C,C 说是 D,D 说 C 胡说。已知 4 人中有 3 人说的是真话,请问做好事的是谁?

问题思路:遍历所有可能做好事的人,统计说真话的人是否为 3 个即可。

问题要点:如何用程序来计算和统计本问题的真假?

C++
char man;for(man = 'A'; man <='D'; man++){if((man!='A')+(man=='C') +(man=='D')+(man!='D')== 3 ) //3个bool=1cout<<"好人就是"<<man<<endl;}

2. 递推法

  • 主要思想:按照某种规律,对同一个变量不断更改其值(递推),从而求解问题。
  • 基本思路:确定递推项 → 确定递推起点 → 分析递推规律 → 判断终止条件

(1) 斐波那契数列

数列背景︰

数列 1,1,2,3,5,8… 为以意大利数学家费波纳切命名的数列。 定义小兔子 t 时间后长为大兔子,大兔子会繁殖且繁殖周期为 t。 则 1 对小兔子 t 时间后长大为 1 对大兔子。 t 时间后变成 2 对(1 对小 1 对大)。 t 时间后变成 3 对(2 对大 1 对小)。 t 时间后变为 5 对(3 对大 2 对小)等等。

问题背景:现用循环求出该数列的前 n 项。

问题思路:不可能定义 n 个变量逐个保存,只能用少数的变量反复使用来达到该效果。

问题要点:如何找出递推的变化规律?务必通过该问题,掌握递推算法的核心思想。

斐波那契数列递推思路:

  1. 确定递推项:三项 f1,f2,f3

  2. 递推起点:f1=f2=1

  3. 递推规律:f3=f1+f2,然后 旧f2 推出 新f1,旧f3 推出 新f2,再重复前述过程

    例:(右上到左下替换)

    f1f2f3
    112
    123
    235
  4. 终止条件:指定次数(本例为 n-2 次)

解法一:

C++
int f1=1, f2=1, f3, i; //只定义3个变量cout << f1 << '\t' << f2 << '\t'; //打印开始for(i=3; i<=20; i++) //打印前20个值{	f3 = f1 + f2; //打印结果    cout<<f3<<'\t';	if(i%5==0) //每5个数换一行	cout << endl;	f1 = f2; //变量递推,准备下一次的值    f2 = f3; //变量递推,顺序问题很重要

解法二:

​ 递推规律:新f1=旧f1+旧f2;新f2=新f1+旧f2

旧 f1旧 f2新 f1新 f2
1123
C++
int f1=1, f2=1, i;for(i=1; i<=10; i++){	cout << f1 << '\t' << f2 << '\t';    f1 = f1 + f2;	f2 = f1 + f2;} //请对照递推规律自行理解体会该思路

(2) sin(x)的求解问题

已知 sin(x) = x - x^(3) / 3! + x^(5) / 5! - x^(7 )/ 7! + ..... 利用该公式求解 sin(x) 的值,精度为 10^(-6)。

问题思路:观察每一项的分子、分母和符号位的值。当前项的符号位乘 -1 就是下一项的符号位;当前项的分子乘 x^(2) 就是下一项的分子;当前项的分母乘以 (2*n+1)*(2*n),即为下一项的分母,n 为当前的项数。

精度为 10^(-6)可用来控制循环的终止,如果计算的通项绝对值小于该值,则循环终止。

示例:求 sin 3 = ?

C++
double x = 3, s = 0, t = x;int n = 1;while(fabs(t) >= 1e-6){	s += t;	t = -t * x * x / (2 * n + 1) / (2 * n);    ++n;}cout << s << endl;

(3) 二分法求解方程问题

问题说明:假设 f(x) 在区间 [a,b] 中单调递增,且肯定有根,求解该根的值。

问题思路:取区间的一半判断其函数值,如果为负,则收缩区间为中点到右区间,反之为左区间到中点。重复上述过程,直到中点值为 0 或满足精度要求即可。

问题要点:反复递推区间的左右值即可,当求出的中点值小于某区间即可终止。

示例:二分法求 x^(2)-2=0

C++
double a=0,b=2,c=(a+b)/2; //递推起点double t = c*c-2;while(fabs(t)>=1e-6)//终止条件 不能写t!=0,因计算机中t实际不能达到精确的0{	if(t>0) b=c; //递推过程1	else a=c; //递推过程1	c=(a+b)/2; //递推过程2	t = c*c-2;}cout << c << endl;

(4) 整数分解问题

问题说明:输入任意正整数,判断其位数、计算各位数字之和、求解其倒序数、判断是否为回文数等问题。

问题思路:从个位数开始依次提取,其方法是对 10 求余,然后将该数除以 10,并重复上述步骤,直到该数为 0 则结束。

问题要点:上述思路仅限分解,如果是组装则逆向进行。

示例:求解 12345 的倒序数。

C++
int n = 12345,m = 0;do{	m = m * 10 + n % 10; //n%10求解末位数,n/=10将末位向左移一位从而循环	n /= 10;} while (n > 0);cout << m << '\n';

3. 模拟法

基本思想:根据题目给出的规则,对题目要求的相关过程进行模拟。模拟法对于步骤比较繁杂的问题比较合适。

报数问题

题目:小张按 1~20 报数,小李按 1~30 报数,若两人同时开始,并以同样速度报数,当两人都报了 1000 个数时,报相同数字的次数有多少?

思路:进行 1000 次循环,每一次小张和小李各报一次数(注意调整下一次的值),统计相等次数即可。

C++
int round, zhang = 0, wang = 0, count = 0;for (round = 1; round <= 1000; ++round){	++zhang; ++wang;    if(zhang == wang) ++count;    if(zhang == 20) zhang = 0;    if(wang == 30) wang = 0;}cout << count;

分享文章