背景
初始化是一个非常重要的语言特性,最常见的就是对对象进行初始化。
在传统 C++ 中,不同的对象有着不同的初始化方法,例如普通数组、POD (plain old data,没有构造、析构和虚函数的类或结构体)类型都可以使用 { }
进行初始化,也就是我们所说的初始化列表。而对于类对象的初始化,要么需要通过拷贝构造、要么就需要使用 ( )
进行。这些不同方法都针对各自对象,不能通用
int arr[3] = {1,2,3}; // 列表初始化
class Foo {
private:
int value;
public:
Foo(int) {}
};
Foo foo(1); // 普通构造初始化
为了解决这个问题,C++11 首先把初始化列表的概念绑定到了类型上,并将其称之为 std::initializer_list
,允许构造函数或其他函数像参数一样使用初始化列表,这就为类对象的初始化与普通数组和 POD 的初始化方法提供了统一的桥梁,例如:
#include <initializer_list>
class Magic {
public:
Magic(std::initializer_list<int> list) {}
};
Magic magic = {1,2,3,4,5};
std::vector<int> v = {1, 2, 3, 4};
这种构造函数被叫做初始化列表构造函数,具有这种构造函数的类型将在初始化时被特殊关照。
初始化列表除了用在对象构造上,还能将其作为普通函数的形参,例如:
void func(std::initializer_list<int> list) {
return;
}
func({1,2,3});
其次,C++11 提供了统一的语法来初始化任意的对象,例如:
struct A {
int a;
float b;
};
struct B {
B(int _a, float _b): a(_a), b(_b) {}
private:
int a;
float b;
};
A a {1, 1.1}; // 统一的初始化语法
B b {2, 2.2};
类中的初始化
初始化列表用于初始化类的数据成员。要初始化的成员列表用构造函数表示为逗号分隔列表,后跟冒号。 以下是使用初始化列表初始化Point类的x和y的示例。
#include<iostream>
using namespace std;
class Point {
private:
int x;
int y;
public:
Point(int i = 0, int j = 0):x(i), y(j) {}
/* The above use of Initializer list is optional as the
constructor can also be written as:
Point(int i = 0, int j = 0) {
x = i;
y = j;
}
*/
int getX() const {return x;}
int getY() const {return y;}
};
int main() {
Point t1(10, 15);
cout<<"x = "<<t1.getX()<<", ";
cout<<"y = "<<t1.getY();
return 0;
}
/* OUTPUT:
x = 10, y = 15
*/
上面的代码只是初始化列表语法的一个示例。在上面的代码中,x和y也可以在构造函数中轻松初始化。但是有些情况下构造函数内的数据成员初始化不起作用,必须使用Initializer List。以下是这种情况:
1)初始化非静态const数据成员:
必须使用Initializer List初始化const数据成员。在以下示例中,“t”是Test类的const数据成员,并使用Initializer List进行初始化。
#include<iostream>
using namespace std;
class Test {
const int t;
public:
Test(int t):t(t) {} //Initializer list must be used
int getT() { return t; }
};
int main() {
Test t1(10);
cout<<t1.getT();
return 0;
}
/* OUTPUT:
10
*/
2)初始化引用成员:
必须使用初始化列表初始化引用成员。在以下示例中,“t”是Test类的引用成员,并使用Initializer List进行初始化。
// Initialization of reference data members
#include<iostream>
using namespace std;
class Test {
int &t;
public:
Test(int &t):t(t) {} //Initializer list must be used
int getT() { return t; }
};
int main() {
int x = 20;
Test t1(x);
cout<<t1.getT()<<endl;
x = 30;
cout<<t1.getT()<<endl;
return 0;
}
/* OUTPUT:
20
30
*/
3)对于没有默认构造函数的成员对象的初始化:
在下面的例子中,类“A”的对象“a”是类“B”的数据成员,而“A”没有默认构造函数。初始化列表必须用于初始化“a”。
#include <iostream>
using namespace std;
class A {
int i;
public:
A(int );
};
A::A(int arg) {
i = arg;
cout << "A's Constructor called: Value of i: " << i << endl;
}
// Class B contains object of A
class B {
A a;
public:
B(int );
};
B::B(int x):a(x) { //Initializer list must be used
cout << "B's Constructor called";
}
int main() {
B obj(10);
return 0;
}
/* OUTPUT:
A's Constructor called: Value of i: 10
B's Constructor called
*/
如果类A同时具有默认构造函数和参数化构造函数,那么如果我们想使用默认构造函数初始化“a”,那么初始化程序列表不是必须的,但是必须使用参数化构造函数初始化“a”。
4)用于初始化基类成员: 与第3点类似,只能使用初始化列表调用基类的参数化构造函数。
#include <iostream>
using namespace std;
class A {
int i;
public:
A(int );
};
A::A(int arg) {
i = arg;
cout << "A's Constructor called: Value of i: " << i << endl;
}
// Class B is derived from A
class B: A {
public:
B(int );
};
B::B(int x):A(x) { //Initializer list must be used
cout << "B's Constructor called";
}
int main() {
B obj(10);
return 0;
}
5)当构造函数的参数名称与数据成员相同时
如果构造函数的参数名称与数据成员名称相同,则必须使用此指针或初始化列表初始化数据成员。在以下示例中,A()的成员名称和参数名称都是“i”。
#include <iostream>
using namespace std;
class A {
int i;
public:
A(int );
int getI() const { return i; }
};
A::A(int i):i(i) { } // Either Initializer list or this pointer must be used
/* The above constructor can also be written as
A::A(int i) {
this->i = i;
}
*/
int main() {
A a(10);
cout<<a.getI();
return 0;
}
/* OUTPUT:
10
*/
6)出于性能原因:
最好初始化Initializer List中的所有类变量,而不是在body中分配值。请考虑以下示例:
// Without Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
variable = a;
}
};
这里编译器遵循以下步骤来创建MyClass
1类型的对象。首先为“a”调用Type的构造函数。
2.“类型”的赋值运算符在MyClass()构造函数体内调用以进行赋值
变量= a;
3.然后最终析构“类型”的析构函数被称为“a”,因为它超出了范围。
现在考虑使用带有Initializer List的MyClass()构造函数的相同代码
// With Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a):variable(a) { // Assume that Type is an already
// declared class and it has appropriate
// constructors and operators
}
};
使用初始化列表,编译器遵循以下步骤:
1。调用“Type”类的复制构造函数初始化:variable(a)。初始化列表中的参数用于直接复制构造“变量”。
2.“类型”的析构函数被称为“a”,因为它超出了范围。
正如我们从这个例子中看到的,如果我们在构造函数体内使用赋值,则有三个函数调用:constructor + destructor +一个附加赋值操作符调用。如果我们使用Initializer List,则只有两个函数调用:copy constructor + destructor call。有关这一点的运行示例,请参阅此文章。
在“真实”应用程序中,这种分配惩罚将更多,其中将存在许多此类变量。感谢ptr添加这一点。
如果您发现任何不正确的内容,或者您想要分享有关上述主题的更多信息,请撰写评论。
原文来自:https://www.geeksforgeeks.org/when-do-we-use-initializer-list-in-c/
更多参考:http://www.informit.com/articles/article.aspx?p=1852519