1 背景
参考资料[1]P39提到,在C++语言中,初始化与赋值并不是同一个概念:
初始化:创建变量时赋予其一个初始值。
赋值:把对象(已经创建)的当前值擦除,而用一个新值来代替。
参考资料[1]P39指出,初始化是一个异常复杂的问题,因此有必要专门对这个问题进行总结。
2 列表初始化
参考资料[1]P39指出,作为C++11新标准的一部分,用花括号来初始化变量得到了全面应用(在此之前,只是在初始化数组的时候用到)。列表初始化有两种形式,如下所示:
int a = {0}; // 列表初始化方式1
int a{0}; // 列表初始化方式2
说明:上述的两种方式都可以将变量a初始化为0。
2.1 局限
当对内置类型使用列表初始化时,若初始值存在丢失的风险,编译将报错,如:
int a = 3.14; // 正确,虽然会丢失小数部分,但是编译器不报错。
int a = {3.14}; // 错误,因为将会丢失小数部分(其实,g++只是对此提示警告而已,并非错误)。
使用g++ 4.8.4对上述两个初始化方式分别进行测试,前者无错误无警告,但是后者提示警告(不是错误!):
warning: narrowing conversion of ‘3.1400000000000001e+0’ from ‘double’ to ‘int’ inside { } [-Wnarrowing]
2.2 拓展
参考资料[1]P39还提到,由花括号括起来的初始值,不仅可以用于初始化变量,还可以用于为对象(旧变量)赋新值。
int a = 1; // 定义变量,并且初始化为1
a = {3}; // 为变量a赋新值3
2.3 用处
可以用在任何需要变量初始化的地方,例如第6章的类成员初始化,以及在for()中定义的变量:
for (int i{0}; i < 10; i++) {
...
}
3 拷贝初始化
参考资料[1]P76指出,如果使用等号初始化一个变量,实际上执行的是拷贝初始化(copy initialization),编译器把等号右侧的初始值拷贝到新创建的对象中去:
string s1; // 默认初始化为空字符串
string s2 = s1; // 拷贝初始化,s2是s1的副本
4 直接初始化
参考资料[1]P76指出,如果在新创建的变量右侧使用括号将初始值括住(不用等号),则执行的是直接初始化(direct initialization):
string s1(); // 直接初始化为空字符串
string s2("hi"); // 直接初始化
string s3(3, 'c'); // 直接初始化,s2的内容是ccc
参考资料[1]P89指出,使用圆括号提供初值是用来构造(construct)对象,因此可以知道,所谓的直接初始化就是显式的调用相应的构造函数。参考资料[1]P262归纳了值初始化的3种情况:
a)在数组初始化过程中如果提供的初值数量少于数组大小时;
b)当不使用初始值定义一个局部静态变量时;
c)当通过T()形式的表达式显式地请求值初始化时(T是类型名)。
参考资料[1]P265指出,explicit构造函数只能用于直接初始化。
4.1 特例——vector
vector使用直接初始化时,需要指定一个重复次数:
vector<string> v1("hello", "world"); // 错误
vector<string> v2{"hello", "world"}; // 正确,可以列表初始化
vector<string> v3(2, "hello"); // 正确,直接初始化要指定一个重复次数,此处v3初始化为"helloheloo"
有时候,就算是用的是花括号,也是起到直接初始化的作用(相当于圆括号):
vector<string> v4{10}; // 直接初始化,v4有10默认初始化的元素
vector<string> v5{10, "hi"};// 直接初始化,v5有10个值为“hi”的元素
原则:参考资料[1]P89指出,初始化过程会尽可能地把花括号内的值当成是元素初始值列表来处理(列表初始化),只有无法执行列表初始化时才会考虑其他初始化方式。
5 默认初始化
参考资料[1]P40指出,如果定义变量时没有指定初值,则变量被默认初始化,具体值由变量类型及其所在位置决定。参考资料[1]P262归纳了发生默认初始化的3种情况。
5.1 内置类型
5.1.1 局部变量
5.1.1.1 非static局部变量
参考资料[1]P40指出,在函数体内部的内置类型变量(包括指针 )将不被初始化。参考资料[1]P47指出,其它内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值。参考资料[1]P185介绍自动对象的时候,也指出这个问题。
3.1.1.2 static局部变量
参考资料[1]P185指出,如果局部变量没有显式的初始化值,它将执行值初始化(不是默认初始化!),内置类型的局部静态变量初始化为0。
5.1.2 全局变量
初始化为0。
5.2 类类型
参考资料[1]P236指出,类通过一个特殊的构造函数来控制默认初始化过程,这个类叫做默认构造函数。默认构造函数无需任何实参。参考资料[1]P637也指出,每个类各自定义其初始化对象的方式(默认构造函数)。例如,std::string,不管是定义于局部还是全局,都会默认初始化为空。
string a; // 默认初始化为空字符串
tuple<size_t, size_t, size_t> threeD; // 使用tuple的默认构造函数对每个成员进行值初始化
5.3 例外情况
参考资料[1]P89指出,有些类不支持默认初始化,必须明确地提供初始值。根据参考资料[1]P236的说明可猜测,此类无默认构造函数。例如下面的定义,将会出现编译错误。
class Person {
public:
Person(string name, string addr): // 由于自定义了构造函数,编译器
name(this->name), addr(this->addr) // 不会为Person生成默认构造函数,
{} // 而Person中并没有显式定义默认
// 构造函数,因此Person没有默认
// 构造函数!
private:
const string name;
const string addr;
};
int main(void)
{
Person p;
}
使用g++4.8.4编译上述代码,提示:
test.cpp|36 col 9| error: no matching function for call to ‘Person::Person()’
6 值初始化
参考资料[1]P88提到了值初始化的例子,只提供vector对象容纳元素数量而不提供初始值,此时会创建一个值初始化的元素初值,并把它赋给容器中的所有元素。这个初值由vector对象中元素的类型决定:
vector<int> ivec(10); // 10个元素,每个都初始化为0
参考资料[1]P262则指出,对象
默认初始化或
值初始化时自动执行
默认构造函数。
7 类成员初始化
7.1 类内初始值
参考资料[1]P65指出,C++11新标准规定,可以为类内数据成员提供一个类内初始值。参考资料[1]P88则指出,类内初始值只能使用拷贝初始化和列表初始化(不能直接初始化)。参考资料[1]P246则指出,当我们提供一个类内初始值时,必须以符号=或者花括号表示。没有提供类内初始值的成员将被默认初始化。
class A {
int a{0}; // 列表初始化为0
int b = {1}; // 列表初始化为1
int c = 2; // 传统初始化为2
int d; // 默认初始化,具体值未定义
int e(3); // 错误:不能将初始值放在括号内(直接初始化)!
};
参考资料
[1]C++ Primer中文版(第5版)