C++程序是一组函数,而每个函数又是一组语句。C++有好多种语句类型,上篇笔记总结的表达式可以转化为语句,通常被称作表达式语句。除此之外,还有空语句(;),返回语句(return 0;),复合语句({}程序块)等。以上又被统称为简单语句。相对于简单语句,C++还有类似于C语言的特殊语句,条件语句、循环语句以及转移语句。本篇笔记总结这些特殊语句的用法。
一、条件语句
1.if语句
if(boolean_expression)
{
// 如果布尔表达式为真将执行的语句
}
如果布尔表达式为true,则if语句内的代码块将被执行。如果布尔表达式为 false,则if语句结束后的第一组代码(闭括号后)将被执行。整个if语句被视为一条语句。
if的测试条件会被强制转化为bool类型,这意味着0被转换为false,一切非零值(包括负数)被转化为正值。常见的编写带有返回值的函数时,用返回0判断程序执行正常,返回负值代表错误代码,在调用该函数时只要用if(function())即可判断。
如果决策结果只有两种,也即“非黑即白”,那么可以引入if else语句。
if(boolean_expression)
{
// 如果布尔表达式为真将执行的语句
}
else
{
// 如果布尔表达式为假将执行的语句
}
如果布尔表达式为true,则执行 if 块内的代码。如果布尔表达式为false,则执行else块内的代码。
如果决策有两个以上的选择,那么将引入if elseif else结构语句。
if(boolean_expression 1)
{
// 当布尔表达式 1 为真时执行
}
else if( boolean_expression 2)
{
// 当布尔表达式 2 为真时执行
}
else if( boolean_expression 3)
{
// 当布尔表达式 3 为真时执行
}
else
{
// 当上面条件都不为真时执行
}
需要注意一个if后可跟零个或一个else,else必须在所有else if之后。一个if后可跟零个或多个else if,else if必须在else之前。一旦某个else if匹配成功,其他的else if或else将不会被测试。另外在书写if的条件表达式时,判别等于的情况最好写作if(value==variable),这可以避免与赋值语句的混淆。另外,浮点数由于截断误差的存在,不要用于比较相等或不等,而要写作相差是否在截断误差内的方式,如if(fabs(a-b)<1e-6);
2.switch语句
如果某种决策有五种以上选择,当然可以用if else if else来实现五个分支。但C++提供了更好的大型分支计算语句switch。
switch语句的基本格式如下:
switch (表达式)
{
case 常量表达式1:《语句序列1》《break;》//《》中的内容可省
……
case 常量表达式n:《语句序列n》《break;》//同上,下同
《default:语句序列》
}
其中:
表达式——条件表达式,用作判断条件,取值为整型、字符型、布尔型或枚举型。
常量表达式——由常量构成,取值类型与条件表达式相同。
语句序列——可以是一个语句也可以是一组语句。
switch语句的执行流程如下:
(1) 求条件表达式的值,并在常量表达式中找到与之相等的分支作为执行入口;
(2) 顺序执行该分支的语句序列,直到遇到break语句或开关语句的关括号“}”为止;这意味着程序进入某一分支后,将依次执行之后的所有语句,程序不会在执行到下一个case处自动停止,除非遇见了break;。但不意味着每个case后都要有break,因为顺序执行的情况也是有的。比如书中这段适应大小写字符输入的情况:
(3) 当条件表达式的值与所有常量表达式的值均不相等时,若有default分支,则执行其语句序列,否则跳出switch语句,执行后续语句。
将两种条件语句作比较,可以发现if语句可以对浮点数和范围进行条件判断,而switch只能对常量(整型),所以if 的应用比较广泛。但如果同样可以使用if语句和switch语句,如果分支大于3,通常要用switch语句。
二、循环语句
1. for循环
for循环允许您编写一个执行特定次数的循环的重复控制结构。
for ( init; condition; increment )
{
statement(s);
}
for循环的执行流程为:
init 会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。
接下来,会判断condition。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会跳转到紧接着for循环的下一条语句(for循环整体是一条语句)。
在执行完for循环主体后,控制流会跳回上面的increment语句。该语句允许您更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。
条件再次被判断。如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为假时,for循环终止。
2.while循环
while循环是没有初始化和更新部分的for 循环,它只有测试条件和循环体。只要给定的条件为真,while循环语句会重复执行一个目标语句。
while(condition)
{
statement(s);
}
首先程序计算括号内的测试条件,如果为true则实执行循环体中的语句。知道测试条件为false为止。
通常,while的应用也要在while语句之前初始化一个值,判断条件也即判断该值,在while循环体内对这个值进行更新。这样的结果其实就是for循环的表达形式。所以,while循环被用在读入文件或输入,以及死循环(while(1))的情况。
3.do…while循环
不像for和while循环,它们是在循环头部测试循环条件。do...while循环是在循环的尾部检查它的条件。因此for循环和while循环被称作入口条件循环,而do…while是出口条件循环。
do...while循环与while循环类似,但do...while循环会确保至少执行一次循环。
do
{
statement(s);
}while( condition );
条件表达式出现在循环的尾部,所以循环中的statement(s)会在条件被测试之前至少执行一次。
如果条件为真,控制流会跳转回上面的do,然后重新执行循环中的statement(s)。这个过程会不断重复,直到给定条件变为假为止。
三. 转移语句
这里的转移语句是指break,continue以及goto语句。
1.break和continue语句
break和continue语句都可以使程序能够跳过部分代码。可以在switch语句和任何循环中应用break语句,可以使程序跳到switch或循环后面的语句执行。continue语句只用于循环中,让程序跳过循环体中剩余的代码,并执行下一次循环。
注意:
-
break如果用于循环是用来终止循环,break只能终止距离它最近的循环
-
break如果用于switch,则是用于终止switch
-
break不能直接用于if中,除非if属于循环的一个句子,但此时它的作用是循环而不是if。
同样:
-
continue只作用于距离它最近的循环:for 、while 、do ...while
-
continue也不能直接用于if中,除非if属于循环的一个句子,同break,此时它的作用是循环而不是if
-
continue的优点之一是当需要有if作为判断条件来判断是否执行某些代码时,continue可以使得不必将所有的代码都放进if语句块中。
2.goto语句
C++和C语言一样也有goto语句。goto语句也成为无条件转移语句,其基本形式如下 :
goto 语句标号
语句标号由一个有效地标识符和符号";"组成,其中,标识符的命名规则与变量名称相同,即由字母、数字和下划线组成,且第一个字符必须是字母或下划线。执行goto语句后,程序就会跳转到语句标号处,并执行其后的语句。
通常goto语句与if条件语句连用,但是goto语句在给程序带来灵活性的同时,也会使得使程序结构层次不清,而且不易读,所以要合理运用该语句。在C++PrimerPlus中作者写明在大多数情况下(或者有的人认为在任何情况下)都不应该用goto语句,而应该用结构化控制语句实现其功能。因为goto使得程序的控制流难以跟踪,使程序难以理解和难以修改。任何使用 goto 语句的程序可以改写成不需要使用 goto 语句的写法。
但我个人觉得有一些场景可以尝试使用goto语句,比如多层嵌套循环的推出:
for(...) {
for(...) {
while(...) {
if(...) goto stop;
.
.
.
}
}
}
stop:
cout << "Error in program.\n";
消除goto会导致一些额外的测试被执行。一个简单的break语句在这里不会起到作用,因为它只会使程序退出最内层循环。
另外,在一些复杂流程的函数退出,尤其是要清理内存的情况也可以用goto。
比如以下是网上一个老外写的例子:
int MapiInit()
{
int Result = DT_ERROR;
HRESULT hResult = S_FALSE;
HANDLE hLock = NULL;
BOOL bLocked = FALSE;
DWORD rc = WAIT_TIMEOUT;
static LPCTSTR szLockName = _T("MAPIInit");
hLock = CreateMutex(NULL,FALSE,szLockName);
if(!hLock)
{
goto error;
}
rc = WaitForSingleObject(hLock,0);
if(rc == WAIT_OBJECT_0)
{
bLocked = TRUE;
}
if(!m_CoInitd)
{
hResult = CoInitialize(NULL);
if(hResult != S_OK)
{
goto error;
}
m_CoInitd = TRUE;
}
if(!m_MapiInitd)
{
hResult = MAPIInitialize(NULL);
if(hResult != S_OK)
{
goto error;
}
m_MapiInitd = TRUE;
}
Result = DT_OK;
done:
if (hLock) {
if (bLocked)
{
ReleaseMutex(hLock);
}
CloseHandle(hLock);
}
return Result;
error:
Result = DT_ERROR;
goto done;
}
如果在函数入口处动态开辟了一些内存空间(动态数组),可以在error处进行统一释放。这就省去了每一个判断失败的地方都释放一遍的重复代码。