初始化¶
参考:Initializers
初始化器指定了变量的初始值,c++
提供了多种初始化方式:
- 零初始化(
zero initialization
) - 默认初始化(
default initialization
) - 值初始化(
value initialization
) - 复制初始化(
copy initialization
) - 直接初始化(
direct initialization
) - 列表初始化(
list initialization
) - 集合初始化(
aggregate initialization
) - 引用初始化(
reference initialization
) - 外部变量初始化(
Initialization of external variables
)
零初始化¶
零初始化是将变量隐式转换为该类型的零值:
- 数值变量初始化为
0
(或0.0
) char
变量初始化为'\0'
- 指针变量初始化为
nullptr
- 数组、结构体等对象的成员初始化为零值
使用场景¶
在以下情况下发生零初始化:
- 程序启动时,所有具有
static
持续时间的变量 - 在值初始化期间,对于使用空大括号初始化的标量类型和类类型
- 对于只显式初始化了其部分成员的数组
示例¶
struct my_struct{
int i;
char c;
};
int i0; // zero-initialized to 0
int main() {
static float f1; // zero-initialized to 0.000000000
double d{}; // zero-initialized to 0.00000000000000000
int* ptr{}; // initialized to nullptr
char s_array[3]{'a', 'b'}; // the third char is initialized to '\0'
int int_array[5] = { 8, 9, 10 }; // the fourth and fifth ints are initialized to 0
my_struct a_struct{}; // i = 0, c = '\0'
}
默认初始化¶
- 对于类、结构体和共同体而言,其默认初始化是使用默认构造函数初始化,没有初始化表达式或使用
new
关键字时会调用默认初始化 - 对于标量变量而言,在没有初始化表达式定义时执行默认初始化,此时它们有不确定的值
- 对数组而言,在没有初始化表达式的情况下定义时是默认初始化的。当数组默认初始化时,其成员默认初始化并具有不确定的值
常量变量的默认初始化¶
常量变量必须和初始化器同时声明。否则,对于标量类型而言会造成编译错误;对有默认构造器的类类型而言,编译器会抛出warning
静态变量的默认初始化¶
未使用初始值设定项声明的静态变量初始化为0
(隐式转换)
示例¶
class MyClass {
private:
int m_int;
char m_char;
};
int main() {
// 标量类型的默认初始化,不缺定值
int i1;
float f;
char c;
// 数组的默认初始化,不缺定值
int int_arr[3];
// 常量变量的初始化,必须加上初始化器
// error
// const MyClass mc1;
// 静态变量的初始化,默认为0
}
值初始化¶
- 对于至少有一个
public
构造器,默认构造器被调用 - 对于没有构造器声明的非共同体类,对象是零初始化,默认呢构造器被调用
- 对于数组,每个元素都是值初始化
- 其他情况下,变量是零初始化
使用场景¶
- 使用空大括号初始化命名值
- 使用空括号或大括号初始化匿名临时对象
- 使用
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
}
复制初始化¶
复制初始化是使用另一个对象初始化当前对象
使用场景¶
- 使用等号初始化变量
- 输入参数到函数
- 函数返回对象
- 被抛出或不捕获的异常
- 使用等号初始化的非
static
数据成员 - 类、结构体和共同体成员在聚合初始化期间通过复制初始化进行初始化
示例¶
#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()
try{
throw MyException();
}
catch (MyException ex){ // copy initialization of ex
cout << ex.what();
}
}
复制初始化无法调用显式构造器。某些情况下,如果类的复制构造器被删除或无法访问,复制初始化会造成编译器错误
直接初始化¶
直接初始化是使用(非空)大括号或圆括号进行初始化。与复制初始化不同,它可以调用显式构造函数
使用场景¶
- 使用非空大括号或圆括号初始化变量
- 用
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' } };
}
集合初始化¶
集合初始化是数组或类类型(通常是结构体或共同体)的列表初始化形式,这些类型具有
- 没有
private
或protected
成员 - 除了显式
defaulted
或deleted
的构造函数,没有用户提供的构造器 - 没有基类
- 没有虚拟成员函数
示例¶
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;
}
初始化共同体/结构体¶
- 如果共同体没有构造器,你可以用单个值初始化它(或者另一个共同体),该值将用于初始化第一个非静态成员
- 对于结果体而言,按序初始化成员,其后的成员置为
0
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'} };
}
引用初始化¶
引用类型的变量必须用派生引用类型的对象初始化,或者用可转换为派生引用类型的对象初始化
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
}
使用临时对象初始化¶
使用临时对象初始化引用的唯一方法是初始化常量临时对象。一旦初始化,引用类型变量总是指向同一个对象;不能修改为指向另一个对象
const int &i = 3;
// int &i2 = 4; // error
引用初始化 vs. 引用赋值¶
尽管语法可以相同,但是引用类型变量的初始化(initialize
)和对引用类型变量的赋值(assign
)在语义上是不同的。在前面的示例中,更改ivar
和lvar
的赋值看起来与初始化类似,但具有不同的效果。初始化指定引用类型变量指向的对象;赋值通过引用分配给引用的对象
没有初始化¶
引用类型变量仅在以下情况可以没有初始化:
- 函数声明(原型)
- 返回返回值类型声明
- 引用类型的类成员声明
- 显式声明为
extern
的变量引用
int func( int& );
int& func( int& );
class c {public: int& i;};
extern int& iVal;
外部变量初始化¶
automatic、static和external
变量的声明可以包含初始值设定项。但是,只有在变量未声明为external
变量时,外部变量的声明才能包含初始值设定项
// name.cpp
int iii = 333;
// main.cpp
extern int iii;
extern int iii = 3; // warning: ‘iii’ initialized and declared ‘extern’