396

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

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

4

13 回答 13

367
Foo(int num): bar(num)    

此构造在 C++中称为成员初始值设定项列表。

简单地说,它将您的成员初始化bar为 value 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 列表混淆为初始化顺序并编写一些依赖于此的代码。

于 2011-12-15T16:29:15.823 回答
245

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

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

FAQ条目的要点是,

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

于 2009-11-10T23:31:41.240 回答
17

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

考虑这两个例子:

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

一切都与效率有关。

于 2009-11-10T23:34:18.007 回答
16

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

于 2009-11-10T23:35:42.047 回答
9

这并不晦涩,它是C++ 初始化列表语法

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

于 2010-12-11T19:56:51.610 回答
8

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

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

在您的情况下,数据成员具有 type intint不是类类型,所以它没有构造函数。对于类型int,此语法仅意味着“bar使用”的值进行初始化,仅此num而已。就这样直接完成了,不涉及任何构造函数,因为再一次,int它不是类类型,因此它不能有任何构造函数。

于 2009-11-11T00:06:50.363 回答
6

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

于 2009-11-10T23:32:54.967 回答
6

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

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

于 2009-11-10T23:42:06.030 回答
5

你是对的,这确实是一种初始化成员变量的方法。除了明确表示这是一个初始化之外,我不确定这有什么好处。在代码中包含“bar=num”可能更容易被移动、删除或误解。

于 2009-11-10T23:32:53.597 回答
5

还有另一个“好处”

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

于 2009-11-10T23:46:41.030 回答
5

这是构造函数的初始化列表。代替默认构造xy然后z将参数中接收的值分配给它们,这些成员将立即使用这些值进行初始化。这对 s 来说可能看起来不是非常有用float,但是对于构建成本高昂的自定义类来说,它可以节省很多时间。

于 2010-12-11T19:58:10.473 回答
3

此线程上尚未提及:从 C++11 开始,成员初始化器列表可以使用列表初始化(又名“统一初始化”、“支撑初始化”):

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

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

于 2017-05-29T23:07:29.957 回答
0

虽然这是一个古老的讨论,但我找不到任何关于委托构造函数的提及,它以下列方式使用奇怪的“:”符号。

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)

在定义委托构造函数时,除了目标构造函数之外,初始化列表中不能有任何成员。

于 2021-08-24T09:20:43.707 回答