最近我看到了一个类似下面的例子:
#include <iostream>
class Foo {
public:
int bar;
Foo(int num): bar(num) {};
};
int main(void) {
std::cout << Foo(42).bar << std::endl;
return 0;
}
这个奇怪的 : bar(num)
是什么意思?它似乎以某种方式初始化了成员变量,但我以前从未见过这种语法。它看起来像一个函数/构造函数调用,但对于 int
?对我来说没有意义。也许有人可以启发我。而且,顺便说一句,有没有像这样的其他深奥的语言特性,你在普通的 C++ 书中找不到?
const
成员变量或引用,则必须使用初始化列表。
Foo(int num): bar(num)
此构造在 C++ 中称为成员初始值设定项列表。
简单地说,它初始化您的成员 bar
到值 num
。
构造函数中的初始化和赋值有什么区别?
成员初始化:
Foo(int num): bar(num) {};
成员分配:
Foo(int num)
{
bar = num;
}
使用成员初始化器列表初始化成员和在构造函数主体内为其分配值之间存在显着差异。
当您通过成员初始化器列表初始化字段时,将调用一次构造函数,并且将在一次操作中构造和初始化对象。
如果您使用赋值,则字段将首先使用默认构造函数初始化,然后使用实际值重新分配(通过赋值运算符)。
如您所见,后者有额外的创建和分配开销,这对于用户定义的类可能相当大。
Cost of Member Initialization = Object Construction
Cost of Member Assignment = Object Construction + Assignment
后者实际上等价于:
Foo(int num) : bar() {bar = num;}
而前者相当于:
Foo(int num): bar(num){}
对于内置(您的代码示例)或 POD 类成员,没有实际开销。
什么时候必须使用成员初始化器列表?
如果出现以下情况,您将不得不(相当被迫)使用成员初始化器列表:
你的班级有一个参考成员
您的班级有一个非静态 const 成员或
您的类成员没有默认构造函数或
用于基类成员的初始化或
当构造函数的参数名称与数据成员相同时(这不是必须的)
代码示例:
class MyClass {
public:
// Reference member, has to be Initialized in Member Initializer List
int &i;
int b;
// Non static const member, must be Initialized in Member Initializer List
const int k;
// Constructor’s parameter name b is same as class data member
// Other way is to use this->b to refer to data member
MyClass(int a, int b, int c) : i(a), b(b), k(c) {
// Without Member Initializer
// this->b = b;
}
};
class MyClass2 : public MyClass {
public:
int p;
int q;
MyClass2(int x, int y, int z, int l, int m) : MyClass(x, y, z), p(l), q(m) {}
};
int main() {
int x = 10;
int y = 20;
int z = 30;
MyClass obj(x, y, z);
int l = 40;
int m = 50;
MyClass2 obj2(x, y, z, l, m);
return 0;
}
MyClass2 没有默认构造函数,因此必须通过成员初始化器列表对其进行初始化。
基类 MyClass 没有默认构造函数,因此要初始化其成员,需要使用 Member Initializer List。
使用成员初始化器列表时需要注意的要点:
类成员变量总是按照它们在类中声明的顺序进行初始化。
它们没有按照在成员初始化器列表中指定的顺序进行初始化。简而言之,Member 初始化列表不决定初始化的顺序。
鉴于上述情况,保持成员初始化的成员顺序与在类定义中声明它们的顺序相同始终是一个好习惯。这是因为如果两个顺序不同,编译器不会发出警告,但是相对较新的用户可能会将成员 Initializer 列表混淆为初始化顺序并编写一些依赖于此的代码。
这是一个成员初始化列表。您应该在任何 good C++ book 中找到有关它的信息。
You should, in most cases, initialize all member objects in the member initialization list(不过,请注意常见问题条目末尾列出的例外情况)。
FAQ条目的要点是,
在所有其他条件相同的情况下,如果您使用初始化列表而不是赋值,您的代码将运行得更快。
int i(23);
、std::vector<double> emptyVec(0);
、std::vector<double> fullVec(10,23.);
等。当然,只有删除了类型,因为类型在成员声明中。
那是构造函数初始化。这是在类构造函数中初始化成员的正确方法,因为它可以防止调用默认构造函数。
考虑这两个例子:
// Example 1
Foo(Bar b)
{
bar = b;
}
// Example 2
Foo(Bar b)
: bar(b)
{
}
在示例 1 中:
Bar bar; // default constructor
bar = b; // assignment
在示例 2 中:
Bar bar(b) // copy constructor
一切都与效率有关。
Bar bar(); // default constructor
你确定吗?
这称为初始化列表。这是一种初始化类成员的方法。使用它而不是简单地为构造函数主体中的成员分配新值是有好处的,但是如果您有是常量或引用的类成员,则必须对其进行初始化。
另一个已经向您解释过,您观察到的语法称为“构造函数初始化列表”。此语法允许您自定义初始化类的基本子对象和成员子对象(而不是允许它们默认初始化或保持未初始化)。
我只想指出,正如您所说,“看起来像构造函数调用”的语法不一定是构造函数调用。在 C++ 语言中,()
语法只是 初始化语法 的一种标准形式。对于不同的类型有不同的解释。对于具有用户定义构造函数的类类型,它意味着一件事(它确实是一个构造函数调用),对于没有用户定义构造函数的类类型,它意味着另一件事(所谓的 value initialization )对于空 ()
)和非类类型它再次意味着不同的东西(因为非类类型没有构造函数)。
在您的情况下,数据成员的类型为 int
。 int
不是类类型,因此它没有构造函数。对于类型 int
,此语法仅表示“使用 num
的值初始化 bar
”,仅此而已。就这样直接完成,不涉及任何构造函数,因为 int
又不是一个类类型,因此它不能有任何构造函数。
我不知道你怎么会错过这个,它很基础。这是初始化成员变量或基类构造函数的语法。它适用于普通的旧数据类型以及类对象。
这是一个初始化列表。它将在构造函数主体运行之前初始化成员。考虑
class Foo {
public:
string str;
Foo(string &p)
{
str = p;
};
};
对比
class Foo {
public:
string str;
Foo(string &p): str(p) {};
};
在第一个示例中, str 将由其无参数构造函数初始化
string();
在 Foo 构造函数的主体之前。在 foo 构造函数中,
string& operator=( const string& s );
将像您一样在“str”上调用 str = p;
而在第二个示例中, str 将通过调用其构造函数来直接初始化
string( const string& s );
以“p”作为参数。
你是对的,这确实是一种初始化成员变量的方法。除了明确表示这是一个初始化之外,我不确定这有什么好处。在代码中包含“bar=num”可能更容易被移动、删除或误解。
const
个成员变量或引用的成员变量时,您必须使用初始化列表。
还有另一个“好处”
如果成员变量类型不支持 null 初始化,或者它是一个引用(不能为 null 初始化),那么您别无选择,只能提供一个初始化列表
这是构造函数的初始化列表。不是默认构造 x
、y
和 z
,然后将参数中接收到的值分配给它们,而是立即使用这些值初始化这些成员。这对于 float
来说可能看起来不是很有用,但是对于构建成本高昂的自定义类来说,它可以节省很多时间。
此线程上尚未提及:从 C++11 开始,成员初始化器列表可以使用列表初始化(又名“统一初始化”、“支撑初始化”):
Foo(int num): bar{num} {}
它与其他上下文中的列表初始化具有相同的语义。
虽然这是一个古老的讨论,但我找不到任何关于 delegating constructor 的提及,它以下列方式使用奇怪的“:”符号。
class Foo
{
public:
Foo(char x, int y)
{}
Foo(int y) : Foo('a', y)
{}
};
它所做的只是将 Foo(y)
委托给 Foo('a', y)
。以便
Foo foo(15); //=> foo('a', 15)
在定义委托构造函数时,除了目标构造函数之外,初始化列表中不能有任何成员。
&a
, int b, int c) : i(a), b(b), k(c) { / / 没有成员初始化器 // this->b = b; } 等声明和调用的相应更改。如果没有此更改,i
将引用a
,但a
不能引用x
,因为它只包含x
的值,因此i
不能间接引用x
。所以如果我们修改i
的值,那么它只修改a
而不是x