int int_arr[3];
如果数组成员没有默认构造函数,则编译器将发出错误。
常量变量的默认初始化
常量变量必须与初始值设定项一起声明。 如果它们是标量类型,则会导致编译器错误,而如果是具有默认构造函数的类类型,则将导致警告:
class MyClass{};
int main() {
//const int i2; // compiler error C2734: const object must be initialized if not extern
//const char c2; // same error
const MyClass mc1; // compiler error C4269: 'const automatic data initialized with compiler generated default constructor produces unreliable results
静态变量的默认初始化
如果静态变量的声明中没有初始值设定项,则初始化为 0(隐式转换为该类型)。
class MyClass {
private:
int m_int;
char m_char;
int main() {
static int int1; // 0
static char char1; // '\0'
static bool bool1; // false
static MyClass mc1; // {0, '\0'}
有关全局静态对象初始化的详细信息,请参阅 main 函数和命令行参数。
值初始化发生在以下情况下:
使用空大括号初始化来初始化已命名值
使用空圆括号或大括号初始化匿名临时对象
对象是使用 new
关键字和空圆括号或大括号来初始化的
值初始化执行以下操作:
对于至少有一个公共构造函数的类,将调用默认构造函数
对于没有声明构造函数的非联合类,该对象进行零初始化,并调用默认构造函数
对于数组,每个元素都进行值初始化
在其他所有情况下,变量进行零初始化
class BaseClass {
private:
int m_int;
int main() {
BaseClass bc{}; // class is initialized
BaseClass* bc2 = new BaseClass(); // class is initialized, m_int value is 0
int int_arr[3]{}; // value of all members is 0
int a{}; // value of a is 0
double b{}; // value of b is 0.00000000000000000
复制初始化
复制初始化是指使用一个不同的对象来初始化另一个对象。 在以下情况下,它会发生:
使用等号初始化变量
参数被传递给函数
从函数返回对象
引发或捕获异常
使用等号初始化非静态数据成员
在聚合初始化期间通过复制初始化来初始化类、结构和联合成员。 相关示例,请参阅聚合初始化。
下面的代码显示复制初始化的几个示例:
#include <iostream>
using namespace std;
class MyClass{
public:
MyClass(int myInt) {}
void set_int(int myInt) { m_int = myInt; }
int get_int() const { return m_int; }
private:
int m_int = 7; // copy initialization of m_int
class MyException : public exception{};
int main() {
int i = 5; // copy initialization of i
MyClass mc1{ i };
MyClass mc2 = mc1; // copy initialization of mc2 from mc1
MyClass mc1.set_int(i); // copy initialization of parameter from i
int i2 = mc2.get_int(); // copy initialization of i2 from return value of get_int()
throw MyException();
catch (MyException ex){ // copy initialization of ex
cout << ex.what();
复制初始化不能调用显式构造函数。
vector<int> v = 10; // the constructor is explicit; compiler error C2440: can't convert from 'int' to 'std::vector<int,std::allocator<_Ty>>'
regex r = "a.*b"; // the constructor is explicit; same error
shared_ptr<int> sp = new int(1729); // the constructor is explicit; same error
有时,如果类的复制构造函数被删除或不可访问,复制初始化将导致编译器错误。
直接初始化
直接初始化是使用(非空)大括号或圆括号的初始化。 不同于复制初始化,它可以调用显式构造函数。 在以下情况下,它会发生:
使用非空大括号或圆括号初始化变量
变量是使用 new
关键字和非空大括号或圆括号来初始化的
使用static_cast
初始化变量
在构造函数中,使用初始值设定项列表初始化基类和非静态成员
lambda 表达式中捕获的变量的副本中
以下代码显示直接初始化的一些示例:
class BaseClass{
public:
BaseClass(int n) :m_int(n){} // m_int is direct initialized
private:
int m_int;
class DerivedClass : public BaseClass{
public:
// BaseClass and m_char are direct initialized
DerivedClass(int n, char c) : BaseClass(n), m_char(c) {}
private:
char m_char;
int main(){
BaseClass bc1(5);
DerivedClass dc1{ 1, 'c' };
BaseClass* bc2 = new BaseClass(7);
BaseClass bc3 = static_cast<BaseClass>(dc1);
int a = 1;
function<int()> func = [a](){ return a + 1; }; // a is direct initialized
int n = func();
列表初始化
使用大括号内的初始值设定项列表初始化变量时,将发生列表初始化。 大括号内的初始值设定项列表可在以下情况中使用:
初始化变量
类是使用 new
关键字来初始化的
从函数返回对象
自变量传递给函数
直接初始化中的自变量之一
在非静态数据成员的初始值设定项中
在构造函数初始值设定项列表中
以下代码显示了列表初始化的一些示例:
class MyClass {
public:
MyClass(int myInt, char myChar) {}
private:
int m_int[]{ 3 };
char m_char;
class MyClassConsumer{
public:
void set_class(MyClass c) {}
MyClass get_class() { return MyClass{ 0, '\0' }; }
struct MyStruct{
int my_int;
char my_char;
MyClass my_class;
int main() {
MyClass mc1{ 1, 'a' };
MyClass* mc2 = new MyClass{ 2, 'b' };
MyClass mc3 = { 3, 'c' };
MyClassConsumer mcc;
mcc.set_class(MyClass{ 3, 'c' });
mcc.set_class({ 4, 'd' });
MyStruct ms1{ 1, 'a', { 2, 'b' } };
聚合初始化
聚合初始化是针对数组或类类型(通常为结构或联合)的一种列表初始化形式:
没有私有或受保护成员
没有用户提供的构造函数,显式默认或删除的构造函数除外
没有虚拟成员函数
在 Visual Studio 2015 及更早版本中,不允许在聚合中对非静态成员使用大括号或等于初始值设定项。 此限制已在 C++14 标准中删除,并在 Visual Studio 2017 中实现。
聚合初始值设定项包括含等号或不含等号的括号内的初始化列表,如以下示例所示:
#include <iostream>
using namespace std;
struct MyAggregate{
int myInt;
char myChar;
struct MyAggregate2{
int myInt;
char myChar = 'Z'; // member-initializer OK in C++14
int main() {
MyAggregate agg1{ 1, 'c' };
MyAggregate2 agg2{2};
cout << "agg1: " << agg1.myChar << ": " << agg1.myInt << endl;
cout << "agg2: " << agg2.myChar << ": " << agg2.myInt << endl;
int myArr1[]{ 1, 2, 3, 4 };
int myArr2[3] = { 5, 6, 7 };
int myArr3[5] = { 8, 9, 10 };
cout << "myArr1: ";
for (int i : myArr1){
cout << i << " ";
cout << endl;
cout << "myArr3: ";
for (auto const &i : myArr3) {
cout << i << " ";
cout << endl;
应会看到以下输出:
agg1: c: 1
agg2: Z: 2
myArr1: 1 2 3 4
myArr3: 8 9 10 0 0
已声明但未在聚合初始化期间显式初始化的数组成员将进行零初始化,如上面的 myArr3
中所示。
初始化联合和结构
如果联合没有构造函数,你可以使用单个值(或使用联合的另一个实例)对其初始化。 该值用于初始化第一个非静态字段。 结构初始化与其不同,其初始值设定项中的第一个值用于初始化第一个字段,第二个值用于初始化第二个字段,依此类推。 比较以下示例中联合和结构的初始化:
struct MyStruct {
int myInt;
char myChar;
union MyUnion {
int my_int;
char my_char;
bool my_bool;
MyStruct my_struct;
int main() {
MyUnion mu1{ 'a' }; // my_int = 97, my_char = 'a', my_bool = true, {myInt = 97, myChar = '\0'}
MyUnion mu2{ 1 }; // my_int = 1, my_char = 'x1', my_bool = true, {myInt = 1, myChar = '\0'}
MyUnion mu3{}; // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
MyUnion mu4 = mu3; // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
//MyUnion mu5{ 1, 'a', true }; // compiler error: C2078: too many initializers
//MyUnion mu6 = 'a'; // compiler error: C2440: cannot convert from 'char' to 'MyUnion'
//MyUnion mu7 = 1; // compiler error: C2440: cannot convert from 'int' to 'MyUnion'
MyStruct ms1{ 'a' }; // myInt = 97, myChar = '\0'
MyStruct ms2{ 1 }; // myInt = 1, myChar = '\0'
MyStruct ms3{}; // myInt = 0, myChar = '\0'
MyStruct ms4{1, 'a'}; // myInt = 1, myChar = 'a'
MyStruct ms5 = { 2, 'b' }; // myInt = 2, myChar = 'b'
初始化包含聚合的聚合
聚合类型可包含其他聚合类型,例如数组的数组、结构的数组等。 这些类型使用嵌套的大括号组进行初始化,例如:
struct MyStruct {
int myInt;
char myChar;
int main() {
int intArr1[2][2]{{ 1, 2 }, { 3, 4 }};
int intArr3[2][2] = {1, 2, 3, 4};
MyStruct structArr[]{ { 1, 'a' }, { 2, 'b' }, {3, 'c'} };
引用初始化
引用类型的变量必须使用引用类型派生自的类型的对象进行初始化,或使用可转换为引用类型派生自的类型的类型的对象进行初始化。 例如:
// initializing_references.cpp
int iVar;
long lVar;
int main()
long& LongRef1 = lVar; // No conversion required.
long& LongRef2 = iVar; // Error C2440
const long& LongRef3 = iVar; // OK
LongRef1 = 23L; // Change lVar through a reference.
LongRef2 = 11L; // Change iVar through a reference.
LongRef3 = 11L; // Error C3892
使用临时对象初始化引用的唯一方式是初始化常量临时对象。 初始化后,引用类型变量始终指向同一对象;不能将它修改为指向另一个对象。
尽管语法可以相同,但引用类型变量的引用和引用类型变量的赋值在语义上不同。 在前面的示例中,更改 iVar
和 lVar
的赋值看起来像初始化,但它们有不同的效果。 初始化指定引用类型变量指向的对象;赋值通过引用向引用目标对象赋值。
由于将引用类型的自变量传递给函数或从函数返回引用类型的值都是初始化,因此会正确初始化函数的形式自变量,就像它们是返回的引用一样。
只有在下列声明中才能在没有初始值设定项的情况下声明引用类型变量:
函数声明(原型)。 例如:
int func( int& );
函数返回类型声明。 例如:
int& func( int& );
引用类型类成员的声明。 例如:
class c {public: int& i;};
显式指定为 extern
的变量的声明。 例如:
extern int& iVal;
初始化引用类型变量时,编译器将使用下图中所示的决策关系图,在创建对对象的引用或创建引用所指向的临时对象之间做出选择:
决策图的开始为:初始值设定项是同一类型的 lvalue 还是从引用类型派生的类型? 如果是,引用会引用初始值设定项中指定的对象。 如果否,下一个决定是引用类型变量是否是正在初始化的常量 T 引用,并且能否将初始值设定项隐式转换为 T? 如果是,则创建临时变量,引用变量会成为该临时变量的名称。 如果否,则为错误。
引用类型初始化的决策图
可使用同一类型的 volatile
对象或使用尚未声明为 volatile
的对象初始化对 volatile
类型(已声明为 typename&identifier)的引用。volatile
但是,不能使用该类型的const
对象初始化它们。 同样,可使用同一类型的 const
对象(或具有到该类型的转换的任何内容或尚未声明为 const
的对象)初始化对 const
类型(已声明为 typename&identifier)的引用。const
但是,不能使用该类型的volatile
对象初始化它们。
未使用const
或volatile
关键字限定的引用只能使用既未声明为const
也未声明为volatile
的对象进行初始化。
外部变量的初始化
自动变量、静态变量和外部变量的声明可包含初始值设定项。 但是,仅当外部变量没有声明为extern
时,其声明中才可以包含初始值设定项。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:https://aka.ms/ContentUserFeedback。
提交和查看相关反馈