对于一种初始化形式,我们说它属于哪种初始化方式,指的是它最表层看出来的初始化方式,也就是说它的类型可以变成其它类型,但是其初始化的方式还是不会变。
而对于给定的形式,可能该形式会根据类型的不一样(包括是否内置类型,构造函数的定义等)而属于其它的初始化方式,甚至,有的会执行两次初始化。但这些都不会影响最表层的初始化方式。
这是在不使用初始化器构造变量时执行的初始化。形式为:
T object ;
new T ;- 拥有
自动、静态或线程局域存储期的变量声明不带初始化器时; - 基类或非静态数据成员未在构造函数初始化器列表提及,且调用了该构造函数时。
- 若 T 是数组类型,则每个数组元素都被默认初始化;
T t[10];//对于每个t都是执行默认初始化- 若T是类类型(如string或其他自定义类型),不管定义于何处,都会执行默认构造函数。如果该类没有默认构造函数(隐式产生的也算)比如构造函数为delete,则会引发错误。如果要定义const T 类类型的变量,则构造函数必须是由用户显式提供(不能是默认生成的)
class T
{
public:
T()=delete;
private:
int member1;
};
class A
{
private:
int member1;
};
int main()
{
T t; // 错误用法,没有默认构造函数
A a; // 正确用法,a执行默认初始化,调用隐式生成的默认构造函数,对于其成员`未在构造函数初始化器列表提及`也是执行默认初始化, 其值不确定
const A a; // 错误用法,const 类型,用户没有显式提供构造函数
}- 对于非类类型,如果是
静态线程局域(包括了全局)变量将先进行零初始化,否则其值未确定。
int x1; // 零初始化为 0 然后默认初始化不做任何事
std::string s; // 零初始化为不确定值 再默认初始化为 ""
int main()
{
static int x2; // 零初始化为0 然后默认初始化不做任何事
int x3; // 未进行初始化,其值不确定
}变量以空初始化器构造时进行的初始化。具体有以下几种形式:
T();
new T ();
Class::Class(...) : member() { ... }
T object {};
new T {};
Class::Class(...) : member{} { ... } 注意 T object();是函数声明而非值初始化。
- 若 T 是数组类型,则数组的每个元素都被值初始化;
new T[3](); // 数组每个元素执行值初始化
std::vector<int> v(3); // 值初始化每个元素 每个元素(int类型)的值为 0- 所有情况下,若使用空花括号对 {} 且 T 是聚合类型,则进行聚合初始化而非值初始化。
- 若 T 是
无默认构造函数或拥有用户提供默认构造函数或删除的构造函数的类类型,则默认初始化对象 - 若 T 是 拥有默认构造函数的类类型,而默认构造函数既非用户提供亦非被删除(即它可以是拥有隐式定义或默认的默认构造函数),则先零初始化,然后若有
非平凡的默认构造函数则默认初始化对象;
以上几种情况举例如下:
#include <iostream>
using namespace std;
class A
{
public:
int member1;
};
class B
{
public:
int member1;
B(const B& b){} // 只有复制构造函数,无默认构造函数
};
class C
{
public:
int member1;
C()=delete; // 默认构造函数为删除
};
class D
{
public:
int member1;
D(){} // 用户提供的默认构造函数
};
class PA
{
public:
A a;
PA(int k):a(){} // 值初始化a
PA():a{}{} // 值初始化a
};
int main(int argc, char *argv[])
{
A(); // 值初始化
A{}; // 值初始化
A a{}; // 值初始化
new A(); // 值初始化
new A{}; // 值初始化
cout<<C{}.member1<<endl; // 使用空花括号对 {} 且 T 是聚合类型,则进行聚合初始化而非值初始化。 初始化member1 为零
// 以下为值初始化
cout<<A().member1<<endl; // 用法正确 隐式构造函数,则零初始化 member1 为零
cout<<B().member1<<endl; // 用法错误 无默认构造函数,只有复制构造函数,执行默认初始化,默认初始化发现没有默认构造函数 所以出错
cout<<C().member1<<endl; // 用法错误 默认构造函数为删除,执行默认初始化,默认初始化发现没有默认构造函数 所以出错
cout<<D().member1<<endl; // 用法正确 用户提供的默认构造函数,执行默认初始化,member1也是默认初始化 其值不确定
return 0;
}
- 否则(例如内置类型),对象被零初始化
聚合类是一种没有用户定义的构造函数,没有私有(private)和保护(protected)非静态数据成员,没有基类,没有虚函数。其实就是只有成员变量的结构体。
以调用构造函数的形式显示初始化对象,有以下几种形式:
T object ( arg ); // A kkk(2); A kkk{2} 以表达式或花括号初始化器列表 (C++11 起)的非空带括号列表初始化
T object ( arg1, arg2, ... ); // A aaa(1,2);
T object { arg }; // int jjj{1} 以单个花括号环绕的初始化器初始化 非类类型对象
T ( other ) // 用函数转型或以带括号表达式列表初始化纯右值临时量
T ( arg1, arg2, ... );
static_cast< T >( other ) // 用 static_cast 表达式初始化纯右值临时量
new T(args, ...) // 用带非空初始化器的 new 表达式初始化拥有动态存储期的对象
Class::Class() : member(args, ...) {...} // 用构造函数初始化器列表初始化基类或非静态成员
[arg](){...} //在 lambda 表达式中从以复制捕捉的变量初始化闭包对象成员- 若T为类类型 直接调用其构造函数初始化
- 否则,若 T 是非类类型但源类型是类类型 调用源类的构造函数 然后使用用户定义的转换 转换为正在初始化的对象
- 否则,使用标准转换。
从另一对象初始化对象,其语法为:
T object = other;
f(other)
return other;
throw object;
catch (T object)
T array[N] = {other}; //作为聚合初始化的一部分,初始化提供了初始化器的每个元素 - 首先,若 T 为类类型 且 初始化器为纯右值表达式 ,而其无 cv 限定类型是与 T 相同的类,就以初始化器表达式自身初始化目标对象(也就是移动构造函数)
- 若 T 为类类型且 other 的类型的无 cv 限定版本是 T 或是从 T 导出的类,则检测 T 的非显式构造函数,并由重载解决选择最佳匹配。然后调用构造函数初始化该对象。 (也就是 复制构造函数)
- 否则(若 T 与 other 的类型均不是类类型),若有需要,则使用标准转换,将 other 的值转换成 T 的无 cv 限定版本。
设置对象的初始值为零,主要由以下的几种形式:
static T object ;
T () ; // 1 int()
T t = {} ; // 2 B b = {}
T {} ; // 3 C{}
char array [ n ] = ""; - 对于每个拥有静态或线程局域存储期的具名变量,零初始化在其他任何初始化之前进行。
- 当字符数组以较短的字符串字面量初始化时,数组剩余部分被零初始化。
- 作为 1
非类类型 的 值初始化序列和 2无默认构造函数的类 类型成员 的 值初始化序列的一部分 以及 3未提供初始化器的聚合体元素 的 值初始化
零初始化的效果是:
- 若 T 是标量类型类型,则对象初值为显式转换到 T 的整数零。
- 若 T 是非联合体类类型,则所有基类和非静态数据成员别零初始化,且所有填充位被初始化为零位。忽略构造函数,若它存在。
- 若 T 是联合体类型,则首个具名非静态数据成员被零初始化且所有填充位被初始化到零位。
- 若 T 是数组类型,则每个元素都被零初始化
- 若 T 是引用类型,则不做任何事。