• C++基础知识要点--语句(Primer C++ 第五版 · 阅读笔记)


    C++基础知识要点–语句

    简单语句

    空语句

    • 使用空语句时应该加上注释,从而令读这段代码的人知道该语句是有意省略的。
    ; //空语句
    
    • 1

    如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句。

    //重复读入数据直至到达文件末尾或某次输入的值等于sought
    while (cin >> s && s != sought)
    	; //空语句
    
    • 1
    • 2
    • 3

    条件语句

    C++语言提供了两种按条件执行的语句。

    • 一种是 if 语句,它根据条件决定控制流;
    • 另外一种是 switch 语句,它计算一个整型表达式的值,然后根据这个值从几条执行路径中选择一条。
    if 语句 (养成好习惯)

    有些编码风格要求在if或else之后 必须写上花括号(对 while 和 for 语句的循环体两端也有同样的要求) 。
    这么做的好处是可以避免代码混乱不清,以后修改代码时如果想添加别的语句,也可以很容易地找到正确位置。

    if(condition){
    	statement
     }else if(condition){
    	statement
     }else{
    	statement
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    switch 语句
    • switch语句(switch statement)提供了一条便利的途径使得我们能够在若干固定选项中做出选择。

    举个例子,假如我们想统计五个元音字母在文本中出现的次数,程序逻辑应该如下所示:

    1. 从输入的内容中读取所有字符。
    2. 令每一个字符都与元音字母的集合比较。
    3. 如果字符与某个元音字母匹配,将该字母的数量加1。
    4. 显示结果。

    要想实现这项功能,直接使用switch语句即可;

    //为每个元音字母初始化其计数值
    unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
    char ch;
    while (cin >> ch) {
    	//如果ch是元音字母,将其对应的计数值加1
    	switch (ch) {
    		case 'a' :
    			++aCnt;
    			break;
    		case 'e':
    			++eCnt;
    			break;
    		case 'i':
    			++iCnt;
    			break;
    		case 'o':
    			++oCnt;
    			break;
    		case 'u':
    			++uCnt;
    			break;
    	}
    }
    //输出结果
    cout<<"Number of vowel a: \t" << aCnt << '\n'
    	<<"Number of vowel e: \t" << eCnt << '\n'
    	<<"Number of vowel i: \t" << iCnt << '\n'
    	<<"Number of vowel o: \t" << oCnt << '\n'
    	<<"Number of vowel u: \t" << uCnt << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    switch语句首先对括号里的表达式求值,该表达式紧跟在关键字switch的后面,可以是一个初始化的变量声明。表达式的值转换成整数类型,然后与每个case标签的值比较。

    break语句的作用是中断当前的控制流。此例中, break语句将控制权转移到switch语句外面。因为switch是 while循环体内唯一的语句,所以从switch语句中断出来以后,程序的控制权将移到while语句的右花括号处。此时 while语句内部没有其他语句要执行,所以 while会返回去再一次判断条件是否满足。

    case关键字和它对应的值一起被称为case标签(case label)。case标签必须是整型常量表达式:

    char ch = getval();
    int ival = 42;
    switch(ch){
    	case 3.14:             //错误:case标签不是一个整数
    	case ival:             //错误:case标签不是一个常量
    	// ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    任何两个 case标签的值不能相同,否则就会引发错误。另外,default也是一种特殊的case标签。

    switch内部的控制流

    理解程序在case标签之间的执行流程非常重要。如果某个case标签匹配成功,将从该标签开始往后顺序执行所有case分支,除非程序显式地中断了这一过程,否则直到switch 的结尾处才会停下来。

    例如,也许我们想统计的是所有元音字母出现的总次数:

    unsigned vowelCnt = 0;
    //...
    switch (ch){
    	//出现了a、e、 i、o或u中的任意一个都会将vowelCnt的值加1
    	case 'a':
    	case 'e':
    	case 'i':
    	case 'o':
    	case 'u':
    		++vowelcnt;
    		break;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在上面的代码中,几个case标签连写在一起,中间没有break语句。因此只要ch是元音字母,不管到底是五个中的哪一个都执行相同的代码。

    C++程序的形式比较自由,所以case标签之后不一定非得换行。把几个case标签写在一行里,强调这些case代表的是某个范围内的值:

    switch (ch)
    {
    	//另一种合法的书写形式
    	case 'a': case 'e': case 'i': case 'o': case 'u':
    		++vowelCnt;
    		break;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    default标签

    • 如果没有任何一个case标签能匹配上 switch 表达式的值,程序将执行紧跟在default标签(default label)后面的语句。

    例如,可以增加一个计数值来统计非元音字母的数量,只要在 default分支内不断递增名为otherCnt 的变量就可以了:

    //如果ch是一个元音字母,将相应的计数值加1
    switch (ch){
    	case 'a': case 'e': case 'i': case 'o': case 'u':
    		++vowe1Cnt;
    		break;
    	default:
    		++otherCnt;
    		break ;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这个版本的程序中,如果 ch 不是元音字母,就从 default标签开始执行并把otherCnt加1。

    switch内部的变量定义

    如前所述,switch的执行流程有可能会跨过某些case标签。如果程序跳转到了某个特定的case,则switch结构中该case标签之前的部分会被忽略掉。这种忽略掉一部分代码的行为引出了一个有趣的问题:如果被略过的代码中含有变量的定义该怎么办?

    • 答案是:如果在某处一个带有初值的变量位于作用域之外,在另一处该变量位于作用域之内,则从前一处跳转到后一处的行为是非法行为。
    case true:
    	//因为程序的执行流程可能绕开下面的初始化语句,所以该switch语句不合法
    	string file_name;                      //错误:控制流绕过一个隐式初始化的变量
    	int ival = 0 ;                         //错误:控制流绕过一个显式初始化的变量
    	int jval;                              //正确:因为jval没有初始化
    	break ;
    case false :
    	//正确:jval虽然在作用域内,但是它没有被初始化
    	jval = next_num() ;                     //正确:给jval赋一个值
    	if (file_name.empty())                  //file_name在作用域内,但是没有被初始化
    		//...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 假设上述代码合法,则一旦控制流直接跳到false分支,也就同时略过了变量file_name和ival的初始化过程。此时这两个变量位于作用域之内,跟在false 之后的代码试图在尚未初始化的情况下使用它们,这显然是行不通的。
    • 因此C++语言规定,不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。

    如果需要为某个case分支定义并初始化一个变量,我们应该把变量定义在块内,从而确保后面的所有case标签都在变量的作用域之外

    case true:
    	{
    		//正确:声明语句位于语句块内部
    		string file_name = get_file_name() ;
    		// ...
    	}
    	break ;
    case false:
    	if (file_name.empty())             //错误:file_name 不在作用域之内
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    迭代语句(循环)

    迭代语句通常称为循环,它重复执行操作直到满足某个条件才停下来。

    • while和 for语句在执行循环体之前检查条件;
    • do while语句先执行循环体,然后再检查条件。
    while语句

    定义在while条件部分或者while循环体内的变量每次迭代都经历从创建到销毁的过程。

    while (condtion)
    	statement
    
    • 1
    • 2
    传统的for语句

    牢记for语句头中定义的对象只在for循环体内可见。

    for (init-statemen; condition; expression)
    	statement
    
    • 1
    • 2

    for语句头中的多重定义

    • 和其他的声明一样,init-statement也可以定义多个对象。但是init-statement只能有一条声明语句,
    • 因此,所有变量的基础类型必须相同。

    举个例子,我们用下面的循环把vector的元素拷贝一份添加到原来的元素后面:

    //记录下v的大小,当到达原来的最后一个元素后结束循环
    for (decltype(v.size()) i = 0, sz = v.size(); i != sz; ++i)
    	v.push_back(v[i]);
    
    • 1
    • 2
    • 3

    在这个循环中,我们在 init-statement里同时定义了索引i和循环控制变量sz。

    省略for语句头的某些部分

    • for语句头能省略掉init-statement、condition和 expression中的任何一个(或者全部)。

    如果无须初始化,则我们可以使用一条空语句作为 init-statement。例如,对于在vector对象中寻找第一个负数的程序,完全能用for循环改写:

    auto beg = v.begin();
    for ( /*空语句 */ ; beg != v.end() && *beg >= 0; ++beg)
    	;     //什么也不做
    
    • 1
    • 2
    • 3

    注意,分号必须保留以表明我们省略掉了init-statement。说得更准确一点,分号表示的是-一个空的init-statement。

    省略condition 的效果等价于在条件部分写了一个true。因为条件的值永远是true,所以在循环体内必须有语句负责退出循环,否则循环就会无休止地执行下去:

    for (int i = 0; /*条件为空*/; ++i){
    	//对i进行处理,循环内部的代码必须负责终止迭代过程!
    }
    
    • 1
    • 2
    • 3

    我们也能省略掉for语句头中的expression,但是在这样的循环中就要求条件部分或者循环体必须改变迭代变量的值。
    举个例子,之前有一个将整数读入vector的 while循环,我们使用for语句改写它:

    vector v;
    for (int i; cin >> i; /*表达式为空*/)
    	v.push_back (i);
    
    • 1
    • 2
    • 3

    因为条件部分能改变i的值,所以这个循环无须表达式部分。其中,条件部分不断检查输入流的内容,只要读取完所有的输入或者遇到一个输入错误就终止循环。

    范围for语句
    • C++11 新标准引入了一种更简单的for语句,这种语句可以遍历容器其他序列的所有元素。

    范围for语句(range for statement)的语法形式是:

    for (declaration : expression)
    	statement
    
    • 1
    • 2
    • expression表示的必须是一个序列,比如用花括号括起来的初始值列表数组或者 vectorstring 等类型的对象,这些类型的共同特点是拥有能返回迭代器的 beginend成员。
    • declaration定义一个变量,序列中的每个元素都得能转换成该变量的类型。确保类型相容最简单的办法是使用 auto 类型说明符,这个关键字可以令编译器帮助我们指定合适的类型。如果需要对序列中的元素执行写操作,循环变量必须声明成 引用类型
    vector v = {0,1,2,3,4,5,6,7,8,9};
    //范围变量必须是引用类型,这样才能对元素执行写操作
    for (auto &r : v)              //对于v中的每一个元素
    	r *= 2;                    //将v中每个元素的值翻倍,改变了r所绑定的元素的值。
    
    //范围for语句的定义来源于与之等价的传统for语句:
    for (auto beg = v.begin(), end = v.end(); beg != end; ++beg){
    	auto &r = *beg;                        //r必须是引用类型,这样才能对元素执行写操作
    	r*= 2;                                 //将v中每个元素的值翻倍
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    学习了范围for语句的原理之后,我们也就不难理解为什么强调不能通过范围for语句增加vector对象的元素了。
    在范围for语句中,预存了end()的值。一旦在序列中添加(删除)元素,end函数的值就可能变得无效了。

    do while 语句
    • do while语句(do while statement)和 while语句非常相似,唯一的区别是,do while语句先执行循环体后检查条件。不管条件的值如何,我们都至少执行一次循环

    do while语句的语法形式如下所示:

    do
    	statement
    while (condition) ;
    
    • 1
    • 2
    • 3

    do while语句应该在括号包围起来的条件后面用一个 分号 表示语句结束。

    我们可以使用do while循环(不断地)执行加法运算:

    //不断提示用户输入一对数,然后求其和
    string rsp;                     //作为循环的条件,不能定义在do的内部
    do {
    	cout << "please enter two values: ";
    	int val1 = 0, val2 = 0 ;
    	cin >> val1 >> val2 ;
    	cout << "The sum of " << val1 << " and " << val2
    		 << " = " << vall + val2 << " \n\n"
      	     << "More? Enter yes or no: ";
      	     cin >> rsp;
    } while ( !rsp.empty() && rsp[0] != 'n' ) ;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    跳转语句

    • 跳转语句中断当前的执行过程。C++语言提供了4种跳转语句:break、continuegoto和return。
    break语句
    • break语句( break statement)负责终止离它 最近while、do while、for或switch语句,并从这些语句之后的第一条语句开始继续执行。

    break 语句只能出现在迭代语句或者 switch语句内部(包括嵌套在此类循环里的语句或块的内部)。break 语句的作用范围仅限于最近的循环或者switch:

    string buf ;
    while (cin >> buf && !buf.empty()){
    	switch (buf[o]){
    	case '-':
    		//处理到第一个空白为止
    		for (auto it = buf.begin() + 1; it != buf.end(); ++it){
    			if(*it == ' ')
    				break ; // #1,离开for循环
    				// ...
    		}
    		// break #1将控制权转移到这里
    		//剩余的'-'处理:
    		break;   //#2,离开switch语句
    		case '+':
    			//...
    	} //结束switch
    	//结束switch: break #2将控制权转移到这里
    }//结束while
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    continue 语句
    • continue语句(continue statement)终止最近的循环中的当前迭代立即开始下一次迭代
    • continue语句只能出现在 for、while和 do while循环的内部,或者嵌套在此类循环里的语句或块的内部。
    • 和 break语句类似的是,出现在嵌套循环中的continue语句也仅作用于离它最近的循环。
    • 和 break语句不同的是,只有当switch语句嵌套在迭代语句内部时,才能在switch里使用continue。

    例如,下面的程序每次从标准输入中读取一个单词。循环只对那些以下画线开头的单词感兴趣,其他情况下,我们直接终止当前的迭代并获取下一个单词:

    string buf;
    while (cin >> buf && !buf.empty()){
    	if (buf[0] != '_' )
    		continue;    //接着读取下一个输入
    	//程序执行过程到了这里?说明当前的输入是以下画线开始的;接着处理buf……
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    goto 语句
    • goto语句( goto statement)的作用是从goto语句无条件跳转到同一函数内的另一条语句。

    注: 不要在程序中使用 goto语句,因为它使得程序既难理解又难修改。

    TRY语句块和异常处理

    异常是指存在于运行时的反常行为,这些行为超出了函数正常功能的范围。

    典型的异常包括 失去数据库连接 以及 遇到意外输入 等。处理反常行为可能是设计所有系统最难的一部分。

    异常处理机制为程序 异常检测异常处理 这两部分的协作提供支持。在 C++语言中,异常处理包括:

    • throw表达式( throw expression),异常检测部分使用throw表达式来表示它遇到了无法处理的问题。我们说throw 引发(raise)了异常。
    • try语句块 (try block),异常处理部分使用try语句块处理异常。try语句块以关键字 try 开始,并以一个或多个 catch子句(catch clause)结束。try语句块中代码抛出的异常通常会被某个catch子句处理。因为 catch子句“处理”异常,所以它们也被称作异常处理代码( exception handler)。
    • 一套 异常类(exception class),用于在 throw表达式和相关的catch子句之间传递异常的具体信息。
    throw 表达式
    • 程序的异常检测部分使用throw表达式引发一个异常。
    • throw表达式包含关键字throw和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型
    • throw表达式后面通常紧跟一个分号,从而构成一条表达式语句。
    //首先检查两条数据是否是关于同一种书籍的
    if (item1.isbn() != item2.isbn())
    	throw runtime_error ( "Data must refer to same ISBN");
    //如果程序执行到了这里,表示两个ISBN是相同的
    cout << item1 + item2 << endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这段代码中,如果 ISBN不一样就抛出一个异常,该异常是类型runtime_error的对象。抛出异常将终止当前的函数,并把控制权转移给能处理该异常的代码。

    类型runtime_error是标准库异常类型的一种,定义在stdexcept头文件中。我们必须初始化 runtime_error的对象,方式是给它提供一个string对象或者一个C风格的字符串,这个字符串中有一些关于异常的辅助信息。

    try 语句块

    try语句块的通用语法形式是

    try {
    	program-statements
    } catch (exception-declaration){
    	handler-statements
    } catch (exception-declaration){
    	handler-statements
    } // ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • try语句块的一开始是关键字try,随后紧跟着一个块,这个块就像大多数时候那样是花括号括起来的语句序列。
    • 跟在try 块之后的是一个或多个 catch子句。catch子句包括三部分:关键字catch、括号内一个(可能未命名的)对象的声明(称作异常声明,exception declaration)以及一个块
    • 当选中了某个catch子句处理异常之后,执行与之对应的块。catch一旦完成,程序跳转到try语句块最后一个catch子句之后的那条语句继续执行。
    • try语句块中的program-statements组成程序的正常逻辑,像其他任何块一样,program-statements可以有包括声明在内的任意C++语句。一如往常,try语句块内声明的变量在块外部无法访问,特别是在catch子句内也无法访问。

    编写处理代码
    在之前的例子里,我们使用了一个throw表达式以避免把两个代表不同书籍的Sales_item相加。
    我们假设执行sales_item对象加法的代码是与用户交互的代码分离开来的。其中与用户交互的代码负责处理发生的异常,它的形式可能如下所示:

    while (cin >> item1 >> item2) {
    	try {
    		//执行添加两个sales_item对象的代码
    		//如果添加失败,代码抛出一个runtime_error异常
    	} catch (runtime_error err){
    		//提醒用户两个ISBN必须一致,询问是否重新输入
    		cout << err.what()
    			 << "\nTry Again? Enter y or n" << endl;
    		char c;
    		cin >> c;
    		if ( !cin ll c == 'n' )
    			break; //跳出while循环
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    程序本来要执行的任务出现在 try语句块中,这是因为这段代码可能会抛出一个runtime_error类型的异常。

    try语句块对应一个catch子句,该子句负责处理类型为runtime_error的异常。如果try语句块的代码抛出了runtime error异常,接下来执行catch 块内的语句。

    给用户的提示信息中输出了err.what()的返回值。我们知道err的类型是runtime_error,因此能推断what是runtime_error类的一个成员函数。每个标准库异常类都定义了名为what的成员函数,这些函数没有参数,返回值是C风格字符串(即 const char*)。其中,runtime_error的 what成员返回的是初始化一个具体对象时所用的string对象的副本。如果上一节编写的代码抛出异常,则本节的catch子句输出

    Data must refer to same ISBN
    Try Again? Enter y or n
    
    • 1
    • 2

    函数在寻找处理代码的过程中退出

    • 在复杂系统中,程序在遇到抛出异常的代码前,其执行路径可能已经经过了多个try语句块。例如,一个try语句块可能调用了包含另一个try语句块的函数,新的try语句块可能调用了包含又一个try语句块的新函数,以此类推。
    • 寻找处理代码的过程与函数调用链刚好相反。当异常被抛出时,首先搜索抛出该异常的函数。如果没找到匹配的 catch子句,终止该函数,并在调用该函数的函数中继续寻找。如果还是没有找到匹配的 catch子句,这个新的函数也被终止,继续搜索调用它的函数。以此类推,沿着程序的执行路径逐层回退,直到找到适当类型的catch子句为止。
    • 如果最终还是没能找到任何匹配的catch子句,程序转到名为terminate的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。
    • 对于那些没有任何try语句块定义的异常,也按照类似的方式处理:毕竟,没有try语句块也就意味着没有匹配的catch子句。如果一段程序没有try语句块且发生了异常,系统会调用terminate函数并终止当前程序的执行。
    标准异常

    C++标准库定义了一组类,用于报告标准库函数遇到的问题。这些异常类也可以在用户编写的程序中使用,它们分别定义在4个头文件中:

    • exception头文件定义了最通用的异常类exception。它只报告异常的发生,不提供任何额外信息。

    • stdexcept头文件定义了几种常用的异常类,详细信息在下表中列出。

    • new头文件定义了bad_alloc异常类型,这种类型将在12.1.2节(第407页)详细介绍。

    • type_info头文件定义了bad_cast异常类型,这种类型将在19.2节(第731页))详细介绍。
      在这里插入图片描述
      标准库异常类只定义了几种运算,包括创建或拷贝异常类型的对象,以及为异常类型的对象赋值。

    • 我们只能以默认初始化的方式初始化 exception、bad_alloc和 bad _cast对象,不允许为这些对象提供初始值。

    • 其他异常类型的行为则恰好相反:应该使用string对象或者C风格字符串初始化这些类型的对象,但是不允许使用默认初始化的方式。当创建此类对象时,必须提供初始值,该初始值含有错误相关的信息。

    • 异常类型只定义了一个名为what的成员函数,该函数没有任何参数,返回值是一个指向C风格字符串的const char*。该字符串的目的是提供关于异常的一些文本信息。

    • what函数返回的C风格字符串的内容与异常对象的类型有关。如果异常类型有一个字符串初始值,则what返回该字符串。对于其他无初始值的异常类型来说,what返回的内容由编译器决定。

  • 相关阅读:
    吴恩达机器学习笔记(一)
    操作系统-进程与线程(进程同步与互斥,进程互斥的软硬件实现方式)
    Multicycle Path
    CICD 持续集成与持续交付——gitlab
    SMSBMS超市订单管理系统详解(一:准备工作)
    计组大作业|硬件小学期的思路
    github搜索方法
    一键批量视频剪辑、合并,省时省力,制作专业视频
    在uniapp中为自定义组件绑定点击事件点击后没有效果
    k8s概念之GVK与GVR
  • 原文地址:https://blog.csdn.net/weixin_43412762/article/details/127941983