首页 > 代码库 > C++_系列自学课程_第_12_课_语句_《C++ Primer 第四版》
C++_系列自学课程_第_12_课_语句_《C++ Primer 第四版》
前面的文章说完了表达式和类型转换的部分内容,在我参考的书里面,接下来讨论的是各种语句,包括:顺序语句、声明语句、复合语句(块语句)、语句作用域
、if语句、while语句、for语句、do...while语句、break语句、continue语句、goto语句、try语句。
这里我们来讨论这些语句,为了方便讨论和记忆,我们可以将这些语句可以分为五大类: 简单语句、条件语句、循环语句、流程控制语句、异常处理。当然也可以
不这么分类,我这么说就是为了自己好理解和记忆。
一、简单语句
我将顺序语句、声明语句、复合语句分到简单语句里面,因为这些语句直观。
1、语句
C++中,大部分语句以分号结束, 例如在表达式后加上分号就构成表达式语句, 如下所示:
2 + 3 ; //表达式语句
2、空语句
在C语言中,单独一个分号表示为空语句,C++继承了这个概念。
; //空语句
空语句不产生编译代码,这个优化过程由编译器自己完成,不需要程序员关心。
空语句在语法要求需要一个语句,但是又不需要执行具体功能的地方。如下所示:
if(iVar > 10) ; //空语句,如果iVar 大于10 则什么也不做 else some statement
当然上面的例子,可以有更简洁的方式来表达,这里只是为了说明空语句的用法,不必考虑实际是否会这么写代码。 下面给一个具体的有实际意义的实例:
我们可以编写一段代码,用于接受用户的输入,在用户输入特定的信息之前不做任何事:
Exp:
int main(){ char chVar = 0; while(cin>>chVar && (chVar != ‘y‘ && chVar != ‘Y‘)) ; cout<<"you have input ‘y‘ or ‘Y‘,then jump out the while loop"<<endl; return 0;}
程序的执行结果如下所示:
[root@localhost cpp_src]# g++ test.cpp [root@localhost cpp_src]# ./a.out a b c d e f h jYyou have input ‘y‘ or ‘Y‘,then jump out the while loop
这段程序在用户输入y 或者 Y 之前,会一直等待用户输入y或者Y, 这里这里当我们输入两者之一的时候,就会退出循环,然后输出提示信息。
通常在程序当中定义
3、顺序语句
如果没有其他控制机制,计算机在执行程序的时候,从上往下、从左往右执行程序代码。 如下所示:
int iVar = 10 ; //声明语句 iVar = 2 * 4 ; //赋值语句 sum(1, iVar); // 函数调用语句
上面的声明语句、赋值语句、函数调用语句就是一组顺序语句,顺序语句从上往下、从左往右执行。
4、声明语句
在C++中,对象的声明或者定义也是语句,,通常定义语句也是声明语句,如下所示:
string strVar; //定义语句 extern int iVar ; //声明语句
5、语句块
语句块也叫复合语句, 使用{ 、} 括起来的语句,复合语句标识了一个作用域,在块中定义的名字其作用域仅在块中或者块中嵌套的块中有效。 在块中定义的标
识符从其定义位置开始,到块结束的位置处有效。
复合语句用作语法规则要求使用语句,但是使用单个语句无法表达程序逻辑的地方。 例如: 在while循环或者for循环的循环体使用复合语句。
例如:
while(i < 100) { cout<<i<<endl; ++i; }
6、语句作用域
我们知道对象有其作用范围,超过其作用范围就不能对其进行访问。 例如在一个函数内部定义的变量就不能再函数的外部进行访问。 有些控制结构的语句
可以在其内部定义对象, 这些对象的作用域就是控制结构内部,超过语句的范围,这些对象就不能再被访问。
例如,前面介绍的内容中我们有如下定义的语句:
int iArray[5] = {1,2,3,4,5}; for(int i = 0; i !=sizeof(iArray); ++i) cout<< iArray[i] <<endl;
我们看for语句,在for语句的头部,我们定义了变量 i, 我们可以在for 语句的内部访问变量i,循环体属于for语句的一部分,因此我们可以在循环体中访问变量i;
这里for语句的循环体只有一个语句,就是 最后的cout流输出语句。
我们不能再for语句之外访问在for语句内部定义的变量,如下所示:
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 #include <bitset> 5 #include <stdio.h> 6 7 using std::cin; 8 using std::cout; 9 using std::endl;10 using std::vector;11 using std::bitset;12 13 int main()14 {15 int iArray[5] = {1,2,3,4,5};16 17 for(int i= 0; i != sizeof(iArray); ++i)18 cout<<iArray[i]<<endl;19 20 i += 10 ;21 22 return 0;23 }
程序编译的结果如下所示:
[root@localhost cpp_src]# g++ test.cpp test.cpp: In function ‘int main()’:test.cpp:20: 错误:在新的 ISO ‘for’ 作用域中,‘i’ 的名称查找有变化test.cpp:17: 错误: 在 ‘i’ 使用过时的绑定
这里指出我们在20行,引用在 for循环中定义的变量i, 因此编译不能通过。
要点:
不能在语句外访问语句内部定义的变量。
我们在访问对象的时候,会出现“屏蔽”其作用范围的情况,就是 小作用域的对象会屏蔽大作用域对象的访问,如下所示:
int main(){ int iVar = 100; cout <<"out iVar ="<<iVar<<endl; { int iVar = 50 ; cout <<"inner iVar ="<<iVar<<endl; } cout <<"out iVar = "<< iVar<<endl; return 0;}
程序执行的结果如下所示:
[root@localhost cpp_src]# ./a.out out iVar =100inner iVar =50out iVar = 100
如上所示, 第一个定义的iVar变量,定义后其作用域为从其定义开始,到main函数结束,因此输出是100;定义的第二个iVar 变量为在语句块里面定义的,所以
其作用域为块语句内部,所以在块语句里面屏蔽了前面定义的第一个iVar变量的作用,因此输出50; 而在块语句之后,再访问变量iVar则是访问在块语句前面定义的
变量, 块语句内部定义的iVar其作用域已经消失,所以输出100. 这个地方需要注意。
通常不建议在程序中定义重名的对象,即便可以通过作用域控制,除非是一些约定俗成的变量,比如循环控制变量i、j等。
二、条件语句
7、 if语句
if语句提供了一种选择执行程序代码的机制, 当if条件成立是执行某段程序,当条件不成立时执行另外一段代码。if语句有三种形式:
形式一:
if( 条件 ) if语句代码;
这种形式的代码执行如下: 当 条件为真时, 执行if语句代码,即执行if后面紧跟的第一条语句, 这条语句可以为空语句、复合语句或。
形式二:
if ( 条件 ) if分支语句代码 else else分支语句代码
这种形式的执行情况如下: 当条件为真时,执行if分支语句代码; 当条件为假时,执行else分支语句代码; if分支语句代码是if后面紧跟的第一条语句,可以为
空语句或者语句块; else分支语句代码是else分支后面紧跟的第一条语句, 同样可以为空语句或者语句块。
形式三:
if ( 条件1) 分支语句代码1 else if(条件2) 分支语句代码2 else if (条件3) 分支语句代码3 .....
else
else分支语句代码
这种形式可以不断的增加else if 分支;如果条件1为真,则只执行分支语句代码1; 如果条件2为真则只执行语句分支代码2;如果条件3为真,则执行语句分支
代码3,以此类推, 当所有的条件分支 1 ... n 都为假时则执行else分支语句代码。
if语句的例子就不举啦,这里需要说明的是一种非常特殊的情况, 就是当几个条件同时成立的时候,会出现一些意想不到的的情况,如下所示:
int main(){ int iVar = 5; if(iVar > 1 ) cout<<"iVar = "<<iVar<<";iVar > 1"<<endl; else if(iVar == 5) cout<<"iVar == "<< iVar<<endl; else if(iVar < 10) cout<<"iVar = "<< iVar<<";iVar < 10"<<endl; else cout<<"not match"<<endl; return 0;}
程序的执行结果如下:
[root@localhost cpp_src]# g++ test.cpp [root@localhost cpp_src]# ./a.out iVar = 5;iVar > 1
可以发现这个程序就对第一个分支进行了匹配然后就退出了, 而我们编写的代码明显可以匹配三个条件,同时编译的时候并没有报错,这里提示这个是要告诉大家,
在使用 if 语句如果需要多个条件任意一个满足就执行某项功能的时候,必须将条件放到一个if条件里面,这一点需要引起注意。
8、switch语句
有时候我们的选择的分支太多了,如果用if语句实现,会使程序显得非常不可读,虽然功能是正确的,但是可能没有在逻辑上反映出程序员的真正用意。这时我们
例如我们要统计某个文件中某些字符的出现的次数, 加入要通知的字符个数很多就会出现很成的if.....else...... 语句, 而对于这种情况我们可以选择使用 switch语句。
switch语句的格式如下:
switch (匹配表达式) {case 选择条件1: 分支条件1的语句; break; case 选择条件2: 分支条件语句2; break; case 选择条件3: 分支条件语句3; break; ..... case 选择条件n: 分支条件语句n; break; default: default分支语句; }
switch语句的执行过程如下: 如果匹配表达式的值 与 其中的一个 case选择条件x 匹配相等, 则执行对应的分支语句;如果没有匹配表达式与switch语句的所有的
case 选择条件不匹配,则执行default分支语句;如果没有匹配表达式与switch语句的所有的case 选择条件不匹配,且没有default分支语句,则会执行switch语句后面
的语句。
这里有两个部分不是必须的: break语句和default语句。
要点:
switch语句中的break非常重要,如下所示:
int main(){ char chVar; cin>>chVar; switch(chVar) { case ‘a‘: cout<<"input is a"<<endl; break; case ‘b‘: cout<<"input is b"<<endl; break; case ‘c‘: cout<<"input is c"<<endl; break; default: cout<<"execute default branch"<<endl; } return 0;}
程序的执行结果如下:
[root@localhost cpp_src]# g++ test.cpp [root@localhost cpp_src]# ./a.out ainput is a[root@localhost cpp_src]# ./a.out binput is b[root@localhost cpp_src]# ./a.out cinput is c[root@localhost cpp_src]# ./a.out fexecute default branch
这里我们将程序进行修改,如下所示:
int main(){ char chVar; cin>>chVar; switch(chVar) { case ‘a‘: cout<<"input is a"<<endl; case ‘b‘: cout<<"input is b"<<endl; case ‘c‘: cout<<"input is c"<<endl; default: cout<<"execute default branch"<<endl; } return 0;}
程序的执行结果如下:
[root@localhost cpp_src]# g++ test.cpp [root@localhost cpp_src]# ./a.out ainput is ainput is binput is cexecute default branch[root@localhost cpp_src]# ./a.out binput is binput is cexecute default branch[root@localhost cpp_src]# ./a.out fexecute default branch
通过这里实例可以知道, 当switch的匹配表达式与case选择条件表达x匹配成功后,就会开始从匹配的地方开始执行, 如果没有break语句进行控制,
就会执行后面所有case选择条件下的语句, 因此这个地方要非常注意。
有时候我们也可以利用上面说的关于break的特性,例如可以利用这个特性统计输入的空白符的总数, 空白符包括空格、制表符\t、换行符。 例如我们可以利用这
个特性统计元音字母出现的次数等等,具体代码就不写啦。
三、循环语句
9、while语句
while语句提供了一种机制,可以让一段代码不断的执行。 while语句的格式如下:
while (条件表达式) while语句循环体;
while语句的执行过程如下: 当 while的条件表达式为真时, 则不断的执行while语句循环体。 while语句循环体为紧跟while条件表达式的第一条语句,可以为空语
句或者语句块。
下面是一个while语句的实例:
int main(){ char chVar; int iVowelCnt = 0; while(cin>>chVar) switch(chVar) { case ‘a‘: case ‘i‘: case ‘o‘: case ‘e‘: case ‘u‘: ++iVowelCnt; break; } cout<<"the count of vowe character is: "<<iVowelCnt<<endl; return 0;}
程序的执行结果为:
[root@localhost cpp_src]# ./a.out hi,volcanol,nice to meet you ,are you Chinese?the count of vowe character is: 18
这个程序演示了不带break语句的switch语句和while语句的用法。
10、for语句
for语句提供了另外一种类似于while循环的功能。 所不同的是语句的形式,以及for语句多用于循环次数容易确定的场合,当然循环次数不确定的时候也能用
for循环。
for语句定义的格式为:
for(init-statement; condition ; expression) for语句循环体
for语句执行的过程如下: 首先执行 init-statemet; 然后判断condition是否为真,如果条件为真就执行for语句循环体, 执行完for语句循环体后,然后再执行
expression语句, 执行完后接着测试condition,如果condition还为真,就继续执行for语句循环体,然后在执行expression语句, 接下来继续判断条件condition,
就这样重复执行下去; 如果条件不为真,就跳过for循环, 执行for语句后面的语句。
要点:
for语句中的 init-statement、 condition、expression都可以省略, 这个是一个比较特殊的地方,需要注意。
Exp:
for( ; ;){}
如果和上面一样定义for语句头部,那么就会是一个死循环。
同样可以在for语句的语句头部分定义变量或者对象,这些在for语句头定义的变量或者对象的作用域为for语句,当离开for语句后,就不再有作用,
这一点也需要注意。
我们用给一个例子来说明for语句:
int main(){ vector<int> vecInt(10,0); vector<int>::iterator iter =vecInt.begin(); for(;iter != vecInt.end(); ++iter) { *iter = *iter + 1; } for(vector<int>::size_type i=0; i!=vecInt.size(); ++i) { cout<<vecInt[i]<<endl; } return 0;}
程序执行的结果如下所示:
[root@localhost cpp_src]# g++ test.cpp [root@localhost cpp_src]# ./a.out 1111111111
11、 do.....while 循环语句
do...while循环语句提供了一种机制:无论循环条件的第一计算是否为真,都会执行一次循环体。 do....while循环的语法格式如下所示:
do statement while(condition);
执行过程是这样的: 首先执行statement, 然后求解condition,如果condition为真则继续执行循环体,如果condition为假,则退出do...while 循环。
这里需要注意的是,不能少了while(condition)后面的分号,如果缺少的话就是语法错误。
下面给出一个实例程序:
[root@localhost cpp_src]# cat test.cpp #include <iostream>#include <string>#include <vector>#include <bitset>#include <stdio.h>using std::cin;using std::cout;using std::endl;using std::vector;using std::bitset;using std::string;int main(){ string strVar1(""); string strVar2(""); string strRsp(""); do { cout<<"please input two string:"; cin>>strVar1>>strVar2; if(strVar1 < strVar2) { cout<<strVar1<< " < " << strVar2<<endl; } else if(strVar1 == strVar2) { cout<<strVar1<< " == " << strVar2<<endl; } else { cout<<strVar1 <<" > " <<strVar2 <<endl; } cout<<"continue?[yes][no]"<<endl; }while(cin>>strRsp && !strRsp.empty() && (strRsp[0] == ‘y‘ || strRsp[0] == ‘Y‘)); return 0;}
程序的执行结果如下所示:
[root@localhost cpp_src]# g++ test.cpp [root@localhost cpp_src]# ./a.out please input two string:volcanol readhatvolcanol > readhatcontinue?[yes][no]yplease input two string:good morninggood < morningcontinue?[yes][no]n
这里需要注意的一点是: 在do....while循环的循环体中定义的对象,其作用域只在循环体中有作用, 在while的条件表达式都没有作用域范围,这一点要非常的注意。
如下所示:
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 #include <bitset> 5 #include <stdio.h> 6 7 using std::cin; 8 using std::cout; 9 using std::endl;10 using std::vector;11 using std::bitset;12 using std::string;13 14 int main()15 {16 do17 {18 int iVar = 10;19 }while(iVar);20 }
编译结果如下所示:
[root@localhost cpp_src]# g++ test.cpp test.cpp: In function ‘int main()’:test.cpp:19: 错误:‘iVar’ 在此作用域中尚未声明
可以发现编译的时候,提示19行的 iVar没有定义。
12、break语句
break语句用来跳出while、do.....while、for、switch语句, 就是说break语句会中断前面几种语句的执行,跳到紧跟着这些语句后面的第一条语句处执行。
要点:
break语句只能出现在while、do....while、 for 和switch语句中,不能在其他语句中出现。
Exp:
int main(){ string strRsp(""); for(;;) //endless loop { cout<<"Continue? [yes]or[no]:"; if(cin>>strRsp && !strRsp.empty() && ((strRsp[0] == ‘n‘)|| (strRsp[0]==‘N‘))) break; } return 0;}
程序的执行结果如下:
[root@localhost cpp_src]# g++ test.cpp [root@localhost cpp_src]# ./a.out Continue? [yes]or[no]:yContinue? [yes]or[no]:yContinue? [yes]or[no]:yContinue? [yes]or[no]:n
这个程序本来是个无限循环,可以无限次的执行下去,为了退出循环就必须使用一种特殊的机制,这里我们利用break语句跳出了循环。
要点:
如果有多个循环语句嵌套,那么break语句只能跳出最里层的循环语句。
break语句例子就到这,以后会有更多的例子。
13、continue语句
continue语句用于不执行循环语句的某一次循环,而不退出循环,这个是与break语句的不同之处。
Exp: 下面的程序求0到100之间偶数的和
int main(){ int sum =0; for(int i=0; i != 100; ++i) //endless loop { if(!(i%2)) continue; else sum += i; } cout<<sum<<endl; return 0;}
程序执行的结果如下所示:
[root@localhost cpp_src]# g++ test.cpp [root@localhost cpp_src]# ./a.out 2500
上面的循环,当 i 不是偶数的时候,就执行if分支,if分支为一个continue语句,这个语句就是不执行本次循环,但不退出循环; 当i 是偶数的时候,就执行else分支
执行sum += i; 这样就可以求0到100之间的偶数的和。
14、 goto语句
goto语句提供了另外一种的程序流程控制机制, goto可以实现从程序的前面跳到后面,也可以从程序的后面跳到前面执行。 在C++中goto语句只能在函数体
里面执行跳转,不能跨函数跳转,这个需要注意。
goto语句的语法格式如下所示:
goto label;
label 是在程序中定义的标识符,用一个在标识符后面表示这是一个标号,如下所示:
loop_lable:
上面就定义了一个lable, goto语句可以调到这个地方执行。
Exp: 利用goto语句实现变量数组
int main(){ int iArray[5] = {1,2,3,4,5}; int i=0;loop: cout<<iArray[i++]<<endl; if(i != 5) goto loop; cout<<"quit goto statement"<<endl; return 0;}
程序的执行结果如下:
[root@localhost cpp_src]# g++ test.cpp [root@localhost cpp_src]# ./a.out 12345quit goto statement
上面的代码演示了如何使用goto语句来实现迭代控制。
四、try块和异常处理
在程序执行的过程中经常会出现一些异常的情况,这些异常的情况需要在程序里面进行处理才能提高程序的健壮性;异常存在于程序的正常功能之外,必须在程序中
得到及时的处理。
当程序检测到无法处理的问题时,异常处理就特别的有用,异常处理是检测异常的代码在检测到异常的时候,将控制权转移到异常处理代码中,由错误处理代码对异
常进行处理。 简单是说就是: 异常检测代码 和 异常处理代码分开, 当异常检测代码检测到异常时通过一种机制将代码的控制权转移到异常处理代码执行。
C++提供的异常处理机制,提供了异常检测代码和异常处理代码中的通信机制,C++的异常处理包括:
1、throw表达式 错误检测代码使用throw表达式通知系统,检测到异常,需要立即进行处理,可以说throw引发/触发了异常条件
2、try块, 错误处理部分使用try块来处理异常,try语句块以try关键字开始,并且以一个或者多个catch子句结束, 在try块中执行的代码所抛出的异常通常会
被其中的一个catch子句所捕获并且进行处理。
3、由标准库定义的一组异常类,用来在throw和相应的catch之间传递有关的异常信息。
1、 throw表达式
系统通过throw表达式抛出异常, throw表达式由关键字throw以及尾随的表达式组成, 通常以分号结束, 这样就构成了表达式语句。
下面我们来看一个简单的throw的例子。
Exp:
int iVar = 0; cin>>iVar; if( iVar == 0) throw out_of_range("iVar == 0, divide can not be zero"); cout<< "12345 / " <<iVar<<"is:"<<12345/iVar<<endl;
这里通过检查输入是否为0 ,如果输入为0,就抛出一个out_of_range 的异常。
2、try语句块
try语句块用来捕捉异常, 其语法格式如下所示:
try{ 程序语句;}catch(异常说明符1){ 异常处理代码段1;}catch(异常说明符2){ 异常处理代码段2;}catch(异常说明符3){ 异常处理代码段3;} ....catch(异常说明符n){ 异常处理代码段n;}
语句的执行流程是: try块里面是程序正常的逻辑代码,执行try块里面的语句,如检测到异常,则在try块里面的语句通过throw表达式抛出异常, 此时程序就中断
try块内正常代码执行;如果抛出的异常与catch中的异常说明符中的一个匹配则执行对应的异常处理代码, 异常处理代码执行完毕后,程序就从try....catch语句后面的
第一条语句继续执行,这样就可以保证程序不会因为产生了异常而退出,可以继续执行。
程序在抛出异常后,会寻找处理异常的错误处理代码,这个过程与函数调用的过程相反, 当抛出异常后,首先在产生异常的函数里面寻找匹配的异常处理代码,如果
没有找到,就到调用函数的上一层函数找,如果在上层函数也没有找到则继续往上找, 如果到main函数时还没有找到,则程序就会跳转到名为 terminate的标准库函数
处执行,这个函数在exception头文件中定义, 这个函数的行为依赖于系统,通常是导致程序的非正常退出。
如果程序中没有定义try块定义,则程序以正常的方式执行,如果此时出现了异常,就会以通常的方式终止程序的执行。
关于异常处理我们在后面的文章中会继续讨论,期待关注。
3、标准库定义的异常类
为了更好的支持异常类,C++标准库提供了一组异常处理类,这些类在不同的头文件中定义:
1、exception头文件定义了最常见的异常类,类名为 exception。 exception类只通知产生了异常,但是不会提供过多的关于异常的信息,这个异常类通常是
用来在检测到异常的时候,在做完一些保存事件后,立即退出程序。
2、stdexcept 头文件定义了一些常见的异常类,这些类的异常产生时会提供关于异常的相关信息, 根据这些信息可以对异常进行特定的处理。
3、new头文件定义了bad_alloc异常类型, 用于抛出无法从堆中申请、分配足够内存的异常。
4、type_info头文件定义了bad_cast异常类型。
下面是stdexcept头文件定义的异常类型
exception异常 最常见的异常类型
runtime_error 运行时异常,仅在运行时产生的异常
range_error 运行时错误,生成的结果超出有意义的值域范围
overflow_error 运行时异常, 产生了上溢出
underflow_error 运行时异常,产生了下溢出
logic_error 逻辑错误, 可在运行前检测到的问题
domain_error 逻辑错误, 参数的结果值不存在
invalid_error 逻辑错误, 不合适的参数
length_error 逻辑错误,试图生成一个超出该类型最大长度的对象
out_of_range 逻辑错误,使用一个超出有效范围的值
上面描述了部分标准库异常类的类型,以及异常类抛出异常时的意义。 标准库异常类只有很少的操作,包括:创建、复制异常类型对象以及类型对象的赋值。
exception、bad_alloc、bad_cast类型指定义了默认构造函数,无法在创建这些异常类的对象时提供初值,这个需要注意。而上面提到的标准异常类,在创建
时只能提供string类型的初始化表达式来初始化对象。
关于异常暂时描述这么多类容,更多相关类容待续。
更多的内容待续...........
C++_系列自学课程_第_12_课_语句_《C++ Primer 第四版》