C/C++编程:默认初始化、值初始化、复制初始化
默认初始化
- 这是不使用初始化器构造变量时执行的初始化
- 如果定义变量时没有指定初值,则变量被默认初始化。其初始值和变量的类型以及变量定义的位置相关。默认初始化类对象和默认初始化内置类型变量有所不同。
语法
语法 | 示例 |
---|---|
T 对象 ; | int i; |
new T | new int |
new T ( ) (C++03 前) | 已过时,C++03之后,执行值初始化 |
默认初始化的效果
- 如果T是非 POD (C++11 前)类类型,则考虑各构造函数并实施针对空实参列表的
重载决议
。调用所选的构造函数默认构造函数之一),以提供新对象的初始值 - 如果T是数组类型,则每个数组元素都被默认初始化
- 否则,不做任何事:具有自动存储期的对象的对象(及其子对象)被初始化为不确定值。
默认初始化的情形
当不带初始化器而声明具有自动、静态或者线程局部存储期的变量时
如果定义标量变量时不使用初始化表达式,则进行默认初始化。 它们的值是不确定的。
int i1;
float f;
char c;如果定义数组时不使用初始化表达式,则进行默认初始化。 数组进行默认初始化时,其成员将进行默认初始化并具有不确定的值(如果数组成员没有默认构造函数,则编译器将发出错误),如以下示例所示:
int int_arr[3];
当以不带初始化器的 new 表达式
new T
创建具有动态存储期的对象时
- 类、结构和联合的默认初始化是具有默认构造函数的初始化。可以在没有初始化表达式或者关键字的情况下调用默认构造函数
new
如果类、结构或者联合没有默认构造函数,则编译器将会发出错误
MyClass mc1;
MyClass* mc3 = new MyClass;
当构造函数初始化器列表中未提及某个基类或非静态数据成员,且调用了该构造函数时
常量变量的默认初始化
常量变量必须与初始值设定项一起使用。
- 如果它们是变量类型,则会导致编译器错误
如果是具有默认构造类型的类类型,则会警告
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'}
}
从不确定字节读取
使用由默认初始化任何非类类型的变量所取得的不确定的值是未定义行为,除了下列情况:
- 将 unsigned char 或 std::byte (C++17 起) 类型的不确定值赋值给另一拥有(可有 cv 限定的)unsigned char 或 std::byte (C++17 起) 类型的变量(变量的值变为不确定,但该行为不是未定义);
- 用 unsigned char 或 std::byte (C++17 起) 类型的不确定值初始化另一拥有(可有 cv 限定的)unsigned char 或 std::byte (C++17 起)类型的变量;
从以下场合产生的 unsigned char 或 std::byte (C++17 起) 类型的不确定值
- 条件表达式的第二或第三操作数,
- 逗号运算符的右操作数,
- 转型或转换到(可有 cv 限定的)unsigned char 或 std::byte (C++17 起)的操作数,
- 弃值表达式。
int f(bool b)
{int x; // OK:x 的值不确定
int y = x; // 未定义行为
unsigned char c; // OK:c 的值不确定
unsigned char d = c; // OK:d 的值不确定
int e = d; // 未定义行为
return b ? d : 0; // 未定义行为,若 b 为 true
}
注意
- 具有自动和动态存储期的非类变量的默认初始化,产生具有不确定值的对象(静态和线程局部对象进行的是零初始化)
- 不能默认初始化引用和const标量对象
对于默认初始化内置类型变量来说:
- 1)定义在函数体之外的变量是全局变量,一般存储在全局区,存储在全局区的变量一般会执行值初始化。此时,其初始值和变量的类型有关。对于int类型其初始值为0,对于char类型其默认初始值为’ ‘。
2)定义在函数体内部的是局部变量,其存储在栈区中,如果没有指定初值,那么该局部变量将不会被初始化,也就是说这个局部变量的值是未定义的,是个随机值。此时,如果不给这个局部变量赋值,那么就不能使用该局部变量,否则就会出错,注意这种情况是没有被初始化,既没有使用默认初始化也没有使用值初始化,没有初始化的值是不能使用的。
include
struct T1 {
int mem; };
struct T2
{int mem;
T2() {
} // "mem" 不在初始化器列表中
};
int n; // 静态非类,进行两阶段初始化:
// 1) 零初始化将 n 初始化为零
// 2) 默认初始化不做任何事,令 n 保留为零
int main()
{int n; // 非类,值不确定
std::string s; // 类,调用默认构造函数,值是 ""(空字符串)
std::string a[2]; // 数组,默认初始化其各元素,值是 {"", ""}
// int& r; // 错误:引用
// const int n; // 错误:const 的非类
// const T1 t1; // 错误:const 的带隐式默认构造函数的类T1 t1; // 类,调用隐式默认构造函数
const T2 t2; // const 类,调用用户提供的默认构造函数
// t2.mem 被默认初始化(为不确定值)
}
官方文档
值初始化
- 这是在以空初始化器构造对象时进行的初始化
语法
语法 | 示例 |
---|---|
1、 T() | |
1、T{} | |
2、new T () | |
2、new T {} | |
3、Class::Class(…) : member() { … } | |
3、 Class::Class(…) : member{} { … } | |
4、T object {}; |
值初始化的场合
- 使用空大括号初始化来初始化已命名值
- 使用空圆括号或大括号初始化匿名临时对象
- 使用 new 关键字和空圆括号或大括号初始化对象
所有情况下,若使用空花括号对 {} 且 T 是聚合类型,则进行聚合初始化而非值初始化。
若 T 是没有默认构造函数但带有接受 std::initializer_list 的构造函数的类类型,则进行列表初始化。(C++11 起)
值初始化执行如下操作:
- 对于至少有一个公告构造函数的类,将调用默认初始化
- 对于没有声明构造函数的非联合类,该对象进行零初始化,并调用默认构造函数
- 对于数组,每个元素都进行值初始化
- 在其他所有情况下,变量进行零初始化
注解
- 引用不能被值初始化。
- 所有标准容器(std::vector、std::list 等),在以单个 size_type 实参进行构造或由对 resize() 的调用而增长时,值初始化其各个元素,除非其分配器定制 construct 的行为。
从 C++11 起,对没有用户提供的构造函数而拥有类类型成员的类进行值初始化,其中成员的类拥有用户提供的构造函数,会在调用成员的构造函数前对成员清零:
struct A
{int i;
A() {
} // 用户提供的默认构造函数,不初始化 i
};
struct B {
A a; }; // 隐式定义的默认构造函数
std::cout << B().a.i << ‘\n’; // 值初始化 B 临时量
// C++03 中令 b.a.i 为未初始化
// C++11 中设 b.a.i 为零
// (注意 C++11 中 B{}.a.i 保留 b.a.i 为未初始化,但因为不同原因:
// 在 DR1301 后的 C++11 中,B{} 是聚合初始化,它值初始化拥有用户提供构造函数的 A)
不同于复制初始化,它可以调用显式构造函数。
例子
class BaseClass {
private:
int m_int;
};
std::string s{
}; // 类 => 默认初始化,值为 ""
int main() {
BaseClass bc{
}; // class is initialized
BaseClass* bc2 = new BaseClass(); // class is initialized, m_int value is 0
int a{
}; // 标量 => 零初始化,值为 0
double b{
}; // value of b is 0.00000000000000000
double f = double(); // 标量 => 零初始化,值为 0.0
int int_arr[3]{
}; // value of all members is 0
int* a = new int[10](); // 数组 => 每个元素的值初始化
// 每个元素的值为 0
std::vector<int> v(3); // 值初始化每个元素
// 每个元素的值为 0
}
#include <string>
#include <vector>
#include <iostream>
struct T1
{
int mem1;
std::string mem2;
}; // 隐式默认构造函数
struct T2
{
int mem1;
std::string mem2;
T2(const T2&) {
} // 用户提供的复制构造函数
}; // 无默认构造函数
struct T3
{
int mem1;
std::string mem2;
T3() {
} // 用户提供的默认构造函数
};
int main()
{
T1 t1{
}; // 有隐式默认构造函数的类 =>
// t1.mem1 被零初始化,值为 0
// t1.mem2 被默认初始化,值为 ""
// T2 t2{}; // 错误:类无默认构造函数
T3 t3{
}; // 有用户提供默认构造函数的类 =>
// t3.mem1 被默认初始化为不确定值
// t3.mem2 被默认初始化,值为 ""
std::cout << t1.mem1 << ' ' << t3.mem1 << '\n';
delete[] a;
}
拷贝初始化(复制初始化)
概念
- 从一个已有的对象拷贝到正在创建的对象,如果需要的话还需要进行类型转换
语法
在下面这6种情况下发生:
T object = other; //(1) 当一个非引用类型T的具名变量通过 '=' 被一个表达式初始化时
T object = {
other}; //(2) 当一个纯量类型T的具名变量通过 '=' 被一个括号表达式初始化时
f(other) //(3) 当通过值传递给函数的参数时
return other; //(4) 当函数返回一个值时
throw object;
catch(T object) //(5) 当throw/catch 一个表达式的值时
T array[N] = {
other}; //(6) 使用聚合初始化,初始化每个元素
- 使用等号初始化变量
- 参数被传递给函数
- 从函数返回对象
- 引发或捕获异常
- 使用等号初始化非静态数据成员
- 在聚合初始化期间通过复制初始化来初始化类、结构和联合成员。
实例
#include <iostream>
using namespace std;
struct Point
{
int x, y;
Point(int x, int y)
: x(x), y(y)
{
}
//拷贝构造 (copy constructor)
Point(Point const& other)
: x(other.x), y(other.y)
{
}
};
int main()
{
Point a(70, -130);
Point b1(a); //直接复制现有对象 a 的值
Point b2(a.x, a.y); //最终也是复制了a,但相对麻烦,不是吗?
cout << a.x << ", " << a.y << endl;
cout << b1.x << ", " << b1.y << endl;
cout << b2.x << ", " << b2.y << endl;
}
#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();
}
}
不过,通常类子中这样的结构(或类),基本不需要“自制”拷贝构造 函数,因为像这样简单的复制对象内所有内存内容 (例子中两人个成员数据x,y)的操作,编译器就会干,所以我们不定义时,它会自动帮我们生成一个。
如果有指令类型的成员数据,编译器生成的拷贝函数,就只会复制指针,从而得到一个相同指向的复制品(浅复制),很可能这并不是我们想要的,这时需要考虑自己动手定义拷贝构造函数。
复制初始化不能调用显式构造函数。
vector<int> v = 10; // the constructor is explicit; compiler error C2440: cannot 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
- 官方文档
- 【原创】c++拷贝初始化和直接初始化的底层区别
还没有评论,来说两句吧...