提问人:nils 提问时间:11/11/2009 最后编辑:Jan Schultkenils 更新时间:6/14/2023 访问量:138875
构造函数中这个奇怪的冒号成员 (“ : ”) 语法是什么?
What is this weird colon-member (" : ") syntax in the constructor?
问:
最近我看到了一个如下示例:
#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++书中找到?
答:
这是一个成员初始化列表。你应该在任何一本好的C++书中找到有关它的信息。
在大多数情况下,您应该初始化成员初始化列表中的所有成员对象(但是,请注意 FAQ 条目末尾列出的例外情况)。
FAQ条目的要点是,
在所有其他条件相同的情况下,如果使用初始化列表而不是赋值,则代码运行速度会更快。
评论
int i(23);
std::vector<double> emptyVec(0);
std::vector<double> fullVec(10,23.);
你是对的,这确实是一种初始化成员变量的方法。我不确定这有什么好处,除了清楚地表达它是初始化之外。在代码中包含“bar=num”可能会更容易被移动、删除或误解。
评论
const
我不知道你怎么会错过这个,这是非常基本的。这是初始化成员变量或基类构造函数的语法。它适用于普通的旧数据类型以及类对象。
评论
这就是构造函数初始化。这是在类构造函数中初始化成员的正确方法,因为它可以防止调用默认构造函数。
请看以下两个例子:
// 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
是否确定?
这称为初始化列表。这是一种初始化类成员的方法。使用它而不是简单地将新值分配给构造函数主体中的成员是有好处的,但是如果类成员是常量或引用,则必须初始化它们。
评论
这是一个初始化列表。它将在运行构造函数主体之前初始化成员。 考虑
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”为参数。
还有另一个“好处”
如果成员变量类型不支持 null 初始化,或者它是引用(不能初始化 null),则别无选择,只能提供初始化列表
另一个已经向你解释过,你观察到的语法称为“构造函数初始值设定项列表”。此语法允许您自定义初始化类的基子对象和成员子对象(而不是允许它们默认初始化或保持未初始化状态)。
我只想指出,正如您所说,“看起来像构造函数调用”的语法不一定是构造函数调用。在 C++ 语言中,语法只是初始化语法的一种标准形式。对于不同的类型,它的解释不同。对于具有用户定义构造函数的类类型,它意味着一件事(它确实是一个构造函数调用),对于没有用户定义构造函数的类类型,它意味着另一件事(所谓的值初始化)对于空),对于非类类型,它再次意味着不同的东西(因为非类类型没有构造函数)。()
()
在本例中,数据成员的类型为 。 不是类类型,因此它没有构造函数。对于类型,此语法的意思是简单地“使用 ” 的值初始化,仅此而已。它就是这样完成的,直接不涉及构造函数,因为,再一次,它不是类类型,因此它不能有任何构造函数。int
int
int
bar
num
int
评论
这并不晦涩难懂,它是 C++ 初始化列表语法
基本上,在您的情况下,将使用 、 和 初始化。x
_x
y
_y
z
_z
它是构造函数的初始化列表。这些成员将立即使用这些值进行初始化,而不是默认构造,然后为它们分配参数中接收的值。这对 s 来说似乎不是很有用,但对于构造成本高昂的自定义类来说,它可以节省相当多的时间。x
y
z
float
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
没有默认构造函数,因此必须通过成员初始值设定项列表进行初始化。- 基类没有默认构造函数,因此要初始化其成员,需要使用 Member Initializer List。
MyClass
使用成员初始值设定项列表时要注意的要点:
类成员变量始终按照它们在类中声明的顺序进行初始化。
它们不会按照在“成员初始值设定项列表”中指定的顺序进行初始化。
简而言之,成员初始化列表并不能确定初始化的顺序。
鉴于上述情况,最好将成员初始化的顺序与在类定义中声明成员的顺序保持相同。这是因为编译器不会警告这两个顺序是否不同,但相对较新的用户可能会将成员 Initializer 列表混淆为初始化顺序,并编写一些依赖于该顺序的代码。
评论
a
), b(b), k(c) { // 没有成员初始值设定项 // this->b = b;}等声明和调用的相应变化。如果没有此更改,将引用但不能引用,因为它只包含值,因此间接无法引用。因此,如果我们修改 的值,那么它只是修改而不是i
a
a
x
x
i
x
i
a
x
此线程中尚未提及:从 C++11 开始,成员初始值设定项列表可以使用列表初始化(又名。“统一初始化”、“支撑初始化”):
Foo(int num): bar{num} {}
它与其他上下文中的列表初始化具有相同的语义。
虽然这是一个古老的讨论,但我找不到任何关于委托构造函数的提及,它以以下方式使用奇怪的 “:” 符号。
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)
定义委托构造函数时,除了目标构造函数之外,初始值设定项列表中不能有任何成员。
类构造函数中的冒号语法 () 是什么,将整数传递给构造函数有什么作用?:
std::vector<>
我想从这个重复的问题中解释下面的例子:
class UnionFind {
public:
UnionFind(int sz) : root(sz) {
for (int i = 0; i < sz; i++) {
root[i] = i;
}
}
private:
vector<int> root;
};
int main() {
UnionFind uf(10);
}
冒号 () 表示“初始化列表”或“初始值设定项列表”的开始,该列表将每个变量初始化为括号中的值。就好像您为每个变量调用了一个构造函数,括号中的该值被传递到该变量的构造函数中。:
因此,使用 初始化变量 ,这就像做 .但是,这样做允许传递给类构造函数。: root(sz)
root
int sz
vector<int> root(sz);
sz
UnionFind
初始化一个大小如下的向量是构造函数 #3 这里 (https://en.cppreference.com/w/cpp/container/vector/vector):
// Constructor (3) as shown at
// https://en.cppreference.com/w/cpp/container/vector/vector
explicit vector( size_type count,
const T& value = T(),
const Allocator& alloc = Allocator());
它将(在上面的例子中)多个元素构造到向量中,每个元素都有值,这意味着在这种情况下,由于是一个 . 看起来像一个函数调用,但基本上是值零 () 的整数默认构造函数。它被称为“值初始化”。可以把它想象成调用一个“整数构造函数”,如果整数是对象并且有构造函数。另请参阅我的问题:在 C++ 中调用 char()
作为函数是什么?count
sz
T()
int()
root
vector<int>
int()
0
所以,让我们回顾一下:在构造函数中就像构造一样,它在向量中创建许多元素,每个元素都有初始值,这是 到 0 的“值初始化”的语法。: root(sz)
vector<int> root(sz);
sz
root
int()
int
另请注意,传递给上面所示的构造函数 #3 的参数实际上应该是一个 ,它可以写成 ,并且通常是 (请参阅此处的“成员类型”部分)。因此,最好改为。因此,请将此构造函数行:更改为 this: 。count
size_type
std::vector::size_type
size_t
int sz
size_t sz
UnionFind(int sz) : root(sz) {
UnionFind(size_t sz) : root(sz) {
上一个:使用成员初始化成员
下一个:带有空括号的默认构造函数
评论
const