【C++ Primer】第二章 变量和基本类型 (练习)

C++ Primer 5th 随堂练习

【C++ Primer】第一章 开始 (练习)

【C++ Primer】第二章 变量和基本类型 (练习)

【C++ Primer】第三章 字符串、向量和数组 (练习)

【C++ Primer】第四章 表达式 (练习)

【C++ Primer】第五章 语句 (练习)

【C++ Primer】第六章 函数 (练习)

【C++ Primer】第七章 类 (练习)

【C++ Primer】第八章 IO 库 (练习)

【C++ Primer】第九章 顺序容器 (练习)

【C++ Primer】第十章 泛型算法 (练习)

【C++ Primer】第十一章 关联容器 (练习)


第二章 变量和基本类型


练习 2.1

类型 int、long、long long 和 short 的区别是什么?无符号类型和带符号类型的区别是什么?float 和 double的区别是什么?

解答

C++ 算术类型表:

类型 含义 最小尺寸
bool 布尔类型 8bits
char 字符 8bits
wchar_t 宽字符 16bits
char16_t Unicode字符 16bits
char32_t Unicode字符 32bits
short 短整型 16bits
int 整型 16bits (在 32 位机中是 32 bits)
long 长整型 32bits
long long 长整型 64bits (新定义于 C++11)
float 单精度浮点数 6位有效数字
double 双精度浮点数 10位有效数字
long double 扩展精度浮点数 10位有效数字

C++ 规定 short 和 int 至少 16 位,long 至少 32 位,long long 至少 64 位。 通常使用 int 执行整数运算。在实际应用中,short 常常显得太小,而 long 一般和 int 有同样的尺寸。若数值超过了 int 的表示范围,可选用 long long。

带符号类型 (signed) 能够表示正数、负数和 0 ,而无符号类型 (unsigned) 只能够表示 0 和正数。

双精度浮点型 (double) 比单精度浮点型 (float) 的有效数字多。


练习 2.2 

计算按揭贷款时,对于利率、本金和付款分别应选择何种数据类型?说明你的理由。

解答

实际应用中,利率、本金和付款既可能为整数,也可能为普通实数,故应选择一种浮点类型来表示。在可选的浮点类型 float、double 和 long double 中,float 通常精度不够,且 double 与之计算代价相差无几 (对某些机器而言 double 运算甚至比 float 运算更快) 而具有更高精度;而 long double 提供的精度通常没有必要,况其带来的大量运行时消耗也不容忽视。综上分析,应选择 double 类型。


练习 2.3

读程序写结果。

解答

unsigned u = 10, u2 = 42;
std::cout << u2 - u << std::endl;  // 32
std::cout << u - u2 << std::endl;  // 4294967264

int i = 10, i2 = 42;
std::cout << i2 - i << std::endl;  // 32
std::cout << i - i2 << std::endl;  // -32
std::cout << i - u << std::endl;   // 0
std::cout << u - i << std::endl;   // 0

练习 2.4

编写程序检查你的估计是否正确,如果不正确,请仔细研读本节直到弄明白问题所在。

解答

见 练习 2.3,u - u2 较复杂,其余尚可。


练习 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): 字符字面值,宽字符字面值,字符串字面值,宽字符串字面值。
  • (b): 十进制整型,十进制无符号整型,十进制长整型,八进制整型,十六进制整型。
  • (c): 双精度浮点型,单精度浮点型,扩展精度浮点型。
  • (d): 十进制整型,十进制无符号整型,双精度浮点型,双精度浮点型。

练习 2.6

下面两组定义是否有区别,如果有,请叙述之:

int month = 9, day = 7;
int month = 09, day = 07;

解答

有区别。第一行定义了两个十进制整型数,第二行定义了两个八进制整型数。但注意,就八进制而言,逢八进一,而不应出现第二行中的 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) 无效,1024 是整型,而后缀 f 只能用于浮点字面量
  • (d) long double

练习 2.8

请利用转义序列编写一段程序,要求先输出 2M,然后转到新一行。修改程序使其先输出 2,然后输出制表符,再输出 M,最后转到新一行。

解答

#include 
int main()
{
   std::cout << 2 << "\115\012";
   std::cout << 2 << "\t\115\012";
   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): 应先定义好变量再用于接收标准输入。改正:
int input_value = 0;
std::cin >> input_value;
  • (b): 用列表初始化内置类型的变量时,如果存在丢失信息的风险,则编译器将报错。改正:
double i = { 3.14 };
  • (c): 此处 wage 是未定义的,应于此前先将其定义好。改正:
double wage;
double salary = wage = 9999.99;
  • (d): 虽然不报错,但小数部分将会因隐式类型转换而被截断。建议改正:
double i = 3.14;

练习 2.10

下列变量的初值分别是什么?

std::string global_str;
int global_int;
int main()
{
    int local_int;
    std::string local_str;
}
解答
  • global_str 和 global_int 均为定义在函数体外的变量 (全局变量),故将被默认初始化为空字符串和 0。
  • local_int 作为函数体内的内置类型 (int) 变量 (局部变量),将不被初始化 (uninitialized),初值未定义。
  • local_str 作为函数体内的 std::string 类变量 (局部变量),其初值由该类决定,为空字符串。

练习 2.11

指出下面的语句是声明还是定义:

(a) extern int ix = 1024;
(b) int iy;
(c) extern int iz;

解答

  • (a): 定义,虽然使用了 extern 关键字,但还是对变量 ix 进行初始化了,以至于抵消了 extern 的作用。
  • (b): 定义,未对变量 iy 初始化而已。
  • (c): 声明,使用了 extern 关键字且未对变量 iz 初始化。

练习 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++ 关键字。
  • (b): 合法,但注意定义在函数体外的标识符不能以下划线开头
  • (c): 非法,使用非法符号 (-) 构成标识符。
  • (d): 非法,标识符必须以字母或下划线开头
  • (e): 合法,但注意变量名一般以小写字母开头,用户自定义类名才通常以大写字母开头

练习 2.13

下面程序中 j 的值是多少?

int i = 42;
int main()
{
    int i = 100;
    int j = i;
}

解答

局部变量 j = 100,因为新建的局部变量 i (100) 覆盖了原先的全局变量 i (42)。除非使用作用域操作符 :: 将局部变量 j 的创建和初始化语句改为 int j = ::i; 即有 j = 42。


练习 2.14

下面的程序合法吗?如果合法,它将输出什么?

int i = 100, sum = 0;
for (int i = 0; i != 10; ++i)
    sum += i;
std::cout << i << " " << sum << std::endl;

解答

该程序合法,因为 for 循环中的临时局部变量 i 将在内层作用域内暂时覆盖外层作用域的变量 i,从而实现正常的循环计数,并在 for 循环结束后被释放。结果将分别输出 100 45,其中 45 是整数 1-9 之和。


练习 2.15

下面的哪个定义是不合法的?为什么?

(a) int ival = 1.01;
(b) int &rval1 = 1.01;
(c) int &rval2 = ival;
(d) int &rval3;

解答

  • (a): 合法,但浮点数 1.01 的小数部分会被截断 (隐式类型转换 double → int)。
  • (b): 非法,引用类型的初始值必须是一个对象,而不能是字面值或表达式计算结果,更何况此处类型还不匹配。
  • (c): 合法,rval2 作为 int 型对象 ival 的引用,成为变量 ival 的别名。
  • (d): 非法,引用不是一个对象而是被绑定对象的一个别名,所以不能定义引用的引用,应当有初始化

练习 2.16

考察下面的所有赋值然后回答:哪些赋值是不合法的?为什么?哪些赋值是合法的?它们执行了哪些操作?

int i = 0, &r1 = i;     
double d = 0, &r2 = d;

(a) r2 = 3.14159;
(b) r2 = r1;
(c) i = r2;
(d) r1 = d;

解答

  • (a): 合法,等价于 d = 3.14159。
  • (b): 合法,等价于 d = 0.0,增加小数部分 (隐式类型转换 int → double)。
  • (c): 合法,等价于 i = 0,小数部分被截断 (隐式类型转换 double → int)。
  • (d): 合法,等价于 i = 0,小数部分被截断 (隐式类型转换 double → int)。

练习 2.17

执行下面的代码段将输出什么结果?

int i, &ri = i;
i = 5; ri = 10;
std::cout << i << " " << ri << std::endl;

解答

该代码段将输出 10 10。注意,ri 作为引用必须在新建时得到初始化,但被引用对象 i 却不一定要在新建时初始化


练习 2.18

编写代码分别改变指针的值以及指针所指对象的值。

解答

#include

int main()
{
    int *ptr = nullptr;
    std::cout << ptr << " " << std::endl;  // 地址值

    int x = 6;
    ptr = &x;  // int 指针 ptr 保存 int 变量 x 的内存地址 (ptr 指向 x)
    std::cout << ptr << " " << *ptr << " " << x << std::endl;  // 地址值、解引用值、变量 x

    *ptr = 9;  // ptr 解引用, 改变 ptr 所指地址中保存的值 (改变 x 的值)
    std::cout << ptr << " " << *ptr << " " << x << std::endl;  // 地址值、解引用值、变量 x

    system("pause");
    return 0;
}

控制台输出:

0
0x61fe34 6 6
0x61fe34 9 9

练习 2.19

说明 指针和引用的主要区别

解答

指针和引用都能提供对其他对象的 间接访问,且在定义时均需满足 类型匹配原则 (但各有两种例外情况)。然而,在具体实现细节上,二者存在很大的不同:

  • 引用本身不是一个对象 (引用没有实际地址),而是另一个对象的别名,故不能定义引用的引用而指针本身就是一个对象 (指针有实际地址),故可以定义指向指针的指针,也可以定义指针的引用
  • 引用在新建时必须绑定一个对象实现初始化,且引用一旦被定义就无法再绑定到其他对象而指针及其保存的地址间则无此限制,指针无须在定义时赋初值 (同其他内置类型一样,在块作用域内定义的指针若未被初始化,将拥有一个不确定的值),且在其生命周期内,可以通过赋值使之重新指向其他对象 (保存其他同类型对象的内存地址)

练习 2.20

请叙述下面这段代码的作用。

int i = 42;
int *p1 = &i;     // 等号右侧, & 为取地址符
*p1 = *p1 * *p1;  // 等号右侧, * 为解引用符

解答

  • Line 1:定义 int 型变量 i,并将 i 的值初始化为 42。
  • Line 2:定义 int 型指针变量 p1 (p1 是指向 int 型对象的指针),并对变量 i  取内存地址 以赋予 p1 实现初始化 (令指针 p1 指向对象 i)。
  • Line 3:对指针变量 p1 解引用 从而为变量 i 赋予新值 1764 (相当于 i = i * i = 42 * 42 = 1764)。

练习 2.21

请解释下述定义。在这些定义中有非法的吗?如果有,为什么?

int i = 0;

(a) double *dp = &i;
(b) int *ip = i;
(c) int *p = &i;

解答

  • (a): 非法。创建 double 型指针变量 dp,并令其指向 int 型对象 i,未能严格遵守类型一致原则。
  • (b): 非法。把 int 变量 i 直接赋给指针 ip 是错误的操作,即便 i 的值恰为 0 也不行。
  • (c): 合法。创建 int 型指针变量 p,并令其指向 int 型对象 i (令 p 保存变量 i 的内存地址)。

练习 2.22

假设 p 是一个 int 型指针,请说明下述代码的含义。

if (p) // ...
if (*p) // ...

解答

  • Line 1:判断 p 是否为空指针。
  • Line 2:判断 p 所指对象的值 (整数) 是否为 0。

练习 2.23

给定指针 p,你能知道它是否指向了一个合法的对象吗?如果能,叙述判断的思路;如果不能,也请说明原因。

解答

不能,因为首先要确定指针 p 是否合法,才能判断 p 所指向的对象是否合法。


练习 2.24

在下面这段代码中为什么 p 合法而 lp 非法?

int i = 42;
void *p = &i;
long *lp = &i;

解答

因为指针变量 lp 的类型为 long,与指向对象 i 的类型 int 不一致。注意,在 C++ 中,继承自 C 的 void * 才可以指向任何类型的对象,而其他指针类型必须要与所指对象严格匹配。


练习 2.25

说明下列变量的类型和值。

(a) int* ip, i, &r = i;
(b) int i, *ip = 0;
(c) int* ip, ip2;

解答

  • (a): ip 为 int 型指针变量 (指向 int 对象的指针);i 为 int 型变量;r 为 对变量 i 的 int 型引用。
  • (b): i 为 int 型变量;ip 为 int 型指针变量 (空指针)。
  • (c): ip 为 int 型指针变量 (指向 int 对象的指针);ip2 为 int 型变量。

练习 2.26

下面哪些语句是合法的?如果不合法,请说明为什么?

(a) const int buf;      
(b) int cnt = 0;        
(c) const int sz = cnt; 
(d) ++cnt; ++sz;       

解答

  • (a): 非法,const 常量对象在定义时必须得到初始化
  • (b): 合法,定义了一个 int 型变量 cnt 并初始化为 0。
  • (c): 合法,定义了一个 int 型常量 sz 并初始化为 cnt = 0。
  • (d): 非法,const 常量对象的内容不可被改变

练习 2.27

下面的哪些初始化是合法的?请说明原因。

(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): 非法,int 型引用 r 在定义时必须引用一个对象以实现初始化
  • (b): 合法,p2 作为 int 型常量指针指向了 i2。
  • (c): 合法,定义 int 型常量 i 并初始化为 -1,定义 int 型常量引用 r 并与 0 绑定。
  • (d): 合法,定义指向常量对象 i2 的 int 型常量指针 p3。
  • (e): 合法,定义指向常量对象 i2 的 int 型指针 p1。
  • (f): 非法,没有 &const 这种用法且未对新定义的引用 r2 初始化。
  • (g): 合法,定义 int 型常量 i2 并初始化为 i = -1,定义对 int 型常量引用 r 并与 i 绑定。

练习 2.28

说明下面的这些定义是什么意思,挑出其中不合法的。

(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): 非法,常量指针 cp 必须在定义时得到初始化。
  • (b): 非法,常量指针 p2 必须在定义时得到初始化。
  • (c): 非法,常量 ic 必须在定义时得到初始化。
  • (d): 非法,指向常量的 int 型常量指针 p3 必须在定义时得到初始化。
  • (e): 合法,指向常量的 int 型指针 p。

练习 2.29

假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。

(a) i = ic;    
(b) p1 = p3;    
(c) p1 = ⁣ 
(d) p3 = ⁣   
(e) p2 = p1;   
(f) ic = *p3;   

解答

  • (a): 合法,int 型常量 ic 赋值给 int 型变量 i。
  • (b): 非法,p3 作为指向常量的 int 型常量指针,不能直接赋值给 int 型普通指针 p1 (类型须匹配)。
  • (c): 非法,int 型普通指针 p1 不能直接指向 int 型常量 ic (类型须匹配,指向常量的 int 型指针才行)。
  • (d): 合法,p3 作为指向常量的 int 型常量指针指向 int 型常量 ic。
  • (e): 合法,将 int 型普通指针 p1 赋值给未经初始化的 int 型常量指针 p2 (常量指针一旦初始化就不能再指向其它对象)。
  • (f): 非法,常量指针 p3 解引用/取值 得到 int 常量对象,然后赋值给 int 常量 ic,然而常量是不可修改的。

练习 2.30

对于下面的这些语句,请说明对象被声明成了顶层 const 还是底层 const ?

const int v2 = 0; int v1 = v2;
int *p1 = &v1, &r1 = v1;
const int *p2 = &v2, *const p3 = &i, &r2 = v2;

解答

  • 顶层 const表示任意的对象 (算术类型、类、指针) 自身是常量
  • 底层 const表示指针所指的对象是一个常量
  1. v2 是顶层 const (不允许修改常量对象自身);
  2. p2 是底层 const (指向常量对象的指针,允许指向其他对象,但不能对 p2 解引用来改变所指对象的值,没有规定那个对象的值不能通过其他途径改变);
  3. p3 既是顶层 const (靠右) 又是底层 const (靠左) (指向常量对象的常量指针,不论是其所指的对象值还是自身存储的地址,都不能改变);
  4. r2 是底层 const (不允许通过 r2 修改绑定的对象 v2,但仍然允许通过其他途径修改 v2,如直接赋值或绑定到其他非常量引用来修改)。

练习 2.31

假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。

(a) r1 = v2;
(b) p1 = p2;
(c) p2 = p1;  
(d) p1 = p3; 
(e) p2 = p3;  

解答

  • (a): 合法,int 型常量 v2 赋值给 v1 的 int 型引用 r1 (在拷贝时顶层 const 不受影响)。
  • (b): 非法,不能令普通指针 p1 指向 指向常量的指针 p2 (p2 是底层 const,若要拷贝入 p1,p1 也必须是底层 const)。
  • (c): 合法,指向 int 型常量的指针 p2 可以指向 int 型普通指针 p1 (int* 可以转换成 const int*)。
  • (d): 非法,不能令普通指针 p1 指向 指向常量的常量指针 p3 (p3 是底层 const,若要拷贝入 p1 其也须为底层 const)。
  • (e): 合法,指向常量的指针 p2 可以指向 指向常量的常量指针 p3 (p2、p3 为底层 const,拷贝时忽略掉顶层 const)。

习题 2.32

下面的代码是否合法?如果非法,请设法将其修改正确。

int null = 0, *p = null;

解答

不合法,虽然指针可以初始化为 0 表示为空指针,但是把 int 变量直接赋值给指针是错误的操作,即便 int 变量的值恰好等于 0 也不行。例外情况是改用不可修改的 int 常量:

// 修改成: const int null = 0, *p = null; 或 constexpr int null = 0, *p = null;
const int null = 0;  
int *p = null;

习题 2.33

利用本节定义的变量,判断下列语句的运行结果。

// P62
a=42; 
b=42; 
c=42; 
d=42; 
e=42; 
g=42;

解答

  • Line 1:a 是一个 int 型变量;
  • Line 2:b 是一个 int 型变量;
  • Line 3:c 是一个 int 型变量;
  • Line 4:d 是一个 int 型指针,不能直接指向 int 型字面值;
  • Line 5:e 是一个 指向 int 型常量的指针,不能直接指向 int 型字面值 (注意指针保存的是地址);
  • Line 6:g 是一个 int 型常量引用,不能通过常量引用修改所绑定的对象 (用于声明引用的 const 都是底层 const)。

习题 2.34

基于上一个练习中的变量和语句编写一段程序,输出赋值前后变量的内容,你刚才的推断正确吗?如果不对,请反复研读本节的示例直到你明白错在何处为止。

解答

见习题 2.33。


习题 2.35

判断下列定义推断出的类型是什么,然后编写程序进行验证。

const int i = 42;
auto j = i; const auto &k = i; auto *p = &i; 
const auto j2 = i, &k2 = i;

解答

  • i 是 int 型常量;
  • j 将自动推断为 int 型变量 (auto 一般会忽略顶层 const,即无需令对象 (算术类型、类、指针) 自身继续保持为常量);
  • k 将显式推断为 int 型常量引用,从而绑定到 int 型常量 i;
  • p 将自动推断为指向 int 型常量的指针,保存 int 型常量 i 的地址;
  • j2 将显式推断为 int 型常量;
  • k2 将显式推断为 int 型常量引用,从而绑定到 int 型常量 i。

源程序:

#include
#include

int main()
{   
    const int i = 42;
    auto j = i;
    const auto &k = i;
    auto *p = &i;
    const auto j2 = i, &k2 = i;

    std::cout << "The type of i is "<< typeid(i).name() << std::endl;
    std::cout << "The type of j is "<< typeid(j).name() << std::endl;
    std::cout << "The type of k is "<< typeid(k).name() << std::endl;
    std::cout << "The type of p is "<< typeid(p).name() << std::endl;
    std::cout << "The type of j2 is "<< typeid(j2).name() << std::endl;
    std::cout << "The type of k2 is "<< typeid(k2).name() << std::endl;

    system("pause");
    return 0;
}

输出:

The type of i is i
The type of j is i
The type of k is i
The type of p is PKi
The type of j2 is i
The type of k2 is i

这显然不对,原因在于 GCC 编译器输出的是 mangle 后的符号名 (VC 编译器则返回 demangle 后的正常结果),要输出完整的类型名需要再作修改,即补充执行 demangle 过程。

新源程序:

#include
#include
#include // 使用 abi

int main()
{   
    const int i = 42;
    auto j = i;
    const auto &k = i;
    auto *p = &i;
    const auto j2 = i, &k2 = i;
    
    // 0 和 nullptr 都是初始化为空指针
    std::cout << "The type of i is "<< abi::__cxa_demangle(typeid(i).name(), nullptr, nullptr, nullptr) << std::endl;
    std::cout << "The type of j is "<< abi::__cxa_demangle(typeid(j).name(), 0, 0, 0) << std::endl;
    std::cout << "The type of k is "<< abi::__cxa_demangle(typeid(k).name(), nullptr, nullptr, nullptr) << std::endl;
    std::cout << "The type of p is "<< abi::__cxa_demangle(typeid(p).name(), 0, 0, 0) << std::endl;
    std::cout << "The type of j2 is "<< abi::__cxa_demangle(typeid(j2).name(), nullptr, nullptr, nullptr) << std::endl;
    std::cout << "The type of k2 is "<< abi::__cxa_demangle(typeid(k2).name(), 0, 0, 0) << std::endl;

    system("pause");
    return 0;
}

输出:

The type of i is int
The type of j is int
The type of k is int
The type of p is int const*
The type of j2 is int
The type of k2 is int

符合推测,验证成功。至于该现象的成因,可以参见下文链接。

知识卡片

ABI Application Binary Interface 的简称。C/C++ 源程序在编译后,函数名会被编译器修改成编译器内部的名字,该名会在链接时用到。将 C++ 源程序标识符 (original C++ source identifier)  转换成 C++ ABI 标识符 (C++ ABI identifier) 的过程称为 mangle;相反的过程称为 demangle

       在 C/C++ 的发展过程中,二进制兼容一直是个问题。不同编译器厂商编译的二进制代码之间兼容性不佳,甚至同一个编译器的不同版本之间兼容性也不好。之后,C 拥有了统一的 ABI,而 C++ 由于其特性的复杂性以及 ABI 标准推进不力,一直没有自己的 ABI。这就涉及到标识符的 mangle 问题。比如,C++ 源码中的同一个函数名,不同的编译器或不同的编译器版本,编译后的名称可能会有不同。

参考文献:

      https://www.cnblogs.com/zfyouxi/p/5060288.html

      https://www.cnblogs.com/robinex/p/7892795.html


习题 2.36

关于下面的代码,请指出每一个变量的类型以及程序结束时它们各自的值。

int a = 3, b = 4;
decltype(a) c = a;
decltype((b)) d = a;
++c;
++d;

解答

  • a 和 b 均为 int 型变量 (int);
  • c 同 a 亦为 int 型变量 (int);
  • d 则由于 “双层括号” 而为 int 型引用 (int &);
  • 程序结束时,a 为 4 (引用 d 的 ++d 操作),b 为 4,c 为 4 (++c 操作),d 为 4 (++d 操作)。

习题 2.37

赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。根据这一特点,请指出下面的代码中每一个变量的类型和值。

int a = 3, b = 4;
decltype(a) c = a;
decltype(a = b) d = a;

解答

  • a 和 b 均为 int 型变量 (int);
  • c 亦为 int 型变量 (int);
  • d 则由于 “赋值表达式” 而为 int 型引用 (int &);
  • 程序结束时,a 为 3,b 为 4,c 为 3,d 为 3。

习题 2.38

说明由 decltype 指定类型和由 auto 指定类型有何区别。请举一个例子,decltype 指定的类型与 auto 指定的类型一样;再举一个例子,decltype 指定的类型与 auto 指定的类型不一样。

解答

decltype 处理顶层 const 和引用的方式与 auto 不同,decltype 会将顶层 const 和引用保留起来。举例:

int i = 0, &r = i;

// 类型相同, a 和 b 均为 int
auto a = i;
decltype(i) b = i;

// 类型不同, c 为 int, d 为 int&
auto c = r;
decltype(r) d = r;

习题 2.39

编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关的信息,以后可能会有用。

struct Foo { /* 此处为空  */ } // 注意:没有分号
int main()
{
    return 0;
}。

解答

在 int 处提示应输入“ ; ” 。


习题 2.40

根据自己的理解写出 Sales_data 类,最好与书中的例子有所区别。

解答

struct Sales_data {
    std::string book_name;       // 书名
    std::string book_index;      // 书编号
    unsigned sales_volume = 0;   // 销售总量
    double unit_price = 0.0;     // 书单价
    double unit_cost = 0.0;      // 单书成本
    double total_revenue = 0.0;  // 总营收
    double net_profit = 0.0;     // 净利润
    // ...
};

习题 2.41

使用你自己的 Sale_data 类重写1.5.1节(第20页)、1.5.2节(第21页)和1.6节(第22页)的练习。眼下先把 Sales_data 类的定义和 main 函数放在一个文件里。

解答

1.5.1 节:

#include
#include

struct Sale_data
{
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

int main()
{
    Sale_data book;
    double price;
    std::cin >> book.bookNo >> book.units_sold >> price;
    book.revenue = book.units_sold * price;
    std::cout << book.bookNo << " " << book.units_sold << " " << book.revenue << " " << price;
    
    system("pause");
    return 0;
}

1.5.2 节:

#include 
#include 

struct Sale_data
{
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

int main()
{
    Sale_data book1, book2;
    double price1, price2;
    std::cin >> book1.bookNo >> book1.units_sold >> price1;
    std::cin >> book2.bookNo >> book2.units_sold >> price2;
    book1.revenue = book1.units_sold * price1;
    book2.revenue = book2.units_sold * price2;

    if (book1.bookNo == book2.bookNo) 
    {
        unsigned totalCnt = book1.units_sold + book2.units_sold;
        double totalRevenue = book1.revenue + book2.revenue;
        std::cout << book1.bookNo << " " << totalCnt << " " << totalRevenue << " ";
        if (totalCnt != 0)
            std::cout << totalRevenue / totalCnt << std::endl;
        else
            std::cout << "(no sales)" << std::endl;
        system("pause");
        return 0;
    }
    else 
    {
        std::cerr << "Data must refer to same ISBN" << std::endl;
        system("pause");
        return -1;  // indicate failure
    }
}

1.6 节:

#include 
#include 

struct Sale_data
{
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

int main()
{
    Sale_data total;
    double totalPrice;
    if (std::cin >> total.bookNo >> total.units_sold >> totalPrice)
    {
        total.revenue = total.units_sold * totalPrice;

        Sale_data trans;
        double transPrice;
        while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice)
        {
            trans.revenue = trans.units_sold * transPrice;

            if (total.bookNo == trans.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;
        system("pause");
        return 0;
    }
    else
    {
        std::cerr << "No data?!" << std::endl;
        system("pause");
        return -1;  // indicate failure
    }
}

习题 2.42

根据你自己的理解重写一个 Sales_data.h 头文件,并以此为基础重做 2.6.2节(第67页)的练习。


参考文献:《C++ Premier 5th》

你可能感兴趣的:(【C++筑基】)