初始化列表Initializer List?

背景

初始化是一个非常重要的语言特性,最常见的就是对对象进行初始化。

在传统 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

                  https://en.cppreference.com/w/cpp/utility/initializer_list

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值