【C++ Primer】第一章 开始 (练习)
【C++ Primer】第二章 变量和基本类型 (练习)
【C++ Primer】第三章 字符串、向量和数组 (练习)
【C++ Primer】第四章 表达式 (练习)
【C++ Primer】第五章 语句 (练习)
【C++ Primer】第六章 函数 (练习)
【C++ Primer】第七章 类 (练习)
【C++ Primer】第八章 IO 库 (练习)
【C++ Primer】第九章 顺序容器 (练习)
【C++ Primer】第十章 泛型算法 (练习)
【C++ Primer】第十一章 关联容器 (练习)
类型 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) 的有效数字多。
计算按揭贷款时,对于利率、本金和付款分别应选择何种数据类型?说明你的理由。
解答
实际应用中,利率、本金和付款既可能为整数,也可能为普通实数,故应选择一种浮点类型来表示。在可选的浮点类型 float、double 和 long double 中,float 通常精度不够,且 double 与之计算代价相差无几 (对某些机器而言 double 运算甚至比 float 运算更快) 而具有更高精度;而 long double 提供的精度通常没有必要,况其带来的大量运行时消耗也不容忽视。综上分析,应选择 double 类型。
读程序写结果。
解答
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.3,u - u2 较复杂,其余尚可。
指出下述字面值的数据类型并说明每一组内几种字面值的区别:
(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
解答
下面两组定义是否有区别,如果有,请叙述之:
int month = 9, day = 7; int month = 09, day = 07;
解答
有区别。第一行定义了两个十进制整型数,第二行定义了两个八进制整型数。但注意,就八进制而言,逢八进一,而不应出现第二行中的 09。
下述字面值表示何种含义?它们各自的数据类型是什么?
(a) "Who goes with F\145rgus?\012" (b) 3.14e1L (c) 1024f (d) 3.14L
解答
请利用转义序列编写一段程序,要求先输出 2M,然后转到新一行。修改程序使其先输出 2,然后输出制表符,再输出 M,最后转到新一行。
解答
#include
int main()
{
std::cout << 2 << "\115\012";
std::cout << 2 << "\t\115\012";
return 0;
}
解释下列定义的含义,对于非法的定义,请说明错在何处并将其改正。
(a) std::cin >> int input_value; (b) int i = { 3.14 }; (c) double salary = wage = 9999.99; (d) int i = 3.14;
解答
int input_value = 0;
std::cin >> input_value;
double i = { 3.14 };
double wage;
double salary = wage = 9999.99;
double i = 3.14;
下列变量的初值分别是什么?
std::string global_str; int global_int; int main() { int local_int; std::string local_str; }
指出下面的语句是声明还是定义:
(a) extern int ix = 1024; (b) int iy; (c) extern int iz;
解答
请指出下面的名字中哪些是非法的?
(a) int double = 3.14; (b) int _; (c) int catch-22; (d) int 1_or_2 = 1; (e) double Double = 3.14;
解答
下面程序中 j 的值是多少?
int i = 42; int main() { int i = 100; int j = i; }
解答
局部变量 j = 100,因为新建的局部变量 i (100) 覆盖了原先的全局变量 i (42)。除非使用作用域操作符 :: 将局部变量 j 的创建和初始化语句改为 int j = ::i; 即有 j = 42。
下面的程序合法吗?如果合法,它将输出什么?
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 之和。
下面的哪个定义是不合法的?为什么?
(a) int ival = 1.01; (b) int &rval1 = 1.01; (c) int &rval2 = ival; (d) int &rval3;
解答
考察下面的所有赋值然后回答:哪些赋值是不合法的?为什么?哪些赋值是合法的?它们执行了哪些操作?
int i = 0, &r1 = i; double d = 0, &r2 = d; (a) r2 = 3.14159; (b) r2 = r1; (c) i = r2; (d) r1 = d;
解答
执行下面的代码段将输出什么结果?
int i, &ri = i; i = 5; ri = 10; std::cout << i << " " << ri << std::endl;
解答
该代码段将输出 10 10。注意,ri 作为引用必须在新建时得到初始化,但被引用对象 i 却不一定要在新建时初始化。
编写代码分别改变指针的值以及指针所指对象的值。
解答
#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
说明 指针和引用的主要区别。
解答
指针和引用都能提供对其他对象的 间接访问,且在定义时均需满足 类型匹配原则 (但各有两种例外情况)。然而,在具体实现细节上,二者存在很大的不同:
请叙述下面这段代码的作用。
int i = 42; int *p1 = &i; // 等号右侧, & 为取地址符 *p1 = *p1 * *p1; // 等号右侧, * 为解引用符
解答
请解释下述定义。在这些定义中有非法的吗?如果有,为什么?
int i = 0; (a) double *dp = &i; (b) int *ip = i; (c) int *p = &i;
解答
假设 p 是一个 int 型指针,请说明下述代码的含义。
if (p) // ... if (*p) // ...
解答
给定指针 p,你能知道它是否指向了一个合法的对象吗?如果能,叙述判断的思路;如果不能,也请说明原因。
解答
不能,因为首先要确定指针 p 是否合法,才能判断 p 所指向的对象是否合法。
在下面这段代码中为什么 p 合法而 lp 非法?
int i = 42; void *p = &i; long *lp = &i;
解答
因为指针变量 lp 的类型为 long,与指向对象 i 的类型 int 不一致。注意,在 C++ 中,继承自 C 的 void * 才可以指向任何类型的对象,而其他指针类型必须要与所指对象严格匹配。
说明下列变量的类型和值。
(a) int* ip, i, &r = i; (b) int i, *ip = 0; (c) int* ip, ip2;
解答
下面哪些语句是合法的?如果不合法,请说明为什么?
(a) const int buf; (b) int cnt = 0; (c) const int sz = cnt; (d) ++cnt; ++sz;
解答
下面的哪些初始化是合法的?请说明原因。
(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 i, *const cp; (b) int *p1, *const p2; (c) const int ic, &r = ic; (d) const int *const p3; (e) const int *p;
解答
假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。
(a) i = ic; (b) p1 = p3; (c) p1 = ⁣ (d) p3 = ⁣ (e) p2 = p1; (f) ic = *p3;
解答
对于下面的这些语句,请说明对象被声明成了顶层 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在每个例子中有何体现。
(a) r1 = v2; (b) p1 = p2; (c) p2 = p1; (d) p1 = p3; (e) p2 = p3;
解答
下面的代码是否合法?如果非法,请设法将其修改正确。
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;
利用本节定义的变量,判断下列语句的运行结果。
// P62 a=42; b=42; c=42; d=42; e=42; g=42;
解答
基于上一个练习中的变量和语句编写一段程序,输出赋值前后变量的内容,你刚才的推断正确吗?如果不对,请反复研读本节的示例直到你明白错在何处为止。
解答
见习题 2.33。
判断下列定义推断出的类型是什么,然后编写程序进行验证。
const int i = 42; auto j = i; const auto &k = i; auto *p = &i; const auto j2 = i, &k2 = 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
关于下面的代码,请指出每一个变量的类型以及程序结束时它们各自的值。
int a = 3, b = 4; decltype(a) c = a; decltype((b)) d = a; ++c; ++d;
解答
赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。根据这一特点,请指出下面的代码中每一个变量的类型和值。
int a = 3, b = 4; decltype(a) c = a; decltype(a = b) d = a;
解答
说明由 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;
编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关的信息,以后可能会有用。
struct Foo { /* 此处为空 */ } // 注意:没有分号 int main() { return 0; }。
解答
在 int 处提示应输入“ ; ” 。
根据自己的理解写出 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; // 净利润
// ...
};
使用你自己的 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
}
}
根据你自己的理解重写一个 Sales_data.h 头文件,并以此为基础重做 2.6.2节(第67页)的练习。
参考文献:《C++ Premier 5th》