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

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


当前回答

Foo(int num): bar(num)    

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

简单地说,它将你的成员栏初始化为值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没有默认构造函数,因此需要使用成员初始化列表来初始化它的成员。


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

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

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

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

其他回答

它是一个成员初始化列表。你可以在任何好的c++书籍中找到有关它的信息。

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

常见问题解答的要点是,

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

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

基本上,在你的例子中,x将被初始化为_x, y被初始化为_y, z被初始化为_z。

Foo(int num): bar(num)    

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

简单地说,它将你的成员栏初始化为值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没有默认构造函数,因此需要使用成员初始化列表来初始化它的成员。


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

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

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

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

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

考虑以下两个例子:

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

这都是效率问题。

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