关于c ++:构造函数中这个奇怪的冒号 – 成员(“:”)语法是什么?

What is this weird colon-member (“ : ”) syntax in the constructor?

最近我看到了一个类似以下的例子:

1
2
3
4
5
6
7
8
9
10
11
12
#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++书籍中找不到?


1
Foo(int num): bar(num)

这个构造在C++中被称为成员初始化器列表。

简单地说,它将您的成员bar初始化为值num

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

成员初始化:

1
Foo(int num): bar(num) {};

成员分配:

1
2
3
4
Foo(int num)
{
   bar = num;
}

在使用成员初始值设定项列表初始化成员和在构造函数体中为其赋值之间存在显著差异。

通过成员初始值设定项列表初始化字段时,将调用一次构造函数,并在一个操作中构造和初始化对象。

如果使用赋值,则字段将首先用默认构造函数初始化,然后用实际值重新赋值(通过赋值运算符)。

正如您所看到的,在后者中还有一个额外的创建和分配开销,这对于用户定义的类来说可能是相当大的。

1
2
Cost of Member Initialization = Object Construction
Cost of Member Assignment = Object Construction + Assignment

后者实际上相当于:

1
Foo(int num) : bar() {bar = num;}

前者相当于:

1
Foo(int num): bar(num){}

对于内置(代码示例)或pod类成员,没有实际开销。

何时必须使用成员初始值设定项列表?

如果出现以下情况,则必须(相当强制)使用成员初始值设定项列表:

  • 你的班级有一个推荐人
  • 您的类有一个非静态常量成员或
  • 您的类成员没有默认构造函数或
  • 用于初始化基类成员或
  • 当构造函数的参数名与数据成员相同时(这不是必须的)

代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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没有默认构造函数,因此要初始化其成员,需要使用成员初始值设定项列表。

使用成员初始值设定项列表时需要注意的要点:

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

它们没有按照成员初始值设定项列表中指定的顺序初始化。简而言之,成员初始化列表不确定初始化的顺序。

鉴于上述情况,保持成员初始化的顺序与类定义中声明成员的顺序相同始终是一个好的实践。这是因为如果两个顺序不同,编译器不会发出警告,但相对较新的用户可能会将成员初始值设定项列表混淆为初始化顺序,并编写一些依赖于该顺序的代码。


这是一个成员初始化列表。你应该在任何一本好的C++书籍中找到有关它的信息。

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

FAQ条目的要点是,

All other things being equal, your code will run faster if you use initialization lists rather than assignment.


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

考虑这两个例子:

1
2
3
4
5
6
7
8
9
10
11
// Example 1
Foo(Bar b)
{
   bar = b;
}

// Example 2
Foo(Bar b)
   : bar(b)
{
}

例1:

1
2
Bar bar();  // default constructor
bar = b;  // assignment

例2:

1
Bar bar(b) // copy constructor

一切都是为了效率。


这称为初始化列表。这是初始化类成员的另一种方法。使用这个方法有好处,而不是简单地将新值赋给构造函数主体中的成员,但是如果您有属于常量或引用的类成员,则必须对它们进行初始化。


这不是模糊的,它是C++初始化列表语法。

基本上,在您的情况下,x将初始化为_xy初始化为_yz初始化为_z


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

我只想注意,正如您所说,"看起来像一个构造函数调用"的语法不一定是一个构造函数调用。在C++语言中,EDCOX1×0的语法只是初始化语法的一种标准形式。对于不同的类型,它的解释是不同的。对于具有用户定义的构造函数的类类型,它意味着一件事(实际上是一个构造函数调用),对于没有用户定义的构造函数的类类型,它意味着另一件事(所谓的值初始化),对于空的();对于非类类型,它又意味着不同的事情(因为非类类型没有构造函数)。

在您的情况下,数据成员的类型为intint不是类类型,因此它没有构造函数。对于int类型,这种语法只意味着"用num的值初始化bar",就这样。这样做是直接的,不涉及构造函数,因为int不是的类类型,因此它不能有任何构造函数。


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


这是初始化列表。它将在运行构造函数体之前初始化成员。考虑

1
2
3
4
5
6
7
8
class Foo {
 public:
   string str;
   Foo(string &p)
   {
      str = p;
   };
 };

VS

1
2
3
4
5
class Foo {
public:
  string str;
  Foo(string &p): str(p) {};
};

在第一个示例中,str将由它的无参数构造函数初始化

1
string();

在foo构造函数的主体之前。在foo构造函数中,

1
string& operator=( const string& s );

会像调用str=p一样调用str;

在第二个示例中,str将由调用其构造函数

1
string( const string& s );

以"p"作为参数。


它是构造函数的初始化列表。不是默认构造xyz,然后将参数中接收到的值分配给它们,这些成员将立即用这些值初始化。对于floats来说,这似乎不是非常有用,但对于构建成本高昂的自定义类来说,这可能是相当大的省时方法。


还有另一个"好处"

如果成员变量类型不支持空初始化或其引用(不能为空初始化),那么除了提供初始化列表之外,您别无选择。


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


在这个线程中还没有提到:因为C++ 11,成员初始化器列表可以使用列表初始化(AKA)。统一初始化","支撑初始化"):

1
Foo(int num): bar{num} {}

它与其他上下文中的列表初始化具有相同的语义。