0. C++中的5种初始化
初始化(initialization)的定义是:
当对象在创建时获得了一个特定的值,我们说这个对象被初始化(initialized)了。
在C++中,有5种初始化的定义
- 默认初始化(default initialization):
int a
;string s
; - 值初始化(value initialization):
vector<int> ivec(2)
;vector<string> svec(2)
; - 直接初始化(direct initialization):
string s("abc")
;string s(10, 'c')
; - 拷贝初始化(copy initialization):
string s("abc"), s1(s)
; - 列表初始化(list initialization):
int a = {1}
;string str = {'a', 'b', 'c'}
;
接下来分别讲解以上初始化的方式。
1. 默认初始化
在定义变量时没有指定初始值,则执行默认初始化:
int a; // a的值与定义a的位置有关
string s; // s是一个空串,调用了默认构造函数
默认初始化是很危险的(除非你胸有成竹),因为它分很多种情况。
当变量类型是内置类型时:
- 定义在函数体外:初始化0;
- 定义在函数体内:未初始化(uninitialized),值是未定义的,由编译器决定。
(注:不同编译器做法不一致,详细请看第6部分)
当变量类型是自定义类型时,会调用类的默认构造函数,由类自行决定它内部的各个属性的初始值。
- 定义为成员变量:未初始化,值是未定义的;
- 定义为成员函数的局部变量:未初始化,值是未定义的。
2. 值初始化
值初始化的行为,与定义在函数体外部的内置类型类似,会自动赋予内置变量某个值。若是自定义类型,则会调用类型的默认构造函数。不同的是,这种初始化发生的地方,不会出现未定义的变量值。
vector<int> ivec(2); // ivec中此时有两个int,且都为0
vector<string> svec(2); // svec中有两个空的string,即'\0'
第1行代码,会将ivec中的2个int元素初始化为0;第2行代码,会将svec中的两个string元素初始化为空串。
3. 直接初始化
在标识符后面使用括号,括号内填入对应的类型的值或构造函数的参数,即可使用直接初始化。
string s("abc"); // s = "abc"
string s(10, 'c'); // s = "cccccccccc"
取名“直接”,实际上是要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。
4. 拷贝初始化
拷贝初始化很容易与直接初始化混淆。
string s("abc"), s1 = s;
拷贝初始化通常使用拷贝构造函数来完成,通常发生在以下几种情况:
- 使用“=”定义变量,=右边是相同类类型
- 将一个对象作为实参传递给一个非引用类型的形参
- 从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表初始化一个数组中的元素或一个聚合类中的成员
(谨记:非引用、赋值)
5. 列表初始化
列表初始化是C++11新引入的初始化方式。
int a = {1};
string str = {'a', 'b', 'c'};
vector<int> ivec = {1, 2, 3};
使用列表初始化,有一个优点是,若用来初始化的值在执行类型转换时存在丢失信息的风险,则编译器将会报错。
上例中,若将{1, 2, 3}
写成{1l, 2l, 3l}
(即long类型)编译器是会报错的。
实际上我们也可以定义自己的列表初始化构造函数,使用的是std::initializer_list
。
#include <vector>
#include <initializer_list>
class MyClass {
std::vector<int> v;
public:
MyClass() : v(2, 1){};
MyClass(std::initializer_list<int> l) {
std::cout << "initialzier list constructor called" << std::endl;
v.insert(v.end(), l.begin(), l.end());
}
void dump() {
for (const auto &e : v) {
std::cout << e << ", ";
}
std::cout << std::endl;
}
};
int main() {
MyClass mc1;
mc1.dump(); // 1, 1
MyClass mc2({1, 2, 3, 4, 5});
mc2.dump(); // 1, 2, 3, 4, 5
MyClass mc3{6, 7, 8};
mc3.dump(); // 6, 7, 8
MyClass mc4 = {11, 12, 13};
mc4.dump(); // 11, 12, 13
return 0;
}
6. 特别的…
以上内容,来自C++ Primer第5版,仅供参考。
若有错误,欢迎大家讨论纠正。
经过编者测试,对于内置类型的默认初始化在不同平台差别相当大。
- 在macOS 10.14.5,xcode11.2.1,使用CLion IDE,定义在普通函数内部和成员函数内部的内置类型可以进行值初始化;只有定义在类内的成员变量才会进行未定义的默认初始化。
- 在Windows 10,在VS中,使用普通函数内部和成员函数内部的内置类型会出现:
error C4700: 使用了未初始化的局部变量
- 类中的成员变量会进行未定义的默认初始化。
- 在Windows 10,MinGW中,使用CLion IDE,其行为和1相同
可以看出,默认初始化是C++留给我们的类很大自由度的一项操作,允许我们自行定义类类型中成员变量的初始化方式,如果我们定义的类允许默认初始化,C++并不进行检查出error来告知我们。