【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;
}
}
}