c++

C++Primer答疑流水账

Posted by SlothSimon's Skytree on August 15, 2016

推荐两个C++Primer阅读辅助资料(or社区)

  • Discussion-for-Cpp: 关于C++Primer的一些疑问可以提到这里,一般当天或者隔天就会有解答。

  • CppPrimer: C++Primer第五版的答案,正确性比网上散乱的blog靠谱多了,也有详尽的解释,如果有不懂的也可以发issue提问。和上面的社区是同一群组织者。

chap 1&2

1. 字(word)、字长(word size)、字节(byte)、比特(bit)

  • 比特(bit):计算机以比特序列存储数据,每个比特非0即1。(电路的晶体管??)
  • 字节(byte):可寻址的最小内存块
  • 字(word):存储的基本单位
  • 字长(word size):CPU一次操作(一个时钟周期)可以处理的二进制比特数(0或1),1字长 = 1 bit。大多数计算机以2的整数次幂个比特座位块来处理内存。字长≠地址总线宽度≠数据总线宽度

Q:为什么没有24位的计算机? 在知乎评论里看到的疑问,查了资料思索良久得出一个自己的答案: A:最初CPU造出来时用的晶体管只有通断两种状态,因此寄存器都是2的n次方,而寄存器一次最大能处理多少数据就决定了CPU一次操作可以处理的字长。据说以后会有三态的量子计算机,但也是3的n次方。而24位不是任何整数的n次方,除非有一个东西可以稳定表达24种状态,否则估计永远不会有24位的计算机吧。可以参考这个问答:为什么计算机没有 3 位、5 位或者 6 位等? - 中央处理器 (CPU)

chap 3

1. “类型直接实现到计算机硬件中”

在3.1节开头前,书中一段话是这么陈述的:

第2章介绍的内置类型是由C++语言直接定义的。这些类型,比如数字和字符,体现了大多数计算机硬件本身具备的能力。标准库定义了另外一组具有更高级性质的类型,它们尚未直接实现到计算机硬件中。

@pezy大大认为翻译不严谨,给出了原文如下:

These types represent facilities present in most computer hardware, such as numbers or characters. The standard library defines a number of additional types of a higher-level nature that computer hardware usually does not implement directly.

与中文翻译的区别是加粗的两个副词。

内置类型之所以可以直接到大多数硬件,是因为大多数硬件编码本身就有基本类型,例如CPU里面有ALU(整型运算器)、FPU(浮点运算器)。而更高级的类型,理论上是可以实现到硬件中,但是会降低性能,性价比太低。

相关issue传送门

chap 4

2. 强制类型转换

ex4.37中需要将C中的类型转换变化成C++标准的强制类型转换。其中,给出的标准答案如下:

int i; char *pc; void *pv;
i = int(*pc);   // i = static_cast<int>(*pc);
pc = (char*)pv; // pc = reinterpret_cast<char*>(pv);

Q:对于pc到i的转换,为什么不用reinterpret_cast呢?不是和原文reinterpret_cast的例子差不多吗? A:原文的例子是地址之间的转换,转换前后都是指针;而i = int(pc)不是pc到i的转换,而是pc到i的转换,是值的转换,自然是用static_cast。

Q:为什么pv到pc不用static_cast?不是和原文static_cast的例子差不多吗? A:原文的例子是指void还原成double,也就是说编程的人有百分百的把握这个void类型是从double转换来的,此处的static_cast是还原;而pv与pc的转换中,我们完全不清楚pv原来是什么,这是一种有风险的强制转换,不是还原,所以用reinterpret_cast。

相关issue传送门

# chap 5

3. 初始化

5.3节给了一个switch内部的变量定义的例子,如下:

case true:
    string file_name;    //  错误:控制流绕过一个隐式初始化的变量
    int ival = 0;
    int jval;    //  正确:因为jval没有初始化
    break;
case false:
    jval = next_num();
    if (file_name.empty())
        // ...

对于file_name和jval为什么一个会隐式初始化而另一个不会感到很困惑。 得到pezy大大提示,重读2.2节的默认初始化小节算是稍微有点理解了。

内置类型在函数体外会被默认初始化,但是在函数体内并不会,于是就会“未初始化”、“未定义的”,可能是任何值,即使在mac上输出是0,也不代表这个变量的值是合法的。

class type是否会隐式初始化,取决于该类的设计(14、15章会细讲)。而string允许隐式初始化,所以在这个switch里面,jval不会出错,file_name会出错。

相关issue传送门

另外,值初始化算默认初始化的一种。

chap 7

4. 聚合类和字面值常量类的作用?

节7.5.5和7.5.6介绍了什么是聚合类和字面值常量类。另外ex8.12中答案说我们这里需要聚合类,为什么需要聚合类却没有讲。

不是需要聚合类, 是因为聚合类就可以满足需求了. 聚合类的优势就是 simple, 直接反映数据结构, 清晰. 尤其面对非面向对象的场景, 聚合类就太常见了. 并非任何情况都需要封装, 继承和多态的.

应用场景就是平时为了方便省力用多了这种类,于是统一取名叫聚合类吧。而字面值常量类则是因为类内初始化static数据成员条件过于苛刻,于是导致了这种类的诞生。

相关issue传送门

5. 类内静态成员的定义

7.6节静态成员的类内初始化

如果某个静态成员的应用场景仅限于编译器可以替换它的值的情况,则一个初始化的const或constexpr static不需要分别定义。相反,如果我们将它用于值不能替换的场景中,则该成员必须有一条定义语句。 例如,如果period的唯一用途就是定义daily_tbl的维度,则不需要在Account外面专门定义period。此时,如果我们忽略了这条定义,那么对程序非常微小的改动也可能造成编译错误,因为程序找不到该成员的定义语句。举个例子,当需要把Account::period传递给一个接受const int&的函数时,必须定义period。

Q:感觉表达的意思有点不对,period已经在类内定义过了,无论什么情况都不能在类外再定义,文中的意思应该是指在类外声明吧? A:

这并不是一次又一次定义的问题, 而是, 定义是否分离的问题.这段话要表达的是, 如果period 就是内部用, 那么定义就用不着分离了, 类内的初始化就足够了. 但如果你需要在类外用这个, 就会出现编译错误了. 但很糟糕的是, 书上给的这个例子不是很好, integer 类型在 In-Class 初始化 static 成员中本身就是个特殊的存在.

举的例子在相关issue的链接中。

相关issue传送门

6. 为什么静态vector应在类外初始化?

ex7.58中答案表示,“we may not specify an in-class initializer inside parentheses.”,然后vector vec在类外初始化了,为什么? A:

vector 既不是 const int, 又不是 constexpr literal type, 它怎么可以类内初始化?

相关issue传送门

chap 9

7. 为什么链表的额外内存开销大?

节9.1 因为链表的每个节点有一个额外的成员变量,指向下一个节点的指针(和指向上一个节点的指针),所以带来额外的内存开销。可研读数据结构了解细节。

8. 为什么位与判断奇偶时,右边使用十六进制的0x1而不直接用十进制的1?

ex 9.20

为了更清晰的表明这是利用 bit 的手段来判断奇偶数. (为了让代码更清晰, 明确). 原理详见 How do I check if an integer is even or odd using bitwise operators

相关issue传送门