语句
本章内容主要涉及一些基本的语句,具体内容就不一一记录了,主要记录一些容易出错的细节。最后重点记一下c++异常处理部分,因为之前很少写过try语句块,所以对这部分的内容快忘干净了,这次再总结一下,方便以后复习。
1、最简单的语句就是空语句,空语句就是只包含一个单独的分号。另外注意别漏写分号,也不要多写分号。
2、条件语句:使用if else时,注意else与if的匹配问题,也称为悬垂else,c++规定else与离它最近的且尚未匹配的if匹配。
3、switch case语句:该语句可以实现在若干条选项中作出选择。case关键字和它对应的值称为case标签,case标签必须是一个整型常量表达式,另外任意两个case标签值不能相同。
int i,j=10;
cin>>i;
switch(i){
case 3.14://错误,case标签不是一个整型
case j://错误,case标签不是一个常量
}
之所以case标签必须是一个整型,我猜测的原因是case标签是否与switch后的表达式匹配是通过比较标签与表达式是否相等。而浮点型数据用于判断是否相等是不精确的,因此标签只能是整型。
另外c++中的switch case具有穿透性,即如果某个case匹配成功的话,将从该case分支开始往后顺序执行各个case分支,直到switch结尾或者遇到break才结束,如下所示:
int number = 2;
int num = 0;
switch (number)
{
case 1: //不匹配,
num++;
case 2: //匹配成功
num++; //num=1;
case 3: //穿透
num++; //num=2;
case 4: //穿透
num++; //num=3
case 5: //穿透
num++; //num=4
break; //遇到了break退出switch语句,往后的语句不再执行,num最终等于4
case 6:
num++;
default: //如果以上6个case都没有匹配成功,则执行default后的语句。注:default同样具有穿透性
num = 100;
}
cout << num << endl;
如上所示,程序会从第二个case开始执行,一直执行到遇到第5个case中的break退出switch语句。
另外,可以在最后一个case后加上一个default标签,如果没有一个case标签匹配成功,那么就会执行default中的语句。注意:default同样具有穿透性,如果上述代码中,第5个case中没有break,则最终会得到num=100。
另外不能跨过变量的初始化语句而直接跳转到该变量作用域内的另一个位置。什么意思呢,首先看看下面的代码。
int a, i=1;
switch(i){
case 0:
int j=10;
case 1:
a=j; //错误,使用了为初始化的变量j
}
由于i=1,因此会匹配第二个case而跳过第一个case中的语句,也就是跳过了对变量j的初始化,此时在第二个case中使用未初始化的j将会发生错误。
4、迭代语句
迭代语句包括:
①、while语句:先判断条件再执行循环体,因此循环体可能一次也不执行;
②、do while语句:先执行循环体,后判断条件,然后再执行循环体,因此循环体至少执行一次。do while语句需要在括号包围起来的条件后面用一个分号表示语句结束
do{
循环体
}
while(条件);
另外do while语句使用的条件不能为空,且条件使用的变量必须定义在循环体之外,作为循环的条件,不能定义在do内部
int num=10;//作为循环体的条件必须定义在循环体以外,不能定义在do内部
do{
cout<<num<<endl;
num--;
}
while(num>0);
另外,不能在条件部分定义变量,因为先执行循环体,后判断条件,如果在条件中定义变量,就会出现变量的使用出现在定义之前的错误情况。
③、for语句:for语句形式如下:
for(initializer;condition;expression){
statement;
}
for循环的执行顺序是:
1、先执行initializer初始化,且只执行一次;
2、判断condition条件,如果符合则执行3,否则退出for语句;
3、执行statement循环体;
4、执行expressin;
5、执行第2步,开始循环。
对于for语句,其中的initializer、conditation、expression都可以省略,但是分号不能省略。
1、省略initialiezr,即无须初始化,有可能变量在之间已经定义过;
2、省略conditition,省略判断语句表示条件值永远为true,这种情况下循环体中必须有语句负责退出循环。
3、省略expression,省略表达式部分,可以在循环体中改变变量的值。
④、基于范围的for语句
在之前的笔记中已经讲到过,不再记录了。值得注意的是:当对vector等其他容器执行基于范围的for语句时,不能通过范围for语句增加vector的对象。因为在范围for语句中以及预存了end()的值(可以理解为容器边界),如果增加或者删除元素,会使得end()函数的值变无效。
5.5 跳转语句
跳转语句有break/continue/goto/return.
1、break语句
break语句只能出现在迭代语句或者switch语句中,负责终止离它最近的while、do while、for 或者switch语句,并且从这些语句之后的第一条语句开始执行。
2、continue语句
continue只能出现在while 、do while 或for循环内部。如果switch嵌套在迭代语句内部时,也可以在switch中使用continue。continue表示终止当前迭代,直接进入下一次迭代。
3、goto语句
goto语句可以无条件的从goto语句跳转到同一函数的另一条语句。其语法形式为:goto lable;其中lable是用于标识一条语句的标识符,带标签语句是一种特殊语句,它包含一个标识符和一个冒号,带标签的语句可以作为goto的目标。goto语句和lable标识符必须位于同一函数内。
int i;
cin >> i;
if (i > 60)
goto end; //当i>60时,直接跳转到end标识的语句,输出成绩
cout << "不及格" << endl;
end : cout << i << endl;
如以上代码:当i>60时,直接跳转到end标识的语句,输出成绩。当i<60时,不执行goto语句,顺序执行往后的语句,即先输出“及格”后输出成绩i。
另外注意一个和switch case类似的问题,由于goto具有跳转作用,因此得小心跳过某个变量的定义而直接使用它,如:
goto end;
int i=10;
end :
i=20; //错误,使用goto直接跳过了变量i的声明产生错误
但是,如果往后跳到一个已经执行的定义是合法的,此时系统会销毁已经定义的变量,然后重新定义它,如:
begin :
int i=10;
其他代码;
goto begin;//正确,此时系统将会销毁已经定义的变量i,然后重新定义它
5.6 try语句块和异常处理
异常就是运行时的反常行为,这些行为超出了函数正常功能的范围。当程序的某一个部分检测到一个它无法处理的问题时,就需要用到异常处理。此时,检测问题的部分应该发出某种信号以表明程序遇到故障。如果程序中含有可能引发异常的代码,应该有专门的代码来检测这段代码,并处理可能会发生的异常问题,这就是异常处理机制。异常处理机制包括异常检测和异常处理两部分,我们先分别讲解这两个概念,然后在看看它们是怎样工作的。
1、异常检测:异常检测部分使用throw表达式来表示遇到的无法处理的问题。“throw”这个单词具有“扔,抛”的意思,也就是throw表达式会抛出一个异常,当抛出一个异常后,throw表达式的任务也就完成了,它不会理会这个异常由谁处理,怎么处理。throw表达式由throw关键字和其后的一个表达式组成,其中表达式的类型就是抛出的异常类型,这个异常类型可以是内置类型如int ,double等,也可以是自定义类型或者c++标准异常类型,如:
throw 1;//异常类型为Int型
throw 3.14;//异常类型为浮点型
throw runtime_error("发生错误");//异常类型为C++标准库异常类型的一种
2、异常处理:异常处理由try块和紧随其后的一个或多个catch子句完成。其中:try块由关键字try和其后的以对花括号组成,catch子句由关键字catch、小括号内一个对象声名(称之为异常声明,不能含有异常声明类型相同的两个case子句)以及一个块组成,当throw表达式抛出的异常类型和某个catch的异常声明类型一致时,就执行相应块中的程序,而其他catch子句不再执行。类似于switch case语句。
try{
代码,一般包含throw表达式或者间接包含throw表达式
}
catch (int i){ //异常声明的类型为int型,若try中throw抛出的异常类型是int型,则执行该case语句
处理代码;
}
catch (double d){ //异常声明的类型为double型。若此次异常声明类型仍然是int型,程序将会报错
处理代码;
}
写完throw表达式和try语句块,他们之间的工作原理也应该明白的差不多了:一般throw表达式位于try块中,或者间接位于try块中,间接的意思是指:如果函数B调用了函数A,若函数A中含有throw表达式,而函数A在函数B中的调用位置位于函数B的try块中,这就是throw表达式间接位于try块中,try负责捕捉throw表达式抛出的异常,然后将该异常类型与其后的case子句中声明的异常类型一一对比,找到类型一致的case子句然后执行相应的代码。举几个例子:
int main()
{
int month;
cin >> month;
try
{
if (month > 12)
throw month; //抛出异常类型为int型
cout << month << endl; //异常抛出后,该语句将不会被执行
}
catch (int i) //抛出类型与异常声明类型匹配,将month的值传给i,并执行块中的语句
{
cout << "输入的月份:"<<i<<" 大于12月" << endl;
}
catch (double d) //由于只有一个异常抛出类型,因此该case子句将永远不会执行。
{
//
}
return 0;
}
以上是属于try块中直接含有throw表达式的例子,再来一个间接的例子:
void func() //该函数只有异常检测功能,没有异常处理功能
{
int month;
cin >> month;
if (month > 12)
throw month; //抛出类型为int型的异常
cout << month << endl;
}
int main()
{
try
{
func(); //主函数捕获到func函数抛出的异常month
}
catch (int i) //month的类型与i类型一致,并将month的值传递给i,执行相应块中的代码
{
cout << "输入的月份:"<<i<<" 大于12月" << endl;
}
catch (double d)
{
cout << d << endl;
}
return 0;
}
如上述代码所示:函数func中只有异常检测,而没有异常处理,因此func函数的throw表达式抛出的异常就交给了他的调用者也就是main函数处理,而func函数在抛出异常后就立即终止。
另外,如果func中有try块,但是在func中throw抛出的异常无法在自身函数中匹配适合的case子句,此时函数func函数将会被终止,然后在调用func函数的函数A中继续匹配case子句,如果函数A中仍然没有匹配case子句,那么函数A也会被终止,然后继续在调用函数A 的函数B中继续匹配,按函数调用的路径逐层回退,直到匹配到适当类型的case子句。如果最终还是没有匹配到合适的case子句,那么程序就会转到名为terminate的标准库函数,该函数的行为与系统有关,一般情况下转到该函数将会使程序非正常退出。
标准异常
c++标准库定义了一组类,用于报告标准库函数遇到的问题,他们分别定义在4个头文件中:
1、exception头文件定义了最通用的异常类exception,只报告异常的发生,不提高任何额外的信息;
2、stdexcept头文件定义了几种常见的异常类,详细信息见《c++ primer》第176页.
3、new头文件往后介绍;
4、type_info头文件往后介绍。
标准库异常类只定义了几种运算,包括创建或拷贝异常类对象,以及异常类对象的赋值。异常类型自定义了一个名为what的成员函数,该函数无任何参数,返回值为指向c风格字符串的const char*,用于提供异常的一些文本信息,如:
#include<stdexcept>
int month;
cin >> month;
try
{
if (month > 12)
throw runtime_error("月份输入错误”); //异常类型为stdexcept定义的异常类
cout << month << endl;
}
catch (runtime_error err)
{
cout << err.what() << endl; //执行异常类型的what函数,即输出“月份输入错误”。
}
更多关于异常处理的知识可以看看这篇文章
最后用一个生动形象的例子总结一下异常处理:(个人编的,可能有不恰当的地方)
来自富土康3号流水线(函数A)的张全蛋(throw)正在流水线上组装手机。突然他组装手机用的螺丝刀坏了,那么3号流水线就出现了异常,张全蛋把异常类型为螺丝刀的异常抛出,此时分为以下几种情况:
1、如果3号流水线上有自己的维修组(try块),并且维修组可以维修螺丝刀(case),那么张全蛋的异常就由3号流水线自己解决,此时张全蛋后面的手机将停止组装,3号流水线的工作将由维修组里维修螺丝刀的地方开始往后顺序进行。3号流水线并没有终止;
2、如果3号流水线没有维修组,或者有维修组但是没有可以修理螺丝刀的case,那么3号流水线将被终止,这把被抛出的坏螺丝刀(异常)将交给调用3号流水线的车间(函数B)处理,如果该车间有维修组并且能够维修螺丝刀,那么这把坏的螺丝刀由该车间处理。如果该车间仍然处理不了,该车间同样将会被终止,这把坏的螺丝刀只能继续交给调用该车间的公司(函数C)处理。
3、如果公司有维修组并且能够维修这把螺丝刀,那么就由公司处理这把螺丝刀,如果公司的维修组里不能修理螺丝刀,或者公司根本就没有维修组,那没办法,有关部门(terminate的标准库函数)只能介入了,而一旦有关部门介入该公司,就会导致该公司非正常退出市场(程序非正常退出)。