关于 C++ 中构造函数(和多重继承)的一些基本问题?
(如果之前有人问过这个问题,我很抱歉;搜索功能似乎已损坏:结果区域完全是空白的,即使它说有几页结果......在 Chrome、FireFox 和 Safari 中)
所以,我刚刚学习 C++……而我正在阅读的这本书在以我可以掌握的方式解释构造函数方面做得非常糟糕。到目前为止,我几乎已经掌握了所有其他内容,但我无法弄清楚构造函数的语法实际上是如何工作的。
例如,我被告知以下内容将导致构造函数调用指定超类的构造函数:
class something : something_else {
something(int foo, double bar) : something_else(int foo) {}
};
另一方面,在本书后面描述如何初始化 const 成员时,使用了相同的语法:
class something : something_else {
private: const int constant_member;
public: something(int foo, double bar) : constant_member(42) {}
};
那么……呃……到底发生了什么?语法 rvsignature(param) : Something_else(what);
实际上意味着什么?我无法弄清楚 something_else(what)
是什么以及它周围的代码。它似乎具有多种含义;我确信它所对应的语言一定有一些底层元素,我只是不知道是什么。
编辑: 另外,我应该提到,上一个示例中的 what
有时是一个参数列表,这非常令人困惑(所以 something_else(what)
看起来像一个函数签名)……有时是一个常量值表达式(所以 something_else(what)
看起来像一个函数调用)。
现在,继续:多重继承和构造函数怎么样?如何指定调用哪些父类的构造函数……以及默认调用哪些构造函数?我知道,默认情况下,以下两个是相同的......但我不确定当涉及多重继承时等效项是什么:
class something : something_else {
//something(int foo, double bar) : something_else() {}
something(int foo, double bar) {}
};
任何帮助理解这些主题的帮助都将非常感激;我不喜欢这种感觉,因为我无法理解一些基本的东西。我一点也不喜欢它。。
编辑2:好的,到目前为止,下面的答案都非常有帮助。不过,他们提出了这个问题的另一部分:“初始化列表”中基类构造函数调用的参数如何与您定义的构造函数相关?它们是否必须匹配……是否必须有默认值?他们必须匹配多少?换句话说,以下哪些行为非法:
class something_else {
something_else(int foo, double bar = 0.0) {}
something_else(double gaz) {}
};
class something : something_else {
something(int foo, double bar) : something_else(int foo, double bar) {} };
class something : something_else {
something(int foo) : something_else(int foo, double bar) {} };
class something : something_else {
something(double bar, int foo) : something_else(double gaz) {} };
(I’m sorry if this has been asked before; the search feature seems to be broken: the results area is completely blank, even though it says there are a few pages of results… in Chrome, FireFox, and Safari)
So, I’m just learning C++… and the book I’m moving through is doing a really bad job of explaining constructors in a way that I can grasp them. I’ve pretty much grokked everything else so far, but I can’t figure out how the syntax for constructors actually works.
For instance, I am told that the following will cause the constructor to call the designated superclass’s constructor:
class something : something_else {
something(int foo, double bar) : something_else(int foo) {}
};
On the other hand, that same syntax was utilized later on in the book, when describing how to initialize const
members:
class something : something_else {
private: const int constant_member;
public: something(int foo, double bar) : constant_member(42) {}
};
So… uh… what the hell is going on there? What does the syntax rv signature(param) : something_else(what);
actually mean? I can’t figure out what that something_else(what)
is, with relation to the code around it. It seems to take on multiple meanings; I’m sure there must be some underlying element of the language that it corresponds to, I just can’t figure out what.
Edit: Also, I should mention, it’s very confusing that the what
in the previous example is sometimes a parameter list (so something_else(what)
looks like a function signature)… and sometimes a constant-value expression (so something_else(what)
looks like a function call).
Now, moving on: What about multiple-inheritance and constructors? How can I specify what constructors from which parent classes are called… and which ones are called by default? I’m aware that, by default, the following two are the same… but I’m not sure what the equivalent is when multiple-inheritance is involved:
class something : something_else {
//something(int foo, double bar) : something_else() {}
something(int foo, double bar) {}
};
Any help in grokking these topics would be very appreciated; I don’t like this feeling that I’m failing to understand something basic. I don’t like it at all.
Edit 2: Okay, the answers below as of now are all really helpful. They raise one more portion of this question though: How do the arguments of base-class-constructor-calls in ‘initialization lists’ relate to the constructor you’re defining? Do they have to match… do there have to be defaults? How much do they have to match? In other words, which of the following are illegal:
class something_else {
something_else(int foo, double bar = 0.0) {}
something_else(double gaz) {}
};
class something : something_else {
something(int foo, double bar) : something_else(int foo, double bar) {} };
class something : something_else {
something(int foo) : something_else(int foo, double bar) {} };
class something : something_else {
something(double bar, int foo) : something_else(double gaz) {} };
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
构造函数定义的语法是:
其中“初始化列表”是对基类和/或成员属性的构造函数的调用的逗号分隔列表。需要初始化任何没有默认构造函数、常量子对象和引用属性的子对象(基类或成员),并且在所有其他情况下应优先于构造函数块中的赋值。
初始化列表的执行顺序在类声明中定义:基类按照声明的顺序,成员属性按照声明的顺序。在上面的示例中,在构造函数主体中执行
f()
之前,该类将按以下顺序初始化其基类和属性:base(int)
构造函数base2(int)
构造函数 用x*2
初始化member1
用值x*2
member2
用值>x
当您引入虚拟继承时,虚拟基类在虚拟继承层次结构的最派生类中进行初始化,因此它可以(或者如果没有默认构造函数,则必须)出现在该初始化列表中。在这种情况下,虚拟基类将在从该基类虚拟继承的第一个子对象之前初始化。
关于编辑 2
我认为您没有阅读答案中的详细信息。初始化列表中的元素是构造函数调用,而不是声明。如果合适,编译器将为调用应用通常的转换规则。
The syntax for a constructor definition is:
Where the 'initialization-list' is a comma separated list of calls to constructors for the bases and/or member attributes. It is required to initialize any subobject (base or member) for which there is no default constructor, constant subobjects and reference attributes, and should be preferred over assignment in the constructor block in all other cases.
The order in which the initialization list is executed is defined in the class declaration: bases in the order in which they are declared, member attributes in the order in which they are declared. In the example above before executing
f()
in the constructor body the class will initialize its base classes and attributes in the following sequence:base(int)
constructor with parameter 1base2(int)
constructor with parameter 5member1
with valuex*2
member2
with valuex
When you throw in virtual inheritance, the virtual base is initialized in the most derived class of the virtual inheritance hierachy, and as such it can (or must if there is no default constructor) appear in that initialization list. In that case, the virtual base will be initialized right before the first subobject that inherits virtually from that base.
On Edit 2
I think you are not reading the details in the answers. The elements in the initialization list are constructor calls, not declarations. The compiler will apply the usual conversion rules for the call if appropriate.
这个习惯用法称为初始化列表。
基本上,对于每个项目,您都调用一个构造函数:
在 (1) 中,您调用基类构造函数,该基类构造函数将
int
作为参数,或者可以执行适当的转换。在 (2) 中,您调用以
char
作为参数的基类构造函数在 (3) 中,您调用“构造函数”来初始化
int
,在本例中是简单的赋值(4) 调用 std::string(const char*) 构造函数。
This idiom is called initialization list.
Basically with each item you call a constructor:
At (1) you call base class constructor that takes
int
as a parameter or can perform appropriate conversion.At (2) you call base class constructor that takes
char
as a parameterAt (3) you call "constructor" to initialize an
int
which in this case is simple assignmentAt (4) you call
std::string(const char*)
constructor.编译器可以确定您是否正在调用基类的构造函数或是否正在进行初始化。
示例 1:
编译器可以看到您提供的名称属于基类。因此它将调用基类中相应的构造函数。
示例 2:
编译器可以看到您的类中有一个名为
constant_member
的成员变量,因此它将使用提供的值对其进行初始化。您可以在同一初始化列表中初始化成员并调用基类构造函数(这就是构造函数中的函数声明语法 - 初始化列表)。
The compiler can determine weather you are calling the constructor of a base class or weather you are making an initialization.
Example 1:
The compiler can see that the name you are supplying belongs to a base class. It will therefore call the corresponding constructor in the base class.
Example 2:
The compiler can see you have a member variable called
constant_member
as part of your class, therefore it will initialize it with the value supplied.You can initialize members and call base class constructors in the same initialization list (thats what the function-declaration syntax in the constructor is -- a initialization list).
在构造函数中,您可以显式调用成员变量的构造函数。
当您的成员变量没有默认构造函数时,这一点至关重要。
同样,当父类的默认构造函数不起作用或不存在时,您可以显式调用父类的构造函数。
无论如何,像这样显式调用构造函数称为初始化程序。
编辑:请务必按照您在标头中声明成员的顺序初始化成员,否则您的代码将在编译时出现警告。
In your constructor, you can explicity call constructors for your member variables.
This is essential when your member variables don't have default constructors.
Similarly, you can explicitly call the constructor for your parent class when its default constructor won't do or doesn't exist.
Anyway, calling a constructor explicitly like this is called an initializer.
Edit: Be sure to initialize members in the order you declared them in your header or your code will have warnings at compile time.
在构造函数初始值设定项列表中,您可以编写
data_member(val)
以便使用val
初始化data_member
。请注意,val
可能是一个表达式,甚至是只能在运行时计算的表达式。如果数据成员是一个对象,则将使用该值调用它的构造函数。此外,如果它有一个需要多个参数的构造函数,您可以将它们全部传递,就像在函数调用中一样,例如data_member(i, j, k)
。现在,为了进行此类初始化,您应该将对象的基类部分视为一个数据成员,其名称只是基类的名称。因此MyBase(val)
或MyBase(i, ,j ,k)
。将调用基类的构造函数。多重继承以同样的方式工作。只需将您想要的基类初始化为列表中的单独项目即可:MyBase1(x)、MyBase2(y)
。您未显式调用其构造函数的基类将由其默认构造函数(如果存在)进行初始化。如果不这样做,除非您显式初始化,否则代码将无法编译。In constructor initializer lists you write
data_member(val)
in order to initializedata_member
withval
. Note thatval
may be an expression, even one that can only be evaluated at runtime. If the data member is an object, it's constructor will be called with that value. Furthermore, if it has a constructor expecting several argumets, you can pass them all, just like in a function call, e.g.,data_member(i, j, k)
. Now, for the purpose of such initializations you should think of the base class part of the object as a data member whose name is simply the base class' names. HenceMyBase(val)
orMyBase(i, ,j ,k)
. The base class's constructor will be called. Multiple inheritence works in the same way. Just initialize whichever base classes you want as separate items on the list:MyBase1(x), MyBase2(y)
. Base classes whose constructors you don't call explicitly will be initialized by their default constructors, if these exist. If they don't, the code won't compile unless you initialize explicitly.本书试图解释 C++ 初始化列表。一般来说,初始化列表由构造函数调用组成,包括对父类构造函数和类属性构造函数的调用。
初始化列表应包含(按顺序):
首先应调用所有基类构造函数。基类构造函数的调用顺序由编译器定义。正如 C++ 常见问题解答所述:
因此,初始化列表中基类构造函数的顺序并不重要。如果初始化列表中未显式列出基类构造函数,则将调用默认构造函数。
基类构造函数调用之后是类属性构造函数调用。这些看起来像函数调用,但本质上是初始化变量的有效方法,称为构造函数初始化。例如,下面的 C++ 代码完全有效:
请注意,初始化列表中类属性的顺序应与类头中定义的顺序相匹配。
最后值得一提的是,使用初始化列表是一种很好的做法。它比在构造函数主体中使用赋值更有效,因为它消除了使用默认或未定义值的类属性的初始构造,并且确保了严格的初始化顺序。
The book is trying to explain C++ initialization lists. In general, an initialization list consists of constructor calls, both to parent class constructors and class property constructors.
An initialization list should consist of (in order):
First all base class constructors should be called. The order of base class constructor calls is defined by the compiler. As stated by the C++ FAQ:
Thus, the order of the base class constructors in the initialization list does not matter. In case a base class constructor is not explicitly listed in the intialization list, the default constructor will be called.
Following the base class constructor calls are the class property constructor calls. These look like function calls, but are in essence a valid way of initializing variables called constructor initialization. For example, the following piece of C++ code is perfectly valid:
Note that the order of the class properties in the initialization list should match the order of definition in the class header.
Finally it is worth mentioning that using initialization lists is good practice. It is more efficient than using assignments in the constructor body since it eliminates the initial construction of the class property with a default or undefined value, and it ensures a strict order of initialization.
C++ 的语法非常模糊,因此要准备好迎接许多具有不同语义的类似语法结构。
例如:
这可能意味着通过使用参数值“b”调用其构造函数“A::A”来创建类“A”的对象“a”。
但这也可以是具有类型“b”的形式参数并返回类型“A”的值的函数“a”的声明。
对于“简单”语法,编译器可以实现为包含几乎独立模块的管道:词法分析器、解析器、语义分析器等。
通常,这样的语言不仅易于编译器决定,而且易于人类(程序员)理解。
C++ 具有非常复杂的语法(和语义)。
因此,如果没有语义信息,C++ 解析器无法决定应用哪种语法规则。
这给设计和实现 C++ 编译器带来了困难。
此外,C++ 给程序员理解程序带来了问题。
所以你在理解语法方面的问题根源不在于你的头脑,而在于 C++ 的语法。
上述原因导致建议不要使用 C++(和其他过于复杂的语言)来向初学者教授编程。
首先,使用简单(但足够强大)的语言来培养编程技能,然后转向主流语言。
C++ has very ambiguous syntax, so be ready to meet many similar syntax constructions with different symantics.
For example:
This could mean a creation of an object 'a' of class 'A' by calling its contructor 'A::A' with a parameter value 'b'.
But also this can be a declaration of function 'a' having a formal parameter of type 'b' and returning a value of type 'A'.
For a "simple" grammar the compiler can be implemented as a pipeline containing almost independent modules: Lexer, Parser, Semantic Analyzer, etc.
Usually, such languages are not only simple for deciding by compilers but also are easy for understanding by humans (programmers).
C++ has very complicated grammar (and semantics).
Therefore, without semantic information, C++ parser cannot decide which grammar rule to apply.
This leads to difficulties in designing and implementing a C++ compiler.
In addition, C++ makes it problematic to understand the programs by programmers.
So the root of your problems in understanding the syntax is not in your head but in the grammar of C++.
The reasons above leads to recommendations not to use C++ (and other overcomplicated languages) for teaching programming to beginners.
First, use a simple (but powerful enought) language for developyng programming skills, then move to mainstream languages.