Week 2 思考题¶
约 1179 个字 49 行代码 1 张图片 预计阅读时间 5 分钟
Abstract
思考题旨在提出场景帮助大家了解一些比较零碎的知识。这些内容比较简单且没有那么重要,因此没有放在课程内容中。
本周内容一览¶
- 类的定义
- 定义引入新的类型
- class-key 通常不必要
- 声明和定义
- 定义是声明的一种
- 类的成员
- type alias
this
- 函数内联
- 构造函数
- 建立起某种「保证」
- 如何无参或有参地构造对象
new
,delete
,new[]
,delete[]
- implicitly-declared default constructor
= default;
,= delete
- member initializer lists
- delegating constructor
- default member initializer
- 函数默认参数和函数重载
- 重载解析
- 为什么 C++ 引入了
nullptr
- 析构函数
- 用来回收资源
- 为什么析构函数无法重载
- 构造和析构的时机和顺序
- lifetime
- storage duration
- automatic
- static
- dynamic
- 构造和析构的时机和顺序
思考题¶
1 Elaborated type specifiers¶
在 C 语言中,这样的定义是合法的:
struct x { /* something */ };
int x;
这里的 x
不会引起歧义,因为 C 语言规定使用结构体的时候必须带上 struct
关键字。因此 x
就使用的是整型变量,而 struct x
使用的就是结构体。
但是,C++ 为了使得用户定义的类不是二等公民,允许用户不使用 class-key
即 struct
和 class
。
问题 :在 C++ 中如何解决上面的歧义问题呢?
答案请参看 4.1 类的定义 中的「Note」块。
2 nested-name-specifier¶
课程中,我们看到了类的定义的形式:
问题 :这里的「nested-name-specifier」是什么呢?
答案请参看 4.1 类的定义。
3 name equivalence¶
假如有这样的定义:
struct X { int a; };
struct Y { int a; };
X a1;
Y a2;
int a3;
复习 C 语言中结构体的内存布局。我们可以得知,a1
, a2
, a3
的内存布局是一致的。
问题 :请问 a1 = a2;
或者 a1 = a3;
的赋值在 C/C++ 中是合法的吗?
答案请参看 4.1 类的定义。
4 Forward Declaration¶
请看下面的代码:
1 2 3 4 5 6 7 8 |
|
在这段代码中,Y
和 X
分别有一个成员,是指向对方类型的指针。我们可以看到,第 2 行有一个编译错误,因为此时 X
是一个未知的名字。
问题 :请问这种情况在 C++ 中是否有解决方案呢?如果有,是什么呢?
答案请参看 4.1 类的定义 中的「Forward Declaration」块。
请注意理解「incomplete type」的含义以及相关限制。
5 Injected Class Name¶
我们在课程中提到,类的定义引入新的类型。那么,这个类型是从何处开始被引入的呢?是从定义结束后被引入的吗?
事实上并不是的,否则这段代码的第 2 行就会像前面那个问题的代码一样报出 unknown Node
的编译错误:
1 2 3 4 |
|
问题 :C++ 中是如何给出相关规定的?结合上一个问题的答案考虑,下面的代码是合法的吗:
struct Node {
Node next;
// ...
};
答案请参看 4.1 类的定义 中的「Injected Class Name」块。
6 关于 Foo f = Foo();
¶
问题 :Foo f = Foo();
的 Foo()
是调用构造函数的函数调用表达式吗?
作为一个提示,我们考虑这样一个事实:如果把一个函数的构造函数放在类外定义,我们需要写 Foo::Foo
作为标识符:
struct Foo { Foo(); };
Foo::Foo() { puts("ctor called"); }
那么,假如 Foo()
是函数调用表达式的话,为什么不需要写 Foo::Foo()
呢?
顺着这个思路,大家可以尝试写 Foo f = Foo::Foo();
,看看会发生什么!
答案请参看 4.3 构造函数 中的「Foo() 是调用构造函数的函数调用表达式吗?」块。
7 关于 implicitly-declared default ctor & dtor¶
考虑这个问题:
struct Foo { Foo(int){} };
class Bar { Foo f; };
即,Foo
类型没有 default constructor(即可以无参调用的构造函数);而 Bar
类型中有一个 Foo
类型的子对象 f
。Bar
类型并没有提供构造函数。
根据我们所说,如果没有提供构造函数,则编译器自动生成一个 implicitly-declared default constructor;但是这里自动生成的构造函数并不能完成 f
的初始化。这种情况怎么办呢?
类似地,考虑以下几个场景:
Foo
的默认构造函数是有歧义的:
struct Foo {
Foo(){}
Foo(int x = 1){}
};
class Bar { Foo f; };
Foo
的析构函数是 deleted 的:
struct Foo { ~Foo() = delete; };
class Bar { Foo f; };
Foo
的析构函数是 private 的:
class Foo { ~Foo() = default; };
class Bar { Foo f; };
或者,这个问题可以对称延伸到析构函数:
class Foo { ~Foo() = default; };
struct Bar {
Foo f;
Bar(){}
};
在这些时候,会发生什么事呢?
答案请看 4.4 析构函数 末尾的「defaulted ctor & dtor 被 delete 的情况」块。
练习题¶
1¶
尝试回答,下面的类的设计有什么问题?
class Container {
int * data;
unsigned capa;
// ...
public:
Container(unsigned c = 512) : capa(c * 4), data(new int[capa]) {}
};