【C++基础】第三十课:条件语句

if语句,switch语句

Posted by x-jeff on October 9, 2021

【C++基础】系列博客为参考《C++ Primer中文版(第5版)》C++11标准)一书,自己所做的读书笔记。
本文为原创文章,未经本人允许,禁止转载。转载请注明出处。

1.条件语句

C++语言提供了两种按条件执行的语句。一种是if语句,另外一种是switch语句。

2.if语句

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

形式一(不包含else分支):

1
2
if (condition)
	statement

形式二(包含else分支):

1
2
3
4
if (condition)
	statement
else
	statement2

需要注意:condition必须用圆括号包围起来且其类型必须能转换成布尔类型。

举个例子:

1
2
3
4
5
if (grade % 10 > 7)//%为取余
	lettergrade += '+';//末尾是8或者9的成绩添加一个加号
else if (grade % 10 < 3)
	lettergrade += '-';//末尾是0,1或2,添加一个减号
//此处后续可以没有else分支

对于悬垂else(dangling else),C++规定else与离它最近的尚未匹配的if匹配,从而消除了程序的二义性:

1
2
3
4
5
6
//else分支匹配的是内层if语句
if (grade % 10 >= 3)
	if (grade % 10 > 7)
		lettergrade += '+';
else
	lettergrade += '-';

上述程序等价于:

1
2
3
4
5
if (grade % 10 >= 3)
	if (grade % 10 > 7)
		lettergrade += '+';
	else
		lettergrade += '-';

3.switch语句

假如实现功能:统计五个元音字母在文本中出现的次数。便可直接使用switch语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
char ch;
while (cin >> ch) {
	switch (ch){
		case 'a':
			++aCnt;
			break;
		case 'e':
			++eCnt;
			break;
		case 'i':
			++iCnt;
			break;
		case 'o':
			++oCnt;
			break;
		case 'u':
			++uCnt;
			break;
	}
}

switch语句首先对括号里的表达式求值,该表达式紧跟在关键字switch的后面,可以是一个初始化的变量声明。表达式的值转换成整数类型,然后与每个case标签的值比较。

如果表达式和某个case标签的值匹配成功,程序从该标签之后的第一条语句开始执行,直到到达了switch的结尾或者是遇到一条break语句为止。

case关键字和它对应的值一起被称为case标签(case label)。⚠️case标签必须是整型常量表达式。

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

任何两个case标签的值不能相同,否则就会引发错误。另外,default也是一种特殊的case标签,后续会介绍。

3.1.switch内部的控制流

如果某个case标签匹配成功,将从该标签开始往后顺序执行所有case分支,除非程序显式地中断了这一过程(例如使用了break),否则直到switch的结尾处才会停下来。

1
2
3
4
5
6
7
8
9
10
11
12
unsigned int c1 = 0, c2 = 0, c3 = 0;
switch ('b') {//此处如果不是a,b,c中的某一个则会报错
    case 'a':
        c1++;
    case 'b':
        c2++;
    case 'c':
        c3++;
}
cout << c1 << endl;//0
cout << c2 << endl;//1
cout << c3 << endl;//1

有时我们会故意省略掉break语句,使得程序能够连续执行若干个case标签。例如,统计所有元音字母出现的总次数:

1
2
3
4
5
6
7
8
9
10
11
12
13
unsigned vowelCnt = 0;
//...
switch (ch)
{
	//出现了a,e,i,o,u中的任意一个都会将vowelCnt的值加1
	case 'a':
	case 'e':
	case 'i':
	case 'o':
	case 'u':
		++vowelCnt;
		break;
}

此外,case标签之后不一定非得换行。把几个case标签写在一行里也可以:

1
2
3
4
5
6
7
switch (ch)
{
	//另一种合法的书写形式
	case 'a': case 'e': case 'i': case 'o': case 'u':
		++vowelCnt;
		break;
}

‼️有一种常见的错觉是程序只执行匹配成功的那个case分支的语句。

3.2.default标签

如果没有任何一个case标签能匹配上switch表达式的值,程序将执行紧跟在default标签(default label)后面的语句。例如,可以增加一个计数值来统计非元音字母的数量:

1
2
3
4
5
6
7
8
9
10
//如果ch是一个元音字母,将相应的计数值加1
switch (ch)
{
	case 'a': case 'e': case 'i': case 'o': case 'u':
		++vowelCnt;
		break;
	default:
		++otherCnt;
		break;
}

标签(case和default)不应该孤零零地出现,它后面必须跟上一条语句(可以是空语句或空块)或者另外一个case标签。

3.3.switch内部的变量定义

switch的执行流程有可能会跨过某些case标签。如果程序跳转到了某个特定的case,则switch结构中该case标签之前的部分会被忽略掉。这种忽略掉一部分代码的行为引出了一个有趣的问题:如果被略过的代码中含有变量的定义该怎么办?

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

1
2
3
4
5
6
7
8
9
10
11
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在作用域内,但是没有被初始化
		//...

正确的例子:

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
    switch ('b') {
        case 'a':
            //string file_name;
            //int ival = 0;
            int jval;
            break;
        case 'b':
            jval = 0;
            cout << jval << endl;//0
    }
}

错误的例子:

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
    switch ('b') {
        case 'a':
            string file_name;
            int ival = 0;
            int jval;
            break;
        case 'b':
            jval = 0;
            cout << jval << endl;
    }
}

错误的例子:

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
    switch ('b') {
        case 'a':
            //string file_name;
            //int ival = 0;
            //int jval;
            break;
        case 'b':
            jval = 0;
            cout << jval << endl;
    }
}

个人理解:排在后面的case可以使用前面case里定义的变量,但是该变量在定义的时候不能被初始化,无论是隐式还是显式初始化都不行。

也可以通过{}限定作用域:

1
2
3
4
5
6
7
8
9
10
case true:
	{
		//...
	}
	break;
case false:
	{
		//...
	}
	break;

错误的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
    switch ('b') {
        case 'a': {
            //string file_name;
            //int ival = 0;
            int jval;
            break;
        }
        case 'b': {
            jval = 0;
            cout << jval << endl;
        }
    }
}