我想知道为什么构造函数的名称总是与类名称相同,以及当我们创建该类的对象时它是如何被隐式调用的。谁能解释在这种情况下的执行流程?
7 回答
我想知道为什么构造函数的名字总是和类名一样
因为这种语法不需要任何新的关键字。除此之外,没有充分的理由。
为了尽量减少新关键字的数量,我没有使用这样的显式语法:
class X { constructor(); destructor(); }
相反,我选择了一种反映构造函数使用的声明语法。
class X { X(); ~X();
这可能过于聪明了。【C++的设计与演进,3.11.2构造函数记法】
谁能解释在这种情况下的执行流程?
对象的生命周期可以这样概括:
- 分配内存
- 调用构造函数
- 使用对象
- 调用析构函数/终结器
- 释放记忆
在 Java 中,第 1 步总是从堆中分配。在 C# 中,类也是从堆中分配的,而结构的内存已经可用(在未捕获的本地结构的情况下在堆栈上或在其父对象/闭包中)。请注意,了解这些细节通常没有必要或很有帮助。在 C++ 中,内存分配极其复杂,这里不再赘述。
第 5 步取决于内存的分配方式。方法一结束,堆栈内存就会自动释放。在 Java 和 C# 中,堆内存在不再需要后的某个未知时间由垃圾收集器隐式释放。在 C++ 中,堆内存在技术上是通过调用delete
. 在现代 C++ 中,delete
很少手动调用。相反,您应该使用 RAII 对象,例如std::string
,std::vector<T>
并std::shared_ptr<T>
自行处理。
为什么?因为您提到的不同语言的设计者决定以这种方式制作它们。有人完全有可能设计一种 OOP 语言,其中构造函数不必与类同名(如评论所述,python 中就是这种情况)。
这是一种将构造函数与其他函数区分开来的简单方法,并使代码中类的构造非常易读,因此作为语言设计选择是有意义的。
该机制在不同的语言中略有不同,但本质上这只是一种由语言特性辅助的方法调用(new
例如 java 和 c# 中的关键字)。
每当创建新对象时,运行时都会调用构造函数。
在我看来,使用单独的关键字来声明构造函数会“更好”,因为它会消除对类本身名称的不必要的依赖。
然后,例如,可以将类内的代码复制为另一个类的主体,而无需更改构造函数的名称。我不知道为什么要这样做(可能是在某些代码重构过程中),但关键是人们总是力求事物之间的独立性,而我认为语言语法与此背道而驰。
析构函数也一样。
构造函数具有相同名称的一个很好的原因是它们的表现力。例如,在 Java 中,您创建一个对象,例如,
MyClass obj = new MyClass(); // almost same in other languages too
现在,构造函数定义为,
class MyClass {
public MyClass () {... }
}
所以上面的语句很好地表达了,你正在创建一个对象,并且在这个过程MyClass()
中调用了构造函数。
现在,无论何时创建一个对象,它总是调用它的构造函数。如果该类是extend
其他基类,那么它们的构造函数将首先被调用,依此类推。所有这些操作都是隐式的。首先分配对象的内存(在堆上),然后调用构造函数来初始化对象。如果您不提供构造函数,编译器将为您的类生成一个。
在 C++ 中,严格来说,构造函数根本没有名字。12.1/1 在标准状态下,“构造函数没有名称”,没有比这更清楚的了。
在 C++ 中声明和定义构造函数的语法使用类的名称。必须有某种方法可以做到这一点,并且使用类的名称简洁易懂。C# 和 Java 都复制了 C++ 的语法,大概是因为至少有一些他们的目标受众会熟悉它。
执行的精确流程取决于您正在谈论的语言,但您列出的三个共同点是首先从某个地方分配一些内存(可能是动态分配的,可能是堆栈内存的某个特定区域或其他)。然后运行时负责确保以正确的顺序调用正确的构造函数,用于最派生类和基类。如何确保发生这种情况取决于实现,但所需的效果由每种语言定义。
对于 C++ 中最简单的可能情况,即没有基类的类,编译器只需发出对由创建对象的代码指定的构造函数的调用,即与提供的任何参数匹配的构造函数。一旦你有几个虚拟基地在玩,它就会变得更加复杂。
我想知道为什么构造函数的名字总是和类名一样
这样就可以明确地将其标识为构造函数。
以及当我们创建该类的对象时它是如何被隐式调用的。
它被编译器调用是因为它已经被明确地识别出来,因为它的命名方式。
谁能解释在这种情况下的执行流程?
- 调用新的 X() 运算符。
- 分配内存,或抛出异常。
- 构造函数被调用。
- new() 操作符返回给调用者。
问题是为什么设计师会这样决定?
在其类之后命名构造函数是一个由来已久的约定,至少可以追溯到 1980 年代早期的 C++ 早期,可能是其 Simula 的前身。
构造函数与类同名的约定是为了便于编程、构造函数链接和语言的一致性。
例如,考虑一个您想使用 Scanner 类的场景,现在如果 JAVA 开发人员将构造函数命名为 xyz!
那么你将如何知道你需要写:
扫描仪 scObj = new xyz(System.in) ;
这可能真的很奇怪,对吧!或者,您可能不得不参考一个巨大的手册来检查每个类的构造函数名称以便创建对象,如果您可以通过命名与类的构造函数相同的构造函数来解决问题,那么这又是没有意义的。
其次,构造函数本身是由编译器创建的,如果您没有明确提供它,那么编译器可以自动选择构造函数的最佳名称,因此程序员很清楚!显然,最好的选择是保持与类相同。
第三,你可能听说过构造函数链接,那么当在构造函数之间链接调用时,编译器是如何知道你给被链接的类的构造函数起什么名字的!显然,问题的解决方案也是相同的,保持构造函数的名称与类的名称相同。
创建对象时,您通过在代码中使用 new 关键字调用构造函数(并在需要时传递参数)来调用构造函数,然后通过链接最终给出对象的调用来调用所有超类构造函数。
感谢您的提问。