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

nil*_*ils 325 c++ syntax constructor c++-faq ctor-initializer

最近我见过如下例子:

#include <iostream>

class Foo {
public:
  int bar;
  Foo(int num): bar(num) {};
};

int main(void) {
  std::cout << Foo(42).bar << std::endl;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

这奇怪: bar(num)意味着什么?它似乎初始化成员变量,但我以前从未见过这种语法.它看起来像一个函数/构造函数调用,但对于一个int?对我没有任何意义.也许有人可以启发我.而且,顺便说一下,还有其他类似的深奥语言功能,你永远不会在一本普通的C++书中找到它吗?

Alo*_*ave 307

Foo(int num): bar(num)    
Run Code Online (Sandbox Code Playgroud)

此构造在C++中称为成员初始化列表.

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


构造函数中的Initializing和Assignment有什么区别?

会员初始化:

Foo(int num): bar(num) {};
Run Code Online (Sandbox Code Playgroud)

会员作业:

Foo(int num)
{
   bar = num;
}
Run Code Online (Sandbox Code Playgroud)

使用成员初始值设定项列表初始化成员并在构造函数体内为其分配值之间存在显着差异.

当你初始化通过成员初始化列表域构造将被调用一次与对象将构造并在一次操作中被初始化.

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

如您所见,在后者中存在创建和分配的额外开销,这对于用户定义的类可能是相当大的.

Cost of Member Initialization = Object Construction 
Cost of Member Assignment = Object Construction + Assignment
Run Code Online (Sandbox Code Playgroud)

后者实际上相当于:

Foo(int num) : bar() {bar = num;}
Run Code Online (Sandbox Code Playgroud)

而前者相当于:

Foo(int num): bar(num){}
Run Code Online (Sandbox Code Playgroud)

对于内置(您的代码示例)或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;
}
Run Code Online (Sandbox Code Playgroud)
  • MyClass2 没有默认构造函数,因此必须通过成员初始化列表初始化它.
  • 基类MyClass没有默认构造函数,因此要初始化其成员,需要使用Member Initializer List.

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

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

它们未按成员初始化列表中指定的顺序进行初始化.
简而言之,成员初始化列表不确定初始化的顺序.

鉴于上述情况,保持成员初始化的成员顺序与在类定义中声明它们的顺序始终是一个好习惯.这是因为如果两个订单不同,编译器不会发出警告,但是相对较新的用户可能会将成员初始化程序列表混淆为初始化顺序,并编写一些依赖于此的代码.

  • @nils这是迄今为止最好的答案.Als指出的初始化顺序也非常重要,而Visual Studio编译器对此没有任何说明,其他编译器如gcc将失败.同样重要的是要注意依赖于您的编译器和情况**并非总是如此**这将提高性能或提高效率. (11认同)
  • 这应该是答案。感谢上帝,我向下滚动,否则我会错过它。 (2认同)

Jam*_*lis 205

这是一个成员初始化列表.您应该在任何优秀的C++书籍中找到有关它的信息.

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

FAQ条目的主要内容是,

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

  • @ mgb,初始化列表不确定初始化的顺序.成员变量按照它们在类中声明的顺序进行初始化,即使它与构造函数上的初始化顺序不同. (16认同)
  • @mgb:我不认为它是伪造的函数调用语法.它的初始化语法,如`int i(23);`,`std :: vector <double> emptyVec(0);`,`std :: vector <double> fullVec(10,23.);`等仅当然,删除类型,因为类型在成员声明中. (12认同)

Jos*_*osh 16

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

考虑这两个例子:

// Example 1
Foo(Bar b)
{
   bar = b;
}

// Example 2
Foo(Bar b)
   : bar(b)
{
}
Run Code Online (Sandbox Code Playgroud)

在示例1中:

Bar bar;  // default constructor
bar = b;  // assignment
Run Code Online (Sandbox Code Playgroud)

在示例2中:

Bar bar(b) // copy constructor
Run Code Online (Sandbox Code Playgroud)

这完全取决于效率.

  • 我不会说这是关于效率的.它是关于提供一种初始化需要初始化的东西的方法,但不能默认初始化.出于某种原因,人们提到常量和引用作为示例,而最明显的示例是没有默认构造函数的类. (7认同)

Leo*_*Hat 14

这称为初始化列表.它是初始化类成员的另一种方法.使用它有一些好处,而不是简单地为构造函数体中的成员分配新值,但如果你有类成员是常量引用,必须初始化它们.


wkl*_*wkl 9

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

基本上,在您的情况下,x将使用_x,ywith _y,zwith 初始化_z.


AnT*_*AnT 8

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

我只想指出,正如你所说,"看起来像构造函数调用"的语法不一定是构造函数调用.在C++语言中,()语法只是初始化语法的一种标准形式.对于不同类型,它的解释不同.对于具有用户定义的构造函数的类类型,它意味着一件事(它确实是构造函数调用),对于没有用户定义的构造函数的类类型,它意味着另一件事(所谓的值初始化)为空())和非类类型它再次意味着不同的东西(因为非类型没有构造函数).

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


Mar*_*som 7

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

  • 写在声明中的一行,就像很容易将它作为初始列表发现 (2认同)

nos*_*nos 6

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

class Foo {
 public:
   string str;
   Foo(string &p)
   {
      str = p;
   };
 };
Run Code Online (Sandbox Code Playgroud)

VS

class Foo {
public:
  string str;
  Foo(string &p): str(p) {};
};
Run Code Online (Sandbox Code Playgroud)

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

string();
Run Code Online (Sandbox Code Playgroud)

在Foo构造函数的主体之前.在foo构造函数里面,

string& operator=( const string& s );
Run Code Online (Sandbox Code Playgroud)

当你做str = p时,将在'str'上调用;

在第二个例子中,str将通过调用其构造函数直接初始化

string( const string& s );
Run Code Online (Sandbox Code Playgroud)

用'p'作为参数.


Ari*_*yck 5

你是对的,这确实是一种初始化成员变量的方法.除了清楚地表明它是初始化之外,我不确定这有什么好处.在代码中包含"bar = num"可以更容易地移动,删除或误解.

  • 好处是它通常更有效率.并且,在某些情况下,例如当你有`const`成员变量或成员变量作为引用时,你*必须*使用初始化列表. (5认同)

pm1*_*100 5

还有另一个"好处"

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


sus*_*att 5

它是构造函数的初始化列表.相反,默认构建的x,y并且z,然后指派他们在参数接收的值,这些成员将马上蝙蝠的值进行初始化.这对于floats 来说似乎并不是非常有用,但是对于构造成本昂贵的自定义类来说可能相当长.