《C++ Primer》第五版习题个人答案
练习2.1
类型 int、long、long long 和 short 的区别是什么?无符号类型和带符号类型的区别是什么?float 和 double的区别是什么?
int、long、long long和short是整数类型,它们的区别在于占用的存储空间和取值范围的大小。以下是各个类型的最小尺寸:short 和 int 至少16位,long 至少32位,long long 至少64位。float和double是浮点数类型,区别在于精度和取值范围的大小。float是单精度浮点数类型,通常占用4个字节(32位)的存储空间。double是双精度浮点数类型,通常占用8个字节(64位)的存储空间。
练习2.2
计算按揭贷款时,对于利率、本金和付款分别应选择何种数据类型?说明你的理由。
利率:利率通常是一个小数,例如3.5%可以表示为0.035。在计算利息时,我们需要进行乘法和除法运算,因此选择浮点数类型更合适。可以选择使用float或double类型来表示利率,具体选择哪种类型取决于所需的精度和范围; 本金:本金是贷款的原始金额,通常是一个整数或浮点数。在计算本金时,我们通常只需要进行加法和减法运算,因此选择整数类型或浮点数类型都可以。如果贷款金额较大,可以选择使用long long或double类型; 付款:付款是按揭贷款的每期还款金额,通常是一个固定的数值。付款金额可以是整数或浮点数,取决于贷款的具体情况。
练习2.3
读程序写结果。
1 2 3 4 5 6 7 8 unsigned u = 10 , u2 = 42 ;std::cout << u2 - u << std::endl; std::cout << u - u2 << std::endl; int i = 10 , i2 = 42 ;std::cout << i2 - i << std::endl; std::cout << i - i2 << std::endl; std::cout << i - u << std::endl; std::cout << u - i << std::endl;
输出:
1 2 3 4 5 6 32 4294967264 32 -32 0 0
练习2.4
编写程序检查你的估计是否正确,如果不正确,请仔细研读本节直到弄明白问题所在。
1 2 3 4 5 6 7 8 9 10 11 12 #include <iostream> int main () {unsigned u = 10 , u2 = 42 ;std::cout << u2 - u << std::endl; std::cout << u - u2 << std::endl; int i = 10 , i2 = 42 ;std::cout << i2 - i << std::endl; std::cout << i - i2 << std::endl; std::cout << i - u << std::endl; std::cout << u - i << std::endl; }
练习2.5
指出下述字面值的数据类型并说明每一组内几种字面值的区别:
(a) ‘a’, L’a’, “a”, L”a”
(b) 10, 10u, 10L, 10uL, 012, 0xC
(c) 3.14, 3.14f, 3.14L
(d) 10, 10u, 10., 10e-2
(a): 字符字面值,类型为char;宽字符字面值,类型为wchar_t;字符串字面值,类型为const char[];宽字符串字面值,类型为const wchar_t[]。
(b): 整型字面值,默认为int类型;无符号整型字面值,类型为unsigned int;长整型字面值,类型为long;无符号长整型字面值,类型为unsigned long;八进制整型字面值,类型为int;十六进制整型字面值,类型为int。
(c): 浮点型字面值,默认为double类型; 单精度浮点型字面值,类型为float;长双精度浮点型字面值,类型为long double。
(d): 整型字面值,默认为 int类型;无符号整型字面值,类型为unsigned int;浮点型字面值,默认为double类型;科学计数法表示的浮点型字面值,默认为double类型。
练习2.6
下面两组定义是否有区别,如果有,请叙述之:
1 2 int month = 9 , day = 7 ;int month = 09 , day = 07 ;
第一行定义的是十进制整型。 以0为开头的整数常量被解释为八进制数,但是八进制只包含数字0到7,所以09是非法的八进制数。
练习2.7
下述字面值表示何种含义?它们各自的数据类型是什么?
(a) “Who goes with F\145rgus?\012”
(b) 3.14e1L
(c) 1024f
(d) 3.14L
(a): Who goes with Fergus?(换行),string 类型
(b): long double
(c): 非法,整型后不可加f
(d): long double
练习2.8
请利用转义序列编写一段程序,要求先输出 2M,然后转到新一行。修改程序使其先输出 2,然后输出制表符,再输出 M,最后转到新一行。
1 2 3 4 5 6 7 #include <iostream> int main () { std::cout<<"2\115\n" ; std::cout<<"2\t\115\n" ; return 0 ; }
练习2.9
解释下列定义的含义,对于非法的定义,请说明错在何处并将其改正。
(a) std::cin >> int input_value;
(b) int i = { 3.14 };
(c) double salary = wage = 9999.99;
(d) int i = 3.14;
(a):非法,std::cin是输入流对象,>>运算符用于读取数据并存储到变量中,不能将变量直接输入,应该先初始化。
1 2 int input_value = 0 ;std::cin >> input_value;
(b): 合法,但会导致精度丢失。用列表初始化内置类型的变量时,如果存在丢失信息的风险,则编译器将报错。
(c): 非法,不能将未定义变量wage的值赋给变量salary。
1 2 double wage = 9999.99 ;double salary = wage;
(d): 合法,但会导致精度丢失。
练习2.10
下列变量的初值分别是什么?
1 2 3 4 5 6 7 std::string global_str; int global_int;int main () { int local_int; std::string local_str; }
在C++中,全局变量和静态变量会被默认初始化为它们的零值,而局部变量(包括函数内部定义的变量)不会被默认初始化,它们的值是未定义的。global_int 作为全局变量的int类型,所以初值为 0 。local_int 是局部变量并且没有初始化,它的初值是未定义的(作用域仅限于main()内部)。global_str 和 local_str 作为全局变量的std::string类型,该对象定义了默认的初始化方式,即初始化为空字符串。
练习2.11
指出下面的语句是声明还是定义:
(a) extern int ix = 1024;
(b) int iy;
(c) extern int iz;
1 2 3 (a): 定义 (b): 声明并定义 (c): 声明
练习2.12
请指出下面的名字中哪些是非法的?
(a) int double = 3.14;
(b) int _;
(c) int catch-22;
(d) int 1_or_2 = 1;
(e) double Double = 3.14;
(a) double 是C++中的关键字,不能用作变量名。 (c)变量名中不能包含连字符(减号),只能使用字母、数字和下划线。 (d) 变量名不能以数字开头。 其他合法。
练习2.13
下面程序中 j 的值是多少?
1 2 3 4 5 6 int i = 42 ;int main () { int i = 100 ; int j = i; }
100。
练习2.14
下面的程序合法吗?如果合法,它将输出什么?
1 2 3 4 int i = 100 , sum = 0 ;for (int i = 0 ; i != 10 ; ++i) sum += i; std::cout << i << " " << sum << std::endl;
合法。最后i为全局变量输出,for循环为一到九相加,输出是 100 45 。
练习2.15
下面的哪个定义是不合法的?为什么?
(a) int ival = 1.01;
(b) int &rval1 = 1.01;
(c) int &rval2 = ival;
(d) int &rval3;
(b) 和 (d) 不合法,(b) 引用类型必须绑定到一个对象上,而不能绑定到字面值上。字面值 1.01 是一个临时值,无法被引用。(d) 引用类型在定义时必须初始化,必须绑定到一个对象。
练习2.16
考察下面的所有赋值然后回答:哪些赋值是不合法的?为什么?哪些赋值是合法的?它们执行了哪些操作?
1 2 3 4 5 int i = 0, &r1 = i; double d = 0, &r2 = d; - (a) r2 = 3.14159; - (b) r2 = r1; - (c) i = r2; - (d) r1 = d;
1 2 3 4 (a): 合法。给 d 赋值为 3.14159。 (b): 合法。会执行自动转换(int->double)。 (c): 合法。会发生小数截取。 (d): 合法。会发生小数截取。
练习2.17
执行下面的代码段将输出什么结果?
1 2 3 int i, &ri = i;i = 5 ; ri = 10 ; std::cout << i << " " << ri << std::endl;
输出:
由于 ri 是 i 的引用,将 ri 的值设置为 10 实际上就是将 i 的值设置为 10。 由于 i 和 ri 引用同一个对象,它们的值都是 10。
练习2.18
编写代码分别改变指针的值以及指针所指对象的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> int main () { int i = 42 ; int *ptr = &i; int j = 10 ; ptr = &j; *ptr = 21 ; std::cout << *ptr << std::endl; std::cout << j << std::endl; return 0 ; }
练习2.19
说明指针和引用的主要区别
引用是另一个对象的别名本身并非一个对象,而指针本身就是一个对象。
引用必须初始化,并且一旦定义了引用就无法再绑定到其他另外的对象,之后每次引用都是访问它最初绑定的那个对象。而指针无须在定义时赋初值,也可以重新赋值让其指向其他对象。
练习2.20
请叙述下面这段代码的作用。
1 2 3 int i = 42 ;int *p1 = &i; *p1 = *p1 * *p1;
让指针 pi 指向 i,然后将 i 的值重新赋值为 42 * 42 (1764)。
练习2.21
请解释下述定义。在这些定义中有非法的吗?如果有,为什么?
1 2 3 4 int i = 0; (a) double* dp = &i; (b) int *ip = i; (c) int *p = &i;
1 2 3 (a): 非法。int型对象不能赋给double型指针 。 (b): 非法。不能直接将 `int` 变量赋给指针。 (c): 合法。
练习2.22
假设 p 是一个 int 型指针,请说明下述代码的含义。
if (p) // ... 这个条件语句检查指针 p是否为非空指针。如果 p的值不为零(即指针指向一个有效的内存地址),条件为真,执行 if 语句块中的代码。如果 p 的值为零(即指针为空指针),条件为假,跳过 if 语句块中的代码。if (*p) // ... 这个条件语句检查指针 p 所指的对象的值是否为非零。通过解引用指针 p,可以访问指针所指的对象。如果解引用后的值不为零,条件为真,执行 if 语句块中的代码。如果解引用后的值为零,条件为假,跳过 if 语句块中的代码。
练习2.23
给定指针 p,你能知道它是否指向了一个合法的对象吗?如果能,叙述判断的思路;如果不能,也请说明原因。
不能。只有在运行时,通过访问指针 p 所指的内存地址,才能确定它是否指向了一个合法的对象。 在C++中,指针的合法性是一个相对的概念。一个指针被认为是合法的,如果它指向了一个已分配的内存块,或者指向了一个有效的对象。然而,即使指针指向了一个已分配的内存块,也不能保证该内存块包含了一个有效的对象。
练习2.24
在下面这段代码中为什么 p 合法而 lp 非法?
1 2 3 int i = 42 ;void *p = &i;long *lp = &i;
void * 可以指向任何类型的对象。long指针类型与int对象不匹配。
练习2.25
说明下列变量的类型和值。
(a) int* ip, i, &r = i;
(b) int i, *ip = 0;
(c) int* ip, ip2;
1 2 3 (a): ip 是一个int指针, i 是int类型, r 是 i 的引用。 (b): i 是int类型, ip 是空指针。 (c): ip 是一个int的指针, ip2 是int类型。
练习2.26
下面哪些语句是合法的?如果不合法,请说明为什么?
1 2 3 4 const int buf; int cnt = 0 ; const int sz = cnt; ++cnt; ++sz;
(a):不合法,const 对象必须初始化。 (b):合法。 (c):合法。 (d):不合法, const 对象不能被改变。
练习2.27
下面的哪些初始化是合法的?请说明原因。
1 2 3 4 5 6 7 (a) int i = -1, &r = 0; (b) int *const p2 = &i2; (c) const int i = -1, &r = 0; (d) const int *const p3 = &i2; (e) const int *p1 = &i2; (f) const int &const r2; (g) const int i2 = i, &r = i;
(a)不合法,因为 &r 是一个引用,引用必须绑定到一个对象上,而不能绑定到一个字面值上。 (f)不合法,因为引用本身就是一个常量,不需要额外的 const 修饰符。 (b), (c), (d), (e), (g) 这些初始化是合法的。
练习2.28
说明下面的这些定义是什么意思,挑出其中不合法的。
1 2 3 4 5 (a) int i, *const cp; (b) int *p1, *const p2; (c) const int ic, &r = ic; (d) const int *const p3; (e) const int *p;
(a)(b)(d)中的常量指针都没有初始化,是不合法的;(c)中引用变量r需要初始化;(e)是合法的。在定义指针变量时,如果没有提供初始值,指针变量会被默认初始化为 nullptr,表示它当前不指向任何有效的内存地址。
练习2.29
假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。
1 2 3 4 5 6 (a) i = ic; (b) p1 = p3; (c) p1 = ⁣ (d) p3 = ⁣ (e) p2 = p1; (f) ic = *p3;
(a)合法;(b)不合法,不能将指向常量的指针赋值给非常量指针,因为这可能导致通过非常量指针修改常量的值;(c)合法,非常量指针可以指向常量对象,但不能通过该指针修改常量对象的值;(d)不合法,不能改变常量指针的指向;(e)不合法,常量指针指针一旦被初始化,就不能再改变它所指向的地址;(f)不合法,不能修改常量的值。
练习2.30
对于下面的这些语句,请说明对象被声明成了顶层const还是底层const?
1 2 3 const int v2 = 0 ; int v1 = v2;int *p1 = &v1, &r1 = v1;const int *p2 = &v2, *const p3 = &i, &r2 = v2;
v2 是顶层const;v1 没有被声明为const,它是一个非常量;p1 和 r1 都没有被声明为const,它们是非常量指针;p2 是底层const;p3 既是顶层const又是底层const(靠右的const是顶层const,靠左的是底层const);r2 是底层const。
练习2.31
假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。
1 2 3 4 5 r1 = v2; p1 = p2; p2 = p1; p1 = p3; p2 = p3;
练习2.32
下面的代码是否合法?如果非法,请设法将其修改正确。
1 int null = 0, *p = null;
非法,将一个整数值直接赋给指针是不允许的。
1 int null = 0, *p = &null;
练习2.33
利用本节定义的变量,判断下列语句的运行结果。
1 2 3 4 5 6 a=42 ; b=42 ; c=42 ; d=42 ; e=42 ; g=42 ;
练习2.34
基于上一个练习中的变量和语句编写一段程序,输出赋值前后变量的内容,你刚才的推断正确吗?如果不对,请反复研读本节的示例直到你明白错在何处为止。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> int main () { int i=0 ,&r=i; auto a=r; const int ci=i,&cr=ci; auto b=ci; auto c=cr; auto d=&i; auto e=&ci; auto &g=ci; a=42 ,b=42 ,c=42 ; d=42 ,e=42 ,g=42 ; return 0 ; }
报错
练习2.35
判断下列定义推断出的类型是什么,然后编写程序进行验证。
1 2 3 const int i = 42 ;auto j = i; const auto &k = i; auto *p = &i; const auto j2 = i, &k2 = i;
j:int,k:const int的引用,p:const int *,j2:const int,k2:const int 的引用。
练习2.36
关于下面的代码,请指出每一个变量的类型以及程序结束时它们各自的值。
1 2 3 4 5 int a = 3 , b = 4 ;decltype (a) c = a;decltype ((b)) d = a;++c; ++d;
a,b,c 是 int;d是int &;程序结束时,a=4,b=4,c=4,d=4。(d 引用的是 a,所以 a 的值增加 1,变为 4 d 的值也是 4,因为它是 a 的引用)
练习2.37
赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。根据这一特点,请指出下面的代码中每一个变量的类型和值。
1 2 3 int a = 3 , b = 4 ;decltype (a) c = a;decltype (a = b) d = a;
a,b,c是int,d是int&;a=3,b=4,c=3,d=3。
练习2.38
说明由decltype 指定类型和由auto指定类型有何区别。请举一个例子,decltype指定的类型与auto指定的类型一样;再举一个例子,decltype指定的类型与auto指定的类型不一样。
decltype 推导出的类型可以是表达式的类型,而 auto 推导出的类型必须是初始化表达式的类型 decltype 处理顶层const和引用的方式与 auto不同,decltype会将顶层const和引用保留起来。 指定类型一样的情况:
1 2 3 int x = 1 ;decltype (x) y = x; auto z = x;
指定类型不一样的情况:
1 2 3 const int x = 1 ;decltype (x) y = x; auto z = x;
练习2.39
编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关的信息,以后可能会有用。
1 2 3 4 5 struct Foo { } int main () { return 0 ; }
练习2.40
根据自己的理解写出 Sales_data 类,最好与书中的例子有所区别。
1 2 3 4 5 6 7 8 9 struct Sale_data { std::string bookNo; std::string bookName; unsigned units_sold = 0 ; double revenue = 0.0 ; double price = 0.0 ; }
练习2.41
使用你自己的Sale_data类重写1.5.1节(第20页)、1.5.2节(第21页)和1.6节(第22页)的练习。眼下先把Sales_data类的定义和main函数放在一个文件里。
1.5.1 以下程序用于计算两个相同IBSN的Sales_data对象,输出他们的和。
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 30 31 32 33 34 #include <iostream> #include <string> struct Sales_data { std::string bookNo; unsigned units_sold = 0 ; double revenue = 0.0 ; double price = 0.0 ; }; int main () { Sales_data data1,data2; double price=0 ; std::cin>>data1.bookNo>>data1.units_sold>>price; data1.revenue=data1.units_sold*price; std::cin>>data2.bookNo>>data2.units_sold>>price; data2.revenue=data2.units_sold*price; if (data1.bookNo==data2.bookNo) { unsigned totalCnt=data1.units_sold+data2.units_sold; double totalRevenue=data1.revenue+data2.revenue; std::cout<<data1.bookNo<<" " <<totalCnt<<" " <<totalRevenue<<" " ; return 0 ; } else { std::cerr<<"Data must refer to the same ISBN" <<std::endl; return -1 ; } return 0 ; }
1.5.2 和上一问程序相同,代码实现了输入多条销售记录并统计每个ISBN(每本书)有几条记录。
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 30 31 32 33 34 #include <iostream> #include <string> struct Sales_data { std::string bookNo; unsigned units_sold = 0 ; double revenue = 0.0 ; double price = 0.0 ; }; int main () { Sales_data data1,data2; double price=0 ; std::cin>>data1.bookNo>>data1.units_sold>>price; data1.revenue=data1.units_sold*price; std::cin>>data2.bookNo>>data2.units_sold>>price; data2.revenue=data2.units_sold*price; if (data1.bookNo==data2.bookNo) { unsigned totalCnt=data1.units_sold+data2.units_sold; double totalRevenue=data1.revenue+data2.revenue; std::cout<<data1.bookNo<<" " <<totalCnt<<" " <<totalRevenue<<" " ; return 0 ; } else { std::cerr<<"Data must refer to the same ISBN" <<std::endl; return -1 ; } return 0 ; }
1.6 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> #include <string> struct Sales_data { std::string bookNo; unsigned units_sold = 0 ; double revenue = 0.0 ; double price = 0.0 ; }; int main () { Sales_data total; double totalprice=0 ; if (std::cin >> total.bookNo>>total.units_sold>>totalprice) { total.revenue=total.units_sold*totalprice; Sales_data trans; double transprice; while (std::cin>>trans.bookNo>>trans.units_sold>>transprice) { if (trans.bookNo==total.bookNo) { total.units_sold +=trans.units_sold; total.revenue +=trans.revenue; } else { std::cout<<total.bookNo<<" " <<total.units_sold<<" " <<total.revenue<<" " ; if (total.units_sold!=0 ) { std::cout<<total.revenue/total.units_sold<<std::endl; } else { std::cout<<"(No Sales)" <<std::endl; } total.bookNo=trans.bookNo; total.units_sold=trans.units_sold; total.revenue=trans.revenue; } } std::cout<<total.bookNo<<" " <<total.units_sold<<" " <<total.revenue<<" " ; if (total.units_sold!=0 ) { std::cout<<total.revenue/total.units_sold<<std::endl; } else { std::cout<<"(No Sales)" <<std::endl; } return 0 ; } else { std::cerr<<"No Data?!" <<std::endl; return -1 ; } }
练习2.42
根据你自己的理解重写一个Sales_data.h头文件,并以此为基础重做2.6.2节(第67页)的练习。
ex2_42.h 1 2 3 4 5 6 7 8 9 10 11 12 #ifndef Sales_data_h #define Sales_data_h #include <string> struct Sales_data { std::string bookNo; unsigned units_sold = 0 ; double revenue = 0.0 ; double price = 0.0 ; }; #endif
1.5.1&1.5.2(将结构体用头文件替换即可) 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 #include <iostream> #include <string> #include "ex2_42.h" int main () { Sales_data data1,data2; double price=0 ; std::cin>>data1.bookNo>>data1.units_sold>>price; data1.revenue=data1.units_sold*price; std::cin>>data2.bookNo>>data2.units_sold>>price; data2.revenue=data2.units_sold*price; if (data1.bookNo==data2.bookNo) { unsigned totalCnt=data1.units_sold+data2.units_sold; double totalRevenue=data1.revenue+data2.revenue; std::cout<<data1.bookNo<<" " <<totalCnt<<" " <<totalRevenue<<" " ; return 0 ; } else { std::cerr<<"Data must refer to the same ISBN" <<std::endl; return -1 ; } return 0 ; }
1.6 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <iostream> #include <string> #include "ex2_42.h" int main () { Sales_data total; double totalprice=0 ; if (std::cin >> total.bookNo>>total.units_sold>>totalprice) { total.revenue=total.units_sold*totalprice; Sales_data trans; double transprice; while (std::cin>>trans.bookNo>>trans.units_sold>>transprice) { if (trans.bookNo==total.bookNo) { total.units_sold +=trans.units_sold; total.revenue +=trans.revenue; } else { std::cout<<total.bookNo<<" " <<total.units_sold<<" " <<total.revenue<<" " ; if (total.units_sold!=0 ) { std::cout<<total.revenue/total.units_sold<<std::endl; } else { std::cout<<"(No Sales)" <<std::endl; } total.bookNo=trans.bookNo; total.units_sold=trans.units_sold; total.revenue=trans.revenue; } } std::cout<<total.bookNo<<" " <<total.units_sold<<" " <<total.revenue<<" " ; if (total.units_sold!=0 ) { std::cout<<total.revenue/total.units_sold<<std::endl; } else { std::cout<<"(No Sales)" <<std::endl; } return 0 ; } else { std::cerr<<"No Data?!" <<std::endl; return -1 ; } }