Answers:
对于POD类成员,这没有什么区别,这只是样式问题。对于属于类的类成员,则它避免了对默认构造函数的不必要调用。考虑:
class A
{
public:
A() { x = 0; }
A(int x_) { x = x_; }
int x;
};
class B
{
public:
B()
{
a.x = 3;
}
private:
A a;
};
在这种情况下,for的构造函数B
将为调用默认的构造函数A
,然后初始化a.x
为3。一种更好的方法是for B
的构造A
函数在初始化列表中直接调用的构造函数:
B()
: a(3)
{
}
这只会调用A
的A(int)
构造函数,而不是其默认构造函数。在此示例中,差异可以忽略不计,但是可以想象一下,如果您愿意的话,A
默认构造函数会做更多的事情,例如分配内存或打开文件。您不想不必要地这样做。
此外,如果类没有默认构造函数,或者您具有const
成员变量,则必须使用初始化列表:
class A
{
public:
A(int x_) { x = x_; }
int x;
};
class B
{
public:
B() : a(3), y(2) // 'a' and 'y' MUST be initialized in an initializer list;
{ // it is an error not to do so
}
private:
A a;
const int y;
};
使用构造函数初始化器列表的一个重要原因(此处未在答案中提及)是基类的初始化。
根据构造顺序,应在子类之前构造基类。如果没有基类构造函数初始化器列表,则可以在基类具有默认构造函数的情况下进行操作,该构造函数将在进入子类的构造函数之前被调用。
但是,如果基类只有参数化的构造函数,则必须使用构造函数初始化器列表来确保基类在子类之前被初始化。
仅具有参数化构造函数的子对象的初始化
效率
使用构造函数初始化程序列表,您可以将数据成员初始化为代码中所需的确切状态,而不是先将它们初始化为默认状态,然后将其状态更改为代码中所需的状态。
如果类中的非静态const数据成员具有默认构造函数,并且您不使用构造函数初始化列表,则将它们初始化为默认状态后,将无法将它们初始化为预期状态。
当编译器进入构造函数时,引用数据成员必须被初始化,因为引用不能在以后声明和初始化。仅在构造函数初始化器列表中才有可能。
除了性能问题,还有另一个非常重要的问题,我称之为代码可维护性和可扩展性。
如果T是POD,并且您开始偏爱初始化列表,则如果一次T将更改为非POD类型,则无需在初始化周围进行任何更改,以避免不必要的构造函数调用,因为它已经被优化。
如果类型T确实具有默认构造函数和一个或多个用户定义的构造函数,并且一次您决定删除或隐藏默认构造函数,则如果使用了初始化列表,则如果用户定义的构造函数不需要更新代码,因为它们已经正确实施。
与const成员或引用成员相同,假设最初T的定义如下:
struct T
{
T() { a = 5; }
private:
int a;
};
接下来,您决定将a限定为const,如果您从一开始就使用初始化列表,那么这只是单行更改,但是具有如上所述的T定义,还需要挖掘构造函数定义以删除赋值:
struct T
{
T() : a(5) {} // 2. that requires changes here too
private:
const int a; // 1. one line change
};
如果代码不是由“代码猴子”而是由工程师根据更深层次的考虑来做出决定来编写的,那么维护起来将变得更加容易且不会出错,这不是秘密。
在构造函数的主体运行之前,将调用其父类以及其字段的所有构造函数。默认情况下,将调用无参数构造函数。初始化列表使您可以选择调用哪个构造函数以及该构造函数接收哪些参数。
如果您具有引用或const字段,或者使用的类之一没有默认构造函数,则必须使用初始化列表。
// 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”。
2.在MyClass()构造函数的内部调用“类型”的赋值运算符进行赋值
variable = a;
最后,由于“类型”的析构函数超出范围,因此将其称为“ a”。
现在考虑使用带有初始化器列表的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
}
};
使用初始化程序列表,编译器执行以下步骤:
只是添加一些额外的信息来演示成员初始化列表可以产生多大的差异。在leetcode 303范围总和查询-不可变中,https: //leetcode.com/problems/range-sum-query-immutable/ ,您需要在其中构造并初始化为一定大小的向量并将其初始化为零。这是两种不同的实现方式和速度比较。
如果没有成员初始化列表,要获得AC,大约要花费212 ms。
class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) {
preSum = vector<int>(nums.size()+1, 0);
int ps = 0;
for (int i = 0; i < nums.size(); i++)
{
ps += nums[i];
preSum[i+1] = ps;
}
}
int sumRange(int i, int j) {
return preSum[j+1] - preSum[i];
}
};
现在使用成员初始化列表,获得AC的时间约为108 ms。通过这个简单的示例,很明显,成员初始化列表更加高效。所有测量均来自LC的运行时间。
class NumArray {
public:
vector<int> preSum;
NumArray(vector<int> nums) : preSum(nums.size()+1, 0) {
int ps = 0;
for (int i = 0; i < nums.size(); i++)
{
ps += nums[i];
preSum[i+1] = ps;
}
}
int sumRange(int i, int j) {
return preSum[j+1] - preSum[i];
}
};
句法:
class Sample
{
public:
int Sam_x;
int Sam_y;
Sample(): Sam_x(1), Sam_y(2) /* Classname: Initialization List */
{
// Constructor body
}
};
需要初始化清单:
class Sample
{
public:
int Sam_x;
int Sam_y;
Sample() */* Object and variables are created - i.e.:declaration of variables */*
{ // Constructor body starts
Sam_x = 1; */* Defining a value to the variable */*
Sam_y = 2;
} // Constructor body ends
};
在上面的程序中,当执行类的构造函数时,将创建Sam_x和Sam_y。然后在构造函数主体中,定义那些成员数据变量。
用例:
在C语言中,必须在创建过程中定义变量。与C ++中的方式相同,我们必须在对象创建期间使用初始化列表初始化Const和Reference变量。如果我们在对象创建后(在构造函数体内)进行初始化,则会得到编译时错误。
Sample1(基)类的成员对象没有默认构造函数
class Sample1
{
int i;
public:
Sample1 (int temp)
{
i = temp;
}
};
// Class Sample2 contains object of Sample1
class Sample2
{
Sample1 a;
public:
Sample2 (int x): a(x) /* Initializer list must be used */
{
}
};
在为派生类创建对象时,该对象将在内部调用派生类构造函数并调用基类构造函数(默认)。如果基类没有默认构造函数,则用户将获得编译时错误。为了避免,我们必须
1. Default constructor of Sample1 class
2. Initialization list in Sample2 class which will call the parametric constructor of Sample1 class (as per above program)
类构造函数的参数名称和类的Data成员相同:
class Sample3 {
int i; /* Member variable name : i */
public:
Sample3 (int i) /* Local variable name : i */
{
i = i;
print(i); /* Local variable: Prints the correct value which we passed in constructor */
}
int getI() const
{
print(i); /*global variable: Garbage value is assigned to i. the expected value should be which we passed in constructor*/
return i;
}
};
众所周知,如果两个变量具有相同的名称,则本地变量具有最高优先级,然后是全局变量。在这种情况下,程序将考虑“ i”值{左侧和右侧变量。即:i = i}作为Sample3()构造函数中的局部变量,并且Class成员变量(i)被覆盖。为了避免,我们必须使用
1. Initialization list
2. this operator.
如C ++核心准则C.49中所述:在构造函数中首选初始化优先于赋值, 这样可以防止对默认构造函数的不必要调用。