4.1 概念介绍

4.11 语句分类介绍

一条语句是指由0个或多个C++的关键字和表达式组成的,末尾是分号;的符号序列。 一条非复合简单语句末尾必须包含分号;

ival + 5 // 一条没什么实际用处的表达式语句
cout << ival // 一条有用的表达式语句

c++所有的非预编译指令代码必须要以语句的形式存在,不能存在只有关键字或者表达式的序列,必须要将其变成语句形式才行,否则编译出错。

语句分为三种:

  • 空语句
  • 简单语句
  • 复合语句

一条简单语句有且只有一个分号,该分号必须在末尾。(在字符或字符串的中分号不算)。

一条简单语句可以跨多行,只要末尾为分号就行。 同样,多个语句(空,简单,复合)也可以在同一行。

空语句和简单语句都是末尾为分号的字符序列。 而空语句是指只有一个分号而不含其它符号的语句。

; //合法,空语句。

空语句可以用于所有能够存在语句的地方。


复合语句(compound statement)是指用花括号{}括起来的==0个或多个语句==序列,复合语句也被称作块(block)。

一个块就是一个作用域,块内定义的变量的作用域为局部作用域。

复合语句只能在函数中单独使用,其他情况下不能单独使用。

复合语句可以为空,也就是只有一对花括号而不含其它符号,与空语句等价。 要注意复合语句不以分号结束。

复合语句可以嵌套,要注意每个复合语句的作用域。

语句之间可以有0个或者多个空白符分隔

{} //合法,空复合语句。

{其他代码}; // 这里是两个语句\
一个复合语句\
一个空语句。

4.11 控制语句概念介绍

在c++中,默认情况下,语句是按顺序执行的。 也就是编译器会按照从上到下的,从左到右,一个一个==语句==的执行(一个语句为复合语句时,也是按照这个顺序执行该语句里的各个语句)。所以先执行的语句里的变量可以用于之后的语句。

// 合法:按照顺序执行,最后输出28。
int in1 = 28; int in2 = in1; 
cout << in2;

但除非是最简单的程序,否则仅有顺序执行是远远不够。因此,C++语言提供了一组控制流(flow-of-control)语句以支持更复杂的执行路径。

c++的所有控制语句只能在函数中单独使用,其他情况下不能单独使用。

c++的控制语句有以下几种:

  • 条件语句
    • if语句
    • switch语句
  • 循环语句
    • while语句
    • do while语句
    • for语句
      • 传统for语句
      • 范围for语句
  • 跳转语句
    • break
    • continue
    • goto
    • return
  • 异常处理语句

这一章不讲return和异常处理语句,之后会详细介绍这两种语句

控制语句是可以改变某些语句执行的顺序的特殊语句,它们都是以其对应的关键字开头,之后跟着一个或多个语句,其中可能有一个圆括号包围的多个语句或表达式(也就是某些控制语句里的条件部分)。

所有含条件部分的控制语句里的条件部分可以是变量或者表达式,但条件部分不能为空。

除了switch语句,其他语句里的条件部分只要该变量或者表达式的运算结果能隐式转换为布尔类型就行,而switch语句是要能显式转换成整型类型。

除了do while语句,其他含条件部分的控制语句里的条件部分中可以定义变量。 条件部分进行变量定义时不能含有存储类说明符。

所有控制语句中每个后面能跟语句的控制关键字之后只能有一条语句。 但是,如果没有明确说明的情况,默认所有后面能跟语句的控制关键字之后可以是一条复合语句。

  • 控制语句中不带条件部分的关键字和之后跟的语句之间可以有1个或多个空白符。
  • 控制语句中的关键字和其条件部分之间可以有0个或多个空白符。
  • 控制语句的条件部分和之后跟的语句之间可以有0个或多个空白符。
int i = 0;
// 错误:if之后只能有一条语句\
cout << "good2\n";语句不是if语句中的。
if (i == 1)
    cout << "good\n";
    cout << "good2\n";

// 正确:if之后是一条复合语句,\
复合语句中包含两个语句。
if (i == 1)
{
    cout << "good\n";
    cout << "good2\n";
}
4.111 控制语句的作用域

以下这两种变量的作用域范围是从其定义的位置一直到该控制语句的最后一个语句的末尾。

  • 在控制语句内的非复合语句中定义的变量。
  • 在控制语句的条件部分定义的变量。

控制语句内的复合语句中定义的变量的作用域在包含其定义位置的最小块中才可见(也就是包含它的最小的复合语句中才可见)。

if (int ju = 1)
    int ins = 55;
// 错误:变量ju和ins只在if语句中可见
cout << ju << " " << ins;

if (int judge = 1)
{
    // 正确:变量judge可见。
    cout << judge << endl;
    int nest = 15;
    {
        int nest2 = 35;
        // 正确:变量nest可见。
        cout << nest << endl;
    }
    // 错误:变量nest2不可见。
    cout << nest2 << endl;
}
else
{
    // 正确:变量judge可见。
    cout << judge << endl;
    // 错误:变量nest和nest2都不可见。
    cout << nest << " " << nest << endl;
}

4.2 条件语句

C++语言提供了两种按条件执行的语句:

  • if语句
  • switch语句

4.21 if语句

if语句(if statement)的作用是: 判断一个指定的条件是否为真,根据判断结果决定是否执行另外一条语句。

if语句包括两种形式,一种含有else分支,另外一种没有。 if语句的语法形式是:

if (条件表达式) 语句

if else语句的语法形式是:

if (条件表达式) 语句 else 语句

在这两个版本的if语句中,每个关键字之后的语句可以是一条复合语句。

这两个版本的if语句的条件表达式都必须用圆括号包围起来。条件表达式可以是一个普通的表达式,也可以是定义表达式。不管是什么,其类型都必须能隐式转换成布尔类型。

4.211 if语句的执行过程

如果条件表达式为真: 则执行条件表达式后面的语句。当语句执行完成后,程序继续执行if语句后面的其他语句。

如果条件表达式为假: 则跳过条件表达式后面的语句。然后执行if语句匹配的else子句中的语句,如果没有该if语句没有匹配的else子句,则程序继续执行if语句后面的其他语句。


if语句是可以包含其else子句的特殊语句。所以其else子句也是整个if语句中的一部分。

一个if语句最多只能有一个else子句。

if语句可以嵌套,所以要注意else子句的匹配问题,这也叫做悬垂else(dangling else)。 c++规定:==else子句与离它最近的尚末匹配的if语句匹配==。

4.22 switch语句

switch语句的使用形式为:

switch (条件表达式) 语句

switch的条件表达式是一个表达式,且其值必须是整型类型,或者能显式转换成整型类型。

条件表达式后面的语句大部分是一个复合语句,该复合语句内可能含有多个case标签。

4.221 switch语句的执行过程

switch语句首先对条件部分求值,然后与其后面语句中的每个case标签的值比较。

  • 如果条件表达式的值和某个case标签的值相等(比较时可能会执行隐式转换),则程序从该标签之后的第一条语句开始执行,直到到达了switch的结尾或者是遇到一条break语句为止。然后程序继续执行switch语句后面的其他语句。
  • 如果没有任何case标签的值等于条件表达式的值且不存在default标签或者该语句中不存在任何case标签,那么程序跳转到switch语句后面的其他语句继续执行。
//为每个元音字母初始化其计数值
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0
char ch
while (cin >> ch) 
{
//如果ch是元音字母,将其对应的计数值加1
    switch (ch) 
    {
    case 'a':
    ++aCnt;
    break;
    case 'e':
    ++eCnt;
    break;
    case 'i':
    ++iCnt;
    break;
    case 'o':
    ++oCnt;
    break;
    case 'u':
    ++uCnt;
    break;
    }
}
//输出结果
cout « "Number of vowel a: \t" « aCnt « ,\n.
« "Number of vowel e: \t" « eCnt « r\nr
« "Number of vowel i :\t" « iCnt « r \nr
« "Number of vowel o: \t" « oCnt « r \nf
« "Number of vowel u: \t" « uCnt « endl

4.222 switch语句中的标签

case标签和default标签都只能用在switch语句中,不能用于其他语句。

不管是什么类型的标签,每个标签都有以下的规则: 标签后面必须紧跟一个冒号(:),然后在冒号之后必须紧跟一条语句(可以是复合语句)或者一个标签(该标签也要遵循这个规则)。

形式为:

标签 : 语句/标签

标签与冒号,冒号与语句之间可以有0个或多个空白符

case标签

case标签(case label)是指case关键字和它对应的值这个整体,它对应的值叫做case标签值,这两个部分缺一不可。 形式为:

case 整型常量表达式

case标签值必须是整型常量表达式,或者是能被显式转换成整型常量表达式。且任何两个case标签的值不能相同,否则就会引发错误。

char ch = getVal()
int ival = 42
switch(ch) {
case 3.14: //错误:case标签不是一个整数
case ival: //错误:case标签不是一个常量
//...

default标签

default标签是只有关键字default的标签,形式为:

default

default标签(default label)也遵循标签的规则。 default标签的作用是在switch语句中,如果没有任何一个case标签能匹配上switch表达式的值或者语句中没有case标签,则程序将执行紧跟在default标签后面的语句。

default标签可以出现在语句中的任何位置,并不会影响switch的正常判断。

每个switch语句中最多只能出现一次default标签。

//如果ch是一个元音字母,将相应的计数值加1
// 如果ch不是元音字母,\
就从default标签开始执行并把otherCnt1.
otherCnt 力卩 lo
switch (ch) {
default:
++otherCnt
break
case 'a':case 'e':case 'i':case 'o':case 'u':
++vowelCnt
break
}

4.223 switch语句中的变量定义

因为switch的执行流程有可能会跨过某些case标签。如果程序跳转到了某个特定的case,则switch语句中该case标签之前的部分会被忽略掉。

比如,如果在某处一个带有初值的变量位于作用域之外,在另一处该变量位于作用域之内,则从前一处跳转到后一处的行为是非法行为。

因此C++语言规定,不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。

也就是说当switch语句的条件部分后面跟的语句中含有两个及以上的case和default标签时,不允许在该语句内存在变量定义的简单语句(可以存在被默认初始化的基本类型变量的定义),所以定义变量要在内嵌的复合语句内定义。

允许存在变量声明的简单语句。

case true:
//因为程序的执行流程\
可能绕开下面的初始化语句,\
所以该switch语句不合法
string file_name; //错误:控制流绕过一个隐式初始化的变量
int ival = 0;  // 错误:控制流绕过一个显式初始化的变量 
int jval;  //正确:因为jval没有初始化
break;
case false:
//正确:jval虽然在作用域内,但是它没有被初始化
jval = next_num(); 
// 正确:给 jval 賦一个值
if (file_name.empty()) //file_name在作用域内,但是没有被初初始化
    //...

4.3 循环语句

循环语句通常称为迭代,它重复执行给定的操作直到满足某个条件才停下来。

C++语言提供了三种循环执行的语句:

  • while语句
  • do while语句
  • for语句
    • 传统for语句
    • 范围for语句

4.31 while语句

while语句的使用形式为:

while (条件表达式) 语句

在while结构中,只要其条件表达式的求值结果为真,就一直重复执行后面紧跟的语句,直到结果为假。 如果条件表达式的求值结果为假,则不再执行后面紧跟的语句。程序从while语句后的语句继续执行。

vector<int> v
int i
//重复读入数据,直至到达文件末尾或者遇到其他输入问题
while (cin >> i)
v.push_back(i)
//寻找第一冬负值元素
auto beg = v.beginO 
while (beg != v.end() && *beg >= 0)
++beg
if (beg == v.end())
//此时我们知道v中的所有元素都大于等于0

4.32 do while语句

do while语句的使用形式为:

do 语句 while (条件表达式) ;

和while结构的执行过程类似,不过是在求其条件表达式的结果前==先执行语句==,为真则重复执行,为假就不再执行,然后程序从do while语句后的语句继续执行。

do while语句的条件表达式中不能定义变量,且该表达式使用的变量不能是do while语句中定义的变量。

4.33 for语句

4.331 传统for语句

for语句的语法形式是:

for (初始化表达式; 条件表达式; 表达式) 语句

关键字for及括号里的部分称作for语句头。

for语句头中的定义的所有变量的作用域和其他控制语句一样,一直到for语句的末尾为止,但是初始化表达式中声明或定义的变量就像定义在for语句外的变量,一直存在,直到for语句终止为止。

所以for语句等价于

{初始化表达式语句 while (条件表达式) {语句 表达式;}}

for语句头中的初始化表达式可以是空,该初始化表达式可以是各种声明表达式(可以有存储类说明符)和普通表达式。

for语句头中的条件表达式可以是空,和其他控制语句的条件表达式一样,可以定义变量,只要能隐式转化为布尔类型就行。条件表达式如果为空时,等价于条件为真。

for语句头中最右边的那个表达式不能是声明或定义表达式,且该表达式可以为空。

4.3311 传统for语句的执行流程

循环开始时,首先执行一次初始化表达式,之后的循环不再执行初始化表达式。

接下来执行条件表达式并求出条件表达式的值: 如果为真,则执行语句头后面的语句,当语句头后面的语句执行完毕后,再执行语句头最右边的表达式,然后进行下一轮循环。 如果为假,则程序从for语句之后的语句继续执行。

//重复处理s中的字符直至我们处理完全部字符或者遇到了一个表示空白的字符
for (decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index) s[index] = toupper (s[index]); //将当前字符改成大写
4.332 范围for语句

C++11新标准引入了一种更简单的for语句,这种语句可以遍历容器或其他序列的所有元素。

范围for语句(range for statement)的语法形式是:

for (声明表达式 : 序列表达式) 语句

序列表达式表示的必须是一个确定元素数量的序列,比如数组或者vector或string等类型的对象,这些类型的共同特点是拥有能返回迭代器的begin和end成员。 所以可以用花括号括起来的初始值列表,只要其列表能转换成拥有迭代器的类型。

声明表达式中只能且必须声明一个变量,且不能用存储类说明符。该变量的类型必须是序列中的每个元素都得能隐式转换成该变量的类型。

因为范围for语句是用每个元素来初始化声明的局部变量的,所以我们需要修改或节省空间时,可以在声明表达式中声明引用类型来达到目的。

4.3321 范围for语句的执行流程

范围for语句的迭代刚开始时,首先会执行声明表达式,并将序列中的第一个元素来初始化刚声明的变量,接着执行for语句头后面的语句,执行完毕后在进行下一轮迭代,此时还是会先执行声明表达式,并将序列中的下一个元素来初始化该变量,然后就和之前的一样,一直迭代,直到遍历完序列中的所有元素。 遍历完序列中的所有元素后,程序从for语句之后的语句继续执行。

要注意不要在进行迭代的序列中增删元素,否则会出错。

vector<int> v = {0,1,2,3,4,5,6,7,8,9};
//范围变量必须是引用类型,这样才能对元素执行写操作
for (auto &r : v) //对于v中的每一个元素
r *= 2  //将v中每个元素的值翻倍

// 上面范围for语句的定义与下面传统for语句的定义等价
for (auto beg = v.begin(), end = v.end() beg != end ++beg) { 
auto &r = *beg // r必须是引用类型,这样才能对元素执行写操作 
r *= 2 //将v中每个元素的值翻

4.4 跳转语句

跳转语句主要的功能是中断当前的执行过程。 C++语言提供了四种跳转语句:

  • 跳转语句
    • break
    • continue
    • goto
    • return
4.41 break语句

break语句(break statement)负责终止离它最近的且未结束的while、do while、for和switch语句,并从这些语句之后的第一条语句开始继续执行。 使用形式为:

break;

break语句中不能有除了关键字break和末尾分号的其他符号。 所以,break语句只能出现在循环语句或者switch语句内部(包括嵌套在此类循环里的语句或块的内部)。

break语句的作用范围仅限于最近的循环或者switch。

4.42 continue语句

continue语句(continue statement)终止最近的循环语句中的当前迭代并立即开始下一次迭代。 使用形式为:

continue;

continue语句中不能有除了关键字continue和末尾分号的其他符号。 continue语句只能出现在for、while和do while循环的内部,或者嵌套在此类循环里的语句或块的内部。

和break语句类似的是,出现在嵌套循环中的continue语句也仅作用于离它最近的循环,但continue语句不能作用于switch语句。

要注意,对于传统的for循环来说,continue语句之后还是会继续执行for语句头最右边的表达式。

4.43 goto语句

goto语句(goto statement)的作用是从goto语句无条件跳转到同一函数内的另一条语句并从该语句开始执行。

goto语句的语法形式是:

goto 标签;

goto语句中不能有除了关键字goto、标签和末尾分号的其他符号。 其中,该标签是用于标识一条语句的标识符,该标签不能是case和default标签。

带标签语句(labeled statement)是一种特殊的语句,在它之前有一个标识符以及一个冒号。 定义带标签语句的形式为:

标签标识符 : 语句/其他标签标识符

标签标识符独立于变量或其他标识符的名字,因此,标签标识符可以和程序中其他实体的标识符使用同一个名字而不会相互干扰。但标签之间不能重名。

每个标签都有以下的规则: 标签后面必须紧跟一个冒号(:),然后在冒号之后必须紧跟一条语句(可以是复合语句)或者一个标签(该标签也要遵循这个规则)。

形式为:

标签标识符 : 语句/其他标签标识符

标签与冒号,冒号与语句之间可以有0个或多个空白符

goto语句指向的带标签的语句必须要位于同一个函数之内。

goto语句和switch语句类似,goto语句也不能将程序的控制权从变量的作用域之外转移到作用域之内(也就是跳过某变量的定义而直接使用),但可以从变量的作用域之内转移到作用域之外。

// ...
goto end;
int ix = 10; //错误:goto语句绕过了一个带初始化的变量定义
end
//错误:此处的代码需要使用ix,但是goto语句绕过了它的声明
ix = 42;

//向后跳过一个带初始化的变量定义是合法的
beginint sz = get_size();
if (sz <= 0) {goto begin}