因为译文找不回了,只能通过笔记的形式记录
第五章语句
5.1简单语句
语句:以分号结尾。
例如:
ival+5; //一条没有实际用处的语句
cout << ival; //一条有用的表达式语句
;//这是一条空语句
注意上面的第三条语句,只有一个分号,只有一个结尾,这表示一个空语句。
空语句的使用场景一般为:语法上需要一条语句,但是逻辑上已经不再需要,例如:
while(cin >> s && s != sought)
;//空语句
上面代码的逻辑部分,已经在括号中处理完毕,不需要再处理其他,但是while又需要一个循环体,因此,这里用空语句代替。
通常情况下,给空语句加上一个注释,这样,有助于程序的阅读。
一个容易出错的例子:
while(iter != svec.end());
++iter;
while语句后面跟了一个分号,这个分号代替了while的循环体,因此,底下的++iter不是循环体的一部分。
所以iter永远都不会自增,循环将无线循环下去
复合语句:将多个语句当成一个语句,就叫做复合语句,此时需要用一个大括号括起来。
在这个大括号内,既可以有语句,也可以有声明。
复合语句也称作语句块
如果在程序上需要一条语句,但是逻辑上面需要多条语句,则可以使用复合语句。
例如:while或者for的循环体,需要一条语句,但是一条语句常常不能满足逻辑需求
所以可以使用语句块
while(val <=10){
sum += val;
++val;
}
逻辑上面来讲,需要两条语句,但是while的循环体只能容纳一个语句,因此此处使用了语句块
注意下面的写法是有问题的:
while(val <= 10);{
sum += val;
++val;
}
上面已经有空语句,称为了while的循环体了
注意下面的写法是正确的:
while(val <= 10 ){
}
大括号内什么都没有,相当于一条空语句
语句的作用域
在语句块中定义的变量只能在这个语句块之内有效,语句块结束,这些变量就失效
例如:
while(int i = get_num()){
int j = 0;
cout << i << " " <<j << endl;
}
j = 0;//错误在语句块之外访问
i = 1;//错误,在语句块之外访问
上面例子中,i并没有定义在语句块中,但是他的作用域依然只能在语句块中,不能超出语句块
5.3条件语句
c++ 只有两种条件语句,一种叫做if,一种叫做switch
5.3.1 if语句
格式如下:
if (condition)
statment1
else
statement2
说明:如果condition为真,则执行statement1,否则执行statement2
其中,else部分,还可以省略,即,表示condition为false的时候,什么也不做
举例如下:
int i = 0;
cin >> i;
if(i){
cout << "i为真" << endl;
}else{
cout << "i为假" << endl;
}
注意:上面的statement,这里使用了语句块,而上面说,在逻辑上需要多个语句时,才需要使用语句块。此处为何要使用语句块。
其中的一个原因是:个人习惯,因为这种写法并不会有问题,而且也不会引入性能上面的差别。之所以这样写,是因为个人习惯。
还有一个原因是:当没有写花括号的时候,经过一段时间再次添加代码可能会忘记花括号,而此时可能并不是我们想要的结果,例子如下:
if(j)
j=0;
k=0;//后面加上去的
cout<< "clear" << endl; //后面,加上去的
//其他代码
后面两条语句是经过一段时间才加上去的
从缩进上面看,这三条语句应该是当作if语句的执行块,但是实际结果却是,只有第一条语句才会当作,执行块。对于不是特别熟悉的人来说,这种错误,不容易找到,因此,有些公司强制要求,if后面,else后面,都使用语句块。
嵌套的if语句
if语句可以当作statement1,也可以当作statement2。举个例子如下:
int j = 0;
cin >> j;
if(j >= 1){
if(j == 1){
cout << "等于1" << endl;
}else{
cout << "大于1" << endl;
}
}else if(j == 0){
cout << " j小于0" << endl;
}
上面statement1的位置中,放入了一个if语句,在statement2的位置也放入了一个if语句,只不过后者这个if语句,省略掉了else部分。
找到正确的if
考虑下面的例子
if(grade %10 >= 3)
if ( grade %10 >7)
lettergrade += '+';
else
lettergrade += '-';
上面程序的意图很明显,希望的是,grade %10 >=3 为false时,执行lettergrade += ‘-’;
但是实际结果与我们的期望不符。
因为:
最后一个else当作了第二个if语句的else部分。因为,else 和if的个数并不匹配,因此,else会匹配到离他最近的if语句,此处为第二个if语句
实际的执程序是:
if(grade %10 >= 3)
if ( grade %10 >7)
lettergrade += '+';
else
lettergrade += '-';
如果想要达到我们的预期目的,可以使用大括来达到效果,如下:
if(grade %10 >= 3){
if ( grade %10 >7)
lettergrade += '+';
}
else
lettergrade += '-';
这样,第二个if语句就被当作了第一个语句的执行块,并且第二个if语句,没有else部分。
5.3.2 switch语句
格式如下:
switch(n){
case N:
case M:
case K:
case J:
default:
}
case N,case M,case K,case J都称为case标签,当n等于case的某个标签时,则从这个标签开始执行,直到大括号结束。
当n跟任何一个标签都不匹配时,则从default处,开始执行
N,M,K,J必须是整型常量表达式
举个例子:
char ch = getVal();
switch(ch){
case 3.14://错误,case不是一个整数
case ival://错误,case不是一个常量
//...
}
注意case内部的流程
当从一个标签开始执行之后,会一直执行下面的所有语句,举个例子:如果从N标签开始执行,除非遇到跳转语句,否则,将会执行M标签,K标签,J标签,default标签的语句。
关于跳转语句,将在本章后续部分做补充
int i = 1;
switch(i){
case 1: cout << "1"<<endl;
case 2: cout << "2"<<endl;
case 3: cout << "3"<<endl;
case 4: cout << "4"<<endl;
default:cout << "0" << endl;
}
打印结果为
1
2
3
4
0
如果只想要执行某个标签后面的case语句,应该叫上跳转语句,如break
如下
int i = 1;
switch(i){
case 1: cout << "1"<<endl;break;
case 2: cout << "2"<<endl;break;
case 3: cout << "3"<<endl;break;
case 4: cout << "4"<<endl;break;
default:cout << "0" << endl;break;
}
打印结果为:
1
上面的例子,说明了有无,break带来的差异,编程时候,请勿忘记是否需要break
注意:default标签,可以放在case标签之前,之中,之后。从default标签进入,开始执行,如果default标签之后,还有case标签,并且default标签后面的语句块,没有跳转语句。那么default后面的case标签的语句,也会被执行。
举个例子如下:
int i = 5;
switch(i){
case 1: cout << "1"<<endl;
case 2: cout << "2"<<endl;
default:cout << "0" << endl;
case 3: cout << "3"<<endl;
case 4: cout << "4"<<endl;
}
打印结果为:
0
3
4
注意:即使不准备在defaul标签中做任何工作,定义一个default标签也是有用的。其目的在于告诉程序的读者,已经考虑了默认情况,只是目前什么也不需要做
switch语句中的变量定义
考虑下面的情况:
switch (i){
case 1:
int j = i+1;
break;
case 2:
j = i+2;
break;
}
当,i等于2的时候,将从case 2处开始运行,此时,变量j还没有定义和初始化,但是马上就要使用j了,显然是错误的。因此上述被编译器识别为错误
5.4 循环语句
5.4.1 while语句
格式:
while(condition)
statement
说明:
当condition为真的时候,statement将会被一直执行,因此,要想退出这个循环,一定要在某种条件下,让condition为false
举个例子:
vector<int> v;
while(cin >> i)
v.push_back(i);
auto beg = v.begin();
while(beg != v.end() && * beg >= 0)
++beg;
if(beg == v.end())
//此时v中的所有元素都大于等于0了
5.4.2 for语句
for(初始化语句;条件语句;expression)
statement
说明
1.for执行的时候,先执行初始化语句,
2.然后执行条件语句,如果结果为true,则执行statement。如果为false,则执行4
3.一旦statement执行完毕,则执行expression,然后再次,执行条件语句,即2中所描述的步骤
4.如果条件语句为false,则退出for
注意:for语句头中定义的对象,只能在for中使用,一旦超出for就失效。
初始化语句,可以定义多个对象,但是只能有一条声明语句。因此所有的变量的基础类型必须相同。
例子无
注意:for语句中的初始化语句,条件语句,expression都可以省略
初始化语句省略表示:已经在其他地方初始化了,for需要的变量条件语句被省略:表示,条件永远为true,如果需要退出for语句,可以使用跳转语句,跳出for
expression省略:表示,在循环体中已经有其他语句达到同样的效果
例子无
5.4.3 范围for语句
c++11 引入了一种新的语句,这种语句用于遍历容器或者序列中的每一个元素
for(声明语句:expression)
statement
expression必须是一个序列,比如用花括号括起来的初始值列表,数组,或者vector,string等类型的对象。这些类的共同点是,拥有begin和end成员,这两个成员,返回响应的迭代器。
生命语句定义一个变量:序列中的每个元素都能转换成这个变量相对应的类型。
如果想要简单一点,可以使用auto关键字,让编译器帮忙推断其类型。
另外如果想要修改序列中的元素,那么需要声明为引用类型。
例子如下:
vector<int> v= {0,1,2,3,4,5,6,7,8,9};
for(auto &r:v)
r*= 2;
声明语句声明了一个r,并且声明为引用类型,因为我们要修改其值。同时使用了auto关键字,让编译器自动推断其类型。
5.4.4 do while语句
格式:
do
statement
while(condition);
他和while语句的执行时一样的,当condition为true时,将会执行statement。
他们之间的唯一区别是,do…while会先执行statement,然后再判断,而while语句是先判断,再执行statemtnt
注意:do…while后面有一个分号,表示语句的结束
而while语句后面没有分号,此时右大括号就可以表示结束,因此不需要分号
5.5 跳转语句
c++ 中提供了4中跳转语句:break,continue,goto,return.
return语句在6.3节中,我再来学习
5.5.1 break语句
break:终止离他最近的while,do while,for或者switch语句
例如如下:
string buf;
while(cin >> buf && !buf.empty())
{
switch(buf[0]){
case '-':
for(auto it = buf.begin() +1;it != buf.end();++it){
if(*it == ' ')
break;//一号break,离开最近的for语句
}
//一号break,将会在此处开始执行
break;//二号break,离开switch语句
case '+':
//...
}
//二号break,在此处开始执行
}
5.5.2 continue 语句
continue:终止当前的循环,并且开始下一个循环
例如如下:
string buf;
while(cin >> buf &&!buf.empty()){
if(buf[0] != '_')
continue;//终止当前的循环,直接开始下一次循环,即,跳到while头部开始
}
5.5.3 goto语句
goto:跳到goto所指的标签处开始执行
例如:
begin:
int sz = get_size();
if(sz < = 0){
goto begin;//跳到begin处开始标签
}
当跳到begin处时,跨过了sz,因此sz将会被销毁,然后重新创建
5.6 try语句
5.6.1 throw表达式
throw抛出一个异常。throw后面的表达式类型,就是抛出的异常的类型。
例子如下:
if(item1.isbn() != item2.isbn())
throw runtime_error("Data must refer to same ISBN");
cout << item1 + item2 << endl;
throw 后面的表达式是:runtime_error(“Data must refer to same ISBN”);他声明一个对象并抛出,runtime_error,这个类型定义在stdexcept头文件中。
5.6.2 try语句块
格式:
try{
程序语句
}catch(异常声明){
异常处理
}catch(异常声明){
异常处理
}//...
说明:执行程序语句的时候,如果抛出了异常,将会挨着顺序,从上往下寻找,满足这个异常类型的异常声明,如果找到了第一个匹配的异常声明,则进入这个异常声明的异常处理语句块。如果没有找到,则向上抛出。如果已经是最顶层的异常处理了,则直接退出程序。
更加具体的异常处理,在后面的章节中,将会更加细致的学习,此处,只需要知道这些即可。
例子如下:
while(cin >> item1>>item2){
try{
//执行添加两个sales_item对象的代码
//如果添加失败,代码抛出一个runtime_error异常
}catch(runtime_error err){
cout << err.what()
<< "\nTry Again? Enter y or n" << endl;
char c;
cin >> c;
if(!cin || c == 'n')
break;
}
}
异常的寻找过程
下面的文字,直接复制于《c++ primer中文版》
在复杂的系统中,程序在遇到抛出异常的代码前,其执行路径可能已经经过了多个try语句块。例如一个try语句块可能调用了包含另一个try语句块的函数,新的try语句块调用有包含了另一个try语句块的新函数,一次类推。
寻找处理代码的过程与函数调用链刚好相反。当异常被抛出,首先搜索抛出该异常的函数。如果没有找到匹配的catch子句,终止该函数,并在调用该函数的函数中继续寻找。如果还是没有找到匹配的子句,这个新的函数也被终止,继续搜索调用它的函数。以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的catch子句为止。
如果最终还是没有找到任何匹配的catch子句,程序转到名为terminate的标准库函数。该函数的行为与系统有关,一般情况下,该执行函数将导致程序非正常退出。
对于那些没有任何try语句块定义的异常,也按照类似的方式处理:毕竟,没有try语句块也就意味着没有匹配的catch子句。如果一段程序没有try语句,且发生了异常,系统会调用terminate函数并终止当前程序的执行。
继续引用
提示:编写异常安全的代码非常困难要好好理解这句话:异常中断了程序的正常流程。异常发生时,调用者请求的一部分计算可能已经完成,另一部分则尚未完成。通常情况下,略过部分程序,意味着某些对象处理到一半,就戛然而止,从而导致对象处于无效或者未完成的状态,或者资源没有正常的释放,等等。那些在异常发生期间正确执行了“清理”工作的程序被称为异常安全的代码。然而经验表明,编写异常安全的代码非常困难,这部分知识也远超本书范围。
对于一些程序来说,当异常发生时只是简单的终止程序。此时,我们不怎么需要担心异常安全的问题。
但是对于那些确实需要处理异常并继续执行的程序,就要加倍注意了。我们必须时刻清楚异常何时发生,异常发生后程序应该如何确保对象有效、资源无泄漏、程序处理合理的状态,等等。
5.6 标准异常
c++定义了一组类,这些类用于异常编程的编写和调试
1.exception 头文件定义了最通用的异常类exception。它只报告异常的发生,不提供任何额外信息。
2.stdexception头文件定义了几种常见的异常类,见表5.1
3.new 头文件定义了bad_alloc异常类型,这种类型将在12.1.2节中详细学习
4.type_info头文件定义了bad_cast异常类型,这种类型,将在19.2中详细学习
---------插入图片---------
以下,直接复制自《c++ primer第五版中文版》
注意:标准库异常类只定义了几种运算,包括创建或者拷贝异常类型,以及为异常类型赋值。
我们只能以默认初始化的方式初始化exception、bad_alloc、bad_cast对象,不允许为这些对象提供初始值。
其他异常类型则刚好相反:应该使用string对象或者c风格字符串初始化这些类型的对象,但是不运行使用默认初始化。当创建此类对象时,必须提供初始值,该初始值含有错误相关的信息。
异常类型只定义了一个名为what的成员函数,该函数没有任何参数,返回值是一个指向c风格的字符串的const char*.该字符串的目的是提供关于异常的一些文本信息。
what函数返回的c风格字符串内容于异常对象的类型有关。如果异常类型有一个字符串初始值,则what返回该字符串。对于其他无初始值的异常类型来说,what返回的内容由编译器决定。
笔记难免有错误,望见着,不吝指出,谢谢。