和大多数语言一样,C++提供了条件执行语句、重复执行相同代码的循环语句和由于中断当前控制流的跳转语句,表达式语句和声明语句等。
语句有简单语句和复合语句之分。简单语句但多数以分号结束,最简单的语句就是空语句,空语句中就只含有一个单独的分号。
复合语句是用花括号括起来的语句或声明,复合语句也叫块。复合语句不同于简单语句的是,复合语句具有作用域,即块作用域,块中引入的名字只能被当前块或者当前块的字块访问。复合语句是不用分号结尾的。
可以在if、switch、while、for语句的控制结构内定义变量,这个变量只能在当前语句内访问,语句结束,变量就消亡了。
if语句的语法形式是
if (condition)
statement
对于if的condition,必须使用括号括起来,condition可以是一个表达式,也可以是一个初始化了的变量声明,并且不允许为空。
switch语句的语法形式是
switch(expression)
{
case value:
}
对于switch语句,首先对expression表达式求值,expression也可以是一个初始化了的变量声明。表达式的值转换成整形。然后与每个case标签比较。
case标签的值必须是个常量表达式,且是整型类型的。
如果没有任何一个case标签能匹配switch表达式的值,在switch语句有default表达式的情况下,将默认执行该default表达式,default表达式的位置无关紧要。
while语句的语法形式是
while (condition)
statement
condition是不允许为空的,可以是个表达式,也可以是一个初始化了的变量声明。
while循环不同于for循环的一点是,while条件部分或者while循环体内定义的变量每次迭代后都会被销毁。
范围for语句的语法形式是
for (declaration : expression)
statement
expression必须是一个序列,比如花括号括起来的初始值列表{1,2,3,4,5},或者数组、vector、string等类型的对象,这些对象的特点是可以使用begin和end成员或者函数来获取其相应的迭代器。
do while语句中while的条件部分使用的变量必须是循环前定义的。
break语句用于终止循环,break语句只能出现在迭代语句或者switch语句的内部,嵌套在循环语句的子语句也是可以的。
continue语句用于停止当前迭代的执行,重新开始下一次的迭代,但是不能终止循环,continue语句只能出现在for、while、do while循环的内部,或者循环内部的子块。
goto语句较难把控,不建议使用
try语句块和异常处理:
当程序的某部分检测到一个它无法处理的问题时,需要使用异常处理,异常分为两块,一块是异常的检测,一块是异常的处理。
检测部分是发出某种信号,表明遇到故障。发出是由throw完成的,称作throw引发了异常。某种信号则是异常的类型。
异常的处理是try和catch负责的。
throw表达式引发一个异常,表达式包含关键字和一个表达式,表达式的类型就是异常的类型,用于发出某种信号。
try语句块是一个关键字加上紧随的复合语句块,必须是复合语句块,不能是一个简单语句。
try语句之后是一个或多个catch子句。catch子句包含一个关键字catch,一个括号括起来的对象声明,一个语句块。当选中某个catch子句处理完毕后,程序就跳到所有catch子句之后的地方继续执行。如果没有catch子句,程序一般会非正常退出。
另外,try语句块具有块作用域,出了该块,不能在外部访问块内引入的变量。
练习5.1:什么是空语句?什么时候会用到空语句?
空语句就是只含有一个单独分号的语句。当语法上需要一条语句,逻辑上不需要时,就需要使用空语句
练习5.2:什么是块?什么时候会用到块?
块就是用花括号括起来的语句或者声明序列,当语法上需要一条语句,但是逻辑上需要多条语句,此时就要使用块。
练习5.3:使用逗号运算符(参见4.10节,第104页)重写1.4.1节(第10页)的 while 循环,使它不再需要块,观察改写之后的代码的可读性提高了还是降低了。
#include <iostream> using namespace std; int main() { int i = 50, sum = 0; while (i <= 100) sum += i, ++i; return 0; }
改写后的可读性降低。
练习5.4:说明下列例子的含义,如果存在问题,试着修改它。
(a) while (string::iterator iter != s.end()) { /* . . . */ } (b) while (bool status = find(word)) { /* . . . */ } if (!status) { /* . . . */ }
(a) 用循环遍历string s,语法错误,while头部只能是一个表达式或者初始化了的变量声明。应该将iter放在while外面定义。
(b) while头部定义的变量只在当前循环内可见,if无法访问,应该将status的定义放在最外面。
练习5.5:写一段自己的程序,使用if else 语句实现把数字成绩转换为字母成绩的要求。
#include <iostream> #include <vector> using namespace std; int main() { const vector<string> scores = {"F", "D", "C", "B", "A", "A++"}; string lettergrade; int grade; while (cin >> grade) { if (grade < 60) lettergrade = scores[0]; else lettergrade = scores[(grade - 50) / 10]; cout << lettergrade << ' '; } return 0; }
练习5.6:改写上一题的程序,使用条件运算符(参见4.7节,第134页)代替if else语句。
#include <iostream> #include <vector> using namespace std; int main() { const vector<string> scores = {"F", "D", "C", "B", "A", "A++"}; string lettergrade; int grade; while (cin >> grade) { (grade < 60) ? ( lettergrade = scores[0]) : (lettergrade = scores[(grade - 50) / 10]); cout << lettergrade << ' '; } 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 "; (d) if (ival = 0) ival = get_value();
(a) ival1 = ival2 后面缺少分号。
(b) 应该用花括号括起来。
(c) if (!ival) 应该改为else。
(d) if (ival = 0) 应该改为 if (ival == 0)。
练习5.8:什么是“悬垂else”?C++语言是如何处理else子句的?
悬垂else就是一个if语句嵌套在另一个if语句内部,使得if分支多于else分支。C++规定else与离它最近的尚未匹配的if匹配。
练习5.9:编写一段程序,使用一系列if语句统计从cin读入的文本中有多少元音字母。
#include <iostream> using namespace std; int main() { char c; size_t num = 0; while (cin >> c) { if (c == 'a') ++num; if (c == 'e') ++num; if (c == 'i') ++num; if (c == 'o') ++num; if (c == 'u') ++num; } cout << num << endl; return 0; }
练习5.10:我们之前实现的统计元音字母的程序存在一个问题:如果元音字母以大写形式出现,不会被统计在内。编写一段程序,既统计元音字母的小写形式,也统计元音字母的大写形式,也就是说,新程序遇到'a'和'A'都应该递增 aCnt 的值,以此类推。
#include <iostream> using namespace std; int main() { char c; size_t num = 0; while (cin >> c) { if (c == 'a' || c == 'A') ++num; if (c == 'e' || c == 'E') ++num; if (c == 'i' || c == 'I') ++num; if (c == 'o' || c == 'O') ++num; if (c == 'u' || c == 'U') ++num; } cout << num << endl; return 0; }
练习5.11:修改统计元音字母的程序,使其也能统计空格、制表符、和换行符的数量。
#include <iostream> using namespace std; int main() { size_t num; char ch; while (cin >> ch) { switch (ch) { case 'a': case 'A': case 'e': case 'E': case 'i': case 'I': case 'o': case 'O': case 'u': case 'U': case ' ': case ' ': case ' ': ++num; } } return 0; }
练习5.12:修改统计元音字母的程序,使其能统计含以下两个字符的字符序列的数量: ff、fl和fi。
#include <iostream> using namespace std; int main() { size_t num; string s; while (cin >> s) { if (s == "ff" || s == "fl" || s == "fi") { ++num; } } 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 'a': aCnt++;
break;
case 'e': eCnt++;
break;
default: iouCnt++;
break;
(b) default标签中的ix 未定义。修改为
int ix;
unsigned index = some_value();
switch (index)
{
case 1:
ix = get_value();
ivec[ ix ] = index;
break;
default:
ix = ivec.size() - 1;
ivec[ ix ] = index;
}
(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 10:
evencnt++;
break;
}
(d) case 标签必须是整型常量表达式。修改为
constexpr 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;
}
练习5.14:编写一段程序,从标准输入中读取若干string对象并查找连续重复出现的单词。所谓连续重复出现的意思是:一个单词后面紧跟着这个单词本身。要求记录连续重复出现的最大次数以及对应的单词。如果这样的单词存在,输出重复出现的最大次数;如果不存在,输出一条信息说明任何单词都没有连续出现过。例如:如果输入是:
how now now now brown cow cow
那么输出应该表明单词now连续出现了3次。
#include <iostream> using namespace std; int main() { string s1, s2; if (cin >> s1) { int cnt = 1; while (cin >> s2) { if (s1 == s2) ++cnt; else { cout << s1 << "occurs " << cnt << " times" << endl; s1 = s2; cnt = 1; } } cout << s1 << "occurs " << cnt << " times" << endl; } return 0; }
练习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) if不可以访问for的局部变量ix,应该将ix放在for的前面定义
(b) for的语法格式错误,应改为for( ; ix != sz; ++ix)
(c) 死循环,ix != sz 恒成立,不应该使用 ++ix,++sz
练习5.16:while 循环特别适用于那种条件不变、反复执行操作的情况,例如,当未达到文件末尾时不断读取下一个值。for 循环更像是在按步骤迭代,它的索引值在某个范围内依次变化。根据每种循环的习惯用法各自编写一段程序,然后分别用另一种循环改写。如果只能使用一种循环,你倾向于哪种?为什么?
#include <iostream> using namespace std; int main() { int i = 0, sum = 0; while (i <= 100) { sum += i; ++i; } for (int i = 0; i != 100; ++i) { sum += i; } return 0; }
如果只能使用一种,我倾向于for循环,因为for循环头部提供更多的选项,可以更多的控制循环,相对while循环来说更强大易用。
练习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, 5, 8}; if (v1.size() < v2.size()) { for (size_t i = 0; i != v1.size(); ++i) { if (v1[i] != v2[i]) cout << "not equal "; } } else { for (size_t i = 0; i != v2.size(); ++i) { if (v1[i] != v2[i]) cout << "not equal "; } } return 0; }
练习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 (ival = get_response()); (c) do { ival = get_response(); } while (ival);
(a) 语法错误,do后面,while前面的语句应该用花括号括起来
(b) 语法错误,do while条件使用的表达式规定必须是在循环前定义!
(c) 语法错误,do while条件使用的表达式规定必须是在循环前定义!
练习5.19:编写一段程序,使用do while 循环重复地执行下述任务:首先提示用户输入两个string对象,然后挑出较短的那个并输出它。
#include <iostream> using namespace std; int main() { string s1, s2; char c; do { cout << "input two strings:"; cin >> s1 >> s2; if (s1.size() > s2.size()) cout << s1 << endl; else cout << s2 << ' ' << "More ? Enter 'y' or 'n' " << endl; cin >> c; } while (c != 'n'); return 0; }
练习5.20:编写一段程序,从标准输入中读取string对象的序列直到连续出现两个相同的单词或者所有单词都读完为止。使用while循环一次读取一个单词,当一个单词连续出现两次时使用break语句终止循环。输出连续重复出现的单词,或者输出一个消息说明没有任何单词是连续重复出现的。
#include <iostream> using namespace std; int main() { string s1, s2; if (cin >> s1) { while (cin >> s2) { if (s1 == s2) { cout << s2; break; } else { s1 = s2; } } } return 0; }
练习5.21:修改5.5.1节练习题的程序,使其找到的重复单词必须以大写字母开头。
#include <iostream> using namespace std; int main() { string s1, s2; if (cin >> s1) { while (cin >> s2) { if (s1 == s2 && isupper(s2[0])) { cout << s2 << endl; break; } else { s1 = s2; } } } return 0; }
练习5.22本节的最后一个例子跳回到 begin,其实使用循环能更好的完成该任务,重写这段代码,注意不再使用goto语句。
#include <iostream> using namespace std; int main() { int sz = get_size(); while (sz <= 0) sz = get_size(); return 0; }
练习5.23:编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果。
#include <iostream> using namespace std; int main() { int i1, i2; cin >> i1 >> i2; if (i2 == 0) throw "ERROR! Division by zero!"; cout << i1 / i2 << endl; return 0; }
练习5.24:修改你的程序,使得当第二个数是0时抛出异常。先不要设定catch子句,运行程序并真的为除数输入0,看看会发生什么?
程序终止。
练习5.25:修改上一题的程序,使用try语句块去捕获异常。catch子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行try语句块的内容。
#include <iostream> using namespace std; int main() { int i1, i2; while (cin >> i1 >> i2) { try { if (i2 == 0) throw string("ERROR! Division by zero!"); } catch (string s) { cout << s << endl; cout << "Try again? 'Y' or 'N'"; char c; cin >> c; if (!cin || c == 'n') break; else continue; } cout << i1 / i2 << endl; } return 0; }