为啥构造函数总是与类具有相同的名称以及它们是如何被隐式调用的?
Posted
技术标签:
【中文标题】为啥构造函数总是与类具有相同的名称以及它们是如何被隐式调用的?【英文标题】:Why constructors will always have same name as of class and how they are invoked implicitly?为什么构造函数总是与类具有相同的名称以及它们是如何被隐式调用的? 【发布时间】:2011-08-08 08:46:45 【问题描述】:我想知道为什么构造函数的名称总是与类名相同,以及当我们创建该类的对象时它是如何被隐式调用的。谁能解释一下这种情况下的执行流程?
【问题讨论】:
这对您的标记语言(C++、Java、C#)是正确的,但还有其他可能性:在 Pascal/Delphi 中,构造函数是用constructor
关键字定义的(并且可以有任何方法名称,但是通常是Create
),在python中,任何类的构造函数都称为__init__
而在D中,构造函数被称为this
。
【参考方案1】:
我想知道为什么构造函数的名字总是和类的名字一样
因为这种语法不需要任何新的关键字。除此之外,没有什么好的理由。
为了尽量减少新关键字的数量,我没有使用这样的显式语法:
class X constructor(); destructor();
相反,我选择了一种反映构造函数的使用的声明语法。
class X X(); ~X();
这可能过于聪明了。 【C++的设计与演进,3.11.2构造函数记法】
谁能解释一下这种情况下的执行流程?
一个对象的生命周期可以这样概括:
-
分配内存
调用构造函数
使用对象
调用析构函数/终结器
释放内存
在 Java 中,第 1 步总是从堆中分配。在 C# 中,类也是从堆中分配的,而结构的内存已经可用(在未捕获的本地结构的情况下在堆栈上或在其父对象/闭包中)。 Note that knowing these details is generally not necessary or very helpful。在 C++ 中,内存分配非常复杂,这里不再赘述。
第 5 步取决于内存的分配方式。方法一结束,堆栈内存就会自动释放。在 Java 和 C# 中,堆内存在不再需要后的某个未知时间由垃圾收集器隐式释放。在 C++ 中,堆内存在技术上是通过调用 delete
来释放的。在现代 C++ 中,delete
很少被手动调用。相反,您应该使用 RAII 对象,例如 std::string
、std::vector<T>
和 std::shared_ptr<T>
,它们会自行处理。
【讨论】:
D&E 当然是许多“为什么 X 像 X 一样工作?”的正确来源。问题。 @MSalters:起初我以为这是一个纯 C++ 问题。但是由于 Java 和 C# 在构造函数名称的情况下明显受到 C++ 的影响,我认为这仍然是相关的。如果 C++ 采用其他方式,Java 和 C# 可能也会采用 那种方式。【参考方案2】:为什么?因为您提到的不同语言的设计者决定以这种方式制作它们。有人完全有可能设计一种 OOP 语言,其中构造函数不必与类同名(如评论所述,python 中就是这种情况)。
这是一种将构造函数与其他函数区分开来的简单方法,并使代码中类的构造非常易读,因此作为一种语言设计选择是有意义的。
不同语言的机制略有不同,但本质上这只是语言特性辅助的方法调用(例如java和c#中的new
关键字)。
每当创建新对象时,运行时都会调用构造函数。
【讨论】:
不知道为什么它被否决了。唯一“缺失”的东西是一个设计示例,其中 c'tor 由不同的名称(即 python)调用。 +1。 小修正:构造函数可以通过没有返回类型来伪装。实际上可以使用带有类名的普通方法(至少在 Java 中),但由于某种原因,它“非常不鼓励”【参考方案3】:在我看来,使用单独的关键字来声明构造函数会“更好”,因为它会消除对类本身名称的不必要的依赖。
然后,例如,可以将类中的代码复制为另一个类的主体,而无需更改构造函数的名称。我不知道为什么要这样做(可能在某些代码重构过程中),但关键是人们总是力求事物之间的独立性,而这里的语言语法与此背道而驰。
析构函数也是如此。
【讨论】:
一个用例是匿名类。如果该类没有明确的名称,那么(遗憾的是)也无法声明自定义 ctor 和 dtor。【参考方案4】:构造函数具有相同名称的一个很好的原因是它们的表现力。例如,在 Java 中,您创建一个类似的对象,
MyClass obj = new MyClass(); // almost same in other languages too
现在,构造函数定义为,
class MyClass
public MyClass () ...
所以上面的语句很好地表达了,你正在创建一个对象,而在这个过程中,构造函数 MyClass()
被调用。
现在,无论何时创建一个对象,它总是调用它的构造函数。如果该类是extend
ing 其他基类,那么它们的构造函数将首先被调用,依此类推。所有这些操作都是隐式的。首先分配对象的内存(在堆上),然后调用构造函数来初始化对象。如果您不提供构造函数,编译器将为您的类生成一个。
【讨论】:
+1 通过给出(imo)构造函数应该具有类名的充分理由来实际回答问题。 这当然有点递归逻辑:你也可以想象一种语言说MyClass obj = new() // You already know the type
,因此用class MyClass public: new() ... ;
定义它的ctor
@MSalters, MyClass obj = new DerivedClass()
也是可能的:)。这就是为什么我们没有简单的MyClass obj = new()
。
new()
(没有类名)只能在引用类型与运行时类型相同的情况下用作快捷方式 - 但像 'MyClass obj = new MyExtendedClass()' 这样的语句不会可能的。编辑:忍者
@iammilind :不辩论。你也有像foo(new myClass)
这样的案例。我只是指出,通过应用另一个通用规则(不要重复自己),我可以按照相同的逻辑得出不同的结论。由于new
是所有这些语言中的关键字,因此它也是一个好的 ctor 名称。【参考方案5】:
在 C++ 中,严格来说,构造函数根本没有名字。 12.1/1 在标准状态下,“构造函数没有名称”,没有比这更清楚了。
在 C++ 中声明和定义构造函数的语法使用类的名称。必须有某种方法可以做到这一点,并且使用类的名称简洁易懂。 C# 和 Java 都复制了 C++ 的语法,大概是因为它们的目标受众至少会熟悉它。
确切的执行流程取决于您所谈论的语言,但您列出的三种语言的共同点是首先从某处分配一些内存(可能是动态分配的,可能是堆栈内存的某个特定区域或其他)。然后运行时负责确保以正确的顺序调用正确的构造函数,用于最派生类和基类。如何确保发生这种情况取决于实现,但所需的效果由每种语言定义。
对于 C++ 中最简单的可能情况,即没有基类的类,编译器只需发出对由创建对象的代码指定的构造函数的调用,即与提供的任何参数匹配的构造函数。一旦你有几个虚拟基地在玩,它就会变得更加复杂。
【讨论】:
【参考方案6】:我想知道为什么构造函数的名字总是和那个一样 类名
这样就可以明确地识别为构造函数了。
以及当我们创建该类的对象时它是如何被隐式调用的。
它被编译器调用是因为它的命名方式已经被明确地识别出来了。
谁能解释一下这种情况下的执行流程?
-
调用了新的 X() 运算符。
内存已分配,或抛出异常。
构造函数被调用。
new() 运算符返回给调用者。
问题是为什么设计师会这样决定?
在构造函数的类之后命名是一个由来已久的约定,至少可以追溯到 1980 年代初期的 C++ 早期,可能是其 Simula 的前身。
【讨论】:
【参考方案7】:构造函数与类同名的约定是为了便于编程、构造函数链接和语言的一致性。
例如,考虑一个你想使用 Scanner 类的场景,现在如果 JAVA 开发人员将构造函数命名为 xyz!
那你怎么知道你需要写:
扫描仪 scObj = new xyz(System.in) ;
这可能真的很奇怪,对吧!或者,您可能不得不参考一个巨大的手册来检查每个类的构造函数名称以便创建对象,如果您可以通过命名与类的构造函数相同的构造函数来解决问题,那么这又是没有意义的。
其次,构造函数本身是由编译器创建的,如果您没有明确提供它,那么编译器可以自动选择构造函数的最佳名称,因此程序员很清楚!显然,最好的选择是保持与类相同。
第三,你可能听说过构造函数链接,那么在构造函数之间链接调用时,编译器将如何知道你给被链接的类的构造函数起什么名字!显然,问题的解决方案还是一样的,保持构造函数的名称与类的名称相同。
创建对象时,通过在代码中使用 new 关键字调用构造函数(并在需要时传递参数)来调用构造函数,然后通过链接最终给出对象的调用来调用所有超类构造函数。
感谢您的提问。
【讨论】:
以上是关于为啥构造函数总是与类具有相同的名称以及它们是如何被隐式调用的?的主要内容,如果未能解决你的问题,请参考以下文章
为啥具有相同名称但不同签名的多个继承函数不会被视为重载函数?