C++ Primer(第4版)习题解答
习题1.1
查看所用的编译器文档,了解它所用的文件命名规范。编译并运行本节的main程序。
【解答】
一般而言,C++编译器要求待编译的程序保存在文件中。C++程序中一般涉及两类文件:头文件和源文件。大多数系统中,文件的名字由文件名和文件后缀(又称扩展名)组成。文件后缀通常表明文件的类型,如头文件的后缀可以是.h或.hpp等;源文件的后缀可以是.cc或.cpp等,具体的后缀与使用的编译器有关。通常可以通过编译器所提供的联机帮助文档了解其文件命名规范。
习题1.2
修改程序使其返回-1。返回值-1通常作为程序运行失败的指示器。然而,系统不同,如何(甚至是否)报告main函数运行失败也不同。重新编译并再次运行程序,看看你的系统如何处理main函数的运行失败指示器。
【解答】
笔者所使用的Windows操作系统并不报告main函数的运行失败,因此,程序返回-1或返回0在运行效果上没有什么区别。但是,如果在DOS命令提示符方式下运行程序,然后再键入echo %ERRORLEVEL%命令,则系统会显示返回值-1。
习题1.3
编一个程序,在标准输出上打印“Hello, World”。
【解答】
#include<iostream>
int main()
{
std::cout << "Hello, World" << std::endl;
return 0;
}
习题1.4
我们的程序利用内置的加法操作符“+”来产生两个数的和。编写程序,使用乘法操作符“*”产生两个数的积。
【解答】
#include <iostream>
int main()
{
std::cout << "Enter two numbers:" << std::endl;
int v1, v2;
std::cin >> v1 >> v2;
std::cout << "The product of " << v1 << " and " << v2
<< " is " << v1 * v2 << std::endl;
return 0;
}
习题1.5
我们的程序使用了一条较长的输出语句。重写程序,使用单独的语句打印每一个操作数。
【解答】
#include <iostream>
int main()
{
std::cout << "Enter two numbers:" << std::endl;
int v1, v2;
std::cin >> v1 >> v2;
std::cout << "The sum of ";
std::cout << v1;
std::cout << " and ";
std::cout << v2;
std::cout << " is ";
std::cout << v1 + v2 ;
std::cout << std::endl;
return 0;
}
习题1.6
解释下面的程序段:
std::cout << "The sum of " << v1;
<< " and " << v2;
<< " is " << v1 + v2
<< std::endl;
这段代码合法吗?如果合法,为什么?如果不合法,又为什么?
【解答】
这段代码不合法。
注意,第1、2、4行的末尾有分号,表示这段代码包含三条语句,即第1、2行各为一个语句,第3、4行构成一个语句。“<<”为二元操作符,在第2、3两条语句中,第一个“<<”缺少左操作数,因此不合法。
在第2、3行的开头加上“std::cout”,即可更正。
习题1.7
编译有不正确嵌套注释的程序。
【解答】
由注释对嵌套导致的编译器错误信息通常令人迷惑。例如,在笔者所用的编译器中编译1.3节中给出的带有不正确嵌套注释的程序:
#include <iostream>
/*
* comment pairs /* */ cannot nest.
* "cannot nest" is considered source code,
* as is the rest of the program
*/
int main()
{
return 0;
}
编译器会给出如下错误信息:
error C2143: syntax error : missing ’;’ before ’<’
error C2501: ’include’ : missing storage-class or type specifiers
warning C4138: ’*/’ found outside of comment (第6行)
error C2143: syntax error : missing ’;’ before ’{’ (第8行)
error C2447: ’{’ : missing function header (old-style formal list?)(第8行)
习题1.8
指出下列输出语句哪些(如果有)是合法的。
std::cout << "/*";
std::cout << "*/";
std::cout << /* "*/" */;
预测结果,然后编译包含上述三条语句的程序,检查你的答案。纠正所遇到的错误。
【解答】
第一条和第二条语句合法。
第三条语句中<<操作符之后至第二个双引号之前的部分被注释掉了,导致<<操作符的右操作数不是一个完整的字符串,所以不合法。在分号之前加上一个双引号即可更正。
习题1.9
下列循环做什么? sum的最终值是多少?
int sum = 0;
for (int i = -100; i <= 100; ++i)
sum += i;
【解答】
该循环求-100~100之间所有整数的和(包括-100和100)。
sum的最终值是0。
习题1.10
用for循环编程,求从50~100的所有自然数的和。然后用while循环重写该程序。
【解答】
用for循环编写的程序如下:
#include <iostream>
int main()
{
int sum = 0;
for (int i = 50; i <= 100; ++i)
sum += i;
std::cout << "Sum of 50 to 100 inclusive is "
<< sum << std::endl;
return 0;
}
用while循环编写的程序如下:
#include <iostream>
int main()
{
int sum = 0, int i = 50;
while (i <= 100) {
sum += i;
++i;
}
std::cout << "Sum of 50 to 100 inclusive is "
<< sum << std::endl;
return 0;
}
习题1.11
用while循环编程,输出10~0递减的自然数。然后用for循环重写该程序。
【解答】
用while循环编写的程序如下:
#include <iostream>
int main()
{
int i = 10;
while (i >= 0) {
std::cout << i << " ";
--i;
}
return 0;
}
用for循环编写的程序如下:
#include <iostream>
int main()
{
for (int i = 10; i >= 0; --i)
std::cout << i << " ";
return 0;
}
习题1.12
对比前面两个习题中所写的循环。两种形式各有何优缺点?
【解答】
在for循环中,循环控制变量的初始化和修改都放在语句头部分,形式较简洁,且特别适用于循环次数已知的情况。在while循环中,循环控制变量的初始化一般放在while语句之前,循环控制变量的修改一般放在循环体中,形式上不如for语句简洁,但它比较适用于循环次数不易预知的情况(用某一条件控制循环)。两种形式各有优点,但它们在功能上是等价的,可以相互转换。
习题1.13
编译器不同,理解其诊断内容的难易程度也不同。编写一些程序,包含本小节“再谈编译”部分讨论的那些常见错误。研究编译器产生的信息,这样你在编译更复杂的程序遇到这些信息时不会陌生。
【解答】
对于程序中出现的错误,编译器通常会给出简略的提示信息,包括错误出现的文件及代码行、错误代码、错误性质的描述。如果要获得关于该错误的详细信息,一般可以根据编译器给出的错误代码在其联机帮助文档中查找。
习题1.14
如果输入值相等,本节展示的程序将产生什么问题?
【解答】
sum的值即为输入值。因为输入的v1和v2值相等(假设为x),所以lower和upper相等,均为x。for循环中的循环变量val初始化为lower,从而val<=upper为真,循环体执行一次,sum的值为val(即输入值x);然后val加1,val的值就大于upper,循环执行结束。
习题1.15
用两个相等的值作为输入编译并运行本节中的程序。将实际输出与你在习题1.14中所做的预测相比较,解释实际结果和你预计的结果间的不相符之处。
【解答】
运行1.4.3节中给出的程序,输入两个相等的值(例如3,3),则程序输出为:
Sum of 3 to 3 inclusive is 3
与习题1.14中给出的预测一致。
习题1.16
编写程序,输出用户输入的两个数中的较大者。
【解答】
#include <iostream>
int main()
{
std::cout << "Enter two numbers:" << std::endl;
int v1, v2;
std::cin >> v1 >> v2; // 读入数据
if (v1 >= v2)
std::cout << "The bigger number is" << v1 << std::endl;
else
std::cout << "The bigger number is" << v2 << std::endl;
return 0;
}
习题1.17
编写程序,要求用户输入一组数。输出信息说明其中有多少个负数。
【解答】
#include <iostream>
int main()
{
int amount = 0, value;
// 读入数据直到遇见文件结束符,计算所读入的负数的个数
while (std::cin >> value)
if (value <= 0)
++amount;
std::cout << "Amount of all negative values read is"
<< amount << std::endl;
return 0;
}
习题1.18
编写程序,提示用户输入两个数并将这两个数范围内的每个数写到标准输出。
【解答】
#include <iostream>
int main()
{
std::cout << "Enter two numbers:" << std::endl;
int v1, v2;
std::cin >> v1 >> v2; // 读入两个数
// 用较小的数作为下界lower、较大的数作为上界upper
int lower, upper;
if (v1 <= v2) {
lower = v1;
upper = v2;
} else {
lower = v2;
upper = v1;
}
// 输出从lower到upper之间的值
std::cout << "Values of " << lower << "to "
<< upper << "inclusive are: " << std::endl;
for (int val = lower; val <= upper; ++val)
std::cout << val << " ";
return 0;
}
习题1.19
如果上题给定数1000和2000,程序将产生什么结果?修改程序,使每一行输出不超过10个数。
【解答】
所有数的输出连在一起,不便于阅读。
程序修改如下:
#include <iostream>
int main()
{
std::cout << "Enter two numbers:" << std::endl;
int v1, v2;
std::cin >> v1 >> v2; // 读入两个数
// 用较小的数作为下界lower、较大的数作为上界upper
int lower, upper;
if (v1 <= v2) {
lower = v1;
upper = v2;
} else {
lower = v2;
upper = v1;
}
// 输出从lower到upper之间的值
std::cout << "Values of " << lower << "to "
<< upper << "inclusive are: " << std::endl;
for (int val = lower, count=1; val <= upper; ++val, ++count) {
std::cout << val << " ";
if (count % 10 == 0) //每行输出10个值
std::cout << std::endl;
}
return 0;
}
粗黑体部分为主要的修改:用变量count记录已输出的数的个数;若count的值为10的整数倍,则输出一个换行符。
习题1.20
编写程序,求用户指定范围内的数的和,省略设置上界和下界的if测试。假定输入数是7和3,按照这个顺序,预测程序运行结果。然后按照给定的数是7和3运行程序,看结果是否与你预测的相符。如果不相符,反复研究关于for和while循环的讨论直到弄清楚其中的原因。
【解答】
可编写程序如下:
// 1-20.cpp
// 省略设置上界和下界的if测试,求用户指定范围内的数的和
#include <iostream>
int main()
{
std::cout << "Enter two numbers:" << std::endl;
int v1, v2;
std::cin >> v1 >> v2; // 读入数据
int sum = 0;
// 求和
for (int val = v1; val <= v2; ++val)
sum += val; // sum = sum + val
std::cout << "Sum of " << v1
<< " to " << v2
<< " inclusive is "
<< sum << std::endl;
return 0;
}
如果输入数据为7和3,则v1值为7,v2值为3。for语句头中将val的初始值设为7,第一次测试表达式val <= v2时,该表达式的值为false,for语句的循环体一次也不执行,所以求和结果sum为0。
习题1.21
本书配套网站(http://www.awprofessional.com/cpp_primer)的第1章的代码目录下有Sales_ item.h源文件。复制该文件到你的工作目录。编写程序,循环遍历一组书的销售交易,读入每笔交易并将交易写至标准输出。
【解答】
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item book;
// 读入ISBN,售出书的本数,销售价格
std::cout << "Enter transactions:" << std::endl;
while (std::cin >> book) {
// 输出ISBN,售出书的本数,总收入,平均价格
std::cout << "ISBN, number of copies sold, "
<< "total revenue, and average price are:"
<< std::endl;
std::cout << book << std::endl;
}
return 0;
}
习题1.22
编写程序,读入两个具有相同ISBN的Sales_item对象并产生它们的和。
【解答】
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item trans1, trans2;
// 读入交易
std::cout << "Enter two transactions:" << std::endl;
std::cin >> trans1 >> trans2;
if (trans1.same_isbn(trans2))
std::cout << "The total information: " << std::endl
<< "ISBN, number of copies sold, "
<< "total revenue, and average price are:"
<< std::endl << trans1 + trans2;
else
std::cout << "The two transactions have different ISBN."
<< std::endl;
return 0;
}
习题1.23
编写程序,读入几个具有相同ISBN的交易,输出所有读入交易的和。
【解答】
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item total, trans;
// 读入交易
std::cout << "Enter transactions:" << std::endl;
if (std::cin >> total) {
while (std::cin >> trans)
if (total.same_isbn(trans)) // ISBN相同
total = total + trans;
else { // ISBN不同
std::cout << "Different ISBN." << std::endl;
return ?1;
}
// 输出交易之和
std::cout << "The total information: " << std::endl
<< "ISBN, number of copies sold, "
<< "total revenue, and average price are:"
<< std::endl << total;
}
else {
std::cout << "No data?!" << std::endl;
return ?1;
}
return 0;
}
习题1.24
编写程序,读入几笔不同的交易。对于每笔新读入的交易,要确定它的ISBN是否和以前的交易的ISBN一样,并且记下每一个ISBN的交易的总数。通过给定多笔不同的交易来测试程序。这些交易必须代表多个不同的ISBN,但是每个ISBN的记录应分在同一组。
【解答】
#include <iostream>
#include "Sales_item.h"
int main()
{
// 声明变量以保存交易记录以及具有相同ISBN的交易的数目
Sales_item trans1, trans2;
int amount;
// 读入交易
std::cout << "Enter transactions:" << std::endl;
std::cin >> trans1;
amount=1;
while (std::cin >> trans2)
if (trans1.same_isbn(trans2))// ISBN相同
++amount;
else {
// ISBN不同
std::cout << "Transaction amount of previous ISBN: "
<< amount << std::endl;
trans1 = trans2;
amount=1;
}
// 输出最后一个ISBN的交易数目
std::cout << "Transaction amount of the last ISBN: "
<< amount << std::endl;
return 0;
}
习题1.25
使用源自本书配套网站的Sales_item.h头文件,编译并执行1.6节给出的书店程序。
【解答】
可从C++ Primer(第4版)的配套网站(http://www.awprofessional.com/cpp_primer)下载头文件Sales_item.h,然后使用该头文件编译并执行1.6节给出的书店程序。
习题1.26
在书店程序中,我们使用了加法操作符而不是复合赋值操作符将trans加到total中,为什么我们不使用复合赋值操作符?
【解答】
因为在1.5.1节中提及的Sales_item对象上的操作中只包含了+和=,没有包含+=操作。(但事实上,使用Sales_item.h文件,已经可以用+=操作符取代=和+操作符的复合使用。)
习题2.1
int、long和short类型之间有什么差别?
【解答】
它们的最小存储空间不同,分别为16位、32位和16位。一般而言,short类型为半个机器字(word)长,int类型为一个机器字长,而long类型为一个或两个机器字长(在32位机器中,int类型和long类型的字长通常是相同的)。因此,它们的表示范围不同。
习题2.2
unsigned和signed类型有什么差别?
【解答】
前者为无符号类型,只能表示大于或等于0的数。后者为带符号类型,可以表示正数、负数和0。
习题2.3
如果在某机器上short类型占16位,那么可以赋给short类型的最大数是什么?unsigned short类型的最大数又是什么?
【解答】
若在某机器上short类型占16位,那么可以赋给short类型的最大数是215-1,即32767;而unsigned short类型的最大数为216-1,即65535。
习题2.4
当给16位的unsigned short对象赋值100000时,赋的值是什么?
【解答】
34464。
100000超过了16位的unsigned short类型的表示范围,编译器对其二进制表示截取低16位,相当于对65536求余(求模,%),得34464。
习题2.5
float类型和double类型有什么差别?
【解答】
二者的存储位数不同(一般而言,float类型为32个二进制位,double类型为64个二进制位),因而取值范围不同,精度也不同(float类型只能保证6位有效数字,而double类型至少能保证10位有效数字)。
习题2.6
要计算抵押贷款的偿还金额,利率、本金和付款额应分别选用哪种类型?解释你选择的理由。
【解答】
利率可以选择float类型,因为利率通常为百分之几。一般只保留到小数点后两位,所以6位有效数字就足以表示了。
本金可以选择long类型,因为本金通常为整数。long类型可表示的最大整数一般为231-1(即2147483647),应该足以表示了。
付款额一般为实数,可以选择double类型,因为float类型的6位有效数字可能不足以表示。
习题2.7
解释下列字面值常量的不同之处。
(a) ’a’,L’a’,"a",L"a"
(b) 10,10u,10L,10uL,012,0xC
(c) 3.14,3.14f,3.14L
【解答】
(a) ’a’,L’a’,"a",L"a"
’a’为char型字面值,L’a’为wchar_t型字面值,"a"为字符串字面值,L"a"为宽字符串字面值。
(b) 10,10u,10L,10uL,012,0xC
10为int型字面值,10u为unsigned型字面值,10L为long型字面值,10uL为unsigned long型字面值,012为八进制表示的int型字面值,0xC为十六进制表示的int型字面值。
(c) 3.14,3.14f,3.14L
3.14为double型字面值,3.14f为float型字面值,3.14L为long double型字面值。
习题2.8
确定下列字面值常量的类型:
(a) ?10 (b) -10u (c) -10. (d) -10e-2
【解答】
(a) int型
(b) unsigned int型
(c) double型
(d) double型
习题2.9
下列哪些(如果有)是非法的?
(a) "Who goes with F\145rgus?\012"
(b) 3.14e1L (c) "two" L"some"
(d) 1024f (e) 3.14UL
(f) "multiple line
comment"
【解答】
(c) 非法。因为字符串字面值与宽字符串字面值的连接是未定义的。
(d) 非法。因为整数1024后面不能带后缀f。
(e) 非法。因为浮点字面值不能带后缀U。
(f) 非法。因为分两行书写的字符串字面值必须在第一行的末尾加上反斜线。
习题2.10
使用转义字符编写一段程序,输出2M,然后换行。修改程序,输出2,跟着一个制表符,然后是M,最后是换行符。
【解答】
输出2M、然后换行的程序段:
// 输出"2M"和换行字符
std::cout << "2M" << ’\n’;
修改后的程序段:
// 输出’2’, ’\t’, ’M’和换行字符
std::cout << ’2’ << ’\t’ << ’M’ << ’\n’;
习题2.11
编写程序,要求用户输入两个数??底数(base)和指数(exponent),输出底数的指数次方的结果。
【解答】
#include <iostream>
int main()
{
// 局部对象
int base, exponent;
long result=1;
// 读入底数(base)和指数(exponent)
std::cout << "Enter base and exponent:" << std::endl;
std::cin >> base >> exponent;
if (exponent < 0) {
std::cout << "Exponent can’t be smaller than 0" << std::endl;
return -1;
}
if (exponent > 0) {
// 计算底数的指数次方
for (int cnt = 1; cnt <= exponent; ++cnt)
result *= base;
}
std::cout << base
<< " raised to the power of "
<< exponent << ": "
<< result << std::endl;
return 0;
}
习题2.12
区分左值和右值,并举例说明。
【解答】
左值(lvalue)就是变量的地址,或者是一个代表“对象在内存中的位置”的表达式。
右值(rvalue)就是变量的值,见2.3.1节。
变量名出现在赋值运算符的左边,就是一个左值;而出现在赋值运算符右边的变量名或字面常量就是一个右值。
例如:
val1=val2/8
这里的val1是个左值,而val2和8都是右值。
习题2.13
举出一个需要左值的例子。
【解答】
赋值运算符的左边(被赋值的对象)需要左值,见习题2.12。
习题2.14
下面哪些(如果有)名字是非法的?更正每个非法的标识符名字。
(a) int double = 3.14159; (b) char _;
(c) bool catch-22; (d) char 1_or_2 =’1’;
(e) float Float = 3.14f;
【解答】
(a) double是C++语言中的关键字,不能用作用户标识符,所以非法。此语句可改为:double dval = 3.14159;。
(c) 名字catch-22中包含在字母、数字和下划线之外的字符“-”,所以非法。可将其改为:catch_22;。
(d) 名字1_or_2非法,因为标识符必须以字母或下划线开头,不能以数字开头。可将其改为:one_or_two;。
习题2.15
下面两个定义是否不同?有何不同?
int month = 9, day = 7;
int month =09, day = 07;
如果上述定义有错的话,那么应该怎样改正呢?
【解答】
这两个定义不同。前者定义了两个int型变量,初值分别为9和7;后者也定义了两个int型变量,其中day被初始化为八进制值7;而month的初始化有错:试图将month初始化为八进制值09,但八进制数字范围为0~7,所以出错。可将第二个定义改为:
int month =011, day = 07;
习题2.16
假设calc是一个返回double对象的函数。下面哪些是非法定义?改正所有的非法定义。
(a) int car = 1024, auto = 2048;
(b) int ival = ival;
(c) std::cin >> int input_value;
(d) double salary = wage = 9999.99;
(e) double calc = calc();
【解答】
(a) 非法:auto是关键字,不能用作变量名。使用另一变量名,如aut即可更正。
(c) 非法:>>运算符后面不能进行变量定义。改为:
int input_value;
std::cin >> input_value;
(d) 非法:同一定义语句中不同变量的初始化应分别进行。改为:
double salary = 9999.99, wage = 9999.99;
注意,(b)虽然语法上没有错误,但这个初始化没有实际意义,ival仍是未初始化的。
习题2.17
下列变量的初始值(如果有)是什么?
std::string global_str;
int global_int;
int main()
{
int local_int;
std::string local_str;
// ...
return 0;
}
【解答】
global_str和local_str的初始值均为空字符串,global_int的初始值为0,local_int没有初始值。
习题2.18
解释下列例子中name的意义:
extern std::string name;
std::string name("exercise 3.5a");
extern std::string name("exercise 3.5a");
【解答】
第一条语句是一个声明,说明std::string变量name在程序的其他地方定义。
第二条语句是一个定义,定义了std::string变量name,并将name初始化为"exercise 3.5a"。
第三条语句也是一个定义,定义了std::string变量name,并将name初始化为"exercise 3.5a",但这个语句只能出现在函数外部(即,name是一个全局变量)。
习题2.19
下列程序中j的值是多少?
int i = 42;
int main()
{
int i = 100;
int j = i;
// ...
}
【解答】
j的值是100。j的赋值所使用到的i应该是main函数中定义的局部变量i,因为局部变量的定义会屏蔽全局变量的定义。
习题2.20
下列程序段将会输出什么?
int i = 100, sum = 0;
for (int i = 0; i != 10; ++i)
sum += i;
std::cout << i << " " << sum << std::endl;
【解答】
输出为:
100 45
for语句中定义的变量i,其作用域仅限于for语句内部。输出的i值是for语句之前所定义的变量i的值。
习题2.21
下列程序合法吗?
int sum = 0;
for (int i = 0; i != 10; ++i)
sum += i;
std::cout << "Sum from 0 to " << i
<< " is " << sum << std::endl;
【解答】
不合法。因为变量i具有语句作用域,只能在for语句中使用,输出语句中使用i属非法。
习题2.22
下列程序段虽然合法,但是风格很糟糕。有什么问题呢?怎样改善?
for (int i = 0; i < 100; ++i)
// process i
【解答】
问题主要在于使用了具体值100作为循环上界:100的意义在上下文中没有体现出来,导致程序的可读性差;若100这个值在程序中出现多次,则当程序的需求发生变化(如将100改变为200)时,对程序代码的修改复杂且易出错,导致程序的可维护性差。
改善方法:设置一个const变量(常量)取代100作为循环上界使用,并为该变量选择有意义的名字。
习题2.23
下列哪些语句合法?对于那些不合法的使用,解释原因。
(a) const int buf;
(b) int cnt = 0;
const int sz = cnt;
(c) cnt++; sz++;
【解答】
(a) 不合法。因为定义const变量(常量)时必须进行初始化,而buf没有初始化。
(b) 合法。
(c) 不合法。因为修改了const变量sz的值。
习题2.24
下列哪些定义是非法的?为什么?如何改正?
(a) int ival = 1.01; (b) int &rval1 = 1.01;
(c) int &rval2 = ival; (d) const int &rval3 = 1;
【解答】
(b)非法。
因为rval1是一个非const引用,非const引用不能绑定到右值,而1.01是一个右值。可改正为:
int &rval1 = ival;
(假设ival是一个已定义的int变量)。
习题2.25
在习题2.24给出的定义下,下列哪些赋值是非法的?如果赋值合法,解释赋值的作用。
(a) rval2 = 3.14159; (b) rval2 = rval3;
(c) ival = rval3; (d) rval3 = ival;
【解答】
(d)非法。因为rval3是一个const引用,不能进行赋值。
合法赋值的作用:
(a)将一个double型字面值赋给int型变量ival,发生隐式类型转换,ival得到的值为3。
(b)将int值1赋给变量ival。
(c)将int值1赋给变量ival。
习题2.26
(a)中的定义和(b)中的赋值存在哪些不同?哪些是非法的?
(a) int ival = 0; (b) ival = ri;
const int &ri = 0; ri = ival;
【解答】
int ival = 0; 定义ival为int变量,并将其初始化为0。
const int &ri = 0; 定义ri为const引用,并将其绑定到右值0。
ival = ri; 将0值赋给ival。
ri = ival; 试图对ri赋值,这是非法的,因为ri是const引用,不能赋值。
习题2.27
下列代码输出什么?
int i, &ri = i;
i = 5; ri =10;
std::cout << i << " " << ri << std::endl;
【解答】
输出:
10 10
ri是i的引用,对ri进行赋值,实际上相当于对i进行赋值,所以输出i和ri的值均为10。
习题2.28
编译以下程序,确定你的编译器是否会警告遗漏了类定义后面的分号。
class Foo {
// empty
} // Note: no semicolon
int main()
{
return 0;
}
如果编译器的诊断结果难以理解,记住这些信息以备后用。
【解答】
在笔者所用的编译器中编译上述程序,编译器会给出如下错误信息:
error C2628: ’Foo’ followed by ’int’ is illegal (did you forget a ’;’?) (第4行)
warning C4326: return type of ’main’ should be ’int or void’ instead of ’Foo’ (第5行)
error C2440: ’return’ : cannot convert from ’int’ to ’Foo’ (第6行)
也就是说,该编译器会对遗漏了类定义后面的分号给出提示。
习题2.29
区分类中的public部分和private部分。
【解答】
类中public部分定义的成员在程序的任何部分都可以访问。通常在public部分放置操作,以便程序中的其他部分可以执行这些操作。
类中private部分定义的成员只能被作为类的组成部分的代码(以及该类的友元)访问。通常在private部分放置数据,以对对象的内部数据进行隐藏。
习题2.30
定义表示下列类型的类的数据成员:
(a) 电话号码 (b) 地址
(c) 员工或公司 (d) 某大学的学生
【解答】
(a) 电话号码
class Tel_number {
public:
//...对象上的操作
private:
std::string country_number;
std::string city_number;
std::string phone_number;
};
(b) 地址
class Address {
public:
//...对象上的操作
private:
std::string country;
std::string city;
std::string street;
std::string number;
};
(c) 员工或公司
class Employee {
public:
// ...对象上的操作
private:
std::string ID;
std::string name;
char sex;
Address addr;
Tel_number tel;
};
class Company {
public:
// ...对象上的操作
private:
std::string name;
Address addr;
Tel_number tel;
};
(d) 某大学的学生
class Student {
public:
// ...对象上的操作
private:
std::string ID;
std::string name;
char sex;
std::string dept; // 所在系
std::string major;
Address home_addr;
Tel_number tel;
};
注意,在不同的具体应用中,类的设计会有所不同,这里给出的只是一般性的简单例子。
习题2.31
判别下列语句哪些是声明,哪些是定义,请解释原因。
(a) extern int ix = 1024 ;
(b) int iy ;
(c) extern int iz ;
(d) extern const int &ri ;
【解答】
(a)是定义,因为extern声明进行了初始化。
(b)是定义,变量定义的常规形式。
(c)是声明,extern声明的常规形式。
(d)是声明,声明了一个const引用。
习题2.32
下列声明和定义哪些应该放在头文件中?哪些应该放在源文件中?请解释原因。
(a) int var ;
(b) const double pi = 3.1416;
(c) extern int total = 255 ;
(d) const double sq2 = squt (2.0) ;
【解答】
(a)、(c)、(d)应放在源文件中,因为(a)和(c)是变量定义,定义通常应放在源文件中。(d)中的const变量sq2不是用常量表达式初始化的,所以也应该放在源文件中。
(b)中的const变量pi是用常量表达式初始化的,应该放在头文件中。
参见2.9.1节。
习题2.33
确定你的编译器提供了哪些提高警告级别的选项。使用这些选项重新编译以前选择的程序,查看是否会报告新的问题。
【解答】
在笔者所用的编译器(Microsoft Visual C++ .NET 2003)中,在Project菜单中选择Properties菜单项,在Configuration Properties→C/C++→General→Warning Level中可以选择警告级别。
习题3.1
用适当的using声明,而不用std::前缀,访问标准库中的名字,重新编写2.3节的程序,计算一给定数的给定次幂的结果。
【解答】
#include <iostream>
using std::cin;
using std::cout;
int main()
{
// 局部对象
int base, exponent;
long result=1;
// 读入底数和指数
cout << "Enter base and exponent:" << endl;
cin >> base >> exponent;
if (exponent < 0) {
cout << "Exponent can’t be smaller than 0" << endl;
return -1;
}
if (exponent > 0) {
// 计算底数的指数次方
for (int cnt = 1; cnt <= exponent; ++cnt)
result *= base;
}
cout << base
<< " raised to the power of "
<< exponent << ": "
<< result << endl;
return 0;
}
习题3.2
什么是默认构造函数?
【解答】
默认构造函数(default constructor)就是在没有显式提供初始化式时调用的构造函数。它由不带参数的构造函数,或者为所有形参提供默认实参的构造函数定义。如果定义某个类的变量时没有提供初始化式,就会使用默认构造函数。
如果用户定义的类中没有显式定义任何构造函数,编译器就会自动为该类生成默认构造函数,称为合成的默认构造函数(synthesized default constructor)。
习题3.3
列举出三种初始化string对象的方法。
【解答】
(1) 不带初始化式,使用默认构造函数初始化string对象。
(2) 使用一个已存在的string对象作为初始化式,将新创建的string对象初始化为已存在对象的副本。
(3) 使用字符串字面值作为初始化式,将新创建的string对象初始化为字符串字面值的副本。
习题3.4
s和s2的值分别是什么?
string s;
int main() {
string s2;
}
【解答】
s和s2的值均为空字符串。
习题3.5
编写程序实现从标准输入每次读入一行文本。然后改写程序,每次读入一个单词。
【解答】
//从标准输入每次读入一行文本
#include <iostream>
#include <string>
using namespace std;
int main()
{
string line;
// 一次读入一行,直至遇见文件结束符
while (getline(cin, line))
cout << line << endl; // 输出相应行以进行验证
return 0;
}
修改后程序如下:
//从标准输入每次读入一个单词
#include <iostream>
#include <string>
using namespace std;
int main()
{
string word;
// 一次读入一个单词,直至遇见文件结束符
while (cin >> word)
cout << word << endl; // 输出相应单词以进行验证
return 0;
}
注意,一般而言,应该尽量避免使用using指示而使用using声明(参见17.2.4节),因为如果应用程序中使用了多个库,使用using指示引入这些库中定义的名字空间,容易导致名字冲突。但本书中的程序都只使用了标准库,没有使用其他库。使用using指示引入名字空间std中定义的所有名字不会发生名字冲突。因此为了使得代码更为简洁以节省篇幅,本书的许多代码中都使用了using指示using namespace std;来引入名字空间std。另外,本题中并未要求输出,加入输出是为了更清楚地表示读入的结果。本书后面部分有些地方与此类似处理,不再赘述。
习题3.6
解释string类型的输入操作符和getline函数分别如何处理空白字符。
【解答】
string类型的输入操作符对空白字符的处理:读取并忽略有效字符(非空白字符)之前所有的空白字符,然后读取字符直至再次遇到空白字符,读取终止(该空白字符仍留在输入流中)。
getline函数对空白字符的处理:不忽略行开头的空白字符,读取字符直至遇到换行符,读取终止并丢弃换行符(换行符从输入流中去掉但并不存储在string对象中)。
习题3.7
编一个程序读入两个string对象,测试它们是否相等。若不相等,则指出两个中哪个较大。接着,改写程序测试它们的长度是否相等,若不相等,则指出两个中哪个较长。
【解答】
测试两个string对象是否相等的程序:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1, s2;
// 读入两个string对象
cout << "Enter two strings:" << endl;
cin >> s1 >> s2;
// 测试两个string对象是否相等
if (s1 == s2)
cout << "They are equal." << endl;
else if (s1 > s2)
cout << "\"" << s1 << "\" is bigger than"
<< " \"" << s2 << "\"" << endl;
else
cout << "\"" << s2 << "\" is bigger than"
<< " \"" << s1 << "\"" << endl;
return 0;
}
测试两个string对象的长度是否相等的程序:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s1, s2;
// 读入两个string对象
cout << "Enter two strings:" << endl;
cin >> s1 >> s2;
// 比较两个string对象的长度
string::size_type len1, len2;
len1 = s1.size();
len2 = s2.size();
if (len1 == len2)
cout << "They have same length." << endl;
else if (len1 > len2)
cout << "\"" << s1 << "\" is longer than"
<< " \"" << s2 << "\"" << endl;
else
cout << "\"" << s2 << "\" is longer than"
<< " \"" << s1 << "\"" << endl;
return 0;
}
习题3.8
编一个程序,从标准输入读取多个string对象,把它们连接起来存放到一个更大的string对象中,并输出连接后的string对象。接着,改写程序,将连接后相邻string对象以空格隔开。
【解答】
#include <iostream>
#include <string>
using namespace std;
int main()
{
string result_str, str;
// 读入多个string对象并进行连接
cout << "Enter strings(Ctrl+Z to end):" << endl;
while (cin>>str)
result_str = result_str + str;
// 输出连接后的string对象
cout << "String equal to the concatenation of these strings is:"
<< endl << result_str << endl;
return 0;
}
改写后的程序:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string result_str, str;
// 读入多个string对象并进行连接
cout << "Enter strings(Ctrl+Z to end):" << endl;
cin >> result_str;//读入第一个string对象,放到结果对象中
while (cin>>str)
result_str = result_str + ’ ’ + str;
// 输出连接后的string对象
cout << "String equal to the concatenation of these strings is:"
<< endl << result_str << endl;
return 0;
}
习题3.9
下列程序实现什么功能?实现合法吗?如果不合法,说明理由。
string s;
cout << s[0] << endl;
【解答】
该程序段输出string对象s所对应字符串的第一个字符。
实现不合法。因为s是一个空字符串,其长度为0,因此s[0]是无效的。
注意,在一些编译器(如Microsoft Visual C++ .NET 2003)的实现中,该程序段并不出现编译错误。
习题3.10
编一个程序,从string 对象中去掉标点符号。要求输入到程序的字符串必须含有标点符号,输出结果则是去掉标点符号后的string对象。
【解答】
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main()
{
string s, result_str;
bool has_punct = false;//用于标记字符串中有无标点
char ch;
//输入字符串
cout << "Enter a string:" << endl;
getline(cin, s);
//处理字符串:去掉其中的标点
for (string::size_type index = 0; index != s.size(); ++index)
{
ch = s[index];
if (ispunct(ch))
has_punct = true;
else
result_str += ch;
}
if (has_punct)
cout << "Result:" << endl << result_str <<endl;
else {
cout << "No punctuation character in the string?!" << endl;
return -1;
}
return 0;
}
习题3.11
下面哪些vector定义不正确?
(a) vector< vector<int> > ivec;
(b) vector<string> svec = ivec ;
(c) vector<string> svec(10,"null");
【解答】
(b)不正确。因为svec定义为保存string对象的vector对象,而ivec是保存vector <int>对象的vector对象(即ivec是vector的vector),二者的元素类型不同,所以不能用ivec来初始化svec。
习题3.12
下列每个vector对象中元素个数是多少?各元素的值是什么?
(a) vector<int> ivec1;
(b) vector<int> ivec2(10);
(c) vector<int> ivec3(10,42);
(d) vector<string> svec1;
(e) vector<string> svec2(10);
(f) vector<string> svec3(10,"hello");
【解答】
(a) 元素个数为0。
(b) 元素个数为10,各元素的值均为0。
(c) 元素个数为10,各元素的值均为42。
(d) 元素个数为0。
(e) 元素个数为10,各元素的值均为空字符串。
(f) 元素个数为10,各元素的值均为"hello"。
习题3.13
读一组整数到vector对象,计算并输出每对相邻元素的和。如果读入元素个数为奇数,则提示用户最后一个元素没有求和,并输出其值。然后修改程序:头尾元素两两配对(第一个和最后一个,第二个和倒数第二个,以此类推),计算每对元素的和,并输出。
【解答】
//读一组整数到vector对象,计算并输出每对相邻元素的和
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec;
int ival;
// 读入数据到vector对象
cout << "Enter numbers(Ctrl+Z to end):" << endl;
while (cin>>ival)
ivec.push_back(ival);
// 计算相邻元素的和并输出
if (ivec.size() == 0) {
cout << "No element?!" << endl;
return -1;
}
cout << "Sum of each pair of adjacent elements in the vector:"
<< endl;
for (vector<int>::size_type ix = 0; ix < ivec.size()-1;
ix = ix + 2) {
cout << ivec[ix] + ivec[ix+1] << "\t";
if ( (ix+1) % 6 == 0) // 每行输出6个和
cout << endl;
}
if (ivec.size() % 2 != 0) // 提示最后一个元素没有求和
cout << endl
<< "The last element is not been summed "
<< "and its value is "
<< ivec[ivec.size()-1] << endl;
return 0;
}
修改后的程序:
//读一组整数到vector对象,计算首尾配对元素的和并输出
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec;
int ival;
//读入数据到vector对象
cout << "Enter numbers:" << endl;
while (cin>>ival)
ivec.push_back(ival);
//计算首尾配对元素的和并输出
if (ivec.size() == 0) {
cout << "No element?!" << endl;
return -1;
}
cout << "Sum of each pair of counterpart elements in the vector:"
<< endl;
vector<int>::size_type cnt = 0;
for (vector<int>::size_type first = 0, last = ivec.size() - 1;
first < last; ++first, --last) {
cout << ivec[first] + ivec[last] << "\t";
++cnt;
if ( cnt % 6 == 0) //每行输出6个和
cout << endl;
}
if (first == last) //提示居中元素没有求和
cout << endl
<< "The center element is not been summed "
<< "and its value is "
<< ivec[first] << endl;
return 0;
}
习题3.14
读入一段文本到vector对象,每个单词存储为vector中的一个元素。把vector对象中每个单词转化为大写字母。输出vector对象中转化后的元素,每8个单词为一行输出。
【解答】
//读入一段文本到vector对象,每个单词存储为vector中的一个元素。
//把vector对象中每个单词转化为大写字母。
//输出vector对象中转化后的元素,每8个单词为一行输出
#include <iostream>
#include <string>
#include <vector>
#include <cctype>
using namespace std;
int main()
{
vector<string> svec;
string str;
// 读入文本到vector对象
cout << "Enter text(Ctrl+Z to end):" << endl;
while (cin>>str)
svec.push_back(str);
//将vector对象中每个单词转化为大写字母,并输出
if (svec.size() == 0) {
cout << "No string?!" << endl;
return -1;
}
cout << "Transformed elements from the vector:"
<< endl;
for (vector<string>::size_type ix = 0; ix != svec.size(); ++ix) {
for (string::size_type index = 0; index != svec[ix].size(); ++index)
if (islower(svec[ix][index]))
//单词中下标为index的字符为小写字母
svec[ix][index] = toupper(svec[ix][index]);
cout << svec[ix] << " ";
if ((ix + 1) % 8 == 0)//每8个单词为一行输出
cout << endl;
}
return 0;
}
习题3.15
下面程序合法吗?如果不合法,如何更正?
vector<int> ivec;
ivec[0] = 42;
【解答】
不合法。因为ivec是空的vector对象,其中不含任何元素,而下标操作只能用于获取已存在的元素。
更正:将赋值语句改为语句ivec.push_back(42);。
习题3.16
列出三种定义vector对象的方法,给定10个元素,每个元素值为42。指出是否还有更好的实现方法,并说明为什么。
【解答】
方法一:
vector<int> ivec(10, 42);
方法二:
vector<int> ivec(10);
for (ix = 0; ix < 10; ++ix)
ivec[ix] = 42;
方法三:
vector<int> ivec(10);
for (vector<int>::iterator iter = ivec.begin();
iter != ivec.end(); ++iter)
*iter = 42;
方法四:
vector<int> ivec;
for (cnt = 1; cnt <= 10; ++cnt)
ivec.push_back(42);
方法五:
vector<int> ivec;
vector<int>::iterator iter = ivec.end();
for (int i = 0; i != 10; ++i) {
ivec.insert(iter, 42);
iter = ivec.end();
}
各种方法都可达到目的,也许最后两种方法更好一些。它们使用标准库中定义的容器操作在容器中增添元素,无需在定义vector对象时指定容器的大小,比较灵活而且不容易出错。
习题3.17
重做3.3.2节的习题,用迭代器而不是下标操作来访问vector中的元素。
【解答】
重做习题3.13如下:
//读一组整数到vector对象,计算并输出每对相邻元素的和
//使用迭代器访问vector中的元素
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec;
int ival;
//读入数据到vector对象
cout << "Enter numbers(Ctrl+Z to end):" << endl;
while (cin>>ival)
ivec.push_back(ival);
//计算相邻元素的和并输出
if (ivec.size() == 0) {
cout << "No element?!" << endl;
return -1;
}
cout << "Sum of each pair of adjacent elements in the vector:"
<< endl;
vector<int>::size_type cnt = 0;
for (vector<int>::iterator iter = ivec.begin();
iter < ivec.end()-1;
iter = iter + 2) {
cout << *iter + *(iter+1) << "\t";
++cnt;
if ( cnt % 6 == 0) //每行输出6个和
cout << endl;
}
if (ivec.size() % 2 != 0) //提示最后一个元素没有求和
cout << endl
<< "The last element is not been summed "
<< "and its value is "
<< *(ivec.end()-1) << endl;
return 0;
}
//读一组整数到vector对象,计算首尾配对元素的和并输出
//使用迭代器访问vector中的元素
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec;
int ival;
//读入数据到vector对象
cout << "Enter numbers(Ctrl+Z to end):" << endl;
while (cin>>ival)
ivec.push_back(ival);
//计算首尾配对元素的和并输出
if (ivec.size() == 0) {
cout << "No element?!" << endl;
return -1;
}
cout << "Sum of each pair of counterpart elements in the vector:"
<< endl;
vector<int>::size_type cnt=0;
for (vector<int>::iterator first = ivec.begin(),
last = ivec.end() - 1;
first < last;
++first, --last) {
cout << *first + *last << "\t";
++cnt;
if ( cnt % 6 == 0) //每行输出6个和
cout << endl;
}
if (first == last) //提示居中元素没有求和
cout << endl
<< "The center element is not been summed "
<< "and its value is "
<< *first << endl;
return 0;
}
重做习题3.14如下:
//读入一段文本到vector对象,每个单词存储为vector中的一个元素。
//把vector对象中每个单词转化为大写字母。
//输出vector对象中转化后的元素,每8个单词为一行输出。
//使用迭代器访问vector中的元素
#include <iostream>
#include <string>
#include <vector>
#include <cctype>
using namespace std;
int main()
{
vector<string> svec;
string str;
//读入文本到vector对象
cout << "Enter text(Ctrl+Z to end):" << endl;
while (cin>>str)
svec.push_back(str);
//将vector对象中每个单词转化为大写字母,并输出
if (svec.size() == 0) {
cout << "No string?!" << endl;
return -1;
}
cout << "Transformed elements from the vector:"
<< endl;
vector<string>::size_type cnt = 0;
for (vector<string>::iterator iter = svec.begin();
iter != svec.end(); ++iter) {
for (string::size_type index = 0; index != (*iter).size();
++index)
if (islower((*iter)[index]))
//单词中下标为index的字符为小写字母
(*iter)[index] = toupper((*iter)[index]);
cout << *iter << " ";
++cnt;
if (cnt % 8 == 0)//每8个单词为一行输出
cout << endl;
}
return 0;
}
习题3.18
编写程序来创建有10个元素的vector对象。用迭代器把每个元素值改为当前值的2倍。
【解答】
//创建有10个元素的vector对象,
//然后使用迭代器将每个元素值改为当前值的2倍
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec(10, 20);//每个元素的值均为20
//将每个元素值改为当前值的2倍
for (vector<int>::iterator iter = ivec.begin();
iter != ivec.end(); ++iter)
*iter = (*iter)*2;
return 0;
}
习题3.19
验证习题3.18的程序,输出vector的所有元素。
【解答】
//创建有10个元素的vector对象,
//然后使用迭代器将每个元素值改为当前值的2倍并输出
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> ivec(10, 20);//每个元素的值均为20
//将每个元素值改为当前值的2倍并输出
for (vector<int>::iterator iter = ivec.begin();
iter != ivec.end(); ++iter) {
*iter = (*iter)*2;
cout << *iter << " ";
}
return 0;
}
习题3.20
解释一下在上几个习题的程序实现中你用了哪种迭代器,并说明原因。
【解答】
上述几个习题的程序实现中使用了类型分别为vector<int>::iterator和vector <string>::iterator的迭代器,通过这些迭代器分别访问元素类型为int和string的vector对象中的元素。
习题3.21
何时使用const迭代器?又在何时使用const_iterator?解释两者的区别。
【解答】
const迭代器是迭代器常量,该迭代器本身的值不能修改,即该迭代器在定义时需要初始化,而且初始化之后,不能再指向其他元素。若需要指向固定元素的迭代器,则可以使用const迭代器。
const_iterator是一种迭代器类型,对这种类型的迭代器解引用会得到一个指向const对象的引用,即通过这种迭代器访问到的对象是常量。该对象不能修改,因此,const_iterator类型只能用于读取容器内的元素,不能修改元素的值。若只需遍历容器中的元素而无需修改它们,则可以使用const_iterator。
习题3.22
如果采用下面的方法来计算mid会产生什么结果?
vector<int>::iterator mid = (vi.begin() + vi.end())/2;
【解答】
将两个迭代器相加的操作是未定义的,因此用这种方法计算mid会出现编译错误。
习题3.23
解释下面每个bitset对象包含的位模式:
(a) bitset<64> bitvec(32);
(b) bitset<32> bv(1010101);
(c) string bstr; cin >> bstr; bitset<8> bv(bstr);
【解答】
(a) bitvec有64个二进制位,(位编号从0开始)第5位置为1,其余位置均为0。
(b) bv有32个二进制位,(位编号从0开始)第0、2、4、5、7、8、11、13、14、16、17、18、19位置为1,其余位置均为0。因为十进制数1010101对应的二进制数为000000000000011110110100110110101。
(c) bv有8个二进制位,(位编号从0开始)用读入的字符串的从右至左的8个字符对bv的0~7位进行初始化。
习题3.24
考虑这样的序列1,2,3,5,8,13,21,并初始化一个将该序列数字所对应的位置设置为1的bitset<32>对象。然后换个方法,给定一个空的bitset对象,编写一小段程序把相应的数位设置为1。
【解答】
bitset<32>对象的初始化:
bitset<32> bv(0x20212e)
方法二:
bitset<32> bv;
int x = 0, y = 1, z;
z = x + y;
while (z <= 21) {
bv.set(z);
x = y;
y = z;
z = x + y;
}
注意,设置为1的数位的位编号符合斐波那契数列的规律。