2.1
int、long、long long 和 short 表示整型类型,C++语言规定它们表示数的范围 short (leq) int (leq) long (leq) long long。其中,数据类型 long long 是在 C++11 中新定义的;
除去布尔型和扩展的字符型之外,其它整型可以划分为带符号的(signed)和无符号的(unsigned)两种。带符号类型可以表示正数、负数和 0,无符号类型则仅能表示大于等于 0 的值。类型 int、short、long 和 long long 都是带符号的,通过在这些类型名前添加 unsigned 就可以得到无符号类型,例如 unsigned long。类型 unsigned int 可以缩写为 unsigned;
float 和 double 用于表示浮点数,其中,float 表示单精度浮点数,double 表示双精度浮点数。执行浮点数运算选用 double,这是因为 float 通常精度不够而且双精度浮点数和单精度浮点数的计算代价相差无几。事实上,对于某些机器来说,双精度运算甚至比单精度还快。long double 提供的精度在一般情况下是没有必要的,况且它带来的运算时消耗也不容忽视。
2.2
利率(rat):float,本金(principal):long long,付款(payment):long long。
利率一般是小数点后保留四位有效数字,float 合适;本金和付款使用最大的带符号整型表示。
2.3
main.cpp
#include <iostream>
int main() {
std::cout << sizeof(unsigned) << std::endl;
std::cout << sizeof(int) << std::endl;
std::cout << "**************************" << std::endl;
unsigned u = 10, u2 = 42;
std::cout << u2 - u << std::endl;
std::cout << u - u2 << std::endl;
std::cout << "**************************" << 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;
return 0;
}
4
4
**************************
32
4294967264
**************************
32
-32
0
0
Process finished with exit code 0
unsigned int 占 4 字节
int 占 4 字节
**************************
u2 - u = 42 - 10 = 32
u - u2 = 10 - 42 = unsigned(-32) = 4294967264
分析一:
32 的二进制表示为 0000 0000 0000 0000 0000 0000 0010 0000
-32 的二进制补码表示为 1111 1111 1111 1111 1111 1111 1110 0000
也就是说 -32 的十六进制表示为 FFFF FFE0
若将 FFFF FFE0 转换为 unsigned int 类型,则高位的符号位全部成为了数值位,FFFF FFE0 按无符号数转换成十进制数为 4294967264。
分析二:
把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。
unsigned int 占 4 字节,4 字节的无符号数模为 4294967296。
-32 + 4294967296 = 4294967264
**************************
i2 - i = 42 - 10 = 32
i - i2 = 10 - 42 = -32
当一个算术表达式中既有无符号数又有 int 值时,那个 int 值就会转换成无符号数。
i - u = unsigned(10) - 10 = 0
u - i = 10 - unsigned(10) = 0
分析:
有符号数 10 转换成无符号数 10,因为 10 是正数,符号位为 0,故转换成无符号数时,符号位不会对对应无符号数产生副作用。
Process finished with exit code 0
程序中 return 0 语句的副作用
2.4
请看 2.3
2.5
(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 int |
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 |
(d) 10, 10u, 10., 10e-2
字面值 | 数据类型 |
---|---|
10 | 整型字面值,类型是 int |
10u | 无符号整型字面值,类型是 unsigned int |
10. | 浮点数字面值,类型是 double |
10e-2 | 浮点数字面值,类型是 double |
2.6
有区别
int month = 9, day = 7; // 9 和 7 是十进制数,正确的赋值
// 以 0 开始表示八进制,八进制各位范围是 0 ~ 7,赋值不合法
int month = 09, day = 07;
2.7
字面值 | 数据类型 |
---|---|
"Who goes with F145rgus? 12" | 字符串字面值,包含两个八进制转义序列 |
3.14e1L | 浮点数字面值,类型是 long double |
1024f | 浮点数字面值,类型是 float |
3.14L | 浮点数字面值,类型是 long double |
2.8
分别写了八进制和十六进制的版本
#include <iostream>
int main() {
std::cout << "-------- Oct --------" << std::endl;
std::cout << "6211512";
std::cout << "---------------------" << std::endl;
std::cout << "62";
std::cout << "11";
std::cout << "115";
std::cout << "12";
std::cout << "-------- Hx ---------" << std::endl;
std::cout << "x32x4dxa";
std::cout << "---------------------" << std::endl;
std::cout << "x32";
std::cout << "x9";
std::cout << "x4d";
std::cout << "xa";
return 0;
}
// 运行结果
-------- Oct --------
2M
---------------------
2 M
-------- Hx ---------
2M
---------------------
2 M
Process finished with exit code 0
2.9
// (a)
// std::cin >> int input_value; // Error: variable must be defined before using for input
int input_value;
std::cin >> input_value;
// (b)
// int i = { 3.14 }; // Error: loss information in list initialization
double d = { 3.14 }; // OK
double d2 = { 3 }; // OK
// (c)
// double salary = wage = 9999.99; // Error
double salary, wage;
salary = wage = 9999.99;
// (d)
int i2 = 3.14; // OK, `i2` is 3
2.10
#include <iostream>
std::string global_str;
int global_int;
int main () {
int local_int;
std::string local_str;
std::cout << "global_str = " << global_str << std::endl;
std::cout << "global_int = " << global_int << std::endl;
std::cout << "local_str = " << local_str << std::endl;
std::cout << "local_int = " << local_int << std::endl;
return 0;
}
// 运行结果
global_str =
global_int = 0
local_str =
local_int = -406316616
Process finished with exit code 0
2.11
extern int ix = 1024; // 定义
int iy; // 声明并定义 iy
extern int iz; // 声明 iz 而非定义 iz
2.12
int double = 3.14; // 错误,c++关键字 double 不能作为标识符
int _; // 正确
int catch-22; // 错误,标识符由数字、字母和下划线组成
int 1_or_2 = 1; // 错误,标识符必须以字母或下划线开头
double Double = 3.14; // 正确,标识符长度没有限制,大小写敏感
2.13
程序中 j 的值是 100
2.14
#include <iostream>
int main () {
int i = 100, sum = 0;
for (int i = 0; i != 10; ++i)
sum += i;
std::cout << i << " " << sum << std::endl;
return 0;
}
// 运行结果
100 45
Process finished with exit code 0
2.15
#include <iostream>
int main () {
// 正确,隐式的将 1.01 转换成 1
int ival= 1.01;
// 错误,除 2.4.1 节(第 55 页)和 15.2.3 节(第 534 页)将
// 要介绍的两种例外情况,引用的类型都要和与之绑定的对象严格匹
// 配。而且,引用只能绑定在对象上,而不能与字面值或某个表达式的
// 计算结果绑定在一起,相关原因将在 2.4.1 节详述
int &rval1 = 1.01;
// 正确
int &rval2 = ival;
// 错误,引用必须被初始化,且初始值必须是一个对象
int &rval3;
return 0;
}
2.16
#include <iostream>
int main () {
int i = 0, &r1 = i;
double d = 0, &r2 = d;
// 正确
r2 = 3.14159;
std::cout << r2 << "
";
// 正确,发送自动类型转换
r2 = r1;
std::cout << r2 << '
';
// 正确,发送自动类型转换
i = r2;
std::cout << i << std::endl;
// 正确,发送自动类型转换
r1 = d;
std::cout << r1 << std::endl;
return 0;
}
2.17
#include <iostream>
int main () {
int i, &ri = i;
i = 5;
ri = 10;
std::cout << i << " " << ri << std::endl;
return 0;
}
// 运行结果
10 10
Process finished with exit code 0
2.18
#include <iostream>
int main () {
int ival = 23, *ip = &ival;
std::cout << "ival = " << ival << " " << "*ip = " << *ip << std::endl;
*ip = 24;
std::cout << "ival = " << ival << " " << "*ip = " << *ip << std::endl;
ival = 25;
std::cout << "ival = " << ival << " " << "*ip = " << *ip << std::endl;
return 0;
}
// 运行结果
ival = 23 *ip = 23
ival = 24 *ip = 24
ival = 25 *ip = 25
Process finished with exit code 0
2.19
引用
引用(reference)为对象起了另外一个名字,引用类型引用(refers to)另外一种类型。
int ival = 1024;
int &refVal = ival; // refVal 指向 ival(是 ival 的另一个名字)
int &refVal2; // 报错:引用必须被初始化
int &refVal4 = 10; // 错误:引用类型的初始值必须是一个对象
// 除特殊情况,其他所有引用的类型都要和与之绑定的对象严格匹配
double dval = 3.14;
int &refVal5 = dval; // 错误:此处引用类型的初始值必须是 int 型对象
指针
指针(pointer)是"指向(point to)"另外一种类型的复合类型。与引用类似,指针也实现了对其他对象的间接访问。然而指针与引用相比又有很多不同点。
-
指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象;
-
指针无须在定义时赋初值;
-
因为引用不是对象,没有实际地址,所以不能定义指向引用的指针;
-
除特殊情况,其他所有指针的类型都要和它所指向的对象严格匹配;
-
空指针的定义方法:
int *p1 = nullptr; // C++11 新标准引人的方法,推荐使用 int *p2 = 0; // 直接将 p2 初始化为字面值常量 0 // 需要首先 #include cstdlib int *p3 = NULL;
三种定义空指针的方法等价,推荐使用第一种。
-
虽然指针无须在定义时赋初值,但是记得初始化所有指针。如果实在不清楚指针应该指向何处,就把它初始化为 nullptr 或者 0,这样程序就能检测并知道它没有指向任何具体的对象了。使用未经初始化的指针是引发运行时错误的一大原因。
2.20
将 (i) 的值修改为 (i^2)
#include <iostream>
int main () {
int i = 42;
int *p1 = &i;
*p1 = *p1 * *p1;
std::cout << "i = " << i << " " << "*p1 = " << *p1;
return 0;
}
// 运行结果
i = 1764 *p1 = 1764
Process finished with exit code 0
2.21
int i = 0;
double *dp = &i; // 错误,指针的类型都要和它所指向的对象严格匹配
int *ip = i; // 错误,忘记取地址符 &
int *p = &i; // 正确
2.22
if (p)
means if the pointer p
is not null.
if (*p)
means if the object pointed by the pointer is not false (which means the object is not null or zero etc.).
#include <iostream>
int main () {
int i = 42;
int *p = &i;
std::cout << "p = " << p << " " << "*p = " << *p << std::endl;
if (p)
std::cout << "true" << std::endl;
else
std::cout << "false" << std::endl;
if (*p)
std::cout << "true" << std::endl;
else
std::cout << "false" << std::endl;
i = 0;
p = &i;
std::cout << "p = " << p << " " << "*p = " << *p << std::endl;
if (p)
std::cout << "true" << std::endl;
else
std::cout << "false" << std::endl;
if (*p)
std::cout << "true" << std::endl;
else
std::cout << "false" << std::endl;
return 0;
}
// 运行结果
p = 0x7ffee92f3818 *p = 42
true
true
p = 0x7ffee92f3818 *p = 0
true
false
Process finished with exit code 0
2.23
No, you can't. Because it would be expensive to maintain meta data about what constitutes a valid pointer and what doesn't, and in C++ you don't pay for what you don't want.
See answer here.
However, a smart pointer can be used to tell if it points to a valid object.
注:此题解答来自
2.24
int main() {
int i = 42;
void *p = &i; // OK, a `void *` pointer can point to any type
//long *lp = &i; // Error, a `long *` pointer can not point to `int *`
return 0;
}
2.25
#include <iostream>
int main() {
{
int* ip, i, &r = i; // `ip` is `int *`, `i` is `int`, `r` is `int &`
std::cout << "(a)" << std::endl;
std::cout << "ip " << typeid(ip).name() << std::endl;
std::cout << "i " << typeid(i).name() << std::endl;
std::cout << "r " << typeid(r).name() << std::endl;
// Note that `typeid` will lose the `const` qualifier and reference
}
{
int i, *ip = 0; // `i` is `int`, `ip` is `int *`
std::cout << "(b)" << std::endl;
std::cout << "i " << typeid(i).name() << std::endl;
std::cout << "ip " << typeid(ip).name() << std::endl;
}
{
int* ip, ip2; // `ip` is `int *`, `ip2` is `int`
std::cout << "(c)" << std::endl;
std::cout << "ip " << typeid(ip).name() << std::endl;
std::cout << "ip2 " << typeid(ip2).name() << std::endl;
}
return 0;
}
// 运行结果
(a)
ip Pi
i i
r i
(b)
i i
ip Pi
(c)
ip Pi
ip2 i
Process finished with exit code 0
注:
Understanding the output of typeid().name()
2.26
本题所有语句应该被看作是顺序执行的,即形如:
const int buf;
int cnt = 0;
const int sz = cnt;
++cnt;
++sz;
(a)是非法的,const 对象一旦创建后其值就不能改变,所以 const 对象必须初始化。该句应修改为 const int buf = 10。
(b)和(c)是合法的。
(d)是非法的,sz 是一个 const 对象,其值不能被改变,当然不能执行自增操作。
2.27
(a)是非法的,非常量引用 r 不能引用字面值常量 0。修改方法:
int i = -1, &r = i;
// 或
const int i = -1, &r = 0;
// 或
const int i = -1, &r = i;
(b)是合法的,p2 是一个常量指针,p2 的值永不改变,即 p2 永远指向变量 i2。
(c)是合法的,i 是一个常量,r 是一个常量引用,此时 r 可以绑定到字面值常量 0。
(d)是合法的,p3 是一个常量指针,p3 的值永不改变,即 p3 永远指向变量 i2;同时 p3 指向的是常量,即我们不能通过 p3 改变所指对象的值。
(e)是合法的,p1 指向一个常量,即我们不能通过 p1 改变所指对象的值。
(f)是非法的,引用本身不是对象,因此不能让引用恒定不变。
(g)是合法的,i2 是一个常量,r 是一个常量引用。
2.28
(a)是非法的,cp 是一个常量指针,因其值(指针存储的那个地址)不能被改变,所以必须被初始化。
(b)是非法的,cp2 是一个常量指针,因其值不能被改变,所以必须被初始化。
(c)是非法的,ic 是一个常量,因其值不能被改变,所以必须被初始化。
(d)是非法的,p3 是一个常量指针,因其值不能被改变,所以必须被初始化;同时 p3 所指向的是常量,即我们不能通过 p3 改变所指对象的值。
(e)是合法的,但是 p 没有指向任何实际的对象。
2.29
(a)是合法的,常量 ic 的值赋给了非常量 i。
(b)是非法的,普通指针 p1 指向了一个常量,从语法上说,p1 的值可以随意改变,显然是不合理的。因为 p1 改变了,p1 所指对象因该也同时改变了,但是 p1 所指对象是一个常量,不能被修改。
(c)是非法的,普通指针 p1 指向了一个常量,错误情况与上一条类似。
(d)是非法的,p3 是一个常量指针,不能被赋值。
(e)是非法的,p2 是一个常量指针,不能被赋值。
(f)是非法的,ic 是一个常量,不能被赋值。
2.30
顶层 const 表示任意的对象是常量,而底层 const 与指针和引用等复合类型的基本类型部分有关。
解答:v2 和 p3 是顶层 const,分别表示一个整型常量和一个整型常量指针;p2 和 r2 是底层 const,分别表示它们所指(所引用)的对象是常量。
2.31
本题考查顶层 const 和底层 const 对于拷贝操作的影响。
解答:
在执行拷贝操作时,顶层 const 和底层 const 区别明显。其中,顶层 const 不受影响,这是因为拷贝操作并不会改变被拷贝对象的值。底层 const 的限制则不容忽视,拷入和拷出的对象必须具有相同的底层 const 资格,或者两个对象的数据类型必须能够转换。一般说来,非常量可以转换成常量,反之则不行。
r1 = v2; 是合法的,r1 是一个非常量引用,v2 是一个常量(顶层 const),把 v2 的值拷贝给 r1 不会对 v2 有任何影响。
p1 = p2; 是非法的,p1 是普通指针,指向的对象可以是任意值,p2 是指向常量的指针(底层 const),令 p1 指向 p2 所指的内容,有可能错误地通过 p1 改变 p2 所指常量的值。
p2 = p1; 是合法的,与上一条语句相反,p2 可以指向一个非常量,只不过我们不会通过 p2 更改它所指的值。
p1 = p3; 是非法的,p3 包含底层 const 定义(p3 所指的对象是常量),不能把 p3 的值赋给普通指针。
p2 = p3; 是合法的,p2 和 p3 包含相同的底层 const,p3 的顶层 const 则可以忽略不计。
2.32
int null = 0, *p = &null;
// 或
int null = 0, *p = nullptr;
2.33
【出题思路】
本题旨在考察 auto 说明符与复合类型、常量混合使用时的各种情形。首先,使用引用其实是使用引用的对象,所以当引用被用作初始值时,真正参与初始化的其实是引用对象的值,编译器以引用对象的类型作为 auto 的推断类型。其次,auto 一般会忽略掉顶层 const,同时保留底层 const。
【解答】
前 3 条赋值语句是合法的,原因如下:
r 是 i 的别名,而 i 是一个整数,所以 a 的类型推断结果是一个整数;ci 是一个整型常量,在类型推断时顶层 const 被忽略掉了,所以 b 是一个整数;cr 是 ci 的别名,而 ci 是一个整型常量,所以 c 的类型推断结果是一个整数。因为 a、b、c 都是整数,所以为其赋值 42 是合法的。
后 3 条赋值语句是非法的,原因如下:
i 是一个整数,&i 是 i 的地址,所以 d 的类型推断结果是一个整型指针;ci 是一个整型常量,&ci 是一个整型常量的地址,所以 e 的类型推断结果是一个指向整型常量的指针;ci 是一个整型常量,所以 g 的类型推断结果是一个整型常量引用。因为 d 和 e 都是指针,所以不能直接用字面值常量为其赋值;g 绑定到了整型常量,所以不能修改它的值。
2.34
【出题思路】
本题旨在考察 auto 说明符与复合类型、常量混合使用时的各种情形。
【解答】
基于上一个练习中的变量和语句编写的程序如下所示:
#include <iostream>
using namespace std;
int main(int argc, char *argv[]) {
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;
const auto f = ci;
auto &g = ci;
cout << a << " " << b << " " << c << " " << d << " " << e << " " << g << std::endl;
a = 42;
b = 42;
c = 42;
// d = 42;
// e = 42;
// g = 42;
cout << a << " " << b << " " << c << " " << d << " " << e << " " << g << std::endl;
return 0;
}
// 运行结果
0 0 0 0x7ffee3e7088c 0x7ffee3e70878 0
42 42 42 0x7ffee3e7088c 0x7ffee3e70878 0
Process finished with exit code 0
2.35
【出题思路】
本题旨在考察 auto 说明符与复合类型、常量混合使用时的各种情形。
【解答】
由题意可知,i 是一个整型常量,j 的类型推断结果是整数,k 的类型推断结果是整型常量,p 的类型推断结果指向整型常量的指针,j2 的类型推断结果是整型常量,k2 的类型推断结果是整型常量的引用。
i
isconst int
j
isint
k
isconst int &
p
isconst int *
j2
isconst int
k2
isconst int &
用于验证的程序是:
#include <iostream>
#include <typeinfo>
int main() {
const int i = 42;
auto j = i;
const auto &k = i;
auto *p = &i;
const auto j2 = i, &k2 = i;
std::cout << typeid(i).name() << std::endl; // i
std::cout << typeid(j).name() << std::endl; // i
std::cout << typeid(k).name() << std::endl; // i
std::cout << typeid(p).name() << std::endl; // PKi
std::cout << typeid(j2).name() << std::endl; // i
std::cout << typeid(k2).name() << std::endl; // i
std::cout << std::endl;
std::cout << std::boolalpha; // 接下来的输出把 bool 值显示成 true/false
std::cout << "i and j have same type? "
<< std::is_same<decltype(i), decltype(j)>::value << std::endl;
std::cout << "i and k have same type? "
<< std::is_same<decltype(i), decltype(k)>::value << std::endl;
std::cout << "i and j2 have same type? "
<< std::is_same<decltype(i), decltype(j2)>::value << std::endl;
std::cout << "j and j2 have same type? "
<< std::is_same<decltype(j), decltype(j2)>::value << std::endl;
std::cout << "k and k2 have same type? "
<< std::is_same<decltype(k), decltype(k2)>::value << std::endl;
return 0;
}
// 运行结果
// print i means int, and PKi means pointer to const int.
i
i
i
PKi
i
i
i and j have same type? false
i and k have same type? false
i and j2 have same type? true
j and j2 have same type? false
k and k2 have same type? true
Process finished with exit code 0
2.36
【出题思路】
本题旨在考察 decltype 与引用的关系。对于 decltype 所用的表达式来说,如果变量名加上一对括号,得到的类型与不加括号时会有不同。具体来说,如果 decltype 使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,编译器就会把它当成一个表达式,从而推断得到引用类型。
【解答】
在本题的程序中,初始情况下 a 的值是 3,b 的值是4。decltype(a) c = a;
使用的是一个不加括号的变量,因此 c 的类型就是 a 的类型,即该语句等同于 int c = a;
,此时 c 是一个新整型变量,值为 3。decltype((b)) d = a;
使用的是一个加了括号的变量,因此 d 的类型是引用,即该语句等同于 int &d = a;
,此时 d 是变量 a 的别名。
执行 ++c;
++d;
时,变量 c 的值自增为 4,因为 d 是 a 的别名,所以 d 自增 1 意味着 a 的值变成了 4。当程序结束时,a、b、c、d 的值都是 4。
#include <iostream>
#include <typeinfo>
int main() {
int a = 3, b = 4;
decltype(a) c = a;
decltype((b)) d = a;
++c;
++d;
std::cout << typeid(c).name() << std::endl; // int
std::cout << typeid(d).name() << std::endl; // int &
std::cout << c << std::endl; // 4
std::cout << d << std::endl; // 4
return 0;
}
2.37
【出题思路】
decltype 的参数既可以是普通变量,也可以是一个表达式。当参数是普通变量时,推断出的类型就是该变量的类型;当参数是表达式时,推断出的类型是引用。
【解答】
根据 decltype 的上述性质可知,c 的类型是 int,值为 3;表达式 a = b 作为decltype 的参数,编译器分析表达式并得到它的类型作为 d 的推断类型,但是不实际计算该表达式,所以 a 的值不发生改变,仍然是 3;d 的类型是 int &,d 是 a 的别名,值是 3;b 的值一直没有发生改变,为 4。
2.38
【出题思路】
auto 和 decltype 是两种类型推断的方式,本题旨在考察二者的区别和联系。
【解答】
auto 和 decltype 的区别主要有三方面:
- auto 类型说明符用编译器计算变量的初始值来推断其类型,而 decltype 虽然也让编译器分析表达式并得到它的类型,但是不实际计算表达式的值。
- 编译器推断出来的 auto 类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。例如,auto 一般会忽略掉顶层 const,而把底层 const 保留下来。与之相反,decltype 会保留变量的顶层 const。
- 与 auto 不同,decltype 的结果类型与表达式密切相关,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。如果 decltype 使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,则编译器将推断得到引用类型。
一个用以说明的示例如下所示:
#include <iostream>
#include <typeinfo>
int main() {
int a = 3;
auto c1 = a;
decltype(a) c2 = a;
decltype((a)) c3 = a;
const int d = 5;
auto f1 = d;
decltype(d) f2 = d;
std::cout << typeid(c1).name() << std::endl; // int
std::cout << typeid(c2).name() << std::endl; // int
std::cout << typeid(c3).name() << std::endl; // int &
std::cout << typeid(f1).name() << std::endl; // int
std::cout << typeid(f2).name() << std::endl; // const int
c1++;
c2++;
c3++;
f1++;
// f2++; // 错误:f2 是整型常量,不能执行自增操作
std::cout << a << " " << c1 << " " << c2 << " " << c3 << " " << f1
<< " " << f2 << std::endl;
return 0;
}
// 运行结果
i
i
i
i
i
4 4 4 4 6 5
Process finished with exit code 0
对于第一组类型推断来说,a 是一个非常量整数,c1 的推断结果是整数,c2 的推断结果也是整数,c3 的推断结果由于变量 a 额外加了一对括号所以是整数引用。c1、c2、c3 依次执行自增操作,因为 c3 是变量的 a 的别名,所以 c3 自增等同于 a 自增,最终 a、c1、c2、c3 的值都变为 4。
对于第二组类型推断来说,d 是一个常量整数,含有顶层 const,使用 auto 推断类型自动忽略掉顶层 const,因此 f1 的推断结果是整数;decltype 则保留顶层 const,所以 f2 的推断结果是整数常量。f1 可以正常执行自增操作,而常量 f2 的值不能被改变,所以无法自增。
附:
- What is the difference between auto and decltype(auto) when returning from a function?
- decltype vs auto
2.39
#include <iostream>
struct Foo { /* 此处为空 */} // 注意:没有分号
int main() {
return 0;
}
// 编译无法通过
main.cpp:3:33: error: expected ';' after struct
【出题思路】
本题旨在考查类定义的语法规范,尤其要注意类体结束之后的分号必不可少。
【解答】
该程序无法编译通过,原因是缺少了一个分号。因为类体后面可以紧跟变量名以示对该类型对象的定义,所以在类体右侧表示结束的花括号之后必须写一个分号。
稍作修改,程序就可以编译通过了。
#include <iostream>
struct Foo { /* 此处为空 */};
int main() {
return 0;
}
Process finished with exit code 0
2.40
【出题思路】
类的设计源于实际应用,设计 Sales_data 类的关键是理解在销售过程中应该包含哪些数据元素,同时为每个元素设定合理的数据类型。
【解答】
原书中的程序包含 3 个数据成员,分别是 bookNo(书籍编号)、units_sold(销售量)、revenue(销售收入),新设计的 Sales_data 类细化了销售收入的计算方式,在保留 bookNo 和 units_sold 的基础上,新增了 sellingprice(零售价、原价)、saleprice(实售价、折扣价)、discount(折扣),其中 discount = saleprice / sellingprice。
#include <iostream>
struct Sales_data {
std::string bookNo; // 书籍编号
unsigned units_sold = 0; // 销售量
double sellingprice = 0.0; // 零售价
double saleprice = 0.0; // 实售价
double discount = 0.0; // 折扣
};
int main() {
return 0;
}
2.41
Sales_data.h
#ifndef SALESDATA_H
// we're here only if SALESDATA_H has not yet been defined
#define SALESDATA_H
// Definition of Sales_data class and related functions goes here
#include <iostream>
#include <string>
// 头文件不应包含 using 声明
// using namespace std;
class Sales_data {
// 友元函数
friend std::istream &operator>>(std::istream &, Sales_data &);
// 友元函数
friend std::ostream &operator<<(std::ostream &, const Sales_data &);
// 友元函数
friend bool operator<(const Sales_data &, const Sales_data &);
// 友元函数
friend bool operator==(const Sales_data &, const Sales_data &);
public: // 构造函数的 3 种形式
Sales_data() = default;
Sales_data(const std::string &book) : bookNo(book) {}
Sales_data(std::istream &is) { is >> *this; }
Sales_data &operator+=(const Sales_data &);
std::string isbn() const { return bookNo; }
private:
std::string bookNo; // 书籍编号,隐式初始化为空串
unsigned units_sold = 0; // 销售量,显式初始化为 0
double sellingprice = 0.0; // 原始价格,显式初始化为 0.0
double saleprice = 0.0; // 实售价格,显式初始化为 0.0
double discount = 0.0; // 折扣,显式初始化为 0.0
};
inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) {
return lhs.isbn() == rhs.isbn();
}
Sales_data operator+(const Sales_data &, const Sales_data &);
inline bool operator==(const Sales_data &lhs, const Sales_data &rhs) {
return lhs.units_sold == rhs.units_sold &&
lhs.sellingprice == rhs.sellingprice &&
lhs.saleprice == rhs.saleprice &&
lhs.isbn() == rhs.isbn();
}
inline bool operator!=(const Sales_data &lhs, const Sales_data &rhs) {
return !(lhs == rhs); // 基于运算符 == 给出 != 的定义
}
Sales_data &Sales_data::operator+=(const Sales_data &rhs) {
units_sold += rhs.units_sold;
saleprice = (rhs.saleprice * rhs.units_sold + saleprice * units_sold)
/ (rhs.units_sold + units_sold);
if (sellingprice != 0)
discount = saleprice / sellingprice;
return *this;
}
Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {
Sales_data ret(lhs); // 把 lhs 的内容拷贝到临时变量 ret 中,这种做法便于运算
ret += rhs; // 把 rhs 的内容加入其中
return ret; // 返回 ret
}
std::istream &operator>>(std::istream &in, Sales_data &s) {
in >> s.bookNo >> s.units_sold >> s.sellingprice >> s.saleprice;
if (in && s.sellingprice != 0)
s.discount = s.saleprice / s.sellingprice;
else
s = Sales_data(); // 输入错误,重置输入的数据
return in;
}
std::ostream &operator<<(std::ostream &out, const Sales_data &s) {
out << s.isbn() << " " << s.units_sold << " "
<< s.sellingprice << " " << s.saleprice << " " << s.discount;
return out;
}
#endif
重写练习 1.20
main.cpp
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main() {
Sales_data book;
std::cout << "请输入销售记录:" << std::endl;
// 输入格式:ISBN 售出本数 原始价格 实际售价
while (std::cin >> book) {
std::cout << "ISBN、售出本数、原始价格、实售价格、折扣为:" << book << std::endl;
}
return 0;
}
// 运行结果
请输入销售记录:
0-201-78345-X 3 20.00 19.00
ISBN、售出本数、原始价格、实售价格、折扣为:0-201-78345-X 3 20 19 0.95
0-202-78345-X 4 30.00 29.00
ISBN、售出本数、原始价格、实售价格、折扣为:0-202-78345-X 4 30 29 0.966667
重写练习 1.21
main.cpp
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main() {
Sales_data trans1, trans2;
std::cout << "请输入两条 ISBN 相同的销售记录:" << std::endl;
std::cin >> trans1 >> trans2;
if (compareIsbn(trans1, trans2))
std::cout << "汇总信息:ISBN、销售本数、原始价格、实售价格、折扣为:"
<< "
" << trans1 + trans2 << std::endl;
else
std::cout << "两条销售记录的 ISBN 不同" << std::endl;
return 0;
}
// 运行结果
请输入两条 ISBN 相同的销售记录:
0-201-78345-X 3 20.00 19.00
0-201-78345-X 5 20.00 18.00
汇总信息:ISBN、销售本数、原始价格、实售价格、折扣为:
0-201-78345-X 8 20 18.6154 0.930769
Process finished with exit code 0
重写练习 1.22
main.cpp
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main() {
Sales_data total, trans;
std::cout << "请输入几条 ISBN 相同的销售记录:" << std::endl;
// 每次输入要求 ISBN 相同,售出本数随便,原始价格相同,实售价格随便
if (std::cin >> total) {
while (std::cin >> trans)
if (compareIsbn(total, trans))
total += trans;
else { // ISBN 不同
std::cout << "当前书籍 ISBN 不同" << std::endl;
break;
}
std::cout << "有效汇总信息:ISBN、售出本数、原始价格、实售价格、折扣为:"
<< "
" << total << std::endl;
} else {
std::cout << "没有数据" << std::endl;
return -1;
}
return 0;
}
// 运行结果(实售价格为均值,具体可查看 Sales_item.h 运算符 += 重载的实现
请输入几条 ISBN 相同的销售记录:
0-201-78345-X 3 20.00 19.00
0-201-78345-X 5 20.00 29.00
0-202-78345-Y 2 200.00 199.00
当前书籍 ISBN 不同
有效汇总信息:ISBN、售出本数、原始价格、实售价格、折扣为:
0-201-78345-X 8 20 22.8462 1.14231
Process finished with exit code 0
重写练习 1.23
#include <iostream>
#include "Sales_data.h"
using namespace std;
int main() {
Sales_data trans1, trans2;
int num = 1; // 记录当前书籍的销售记录总数
std::cout << "请输入若干销售记录:" << std::endl;
if (std::cin >> trans1) {
while (std::cin >> trans2) {
if (compareIsbn(trans1, trans2)) // ISBN 相同
num++;
else { // ISBN 不同
std::cout << trans1.isbn() << " 共有 "
<< num << " 条销售记录" << std::endl;
trans1 = trans2;
num = 1;
}
std::cout << trans1.isbn() << " 共有 "
<< num << " 条销售记录" << std::endl;
}
} else {
std::cout << "没有数据" << std::endl;
return -1;
}
return 0;
}
// 运行结果
请输入若干销售记录:
0-201-78345-X 3 20.00 19.00
0-201-78345-X 2 20.00 18.00
0-201-78345-X 共有 2 条销售记录
0-201-78345-X 5 20.00 19.00
0-201-78345-X 共有 3 条销售记录
0-202-78345-Y 3 220.00 129.00
0-201-78345-X 共有 3 条销售记录
0-202-78345-Y 共有 1 条销售记录
0-201-78345-X 7 20.00 19.99
0-202-78345-Y 共有 1 条销售记录
0-201-78345-X 共有 1 条销售记录
注:1.6 节练习与重写练习 1.23 相同
2.42
2.41 已实现该题功能