初始化
当对象在创建时获得了一个特定的值,我们说这个对象被初始化(initialized)。
这里说的对象特指一块能存储数据并具有某种类型的内存空间。
初始化与赋值的区别:
- 初始化:为对象申请存储空间,创建新的对象,并赋予其一个初始值。
- 赋值:是把已经创建的对象的当前值擦除,并以一个新值来替代。
int a = 1; // 初始化
a = 2; // 赋值
虽然上面的初始化和赋值在形式上都用了等号 =
,但初始化的等号和赋值的等号具有不同的含义,是两种完全不同的操作,只是恰巧都用了等号 =
。就好比乘法和解引用都用了 *
,但含义却完全不同。
默认初始化
如果定义变量时没有指定初值,则变量被默认初始化(default initialized),此时变量被赋予了默认值。如何默认初始化取决于变量的类型以及变量定义的位置。
内置类型的默认初始化
如果是内置类型的变量未被显式初始化,则默认值由定义的位置决定:
- 定义于任何函数体之外的对象和函数体内的静态对象进行零初始化。
char
类型初始化为''
。- 算数类型初始化为
0
。 bool
类型初始化为false
。
- 定义于函数体内的非静态对象不被初始化(uninitialized),一个未被初始化的内置类型的变量的值是未定义的(undefined)。
类类型的默认初始化
类对象的默认初始化本质上是类的成员变量的默认初始化。如果类的成员变量是内置类型,则遵循内置类型的默认初始化;如果类的成员变量是类类型,则遵循类类型的默认初始化。
- 定义于任何函数体之外的类对象和函数体内的静态对象会先进行零初始化再调用类的默认构造函数。
- 定义在函数体内部的非静态类对象会直接调用类的默认构造函数。
- 如果没有显式初始化内置类型的成员变量,则其值是未定义的。
- 没有默认构造函数的类是不能执行默认初始化。
new的默认初始化
对于内置类型,有括号则进行零初始化,没有括号的值未定义!
int *pi1 = new int; // *pi1 值未定义
int *pi2 = new int(); // *pi2 为 0
对于类类型,有无括号没区别,都会调用默认构造函数。
Foo *f1 = new Foo; // 调用默认构造函数
Foo *f2 = new Foo(); // 调用默认构造函数
显式初始化
无论是内置类型还是类类型,以下初始化形式都是合法的
int i1 = 0; // (1)
int i2 = {0}; // (2)
int i3{0}; // (3)
int i4(0); // (4)
std::string s1 = "hello"; // (1)
std::string s2 = {"hello"}; // (2)
std::string s3{"hello"}; // (3)
std::string s4("hello"); // (4)
内置类型显式初始化
对于内置类型,上面四种初始化形式几乎没有区别,除了
- 在使用 auto 自动推导类型时:
auto i2 = {0}; // (2) i2 类型推导为 std::initializer_list
auto i3 {0}; // (3) i3 类型推导为 int
auto m2 = {0, 1}; // (2) m2 类型推导为 std::initializer_list
auto m3 {0, 1}; // (3) error: initializer for variable 'm3'
// with type 'auto' contains multiple expressions
- 在窄化转换时:
double ld = 3.1415;
int a{ld}; // warning: narrowing conversion of ‘ld’ from ‘double’ to ‘int’ [-Wnarrowing]
int b = {ld}; // warning: narrowing conversion of ‘ld’ from ‘double’ to ‘int’ [-Wnarrowing]
int c(ld); // 无warning
int d = ld; // 无warning
类类型显式初始化
对于类类型:
-
前两种初始化形式 (1)(2) 使用了等号,叫做拷贝初始化;
-
后两种 (3)(4) 没有等号,叫做直接初始化。
无论是拷贝初始化,还是直接初始化,都是初始化,不是赋值!都是调用构造函数,不是调用赋值运算符!
拷贝初始化最大的限制在于不能用于 explicit
的单参构造函数。