第04讲:控制结构(2)

《计算机程序设计》

苏醒

计算机学院计算机研究所编译系统研究室

课前准备

  • 在WSL Ubuntu中建立一个专门的目录用于课上练习,如~/course
$ mkdir ~/course
  • 打开VSCode并连接到WSL,打开此目录并新建文件,如~/course/main.cpp
  • 编辑main.cpp,随堂编程练习的代码请直接在此文件中编辑
main.cpp
#include <iostream>
#include <iomanip>
using namespace std;

int main() {
  // ============= begin =============

  // =============  end  =============
  return 0;
}

温故

测一测

阅读下面的代码,判断输出结果

int score = 96;
char grade, plus = ' ';
switch (score / 10) {
  case 10:
  case 9:
    if (score > 95) {
      grade = 'A';
      plus = '+';
    } else {
      grade = 'A';
      plus = ' ';
    }
  case 8:
  case 7:
    grade = 'B';
  default:
    grade = 'C';
}
cout << grade << plus << endl;

“物不知数”与“百钱百鸡”问题

编程求解

编程求解《孙子算经》中两道著名的问题

物不知数:“有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?”

百钱百鸡:“今有鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一。凡百钱买鸡百只,问鸡翁、母、雏各几何?”

——《孙子算经》

学习目标

  • 学习内容
    • 循环结构
    • 跳转语句
    • 嵌套循环

学习目标

  • 理解C++中三种不同的循环结构
  • 选择恰当的循环结构解决实际编程问题
  • 运用跳转语句实现灵活的循环控制

运用本次课所学内容,求解“物不知数”与“百钱百鸡”问题!

循环结构

循环结构

循环结构
  • 循环结构用于重复执行一段代码,直到满足某个条件为止
  • 循环的要素
    • 循环初始化:初始化循环控制变量
    • 循环条件判断:判断循环体是否执行
    • 循环体:每次循环迭代执行的程序代码
    • 循环参数更新:更新循环控制变量

教员箴言

计算机擅长简单而重复的工作!

三种循环

  • C++提供了三种循环结构
    • for循环适用于循环次数已知的情况
    • while循环适用于循环次数不确定的情况
    • do-while循环循环次数不确定但至少会执行一次的情况
for循环语法结构
for (初始化语句 循环条件; 迭代表达式)
  循环体语句
while循环语法结构
while (循环条件)
  循环体语句
do-while循环语法结构
do
  循环体语句
while (循环条件);
  • 三种循环结构的表达能力原则上是等价

三种循环应用场景举例

  • for循环:绕操场跑10圈
  • while循环:每跑一圈前观察计时器,若计时器没有停止则继续跑
  • do-while循环:每跑一圈后观察计时器,若计时器没有停止则继续跑

for循环

for循环
  • for循环是一种计数循环,适用于循环次数已知的情况
    1. 初始化语句执行一次
    2. 判断循环条件是否为真,为真则执行循环体语句,否则退出循环
    3. 执行迭代表达式
    4. 重复步骤2和3,直到循环条件为假
for循环语法结构
for (初始化语句 循环条件; 迭代表达式)
  循环体语句

明察秋毫

  • 初始化语句、循环提语句是语句,循环条件和迭代表达式是表达式
  • 由于初始化语句只能是声明语句表达式语句(必然以分号结尾),因此圆括号内必然存在两个分号
for循环语法结构(非正式写法)
for (初始化声明或表达式; 循环条件; 迭代表达式)
  循环体语句

for循环的初始化语句

  • 初始化语句在循环开始之前执行,且仅执行一次
  • 初始化语句可以是空语句,表示不做任何初始化操作
初始化语句为空语句
int i = 0;
for (; i < 10; i++)
  cout << i << endl;
  • 初始化语句可以是逗号表达式语句,用于初始化多个变量
初始化多个变量
int i, j;
for (i = 0, j = 0; i < 10; i++, j+=2) {}
cout << j << endl;
  • 初始化语句可以是声明语句,其中声明的变量作用域仅限于for循环内
初始化声明语句
for (int i = 0; i < 10; i++) {}
cout << i << endl; // 编译错误

for循环的迭代表达式

  • 迭代表达式在每次迭代结束后、下次条件判断前执行
  • 迭代表达式可以为空,表示不做任何操作
迭代表达式为空
for (int i = 0; i < 10;) {
  cout << i << endl;
  i++;
}
  • 迭代表达式可以是逗号表达式,用于执行多个操作
迭代表达式为逗号表达式
for (int i = 0, j = 0; i < 10; i++, j += 2) {}

for循环的循环条件

  • 循环条件在每次迭代前被计算,为true则执行循环体,否则退出循环
  • 第一次迭代前,循环条件若为false,则循环一次也不执行
for循环可能执行0次迭代
for (int i = 0; i > 0; i--) { cout << i << endl; }
  • 循环条件可以是任意表达式,但必须是布尔类型,或者能隐式转换为布尔类型
A
int i = 0;
for (; i++, i < 10;) { cout << i << endl; }
B
int i = 0;
for (; i < 10; i++) { cout << i << endl; }
  • 循环条件为空时,相当于循环条件永远为true,需在循环体内显式退出循环
循环条件为空
for (int i = 0; ; i++) {
  if (i >= 10) break;
  cout << i << endl;
}

for循环的循环体

  • 循环体语句是循环每次迭代执行的代码,可以是任意语句
  • 循环体语句是单条语句
循环体语句为单条语句
for (int i = 0; i < 10; i++)
  cout << i << endl;
  • 循环体语句是复合语句
循环体语句为复合语句
for (int i = 0; i < 10; i++) {
  cout << i << endl;
  cout << "Hello, world!" << endl;
}
  • 循环体语句是空语句
循环体语句为空语句
for (int i = 0; i < 10; i++)
  ;

for循环练习

测一测

使用for循环,计算给定整数n的阶乘\[n!=1\times2\times3\times\cdots\times n\]

组合数计算
unsigned int n = 0;
unsigned int factorial = 1;
cin >> n;
// ============== begin =============
// 计算n的阶乘

// ==============  end  =============
cout << factorial << endl;

while循环

  • while循环适用于循环次数不确定的情况
    1. 计算循环条件,为true则执行循环体语句,否则退出循环
    2. 重复步骤1,直到循环条件为false
while循环语法结构
while (循环条件)
  循环体语句
while循环示例
int i = 0;
while (i < 10) {
  cout << i << endl;
  i++;
}

while循环

考考你

while循环的循环参数更新要素位于右侧流程图中哪个位置?

while循环的循环条件

  • 循环条件在每次迭代前被计算,为true则执行循环体,否则退出循环
  • 第一次迭代前,循环条件若为false,则循环一次也不执行
while循环可能执行0次迭代
int i = 0;
while (i > 0) {
  cout << i << endl;
  i--;
}
  • 循环条件不能为空,但可以为true
循环条件为true
int i = 0;
while (true) {
  cout << i << endl;
  i++;
  if (i >= 10) break;
}

while循环的循环体

  • 循环体语句是循环每次迭代执行的代码,可以是任意语句
  • 循环体语句是单条语句
循环体语句为单条语句
while (i < 10)
  i++;
  • 循环体语句是复合语句
循环体语句为复合语句
while (i < 10) {
  i++;
}
  • 循环体语句是空语句
循环体语句为空语句
while ((cin >> i, i > 0))
  ;

while循环练习

测一测

使用while循环求解“物不知数”问题在(800, 1000)区间以内的所有解

有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二,问物几何?

物不知数问题求解
// ============== begin =============
// 求解“物不知数”问题在(800, 1000)区间以内的所有解
int n = 800;
while (n <= 1000) {

}
// ==============  end  =============

“物不知数”问题与中国剩余定理

  • “物不知数”问题实质上是初等数论中的一元线性同余方程组问题
    • 最早见于古代中国算书《孙子算经》(成书于公元3–5世纪之间),但并未给出具体解法
    • 1247年,南宋数学家秦九韶给出了中国剩余定理的一般解法,称为“大衍术”
    • 1801年,高斯独立给出了中国剩余定理问题的详细证明
    • 1852年,“大衍术”由传教士传至欧洲,西方称此方法为“中国剩余定理”,欧洲始知

一元线性同余方程组问题的数学表述

给定整数\(a_1, a_2, \ldots, a_n, m_1, m_2, \ldots, m_n\),且\(m_1, m_2, \ldots, m_n\)两两互质,求满足下列条件的整数\(x\)

\[ \begin{cases} x \equiv a_1 \pmod{m_1} \\ x \equiv a_2 \pmod{m_2} \\ \vdots \\ x \equiv a_n \pmod{m_n} \end{cases} \]

孙子算经影像

秦九韶

do-while循环

  • do-while循环适用于循环次数不确定至少执行一次的情况
    1. 执行循环体语句
    2. 计算循环条件,为false则退出循环
    3. 重复步骤1–2,直到循环条件为false
do-while循环语法结构
do
  循环体语句
while (循环条件);
do-while循环示例
int i = 0;
do {
  cout << i << endl;
  i++;
} while (i < 10);

do-while循环

do-while循环的循环条件

  • 循环条件在每次迭代后被计算,为false则退出循环,否则再次执行循环提
  • do-while循环至少执行一次迭代
while循环至少执行一次迭代
int i = 0;
do {
  cout << i << endl;
  i--;
} while (c > 0)
  • 循环条件不能为空,但可以为true
循环条件为true
int i = 0;
do {
  cout << i << endl;
  i++;
  if (i >= 10) break;
} while (true);

do-while循环的循环体

  • 循环体语句是循环每次迭代执行的代码,可以是任意语句
  • 循环体语句是单条语句时不可遗漏分号
循环体语句为单条语句
int i = 0;
do
  i++;
while (i < 10);
  • 循环体语句是复合语句
循环体语句为复合语句
int i = 0;
do {
  cout << i << endl;
  i++;
} while (i < 10);

教员箴言

do-while使用复合语句作为循环体,哪怕只有一条语句!

do-while循环练习

测一测

输入任意正整数,将第i位数字(个位为第1位,十位为第2位……)乘以i后累加求和输出,如输入423,则输出

\[3*1+2*2+4*3=19\]

计算正整数各位数字加权和
int n = 0;
cin >> n;
if (n <= 0) {
  cout << "错误: 输入数字不是正数!" << endl;
  return 1;
}
// ============== begin =============
int sum = 0;
// 计算各位数字加权和
do {

} while (n > 0);
cout << sum << endl;
// ==============  end  =============

三种循环的等价性

  • 三种循环结构的表达能力原则上是等价
    • 任何一种循环结构都可以用另外两种结构来实现
    • 合适的循环结构可以提升代码的可读性

温馨提示

  • 循环次数确定,用for
  • 先判断再执行,用while
  • 先执行再判断,用do-while

考考你

猜数字游戏适合使用哪种循环实现?

随机产生1-100之间的数字,用户输入猜的数字,猜中则游戏结束,否则游戏输出偏大或偏小后继续猜

循环结构练习

考考你

将下列while循环和do-while循环分别改写为for循环

while循环
int i = 0;
while (i < 10) {
  i++;
  cout << i << endl;
}
等价for循环
int i = 0;
for (; i < 10;) {
  i++;
  cout << i << endl;
}
do-while循环
int i = 0;
do {
  i++;
  cout << i << endl;
} while (i < 10);
等价for循环
int i = 0;
i++;
cout << i << endl;
for (; i < 10;) {
  i++;
  cout << i << endl;
}

嵌套循环

嵌套循环

  • 循环语句本身也是语句,因此可以嵌套在其他循环语句中
嵌套循环示例:打印九九乘法表
// 打印行头
cout << "   ";
for (int j = 1; j <= 9; ++j)
    cout << setw(3) << j;
cout << endl;
// 逐行打印
for (int i = 1; i <= 9; i++) {
  // 打印列头
  cout << setw(3) << i;
  // 打印一行
  for (int j = 1; j <= i; j++) {
    cout << setw(3) << i * j;
  }
  cout << endl;
}
     1  2  3  4  5  6  7  8  9
  1  1
  2  2  4
  3  3  6  9
  4  4  8 12 16
  5  5 10 15 20 25
  6  6 12 18 24 30 36
  7  7 14 21 28 35 42 49
  8  8 16 24 32 40 48 56 64
  9  9 18 27 36 45 54 63 72 81

嵌套循环练习

测一测

求解“百钱百鸡”问题的所有

今有鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一。凡百钱买鸡百只,问鸡翁、母、雏各几何?

百钱百鸡问题求解
// ============== begin =============
// 求解“百钱百鸡”问题
for (int x = 0; x <= 20; x++) {
  for (int y = 0; y <= 33; y++) {

  }
}
// ==============  end  =============

跳转语句

跳转语句

  • C++支持四种跳转语句
    • goto语句:无条件跳转到指定标签位置
    • break语句:跳出当前循环/选择结构
    • continue语句:跳过当前循环的剩余部分
    • return语句:返回函数调用点

跳转语句

  • 跳转语句用于改变程序的执行顺序
  • 打破三种基本控制结构的约束,更灵活(也更容易出错)

break语句

  • break语句
    • 跳出switch语句
    • 跳出循环语句:将控制流跳转到所在最内层循环语句的下一语句
      • forwhiledo-while均适用

break for循环

break while循环

break dowhile循环

break应用场景

  • 提前结束循环,提升程序效率
break语句应用:判断素数
int n = 0;
cin >> n;
bool is_prime = true;
for (int i = 2; i < n; i++) {
  // 若找到一个因子,提前跳出循环
  if (n % i == 0) {
    is_prime = false;
    break;
  }
}
cout << (is_prime ? "是" : "不是") << "素数" << endl;

break语句练习

考考你

修改下面“百钱百鸡”的求解程序,求买公鸡最多的解

今有鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一。凡百钱买鸡百只,问鸡翁、母、雏各几何?

百钱百鸡问题求解
// ============== begin =============
// 求解“百钱百鸡”问题
for (int x = 0; x <= 20; x++) {
  for (int y = 0; y <= 33; y++) {
    for (int z = 0; z <= 100; z += 3) {
      if (x + y + z == 100 && 5 * x + 3 * y + z / 3 == 100) {
        cout << "鸡翁:" << x << ",鸡母:" << y << ",鸡雏:" << z << endl;
      }
    }
  }
}
// ==============  end  =============

考考你

有没有更加清晰的程序流控制方案?

continue语句

  • continue语句,用于跳过当前循环的剩余部分,开始下一次循环迭代
    • forwhiledo-while均适用
    • whiledo-while循环中,continue语句跳转至判断循环条件
    • for循环中,continue语句跳转至执行迭代表达式,尔后判断循环条件

continue for循环

continue while循环

continue dowhile循环

continue应用场景

  • 略过后续处理,进入下一次循环
    • 尤其是在后续处理具有比较复杂逻辑的时候
continue语句应用:逢7过游戏
for (int i = 1; i <= 100; i++) {
  if (i % 7 == 0 or i % 10 == 7 or i / 10 == 7)
    continue;
  // do {
    // lots of {
      // work
    // }
  // }
}
不使用continue:逢7过游戏
for (int i = 1; i <= 100; i++) {
  if (not (i % 7 == 0) and not (i % 10) == 7 and not (i / 10 == 7)) {
    // do {
      // lots of {
        // work
      // }
    // }
  }
}

continue语句练习

考考你

若一个三位数,其各位数字的立方和等于该数本身,则称为水仙花数,如\(153=1^3+5^3+3^3\)

下面的程序可以求解1000以内的所有水仙花数,请修改,使用continue语句去除各位数字中包含3的水仙花数。

求所有水仙花数
// 求解水仙花数
for (int i = 100; i < 1000; i++) {
  int a = i / 100;
  int b = i / 10 % 10;
  int c = i % 10;
  if (a * a * a + b * b * b + c * c * c == i) {
    cout << i << endl;
  }
}

循环综合练习

测一测

若一个数等于它的因子之和,则称为完数,如\(6=1+2+3\)。编程求解1000以内的所有完数。

求解完数
// ============== begin =============
// 求解1000以内的所有完数
for (int i = 1; i < 1000; i++) {
  int sum = 0;
  

  if (sum == i) {
    cout << i << endl;
  }
}
// ==============  end  =================

总结

本节内容

---
config:
  look: handDrawn
  themeVariables:
    fontSize: 20px
---
mindmap
  控制结构(2)
    for循环
      初始化语句
      迭代表达式
      循环条件
      循环体
    while循环
      循环条件
      循环体
    do-while循环
      循环条件
      循环体
    嵌套循环
      goto与深层嵌套循环
    跳转语句
      break语句
      continue语句

学习目标

  • 理解C++中三种不同的循环结构
  • 选择恰当的循环结构解决实际编程问题
  • 运用跳转语句实现灵活的循环控制

课后作业

  • 实训(截止时间2025.03.12
    • 综合练习—C&C++循环结构
    • C&C++控制结构实训
  • 预习
    • 教材9.1–9.2:输入输出

计算机程序设计