第03讲:控制结构(1)

《计算机程序设计》

苏醒

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

课前准备

  • 在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;
}

温故

测一测

C++中没有逻辑异或运算符,请问下列程序是否可以通过编译?如果可以,输出是什么?

  1. 可以,输出1
  2. 可以,输出true
  3. 不可以
bool a = true, b = false;
cout << (a ^ b) << '\n';

分制转换问题

编程求解

输入一个百分制成绩,输出对应的分等级的五分制成绩,例如:输入88,输出“B”

$ ./score-conversion
输入百分制成绩:92
五分制成绩:A
$ ./score-conversion
输入百分制成绩:88
五分制成绩:B
$ ./score-conversion
输入百分制成绩:75
五分制成绩:C
$ ./score-conversion
输入百分制成绩:65
五分制成绩:D
$ ./score-conversion
输入百分制成绩:36
五分制成绩:E

学习目标

  • 学习内容
    • 语句与控制结构
    • 顺序结构
    • 选择结构

学习目标

  • 掌握C++程序的语句构造规则
  • 理解程序结构定理以及三种基本控制结构
  • 应用合适的选择结构实现实际问题中的条件判断

运用本次课所学内容,采用不同的选择结构实现分制转换问题!

语句与控制结构

语句

  • C/C++语言中,语句是构成程序的基本单元,分为
    • 声明语句用于声明变量、常量、类型、函数等
    • 执行性语句用于执行操作
      • 表达式语句用于实现计算,包括赋值、运算、函数调用等
      • 控制语句用于实现程序流程控制

语句的种类

分号

  • C++使用分号;作为语句结束符
    • 以花括号{}括起的复合语句块不需要分号
helloworld.cpp
#include <iostream>
using namespace std;

int main() {
  cout << "Hello world!\n"
  return 0;
}
$ g++ helloworld.cpp 
helloworld.cpp: In function ‘int main()’:
helloworld.cpp:5:27: error: expected ‘;’ before ‘return’
    5 |   cout << "Hello world!\n"
      |                           ^
      |                           ;
    6 |   return 0;
      |   ~~~~~~     

温馨提示

编译器错误信息是解决编译错误最重要的线索

声明语句

  • 声明语句用于声明变量、常量、类型、函数等
声明语句
// 变量声明
int a;
double b = 3.14;
// 常量声明
const double PI = 3.141592653;
// 函数声明
int max(int a, int b);
// 类型声明
struct Point { int x, y; };

表达式语句

  • 表达式语句是最常见的执行性语句,由表达式和分号组成,用于执行计算操作
    • 如赋值语句、函数调用、递增递减等
    • 表达式语句的值通常被忽略,其目的是为了执行表达式的副作用
a = 1;
b = a + 1;
cout << "Hello world!\n";
d = sin(f);
++i;
  • 空语句是一种特殊的表达式语句,不执行任何操作
    • 可用于占位
while (cin >> a)
  ;

控制语句

  • 控制语句用于实现程序控制结构,C++支持四类控制语句
    • 复合语句顺序结构):程序按照语句出现的顺序执行
    • 选择语句选择结构):根据条件选择执行不同的语句
    • 循环语句循环结构):根据条件重复执行语句
    • 跳转语句:跳转到程序指定位置执行

结构化控制

程序结构定理(Böhm-Jacopini定理)

任何程序都可以由顺序结构选择结构循环结构组合而成

— Corrado Böhm and Giuseppe Jacopini, 1966

顺序结构

选择结构

循环结构

跳转语句

  • 跳转语句用于无条件的控制流转移
    • goto语句:无条件跳转到指定标签位置
    • break语句:跳出当前循环/选择结构
    • continue语句:跳过当前循环的剩余部分
    • return语句:返回函数调用点

goto语句

  • goto语句用于无条件跳转到指定标签位置
if (a > 0)
  goto positive;
goto end;
positive: // label
  cout << "Positive\n";
end:      // label
  return 0;
  • goto语句,灵活、强大,但抽象层次较低,会导致程序的可读性可维护性降低

名人名言

Goto Statement Considered Harmful!

— Edsger W. Dijkstra, 1968

计算机科学史讲堂

Edsger W. Dijkstra,荷兰计算机科学家,1972年图灵奖、1980年IEEE计算机先驱奖获得者,他对整个计算机科学学科的贡献广泛而深远,被认为是计算机科学领域的奠基人之一

  • 算法设计(最短路径算法)
  • 编程语言(Algol语言极其编译器)
  • 操作系统(分页虚拟内存)
  • 并行算法(互斥、死锁、同步)
  • 程序设计方法(结构化程序设计)等

Edsger W. Dijkstra (1930-2002)

语句与控制结构练习

考考你

汇编程序中的程序流程控制是结构化控制还是非结构化控制?

测一测

下列语句,哪些是合法的C++表达式语句?

  1. const int a = 1;
  2. a = a + 1;
  3. cout << "Hello world!\n"
  4. d = sin(f);

顺序结构

顺序结构

  • 顺序结构是程序中最基本的控制结构,是其他控制结构的基础
    • 语句的执行顺序与其出现的顺序一致
    • 单入口、单出口

顺序结构案例

  • 火炮射击:确认目标位置→确认环境参数→数据输入→计算诸元→装填弹药→瞄准校准→射击→毁伤评估
  • 课堂报告:起立→稍息→立正→报告→坐下
  • C++中使用复合语句实现顺序结构

复合语句

  • 复合语句由花括号{}括起来的多条语句组成,也称语句块
    • 复合语句可以嵌套
    • 结束的}后不需要分号
复合语句
{
  int a = 1;
  int b = 2;
  {
    int c = a + b;
    cout << c << '\n';
  }
  cout << a + b << '\n';
}

考考你

若上述程序中第7行末尾加上分号,是否会发生编译错误?试一试!

缩进风格

  • 书写复合语句的应采用良好的缩进风格
    • 左右花括号独占一行且对齐
    • 语句块内部缩进(建议2或4个空格)
良好缩进风格
{
  int a = 1;
  int b = 2;
  {
    int c = a + b;
    cout << c << '\n';
  }
  cout << a + b << '\n';
}
反面教材
{
  int a = 1;
  int b = 2;
  {
      int c = a + b;
      cout << c << '\n';}

  cout << a + b << '\n';
}

变量的作用域

  • 作用域:变量能被使用(可见)的代码区域
  • 复合语句中定义的变量只能在复合语句内部使用
    • 复合语句外定义的变量可以在复合语句内部使用
    • 嵌套复合语句中定义的变量会隐藏外部同名变量

---
config:
  look: handDrawn
  themeVariables:
    fontSize: 28px
---

mindmap
Root(变量)
  变量名:标识符
  类型:数据类型
  值:存储的数据
  地址:内存中的位置
  作用域:变量的可见范围
  生存周期:变量的有效时间

变量的六个属性

变量作用域
{
  int a = 3, b = 2;     // a, b的作用域:2-12行
  {
    int c = 0, d = 0;   // c, d的作用域:4-8行
    int a = 0;          // a的作用域:5-8行,内层的a隐藏外层的a,
    a += b;             // 外层定义的变量可在内层使用
    cout << a << '\n';  // 2
  }
  cout << a << '\n';    // 3
  cout << c << '\n';    // 编译错误,已超出变量c的作用域
  return 0;
}

顺序结构练习

测一测

下面的代码片段,存在问题的是?

A
{                                   
  double x, y, z;
  cin >> x >> y;
  {                               
    z = x + y;
    cout << z;
  }

}
B
{                                   
  double x, y;
  cin >> x >> y;
  {                               
    double z;
    z = x + y;
    cout << z;
  }
} 
C
{                                   
  double x, y;
  cin >> x >> y;
  {                               
    double z;
    z = x + y;
  }
  cout << z;
} 
D
{                                   
  double x, y, z;
  cin >> x >> y;
  {                               
    double z;
    z = x + y;
  }
  cout << z;
} 

顺序结构练习

编程求解

书写一个复合语句,实现变量定义、输入提示、读入百分制成绩、输出用户输入的百分制成绩

输入一个百分制成绩,输出对应的分等级的五分制成绩,例如:输入88,输出“B”

scoreconversion.cpp
{
  // =========== begin ===========

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

运行结果示例:

$ ./score-conversion
输入百分制成绩:92
百分制成绩:92

选择结构

选择结构

  • 选择结构是根据条件选择执行不同的语句,C++提供两种选择结构
    • if语句:根据布尔表达式条件执行语句
    • switch语句:根据整型(或兼容整型)表达式的值选择执行语句

if语句

switch语句

if语句

  • if语句的三种形式
if-then型
if (a > 0)
  cout << "Positive\n";
if-then-else型
if (a > 0)
  cout << "Positive\n";
else
  cout << "Non-positive\n";
if-then-else-if型
if (a > 0)
  cout << "Positive\n";
else if (a < 0)
  cout << "Negative\n";
else
  cout << "Zero\n";

if语句语法要点

  • if后的条件表达式必须使用圆括号括起来
  • 条件表达式必须是布尔类型,或能隐式转换为布尔类型
  • ifelse后的语句块必须是单条语句

考考你

  • C++if语句的三种形式和Python中对应语法结构的区别?
  • 哪些基础类型可以隐式转换为布尔类型?
  • 如果想使用条件控制多条语句,应该如何做?

语句块与缩进

测一测

下列两个程序输出结果有何不同?

int a = 3;
if (a <= 2) {
  a++;
  a++;
}
cout << a << endl;
int a = 3;
if (a <= 2)
  a++;
  a++;

cout << a << endl;

明察秋毫

Python依靠缩进界定代码块的边界,而C++中缩进、换行等编程风格的目的是提升程序可读性,对程序语义无影响

if与else的匹配规则

测一测

下列两个程序分别输出什么结果?

int x = 10, y = 10;
if (x > 5)
  if (y > 15)
    cout << "yes" << endl;
else
  cout << "no" << endl;
int x = 10, y = 10;
if (x > 5) {
  if (y > 15)
    cout << "yes" << endl;
} else {
  cout << "no" << endl;
}

明察秋毫

  • C++中else总是与最近的没有匹配elseif匹配,称为最近匹配规则
  • 上例是C/C++程序设计中著名的The hanging else problem刚从Python过来的同志们请小心!

教员箴言

安全且优美:即使只有一个语句,也使用花括号!

级联与嵌套

  • 级联if语句可以使用嵌套if语句替代
级联
if (condition1) {
  statement1;
} else if (condition2) {
  statement2;
} else {
  statement3;
}
嵌套
if (condition1) {
  statement1;
} else {
  if (condition2) {
    statement2;
  } else {
    statement3;
  }
}

温馨提示

两者的选择取决于代码的可读性逻辑结构,在逻辑清晰的情况下,级联if语句更易读

if语句练习

考考你

阅读下列程序,解释其功能

if-then
double a;
cin >> a;
if (a < 0) {
  a = -a;
}
if-then-else
double a, result;
cin >> a;
if (a > 0) {
  result = a * a / 2;
} else {
  result = a * a * a * 2;
}
if-then-else-if
char c;
cin >> c;
if (c >= 'a' && c <= 'z') {
  c = c - 'a' + 'A';
} else if (c >= 'A' && c <= 'Z') {
  c = c - 'A' + 'a';
} else if (c >= '0' && c <= '9') {
  c = (c - '0' + 9) % 10 + '0';
}

if语句练习

考考你

请使用if语句完成分制转换问题

输入一个百分制成绩,输出对应的分等级的五分制成绩,例如:输入88,输出“B”

scoreconversion.cpp
int score = 0;
cin >> score;
// =========== begin ===========

// ===========  end  ===========

switch语句

  • switch结构适用于根据整数的值进行多分支选择的情况
    • switch后的v必须是整型或兼容整型的类型
    • case后的c1/c2必须是整型常量表达式
    • default为默认分支,即v不匹配任何case时的分支
    • v匹配的case及其下方所有case的语句都会被执行,称为穿透
switch语句
int v = 2;
switch (v) {
  case 1:
    cout << "One\n";
  case 2:
    cout << "Two\n";
  case 3:
    cout << "Three\n";
  default:
    cout << "Default\n";
}

switch结构

打破穿透!

  • 如果只希望执行匹配的case,可使用break语句跳出整个switch结构
break
int v = 2;
switch (v) {
  case 1:
    cout << "One\n";
    break;
  case 2:
    cout << "Two\n";
    break;
  case 3:
    cout << "Three\n";
    break;
  default:
    cout << "Default\n";
}

break

避坑

使用break打破穿透的用法才是switch语句的惯用法——不要忘记break

default分支

  • default分支可以出现在switch结构的任意位置,但通常放在最后
  • default分支缺失,v不匹配任何case时,switch结构不执行任何语句
无default分支的switch语句
int v = 4;
switch (v) {
  case 1:
    cout << "One\n";
    break;
  case 2:
    cout << "Two\n";
    break;
  case 3:
    cout << "Three\n";
    break;
}

switch与级联if

  • breakswitch结构可表示为级联if结构
    • 当需要匹配的值较多时,switch结构更加清晰
switch
switch (v) {
  case 1:
    cout << "One\n";
    break;
  case 2:
    cout << "Two\n";
    break;
  case 3:
    cout << "Three\n";
    break;
  default:
    cout << "Default\n";
}
级联if
if (v == 1) {
  cout << "One\n";
} else if (v == 2) {
  cout << "Two\n";
} else if (v == 3) {
  cout << "Three\n";
} else {
  cout << "Default\n";
}

switch语句的限制

  • switch的条件表达式与case标签必须是整型或兼容整型的类型
    • 如整型、字符类型、枚举类型

考考你

根据浮点数的值选择执行不同的语句,能否通过switch语句实现?

floatswitch.cpp
double x = 3.14;
switch (x) {
  case 9.8:
    cout << "G\n";
    break;
  case 3.14:
    cout << "PI\n";
    break;
  default:
    cout << "Default\n";
}
$ g++ floatswitch.cpp
floatswitch.cpp: In function ‘int main()’:
floatswitch.cpp:6:11: error: switch quantity not an integer
    6 |   switch (x) {
      |           ^

switch语句练习

考考你

能否使用switch语句完成分制转换问题?

输入一个百分制成绩,输出对应的分等级的五分制成绩,例如:输入88,输出“B”

scoreconversion.cpp
int score = 0;
cin >> score;
// =========== begin ===========

// ===========  end  ===========

总结

本节内容

---
config:
  look: handDrawn
  themeVariables:
    fontSize: 20px
---
mindmap
  控制结构(1)
    顺序结构
      变量作用域
    语句
      声明语句
      执行性语句
        表达式语句
        控制语句
          复合语句
          选择语句
          循环语句
          跳转语句
    选择结构
      if语句
        if-then型
        if-then-else型
        if-then-else-if型
      switch语句
        穿透与break
        与级联if的比较

学习目标

  • 掌握C++程序的语句构造规则
  • 理解程序结构定理以及三种基本控制结构
  • 应用合适的选择结构实现实际问题中的条件判断

课后作业

  • 实训(截止时间2025.03.10
    • 综合练习—C&C++选择结构
  • 预习
    • 教材4.4–4.5:循环结构

计算机程序设计