c++ primer 第五版 笔记 第五章

因为译文找不回了,只能通过笔记的形式记录

第五章语句

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返回的内容由编译器决定。

笔记难免有错误,望见着,不吝指出,谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值