本篇博客用来总结自己在学习/复习C++的过程中遇到的新的/没掌握的细节。章节划分就参照《c++ Primer》一书的章节。
第七章、类
7.5.3 默认构造函数的作用
一个类对象被在被默认初始化或值初始化时执行默认构造函数。
默认初始化发生在:
在块作用域内不使用任何初始值定义一个非静态变量或者数组时;
1
2
3
4class Foo{
int i; // 执行默认初始化,调用合成的默认构造函数
int arr[20]; // 执行默认初始化,调用合成的默认构造函数
}当一个类的本身含有类类型的成员且使用合成的默认构造函数时;也意味着类类型的成员没有显式地定义构造函数:
1
2
3
4
5
6
7// 由于该类的类成员Foo1使用合成的默认构造函数,因此Foo使用默认初始化
class Foo{
int i = 0;
struct Foo1{
double d;
}
}当类类型的成员没有在构造函数初始化列表中显式初始化时:
1
2
3
4
5
6
7
8
9
10
11class Foo{
public:
Foo() = default;
Foo(int ii): i(ii) {} // 未在初始化列表中显式初始化Foo1
private:
int i;
struct Foo1{
Foo1(): d(0) {}
double d;
}
}
值初始化发生在:
数组初始化过程中,提供的初始值数量小于数字大小时(默认填0);
不使用初始值定义一个局部静态变量时;
使用形如
T()
的表达式显式的请求值初始化时,其中T为类型名。比如vector
接受单参数的构造函数就是就用这个参数对他的元素初始化器进行值初始化。1
2
3
4
5
6
7
8
9class Foo{
int arr[100] = {0, 0}; // 类初始化时执行值初始化
int arr[10][10] = {{0, 0}, {0}}; // 类初始化时执行值初始化
int getcount(){
static int cnt; // 值初始化为0
vector<int> v(10); // vector被值初始化
return cnt;
}
}
特别地,当一个类含有类类型成员,但是该类成员的类类型没有默认构造函数,并且包含这个类的类也没有在构造函数中显式初始化这个成员,那么出现错误:这个类类型成员没有初始值。
1 | class NoDefault{ |
使用默认构造函数
如果想要使用类的默认构造函数实例化一个类:
1 | class Foo; |
7.5.4 隐式的类类型转换
如果构造函数只接受一个实参,那么它实际上定义了一个将该实参隐式转换为类类型的隐式转换机制,这种构造函数有时被称为转换构造函数。
1 | class Foo{ |
抑制构造函数定义的隐式转换
再要求隐式转换的程序上下文中,将构造函数声明为explicit
来阻止隐式转换
1 | class Foo{ |
explicit
关键字只对单参数构造函数有效,因为多实参构造函数不能进行隐式转换,也就无需使用。
只能在类内声明构造函数时使用explicit关键字,类外定义时不应重复。
explicit构造函数只能用于直接初始化
由于在拷贝初始化的过程中发生了隐式转换,因此explicit的构造函数只能用于直接初始化。
为转换显式地使用构造函数
我们可以使用explicit构造函数进行显式的强制转换
1 | foo.get(Foo("11111")); // 正确,隐式转string,显式转Foo |
标准库中含有显式构造函数的类
- 接受
const char*
的string
构造函数,不是explicit
的; - 接受一个容量参数的
vector
构造函数,是explicit
的。
第十三章
13.6.1 右值引用
右值引用的引入是为了支持移动操作,所谓右值引用是指必须绑定到右值的引用,即:能且只能绑定到一个即将要销毁的对象上,可以实现自由地将一个右值引用的资源“移动”到另一个对象中。
右值引用的特性与常规引用的特性完全相反:可以将一个右值引用绑定到
- 要求转换的表达式
- 字面常量
- 返回右值的表达式
上,但是不能把一个右值引用绑定到左值上:
1 | int i = 42; |
返回左值引用的函数:赋值、下标、解引用、前置递增/递减 运算符
返回非引用类型的函数:算数、关系、位、后置递增/递减 运算符
Note:由于变量是一个只有运算对象而没有运算符的表达式,而变量表达式都是左值,因此我们不能将一个右值引用绑定到一个右值引用类型的表达式上。