ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

一本没有提到这一点的“普通 c++ 书”可能是一本有人认为“++”在封面上看起来很酷的书……
“你永远不会在一本普通的 C++ 书中找到”。哦。亲爱的。现在就扔掉你的“普通 C++ 书”。不是窗外——其他人可能会捡到它。最好将其切碎并放入回收利用。完毕?现在咨询 stackoverflow.com/questions/388242/… 以获得一本新书。
这种语言特性并不深奥。这是对象构造的一个相当重要的特征。
事实上,远非深奥,您通常别无选择,只能使用初始化列表。例如,如果您的类包含 const 成员变量或引用,则必须使用初始化列表。

o
orestisf
Foo(int num): bar(num)    

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

简单地说,它初始化您的成员 bar 到值 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 List。

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

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

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

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


@nils这是迄今为止最好的答案。 Als 指出的初始化顺序也非常重要,而 Visual Studio 编译器对此不会说什么,其他编译器(如 gcc)会失败。同样重要的是要注意取决于您的编译器和情况,这并不总是会提高性能或提高效率。
@ryf9059:你为什么觉得不方便?无论如何您都必须列出它们,所以为什么不按照与声明相同的顺序。
这应该是答案。感谢上帝,我向下滚动,否则我会错过它。
@AlokSave MyClass(int a, int b, int c) : i(a), b(b), k(c) { // 没有成员初始化器 // this->b = b ; } 应该是这样 MyClass(int &a , int b, int c) : i(a), b(b), k(c) { / / 没有成员初始化器 // this->b = b; } 等声明和调用的相应更改。如果没有此更改,i 将引用 a,但 a 不能引用 x,因为它只包含 x 的值,因此 i 不能间接引用 x。所以如果我们修改 i 的值,那么它只修改 a 而不是 x
@AbhishekMane 你是对的,这里是相关问题的链接:stackoverflow.com/q/67619383/3150802
T
TallChuck

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

You should, in most cases, initialize all member objects in the member initialization list(不过,请注意常见问题条目末尾列出的例外情况)。

FAQ条目的要点是,

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


知道术语很关键——我很嫉妒我没有想到它。
使用初始化列表还有很多其他原因。特别是当初始化的顺序很重要时。可惜它有这么愚蠢的假函数调用语法。
@mgb,初始化列表不决定初始化的顺序。成员变量按照它们在类中声明的顺序进行初始化,即使这与构造函数的初始化顺序不同。
@mgb:我不认为它是假的函数调用语法。它是初始化语法,如 int i(23);std::vector<double> emptyVec(0);std::vector<double> fullVec(10,23.); 等。当然,只有删除了类型,因为类型在成员声明中。
@Martin:它没有函数调用语法,它有构造语法(ala:new String("Name"))。它比 Foo(int num) 更适合构造函数:m_Count = 5。更不用说此时必须构造类,因为它已在此处初始化。 Foo(int num) : Bar = num,编译不正确。看到 Foo(int num) : m_Count(num) 似乎很奇怪,因为没有构造原始类型。
L
Lightness Races in Orbit

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

考虑这两个例子:

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

一切都与效率有关。


我不会说这是关于效率的。它是关于提供一种方法来初始化需要初始化但不能默认初始化的东西。出于某种原因,人们提到常量和引用作为示例,而最明显的示例是没有默认构造函数的类。
我们都是对的;在他的例子中,你可以为效率争论;对于 const/reference/no 默认构造函数问题,它既是效率也是必要的。因为它,我在下面投了一个答案:) [Farnsworth voice] 它可以做其他事情。为什么不应该呢?
Bar bar(); // default constructor 你确定吗?
@LightnessRacesinOrbit 只是想知道:你应该怎么做?
L
LeopardSkinPillBoxHat

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


@LightnessRacesinOrbit - 取点,但常量或参考点在我的回答中仍然有效。
w
wkl

这并不晦涩,它是 C++ initialization list syntax

基本上,在您的情况下,x 将用 _x 初始化,y_y 初始化,z_z 初始化。


D
Destructor

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

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

在您的情况下,数据成员的类型为 intint 不是类类型,因此它没有构造函数。对于类型 int,此语法仅表示“使用 num 的值初始化 bar”,仅此而已。就这样直接完成,不涉及任何构造函数,因为 int 又不是一个类类型,因此它不能有任何构造函数。


但是 bjarne stroustrup 在他的书 TC++PL & C++ 编程语言说 "内置类型也有默认构造函数"geeksforgeeks.org/c-default-constructor-built-in-types & informit.com/guides/content.aspx?g=cplusplus&seqNum=15 还说内置类型有构造函数。我亲自通过邮件向 bjarne 提出了这个问题。他说是的,内置类型也有构造函数。所以你的答案是错误的!
@Destructor:我的回答是绝对正确的。为了简化,Bjarne Stroustrup 在他的书中故意而明确地撒谎。比较 TC++PL 书的大小和 C++ 语言标准的大小。看到不同?为 TC++PL 的相对紧凑性付出的代价是显而易见的(并且众所周知)错误和遗漏,就像你提到的那样(还有很多其他的)。所以,更简洁地说:我的回答是对的,TC++PL 是错的。但是对于你这种刚开始学习的人来说,TC++PL 已经足够好了。
请不要告诉我们关于“通过邮件向 bjarne 提出这个问题”的荒谬童话。这个问题又是众所周知的,很久以前就已经被详尽地讨论和关闭了。 (但即使他对你说了这样的话,也没关系。)最后,唯一重要的是语言规范所说的内容。 Bjarne Stroustrup 所说的无关紧要。您链接的 geeksforgeeks 上的帖子完全是伪造的。
那么,您认为该标准没有错误吗? C++ 标准也有错误。
@Destructor:当然可以。但是,标准定义了语言。根据定义,标准所说的一切都是绝对真理。它可能存在的唯一“错误”主要是与措辞相关的事情,例如自相矛盾的措辞、模棱两可的措辞、指定不足等。此类错误被积极寻找、报告、记录、讨论和解决。意图中的“错误”也可能存在,但它们是一个有争议的问题。
M
Mark Ransom

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


像这样写在声明内的一行上,很容易不将其视为初始化列表
n
nos

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

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


A
Aric TenEyck

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


好处是它通常更有效。而且,在某些情况下,例如当您有 const 个成员变量或引用的成员变量时,您必须使用初始化列表。
p
pm100

还有另一个“好处”

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


s
suszterpatt

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


M
M.M

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

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

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


K
Karen Baghdasaryan

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

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)

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