c++ primer 笔记:语句及练习题解

1、简单语句

C++语言中的大多数语句都以分号结束,一个表达式加上分号就变成了表达式语句(expression statement)。如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,则应该使用 空语句(null statement),空语句中只含有一个单独的分号。

ival + 5; 		// 一条没实际用处的表达式语句
cout << ival; 	// 一条有用的表达式语句
// 重复读入数据直至到达文件末尾或某次输入的值等于sought
while (cin >> s && s != sought)
    ;   		// 空语句

别漏写也别多写分号;

别漏写分号。因为空语句是一条语句,所以可用在任何允许语句的地方,但多余的空语句并非能随便使用:

ival = vl + v2;;	 			// 正确: 第二个分号表示一条多余的空语句
// 出现了糟糕的情况:额外的分号,循环体是那条空语句
while (iter != svec.end()) ;    // while循环体是那条空语句
    ++iter;     				// 递增运算不属于循环的一部分

复合语句
复合语句(compound statement) 是指用花括号括起来的(可能为空)语句和声明的序列,也叫做块(block),一个块就是一个作用域,在块中引入的名字只能在块内部以及嵌套在块中的子块里访问。通常,名字在有限的区域内可见,该区域从名字定义处开始,到名字所在(最内层)块的结尾处为止。

如果在程序的某个地方,语法上需要一条语句,但是逻辑上需要多条语句,则应该使用复合语句。把要执行的语句用花括号括起来, 就将其转换成了一条(复合〉语句。

while (val <= 10) {
    
    
	sum += val;		// 把sum+val的值赋给sum
	++val;			// 给val加1
}

练习 5.1:什么是空语句?什么时候会用到空语句?

答:空语句是最简单的语句,空语句由一个单独的分号构成。如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句,空语句什么也不做。

练习 5.2:什么是块?什么时候会用到块?

答:块是指用花括号括起来的语句和声明的序列,也称为复合语句。如果在程序的某个地方,语法上需要一条语句,但是逻辑上需要多条语句,此时应该使用块。块不需要以分号结束。

练习 5.3:使用逗号运算符重写1.4.1节的 while 循环,使它不再需要块,观察改写之后的代码可读性提高了还是降低了。

答:
原文的while循环使用了块,其形式是:

while (val <= 10)
{
    
    
    sum += val;
    ++val;
}

改写后:

while (val <= 10)
    sum += val, ++val;

改写之后的代码不够清晰,可读性降低了。

2、语句作用域

可以在 if、switch、while 和 for 语句的控制结构内定义变量,这些变量只在相应语句的内部可见,一旦语句结束,变量也就超出了其作用范围。

while (int i = get_num())   // 每次迭代时创建并初始化i
    cout << i << endl;
i = 0;  					// 错误:在循环外部无法访问i

如果其他代码也需要访问控制变量,则变量必须定义在语句的外部:

// 寻找第一个负位元素
auto beg = v.begin();
while (beg != v.end() && *beg >= 0)
	++beg;
if (beg == v.end())
	// 此时我们知道v中的所有元素都大于等于0

练习 5.4:说明下列例子的含义,如果存在问题,试着修改它。

(a) while (string::iterator iter != s.end()) {
    
    /*...*/}
(b) while (bool status = find(word)) {
    
    /*...*/}
		if (!status) {
    
    /*...*/}

答:
(a)是非法的,它的原意是希望在 while 语句的控制结构当中定义一个 string::iterator 类型的变量 iter,然后判断 iter 是否到达了 s 的末尾,只要还没有到达末尾就执行循环体的内容。但是该式把变量的定义和关系判断混合在了一起,如果要使用 iter 与其他值比较,必须首先为 iter 赋初值。
修改后的程序应该是:

string::iterator iter=s.begin();
while (iter!=s.end())
{
    
    
	++iter;
	/*...*/
}

(b)是非法的,变量 status 定义在 while 循环控制结构的内部,其作用域仅限于 while 循环。if 语句已经位于 while 循环的作用域之外,status 在 if 语句内是一个未命名的无效变量。要想在 if 语句中继续使用 status,需要把它定义在 while 循环之前。
修改后的程序:

bool status;
while (status=find(word)) {
    
    /*...*/}
if (!status) {
    
    /*...*/}

3、条件语句

3.1 if 语句

**if 语句(if statement)**的作用是:判断一个指定的条件是否为真,根据判断结果决定是否执行另外一条语句,if 语句包括两种形式:一种有else分支,另一种没有。

没有else分支:

if (condition)
    statement

有else分支:

if (condition)
    statement
else
    statement2

condition 可以是一个表达式,也可以是一个初始化了的变量声明。

  • 如果 condition 为真,则执行 statement。执行完成后,程序继续执行 if 语句后面的其他语句。
  • 如果 condition 为假,则跳过 statement。对于简单 if 语句来说,程序直接执行 if 语句后面的其他语句;对于 if-else 语句来说,程序先执行 statement2,再执行 if 语句后面的其他语句。

嵌套 if 语句

if 语句可以嵌套,其中 else 与离它最近的尚未匹配的 if 相匹配。

嵌套时注意使用花括号,否则很容易出现一个错误,就是本来程序中有几条语句应该作为一个块来执行,如果忘记把这些块包围,那么就只有第一条是在 if 语句中的,剩余语句就会顺序执行。

通过练习来深刻认识 if 语句:
练习 5.5:写一段自己的程序,使用 if else 语句实现把数字转换为字母成绩的要求。

答:

#include <iostream>
#include <vector>
#include <string>
using namespace std;

int main()
{
    
    
	vector<string> scores = {
    
     "F", "D", "C", "B", "A", "A++" };
	cout << "请输入您的成绩:" << endl;
	for (int g; cin >> g;)
	{
    
    
		string letter;
		if (g < 0 || g > 100)
		{
    
    
			cout << "该成绩不合法!!!" << endl;
			continue;
		}
		else if (g < 60)
		{
    
    
			letter = scores[0];
		}
		else
		{
    
    
			letter = scores[(g - 50) / 10];
			if (g != 100){
    
    
				if (g % 10 > 7){
    
    
					letter += "+";
				}
				else if (g % 10 < 3)
				{
    
    
					letter += "-";
				}
				else
				{
    
    
					letter += "";
				}
			}
		}
		cout << "等级成绩是:" << letter << endl;
	}
	return 0;
}

练习 5.6:改写上一题的程序,使用条件运算符代替 if else 语句。

答:

#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
    
    
	vector<string> scores = {
    
     "F", "D", "C", "B", "A", "A++" };
	cout << "请输入您的成绩:" << endl;
	for (int g; cin >> g;)
	{
    
    
		string letter;
		if (g < 0 || g > 100)
		{
    
    
			cout << "该成绩不合法!!!" << endl;
			continue;
		}
		letter = (g < 60) ? scores[0] : scores[(g - 50) / 10];
		letter += (g == 100 || g < 60) ? "" : g % 10 > 7 ? "+" : g % 10 < 3 ? "-" : "";
		cout << "等级成绩是:" << letter << endl;
	}
	return 0;
}

练习 5.7:改写下列代码段中的错误。

(a) if (ival1 != ival2) 
		ival1 = ival2
    else 
    	ival1 = ival2 = 0;
(b) if (ival < minval) 
		minval = ival;
    	occurs = 1;
(c) if (int ival = get_value())
    	cout << "ival = " << ival << endl;
    if (!ival)
    	cout << "ival = 0\n";
(d) if (ival = 0)
    	ival = get_value();

答:
(a)第二行少了一个分号
(b)两个语句应用大括号括起来
(c)ival 是定义在 if 语句中的变量,其作用域仅限于第一个 if 语句,要想在第二个 if 语句中也使用它,就必须把它定义在两个 if 语句的外部。程序修改为:

int ival;
if (ival = get_value())
    	cout << "ival = " << ival << endl;
if (!ival)
	cout << "ival = 0\n";

(d)程序的原意是判断 ival 的值是否是0,原题使用赋值运算符的结果是把0赋给了 ival,然后检验 ival 的值,这样使得条件永远不会满足。应在条件处使用 ==。

练习 5.8:什么是“悬垂else”?C++语言是如何处理else子句的?

答:悬垂 else 是指当程序中的 if 分支多于 else 分支时,如何为 else 寻找与之匹配的 if 分支的问题。
C++规定,else 与离它最近的尚未匹配的 if 匹配,从而消除了二义性。

3.2 switch 语句

switch 语句的形式:

switch(number)
{
    
    
	case 1: statement 1;
			break;
	case 2: statement 2;
			break;
	case 3: statement 3;
			break;
	default: statement 4;
}

举一个例子,输入文本,统计五个元音字母在文本中出现的次数,程序如下:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    
    
	// 为每个元音字母初始化其计数值
	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 '0':
				++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 0 : \t" << oCnt << '\n'
		<< "Number of vowel u : \t" << uCnt << endl;

	return 0;
}

在这里插入图片描述
switch 语句先对括号里的表达式求值,值转换成整数类型后再与每个 case 标签(case label)的值进行比较(case 标签必须是整型常量表达式)。如果表达式的值和某个 case 标签匹配,程序从该标签之后的第一条语句开始执行,直到到达 switch 的结尾或者遇到 break 语句为止。

通常情况下每个 case 分支后都有 break 语句,如果确实不应该出现 break 语句,最好写一段注释说明程序的逻辑。尽管 switch 语句没有强制一定要在最后一个 case 标签后写上 break,但为了安全起见,最好添加 break,这样即使以后增加了新的 case 分支,也不用再在前面补充 break 语句了。

另外,switch 语句中可以添加一个 default 标签(default label),如果没有任何一个 case 标签能匹配上 switch 表达式的值,程序将执行 default 标签后的语句。即使不准备在 default 标签下做任何操作,程序中也应该定义一个 default 标签,其目的在于告诉他人我们已经考虑到了默认情况,只是目前不需要实际操作。

不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。如果需要为 switch 的某个 case 分支定义并初始化一个变量,则应该把变量定义在块内。

case true:
	{
    
    
	    // 正确:声明语句位于语句块内部
	    string file_name = get_file_name();
    	// ...
	}
	break;
	
case false:
	if (file_name.empty()) // 错误:file_name不在作用域之内

练习 5.9:编写一段程序,使用一系列if语句统计从cin读入的文本中有多少元音字母。

答:

#include <iostream>
using namespace std;
int main()
{
    
    
	unsigned vowelCnt = 0;
	char ch;
	cout << "请输入一段文本:" << endl;
	while (cin >> ch)
	{
    
    
		if (ch == 'a')
			++vowelCnt;
		else if (ch == 'e')
			++vowelCnt;
		else if (ch == 'i')
			++vowelCnt;
		else if (ch == 'o')
			++vowelCnt;
		else if (ch == 'u')
			++vowelCnt;
	}
	cout << "您输入的文本中共有 " << vowelCnt << " 个元音字母" << endl;
	return 0;
}

练习 5.10:我们之前实现的统计元音字母的程序存在一个问题:如果元音字母以大写形式出现,不会被统计在内。编写一段程序,既统计元音字母的小写形式,也统计元音字母的大写形式,也就是说,新程序遇到’a’和’A’都应该递增aCnt的值,以此类推。

答:

#include <iostream>
using namespace std;
int main()
{
    
    
	unsigned int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
	char ch;
	cout << "请输入一段文本:" << endl;
	while (cin >> ch)
	{
    
    
		switch (ch)
		{
    
    
			case 'a':
			case 'A':
				++aCnt;
				break;
			case 'e':
			case 'E':
				++eCnt;
				break;
			case 'i':
			case 'I':
				++iCnt;
				break;
			case 'o':
			case 'O':
				++oCnt;
				break;
			case 'u':
			case 'U':
				++uCnt;
				break;
		}
	}
	cout << "Number of vowel a(A): \t" << aCnt << '\n'
		<< "Number of vowel e(E): \t" << eCnt << '\n'
		<< "Number of vowel i(I): \t" << iCnt << '\n'
		<< "Number of vowel o(O): \t" << oCnt << '\n'
		<< "Number of vowel u(U): \t" << uCnt << endl;
	return 0;
}

练习 5.11:修改统计元音字母的程序,使其也能统计空格、制表符、和换行符的数量。

答:

#include <iostream>
using namespace std;
int main()
{
    
    
	unsigned int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
	unsigned int spaceCnt = 0, tabCnt = 0, newLineCnt = 0;
	char ch;
	cout << "请输入一段文本:" << endl;
	while (cin.get(ch))
	{
    
    
		switch (ch)
		{
    
    
			case 'a':
			case 'A':
				++aCnt;
				break;
			case 'e':
			case 'E':
				++eCnt;
				break;
			case 'i':
			case 'I':
				++iCnt;
				break;
			case 'o':
			case 'O':
				++oCnt;
				break;
			case 'u':
			case 'U':
				++uCnt;
				break;
			case ' ':
				++spaceCnt;
				break;
			case '\t':
				++tabCnt;
				break;
			case '\n':
				++newLineCnt;
				break;
		}
	}
	cout << "Number of vowel a(A): \t" << aCnt << '\n'
		<< "Number of vowel e(E): \t" << eCnt << '\n'
		<< "Number of vowel i(I): \t" << iCnt << '\n'
		<< "Number of vowel o(O): \t" << oCnt << '\n'
		<< "Number of vowel u(U): \t" << uCnt << '\n'
		<< "Number of space: \t" << spaceCnt << '\n'
		<< "Number of tab char: \t" << tabCnt << '\n'
		<< "Number of new line: \t" << newLineCnt << endl;
	return 0;
}

练习 5.12:修改统计元音字母的程序,使其能统计含以下两个字符的字符序列的数量:ff、fl 和 fi。

答:
假定一个字符可以被统计多次。

#include<iostream>
using namespace std;

int main(){
    
    
    unsigned int ff = 0, fl = 0, fi = 0;
    char ch = '\0', prev = '\0';
    while(cin >> ch){
    
    
        if(prev == 'f'){
    
    
            switch (ch)
            {
    
    
            case 'f':
                ++ff;
                break;
            case 'l':
                ++fl;
                break;
            case 'i':
                ++fi;
                break;
            default:
                
                break;
            }
        }
        if(ch == 'f')
            prev = 'f';
        else
            prev = '\0';
    }
    cout << "Number of ff: \t" << ff << '\n'
		<< "Number of fl: \t" << fl << '\n'
		<< "Number of fi: \t" << fi << endl;
	return 0;
}

在这里插入图片描述

练习 5.13:下面显示的每个程序都含有一个常见的编程错误,指出错误在哪里,然后修改它们。

(a) unsigned aCnt = 0, eCnt = 0, iouCnt = 0;
    char ch = next_text();
    switch (ch) {
    
    
        case 'a': aCnt++;
        case 'e': eCnt++;
        default: iouCnt++;
    }
(b) unsigned index = some_value();
    switch (index) {
    
    
        case 1:
            int ix = get_value();
            ivec[ ix ] = index;
            break;
        default:
            ix = ivec.size()-1;
            ivec[ ix ] = index;
    }
(c) unsigned evenCnt = 0, oddCnt = 0;
    int digit = get_num() % 10;
    switch (digit) {
    
    
        case 1, 3, 5, 7, 9:
            oddcnt++;
            break;
        case 2, 4, 6, 8, 10:
            evencnt++;
            break;
    }
(d) unsigned ival=512, jval=1024, kval=4096;
    unsigned bufsize;
    unsigned swt = get_bufCnt();
    switch(swt) {
    
    
        case ival:
            bufsize = ival * sizeof(int);
            break;
        case jval:
            bufsize = jval * sizeof(int);
            break;
        case kval:
            bufsize = kval * sizeof(int);
            break;
    }

答:
(a)的错误是在每个 case 分支中都缺少了 break; 语句,造成的后果是一旦执行了前面的 case 分支,必定还会继续执行接下来的其他 case 分支,这显然与程序的预期是不相符的。

unsigned aCnt = 0, eCnt = 0, iouCnt = 0;
    char ch = next_text();
    switch (ch) {
    
    
    	case 'a': 
	    	aCnt++; 
	    	break;
    	case 'e': 
	    	eCnt++; 
	    	break;
    	default: 
	    	iouCnt++; 
	    	break;
    }

(b)的错误是在 case 分支中定义并初始化了变量 ix,同时在 default 分支中使用了该变量,此时如果控制流跳过 case 分支而直接到达 default 分支,则会试图使用未经初始化的变量,因而该程序无法通过编译。

unsigned index = some_value();
int ix;
switch (index) {
    
    
    case 1:
        ix = get_value();
        ivec[ ix ] = index;
        break;
    default:
        ix = static_cast<int>(ivec.size())-1;
        ivec[ ix ] = index;
}

(c)的错误是在同一个 case 标签中放置了多个值,而 C++ 规定一个 case 标签只能对应一个值。

unsigned evenCnt = 0, oddCnt = 0;
int digit = get_num() % 10;
switch (digit) {
    
    
    case 1: 
    case 3: 
    case 5: 
    case 7: 
    case 9:
        oddcnt++;
        break;
    case 2: 
    case 4: 
    case 6: 
    case 8: 
    case 0:
        evencnt++;
        break;
}

(d)的错误是使用变量作为 case 标签的内容,C++规定,case 标签的内容只能是整型常量表达式。

修改后的程序如下所示:

const unsigned ival=512, jval=1024, kval=4096;
unsigned bufsize;
unsigned swt = get_bufCnt();
switch(swt) {
    
    
    case ival:
        bufsize = ival * sizeof(int);
        break;
    case jval:
        bufsize = jval * sizeof(int);
        break;
    case kval:
        bufsize = kval * sizeof(int);
        break;
}

4、迭代语句

迭代语句通常称为循环,它重复执行操作直到满足某个条件才停止。while 和 for 语句在执行循环体之前检查条件,do-while 语句先执行循环体再检查条件。

4.1 while语句

while 语句的形式:

while (condition)
    statement

只要 condition 的求值结果为 true,就一直执行 statement(通常是一个块)。condition 不能为空,如果 condition 第一次求值就是 false,statement 一次都不会执行。

定义在 while 条件部分或者 while 循环体内的变量,每次迭代都经历从创建到销毁的过程。在不确定迭代次数,或者想在循环结束后访问循环控制变量时,使用 while 比较合适。

练习 5.14:编写一段程序,从标准输入中读取若干 string 对象并查找连续重复出现的单词,所谓连续重复出现的意思是:一个单词后面紧跟着这个单词本身。要求记录连续重复出现的最大次数以及对应的单词。如果这样的单词存在,输出重复出现的最大次数;如果不存在,输出一条信息说明任何单词都没有连续出现过。
例如:如果输入是:
how now now now brown cow cow
那么输出应该表明单词 now 连续出现了3次。

答:

#include<iostream>
#include<string>
using namespace std;
int main(){
    
    
    string str(""), str_new, result;
    int cnt = 0, max = -1;
    while(cin >> str_new){
    
    
        if(str_new != str){
    
    
            str = str_new;
            cnt = 1;
        }else{
    
    
            ++cnt;
        }
        if(cnt > max){
    
    
            max = cnt;
            result = str;
        }
    }
    cout << result << " repeat " << max << " times." << endl;
}

在这里插入图片描述

4.2 传统的for语句

for 语句的形式:

for (initializer; condition; expression)
    statement

一般情况下,initializer 负责初始化一个值,这个值会随着循环的进行而改变。condition 作为循环控制的条件,只要 condition 的求值结果为 true,就执行一次 statement,执行后再由 expression 负责修改 initializer 初始化的变量,修改发生在每次循环迭代之后,这个变量就是 condition 检查的对象。如果 condition 第一次求值就是 false,statement 一次都不会执行。statement 可以是一条单独的语句也可以是一条复合语句。

执行流程:

  1. 首先执行一次 initializer
  2. 判断 condition,真则执行循环体,假则终止循环;
  3. 最后执行 expression

initializer 中也可以定义多个对象,但是只能有一条声明语句,因此所有变量的基础类型必须相同。

练习 5.15:说明下列循环的含义并改正其中的错误。

(a) for (int ix = 0; ix != sz; ++ix) {
    
     /* ... */ }
    if (ix != sz)
    	// . . .
(b) int ix;
    for (ix != sz; ++ix) {
    
     /* ... */ }
(c) for (int ix = 0; ix != sz; ++ix, ++sz) {
    
     /*...*/ }

答:(a)的错误是在 for 语句中定义了变量 ix,然后试图在 for 语句之外继续使用 ix。因为 ix 定义在 for 语句的内部,所以其作用域仅限于 for 语句。在 if 语句中 ix 已经失效,因此程序无法编译通过。

(a) int ix;
    for (ix = 0; ix != sz; ++ix)  {
    
     /* ... */ }
    if (ix != sz)
    // . . .

(b)的错误有两个,一是变量 ix 未经初始化就直接使用,二是 for 语句的控制结构缺少一句话,在语法上是错误的。
修改后的程序如下:

(b) int ix;
    for (; ix != sz; ++ix) {
    
     /* ... */ }

(c)的错误是一旦进入循环,程序就会无休止地执行下去。也就是说,当初始情况下 ix != sz 时,由题意可知 ix 和 sz 一直同步增长,循环的终止条件永远不会满足,所以该循环是一个死循环。

(c) for (int ix = 0; ix != sz; ++ix) {
    
     /* ... */ }

练习 5.16:while循环特别适用于那种条件不变、反复执行操作的情况,例如,当未达到文件末尾时不断读取下一个值。

for循环更像是在按步骤迭代,它的索引值在某个范围内一次变化。根据每种循环的习惯各自编写一段程序,然后分别用另一种循环改写。

如果只能使用一种循环,你倾向于哪种?为什么?

答:按需决定

练习 5.17:假设有两个包含整数的 vector 对象,编写一段程序,检验其中一个 vector 对象是否是另一个的前缀。
为了实现这一目标,对于两个不等长的 vector 对象,只需挑出长度较短的那个,把它的所有元素和另一个 vector 对象比较即可。例如,如果两个 vector 对象的元素分别是0、1、1、2 和 0、1、1、2、3、5、8,则程序的返回结果为真。

答:

#include<iostream>
#include<vector>
using namespace std;
int main(){
    
    
    vector<int> v1{
    
    0, 1, 1, 2}, v2{
    
    0, 1, 1, 2, 3, 4, 5};
    auto v1_it = v1.cbegin(), v2_it = v2.cbegin();
    for(;v1_it != v1.end() && v2_it != v2.end();++v1_it, ++v2_it){
    
    
        if(*v1_it != *v2_it){
    
    
            cout << "no no no!" << endl;
            return 0;
        }
    }
    if(v1_it == v1.end())
        cout << "v1是v2的前缀" << endl;
    if(v2_it == v2.end())
        cout << "v2是v1的前缀" << endl;
    return 0;
}

4.3 范围 for 语句

范围 for 语句的形式:

for (declaration : expression)
    statement

其中 expression 表示一个序列,拥有能返回迭代器的 begin 和 end 成员,比如用花括号括起来的初始值列表、数组或者者 vector 或 string 等类型的对象。
declaration 定义一个变量,序列中的每个元素都应该能转换成该变量的类型(可以使用 auto)。如果需要对序列中的元素执行写操作,循环变量必须声明成引用类型,每次迭代都会重新定义循环控制变量,并将其初始化为序列中的下一个值,之后才会执行 statement。

vector<int> v = {
    
    O, 1, 2, 3, 4, 5, 6, 7, 8, 9};

// 范围变量必须是引用类型,这样才能对元素执行写操作
for (auto &r : v) 		// 对于v中的每一个元素
	r *= 2; 			// 将v中每个元素的值翻倍

// 等价于<=>
for (auto beg = v.begin(), end = v.end(); beg != end; ++beg) {
    
    
	auto &r = *beg; 	// r必须是引用类型,这样才能对元素执行写操作
	r *= 2; 			// 将v中每个元素的值翻倍
}

4.4 do-while语句

do-while 语句的形式:

do
    statement
while (condition);

计算 condition 的值之前会先执行一次 statement,condition 不能为空。如果 condition 的值为 false,循环终止;否则重复执行 statement。

因为 do-while 语句先执行语句或块,再判断条件,所以不允许在条件部分定义变量。condition 使用的变量必须定义在循环体之外

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

练习 5.18:说明下列循环的含义并改正其中的错误。

(a) do
        int v1, v2;
        cout << "Please enter two numbers to sum:" ;
        if (cin >> v1 >> v2)
            cout << "Sum is: " << v1 + v2 << endl;
    while (cin);
(b) do {
    
    
        // . . .
    } while (int ival = get_response());
(c) do {
    
    
        int ival = get_response();
    } while (ival);

答:
(a)的含义是每次循环读入两个整数并输出他们的和。因为 do-while 语句的循环体必须是一条语句或者一个语句块,所以在本题中应该把循环体的内容用花括号括起来。

do {
    
    
    int v1, v2;
    cout << "Please enter two numbers to sum:" ;
    if (cin >> v1 >> v2)
        cout << "Sum is: " << v1 + v2 << endl;
} while (cin);

(b)的含义是当 get_response 的返回值不为0时执行循环体。因为 do-while 语句不允许在循环条件内定义变量,所以该程序是错误的。

int ival;
do {
    
    
    // . . .
} while (ival = get_response());

(c)的含义是当 get_response 的返回值不为0时执行循环体。因为出现在 do-while 语句条件部分的变量必须定义在循环体之外,所以该程序是错误的。

int ival = get_response();
do {
    
    
    ival = get_response();
} while (ival);

练习 5.19:编写一段程序,使用 do while 循环重复地执行下述任务:
首先提示用户输入两个 string 对象,然后挑出较短的那个并输出它。

答:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    
    
	do
	{
    
    
		cout << "请输入两个字符串:" << endl;
		string str1, str2;
		cin >> str1 >> str2;
		if (str1.size() < str2.size())
			cout << "长度较小的字符串是:" << str1 << endl;
		else if (str1.size() > str2.size())
			cout << "长度较小的字符串是:" << str2 << endl;
		else
			cout << "两个字符串等长!" << endl;
	} while (cin);

	return 0;
}

5、跳转语句

跳转语句中断当前的执行过程。包括 break、continue、goto 和 return。本节介绍前 3 个。

5.1 break 语句

break 语句只能出现在迭代语句或者 switch 语句的内部(包括嵌套在此类循环里的语句或块的内部),负责终止离它最近的 while、do-while、for 或者 switch 语句,并从这些语句之后的第一条语句开始执行。break 语句的作用范围仅限于最近的循环或者 switch。

练习 5.20:编写一段程序,从标准输入中读取 string 对象的序列直到连续出现两个相同的单词或者所有的单词都读完为止。
使用 while 循环一次读取一个单词,当一个单词连续出现两次时使用 break 语句终止循环。
输出连续重复出现的单词,或者输出一个消息说明没有任何单词是连续重复出现的。

答:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    
    
	string currString, preString;
	bool bl = true;
	cout << "请输入一组字符串:" << endl;
	while (cin >> currString)
	{
    
    
		if (currString == preString)
		{
    
    
			bl = false;
			cout << "连续出现的字符串是:" << currString << endl;
			break;
		}
		preString = currString;
	}
	if (bl)
		cout << "没有连续出现的字符串!" << endl;

	return 0;
}

5.2 continue 语句

continue 语句只能出现在迭代语句的内部,负责终止离它最近的循环的当前一次迭代并立即开始下一次迭代。continue 语句只能出现在 for、while 和 do while 循环的内部,或者嵌套在此类循环里的语句或块的内部。和 break 语句不同的是,只有当 switch 语句嵌套在迭代语句内部时,才能在 switch 中使用 continue。

continue 语句中断当前迭代后,具体操作视迭代语句类型而定:

  • 对于 while 和 do-while 语句来说,继续判断条件的值。
  • 对于传统的 for 语句来说,继续执行 for 语句头中的第三部分,之后判断条件的值。
  • 对于范围 for 语句来说,是用序列中的下一个元素初始化循环变量。
string buf ;
while (cin >> buf && !buf.empty()) {
    
    
	if (buf[O] !='_')
		continue; // 接着读取下一个输入
	// 程序执行过程到了这里?说明当前的输入是以下画线开始的;接着处理buf......

练习 5.21:修改5.5.1节练习题的程序,使其找到的重复单词必须以大写字母开头。

答:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    
    
	string currString, preString;
	bool bl = true;
	cout << "请输入一组字符串:" << endl;
	while (cin >> currString)
	{
    
    
		if (!isupper(currString[0]))
			continue;
		if (currString == preString)
		{
    
    
			bl = false;
			cout << "连续出现的字符串是:" << currString << endl;
			break;
		}
		preString = currString;
	}
	if (bl)
		cout << "没有连续出现的字符串" << endl;

	return 0;
}

5.3 goto 语句

goto 语句(labeled statement)的作用是从 goto 语句无条件跳转到同一函数内的另一条语句。

语法:

goto label;

goto 语句使程序无条件跳转到标签为 label 的语句处执行。

end: return; // 带标签语句,可以作为goto的目标

标签标识符独立于变量和其他标识符的名字,它们可以相同而不会相互干扰。但两者必须位于同一个函数内,同时 goto 语句也不能将程序的控制权从变量的作用域之外转移到作用域之内:

	// ...
	goto end;
	int ix =10; // 错误:goto语句绕过了一个带初始化的变量定义
end:
	// 错误:此处的代码需妥使用ix,但是goto语句绕过了它的声明
	ix = 42;

练习 5.22:本节的最后一个例子跳回到 begin,其实使用循环能更好的完成该任务,重写这段代码,注意不再使用 goto 语句。

// 向后跳过一个带初始化的变量定义是合法的
begin:
    int sz = get_size();
    if (sz <= 0) {
    
    
        goto begin;
    }

答:

for(int sz = get_size(); sz <= 0;sz = get_size());

int sz = get_size();
while(sz<=0){
    
    
	sz = get_size();
}

int sz;
do{
    
    
	sz = get_size();
}while(sz <= 0)

6、try 语句块和异常处理

异常(exception) 是指程序运行时的反常行为,这些行为超出了函数正常功能的范围。当程序的某一部分检测到一个它无法处理的问题时,需要使用 异常处理(exception handling)

异常处理机制为程序中 异常检测异常处理 这两部分的协作提供支持,包括 throw 表达式(throw expression)、try 语句块(try block)和异常类(exception class)。

6.1 throw 表达式

throw 表达式包含关键字 throw 和紧随其后的一个表达式,其中表达式的类型就是抛出的异常类型。throw 表达式后面通常紧跟一个分号,从而构成一条表达式语句。

// 首先检查两条数据是否是关于同一种书籍的
if (item1.isbn() != item2.isbn())
	throw runtime_error("Data must refer to same ISBN");
// 如果程序执行到了这里,表示两个ISBN
cout << item1 + item2 << endl;

6.2 try语句块

try 语句块的通用形式:

try {
    
    
    program-statements
} 
catch (exception-declaration) {
    
    
    handler-statements
} 
catch (exception-declaration) {
    
    
    handler-statements
} // . . .

try 语句块中的 program-statements 组成程序的正常逻辑,其内部声明的变量在块外无法访问,即使在 catch 子句中也不行。语句块之后是 catch 子句,catch 子句包含:关键字 catch、括号内一个对象的声明(异常声明,exception declaration)和一个块。当选中了某个 catch 子句处理异常后,执行与之对应的块。catch 一旦完成,程序会跳过剩余的所有 catch 子句,继续执行后面的语句。

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 || c == 'n')
			break; // 跳出while循环
	}
}

寻找处理代码的过程与函数调用链刚好相反。当异常被抛出时, 首先搜索抛出该异常的函数。如果没找到匹配的 catch 子句, 终止该函数, 并在调用该函数的函数中继续寻找。如果还是没有找到匹配的 catch 子句,这个新的函数也被终止, 继续搜索调用它的函数。以此类推,沿着程序的执行路径逐层回退,直至找到适当类型的 catch 子句为止。如果最终没能找到与异常相匹配的 catch 子句,程序会执行名为 terminate 的标准库函数。该函数的行为与系统有关,一般情况下,执行该函数将导致程序非正常退出。类似地,如果一段程序没有 try 语句块且发生了异常,系统也会调用 terminate 函数并终止当前程序的执行。

6.3 标准异常

C++标准库定义了一组类,用于报告标准库函数遇到的问题。分别定义在4个头文件中:

  • 头文件 exception 定义了最通用的异常类 exception。它只报告异常的发生,不提供任何额外信息。
  • 头文件 stdexcept 定义了几种常用的异常类。如下图所示。
  • 头文件 new 定义了 bad_alloc 异常类。
  • 头文件 type_info 定义了 bad_cast 异常类。

在这里插入图片描述
标准库异常类只定义了几种运算,包括创建或拷贝异常类型的对象,以及为异常类型的对象赋值。
练习 5.23:编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果。

答:

#include <iostream>
using namespace std;
int main()
{
    
    
	cout << "请依次输入被除数和除数:" << endl;
	int ival1, ival2;
	cin >> ival1 >> ival2;
	if (ival2 == 0)
	{
    
    
		cout << "除数不能为0" << endl;
		return -1;
	}
	cout << "两数相除的结果是:" << ival1 / ival2 << endl;

	return 0;
}

练习 5.24:修改你的程序,使得当第二个数是0时抛出异常。先不要设定 catch 子句,运行程序并真的为除数输入0,看看会发生什么?

答:

#include<iostream>
#include<stdexcept>
using namespace std;
int main(){
    
    
    int a, b;
    cin >> a >> b;
    if(b == 0)
        throw runtime_error("divide by 0.");
    double c = a/b;
    cout << c << endl;
    return 0;
}

练习 5.25:修改上一题的程序,使用 try 语句块去捕获异常。catch 子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行 try 语句块的内容。

答:

#include<iostream>
#include<stdexcept>
using namespace std;
int main(){
    
    
    int a, b;
    double c;
    while(cin >> a >> b){
    
    
        try{
    
    
            if(b == 0)
                throw runtime_error("divided by 0");
            c = static_cast<double>(a)/b;
            cout << c << endl;
        }catch(runtime_error err){
    
    
            cout << err.what() << "\nTry again (y or n) ? ";
            char c;
            cin >> c;
            if(c == 'n'){
    
    
                break;
            }
        }
    }

    
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_45773137/article/details/125869208
今日推荐