这个奇怪的冒号成员("构造函数中的:")语法?

最近我看到了这样一个例子:

#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++书中找不到?

128485 次浏览

它是成员初始化列表。你应该在任何很好的c++书中找到关于它的信息。

在大多数情况下,应该初始化成员初始化列表中的所有成员对象(但是,请注意FAQ条目末尾列出的例外)。

常见问题解答的要点是,

在其他条件相同的情况下,如果使用初始化列表而不是赋值,代码将运行得更快。

你是正确的,这确实是一种初始化成员变量的方法。我不确定这样做有什么好处,除了清楚地表示它是一个初始化。在代码中使用“bar=num”可能更容易被移动、删除或误解。

我不知道你怎么会错过这个,这是非常基本的。这是初始化成员变量或基类构造函数的语法。它既适用于普通的旧数据类型,也适用于类对象。

这就是构造函数初始化。这是在类构造函数中初始化成员的正确方法,因为它可以防止调用默认构造函数。

考虑以下两个例子:

// 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

这都是效率问题。

这叫做初始化列表。它是初始化类成员的一种方式。使用此方法比简单地为构造函数体中的成员赋新值更有好处,但如果类成员为常量参考文献,则会初始化必须

初始化列表。它将在运行构造函数体之前初始化成员。 考虑< / p >

class Foo {
public:
string str;
Foo(string &p)
{
str = p;
};
};

vs

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'作为参数。

还有一个“好处”

如果成员变量类型不支持空初始化,或者它是一个引用(不能被空初始化),那么你别无选择,只能提供一个初始化列表

另一个已经向您解释过,您观察到的语法称为“构造函数初始化列表”。此语法允许自定义初始化类的基子对象和成员子对象(而不是允许它们默认初始化或保持未初始化)。

我只是想指出,正如你所说,“看起来像一个构造函数调用”的语法,不一定是一个构造函数调用。在c++语言中,()语法只是初始化语法的一种标准形式。对于不同的类型有不同的解释。对于具有用户定义构造函数的类类型,这意味着一件事(它确实是一个构造函数调用),对于没有用户定义构造函数的类类型,对于空的(),这意味着另一件事(称为<强> < em >初始化值< / em > < / >强),对于非类类型,这意味着另一件事(因为非类类型没有构造函数)。

在你的例子中,数据成员的类型是intint不是类类型,所以它没有构造函数。对于类型int,此语法仅表示“用num的值初始化bar”,仅此而已。它就像这样直接完成,没有涉及构造函数,因为,再一次,int不是类类型,因此它不能有任何构造函数。

这并不难理解,它是c++初始化列表语法

基本上,在你的例子中,x将用_x初始化,y_y初始化,z_z初始化。

它是构造函数的初始化列表。而不是默认构造xyz,然后将参数中接收到的值分配给它们,这些成员将立即使用这些值进行初始化。这对于floats来说似乎不是特别有用,但对于构造成本很高的自定义类来说,这可以节省很多时间。

Foo(int num): bar(num)

这个构造在c++中称为成员初始化列表

简单地说,它初始化你的成员barnum


构造函数中的初始化和赋值有什么区别?

成员初始化:

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没有默认构造函数,因此要初始化它的成员,需要使用成员初始化列表。

在使用成员初始化列表时需要注意的要点:

类成员变量总是按照它们在类中声明的顺序进行初始化。

它们按照在成员初始化列表中指定的顺序进行初始化。
简而言之,成员初始化列表不决定初始化的顺序

综上所述,保持与类定义中声明成员的顺序相同的成员初始化顺序始终是一种良好的实践。这是因为编译器不会在两个顺序不同时发出警告,但相对较新的用户可能会将成员Initializer列表误认为初始化顺序,并据此编写一些代码。

在这个线程中还没有提到:从c++ 11开始,成员初始化器列表可以使用list-initialization(又名list-initialization)。“统一初始化”,“带括号初始化”):

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);
}

冒号(:)表示“初始化列表”的开始,或“初始化list",它将每个变量初始化为括号中的值。这就好像你在为每个变量调用一个构造函数,将括号中的值传递给该变量的构造函数。

因此,: root(sz)使用int sz初始化root变量,这就像执行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());

它在vector中构造了count(上面例子中的sz)个数的元素,每个元素的值都是T(),在这种情况下意味着int(),因为rootvector<int>int()看起来像一个函数调用,但实际上是一个值为0的整数默认构造函数(0)。它被称为“价值initialization"。把它想象成调用“整数构造器”,如果整数是对象并且有构造器的话。另见我的问题在这里:sz0

因此,让我们回顾一下:构造函数中的: root(sz)类似于构造vector<int> root(sz);,它在root向量中创建了sz个数的元素,每个元素的初始值都是int(),这是int“价值initialization"到0的语法。

还要注意,上面显示的传递给构造函数#3的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) {