ReadingLab / Discussion-for-Cpp

C++ 中文讨论区
MIT License
89 stars 63 forks source link

第7章的几个疑问 #67

Closed SlothSimon closed 8 years ago

SlothSimon commented 8 years ago

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

节7.5.5和7.5.6介绍了两者,另外ex8.12答案中说我们这里需要聚合类,但是为什么需要聚合类却没有讲。 所以我想问问,二者的应用场景或者作用是什么?

2. 类内静态成员的定义

7.6节静态成员的类内初始化一小节中说:

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

就看代码例子的解释,似乎表达的意思不太对,period已经在类内定义过了,无论什么情况都不可能定义第二次,文中的意思应该是指声明吧?

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

ex7.58中答案表示:

we may not specify an in-class initializer inside parentheses. 然后vector vec在类外初始化了,为什么?

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

ex9.20遇到的小疑问,虽然死记硬背也行,但是实在想知道为什么不直接用十进制。

pezy commented 8 years ago

但是为什么需要聚合类却没有讲

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

二者的应用场景或者作用是什么?

聚合类上面已经说了, Literal Classes 就更明确了, 首先要理解什么是 literal types:

In addition to the arithmetic types, references, and pointers, certain classes are also literal types

除了书中提及的这些, 如果自定义一个类, 也想属于 LiteralType, 那么就需要遵循 7.5.6 中的若干标准. 使用场景便是你的第二个问题, 想要 In-Class 初始化 static 数据成员, 其实条件很苛刻:

However, we can provide in-class initializers for static members that have const integral type and must do so for static members that are constexprs of literal type.

要么是 const int, 要么是 constexprs of literal type, 这不就是 literal types 的使用场景吗?


就看代码例子的解释,似乎表达的意思不太对,period已经在类内定义过了,无论什么情况都不可能定义第二次,文中的意思应该是指声明吧?

这并不是一次又一次定义的问题, 而是, 定义是否分离的问题.

If the member is used only in contexts where the compiler can substitute the member’s value, then an initialized const or constexpr static need not be separately defined.

这段话要表达的是, 如果period 就是内部用, 那么定义就用不着分离了, 类内的初始化就足够了. 但如果你需要在类外用这个, 就会出现编译错误了.

但很糟糕的是, 书上给的这个例子不是很好, integer 类型在 In-Class 初始化 static 成员中本身就是个特殊的存在. 在一些编译器中(如 Clang), 即使 pass Account::period to a function that takes a const int&, 也并不需要有这一步定义: constexpr int Account::period;.

但如果你用这个例子:

#include <iostream>

class Account {
public:
    static double GetCircumference(const double& dR) { return 2 * dR * 3.1415926; }
    static constexpr double cd = 3.0;
};

// constexpr double Account::cd;

int main()
{
    std::cout << Account::GetCircumference(Account::cd) << std::endl;
}

在 Clang 下编译报的错误:

statics-39a798.o : error LNK2019: unresolved external symbol "public: static double const Account::cd" (?cd@Account@@2NB) referenced in function main
a.exe : fatal error LNK1120: 1 unresolved externals
clang++.exe: error: linker command failed with exit code 1120 (use -v to see invocation)

此时, 将上述代码中的注释取消, 再次编译, 则可以顺利通过了. 不过细心看一下报出的错误, 可以看到, 该错误出现在链接阶段. 再次说明了这属于定义(definition) 的范畴.

如果你不清楚声明与链接的区别, 请见: What is the difference between a definition and a declaration?

大体说来: 声明属于编译阶段检查的东西, 而定义则是属于链接阶段检查的东西.


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

请再仔细复习一下你的问题2 中涉及的知识点, 那些东西才可以在类内初始化? vector 既不是 const int, 又不是 constexpr literal type, 它怎么可以类内初始化?


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

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

SlothSimon commented 8 years ago

受益匪浅,感谢!

ghost commented 2 years ago

但如果你用这个例子:

#include <iostream>

class Account {
public:
    static double GetCircumference(const double& dR) { return 2 * dR * 3.1415926; }
    static constexpr double cd = 3.0;
};

// constexpr double Account::cd;

int main()
{
    std::cout << Account::GetCircumference(Account::cd) << std::endl;
}

那为什么这个例子里把&去掉又能通过呢,困扰好久了

pezy commented 2 years ago

那为什么这个例子里把&去掉又能通过呢,困扰好久了

请关注前面讨论时,书里的这句话:

If the member is used only in contexts where the compiler can substitute the member’s value, then an initialized const or constexpr static need not be separately defined.

去掉 & 之后,就不是传递引用,而是传递值。而函数调用的地方,Account::cd 编译器很容易就可以替换成 3.0 这个常量。所以自然也就不需要分离定义了。