为什么构造函数总是具有与类相同的名称,以及如何隐式调用它们

Why constructors will always have same name as of class and how they are invoked implicitly?

本文关键字:何隐式 调用 构造函数 为什么      更新时间:2023-10-16

我想知道为什么构造函数的名称总是与类名相同,以及当我们创建该类对象时如何隐式调用它。有人能解释一下这种情况下的执行流程吗?

我想知道为什么构造函数的名字总是和类的名字一样

因为这个语法不需要任何新的关键字。除此之外,没有什么好的理由。

为了尽量减少新关键字的数量,我没有使用这样的显式语法:

class X {
    constructor();
    destructor();
}

相反,我选择了一种声明语法,它反映了构造函数的使用

class X {
    X();
    ~X();

这可能太聪明了。[c++的设计与演变,3.11.2构造函数符号]


谁能解释一下这种情况下的执行流程?

对象的生命周期可以总结如下:

  1. <
  2. 调用构造函数/gh>
  3. 使用对象
  4. 调用析构函数/终结器
  5. 释放内存

在Java中,步骤1总是从堆中分配。在c#中,类也是从堆中分配的,而结构体的内存已经可用(在未捕获的局部结构体的情况下在堆栈上或在它们的父对象/闭包中)。请注意,了解这些细节通常不是必要的,也不是很有帮助。在c++中,内存分配是非常复杂的,所以我不会在这里深入讨论。

步骤5取决于内存的分配方式。一旦方法结束,堆栈内存就会自动释放。在Java和c#中,堆内存在不再需要后的某个未知时间由垃圾收集器隐式释放。在c++中,堆内存是通过调用delete释放的。在现代c++中,很少手动调用delete。相反,您应该使用RAII对象,如std::stringstd::vector<T>std::shared_ptr<T>,它们会自行处理这些问题。

为什么?因为你提到的不同语言的设计者都是这么做的。完全有可能有人设计一种OOP语言,其中构造函数不必与类具有相同的名称(如前所述,这是python中的情况)。

这是一种将构造函数与其他函数区分开来的简单方法,并且使代码中的类构造非常可读,因此作为语言设计选择是有意义的。

该机制在不同的语言中略有不同,但本质上这只是一个由语言特性(例如java和c#中的new关键字)辅助的方法调用。

构造函数在创建新对象时由运行时调用。

在我看来,使用单独的关键字来声明构造函数会"更好",因为它将消除对类本身名称的不必要依赖。

然后,例如,类内部的代码可以作为另一个类的主体复制,而不必对构造函数的名称进行更改。我不知道为什么要这样做(可能是在一些代码重构过程中),但关键是人们总是在争取事物之间的独立性,我认为这里的语言语法违背了这一点。

同样适用于析构函数。

构造函数具有相同名称的一个很好的原因是它们的表达性。例如,在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++中最简单的情况,一个没有基类的类,编译器只是调用创建该对象的代码指定的构造函数,即与提供的任何参数匹配的构造函数。一旦你有了几个虚拟基地,情况就会变得更加复杂。

我想知道为什么构造函数的名字总是一样的类名

以便可以明确地将其标识为构造函数。

以及在创建该类对象时如何隐式调用

编译器调用它是因为它的命名方案已经明确地标识了它。

谁能解释一下这种情况下的执行流程?

  1. 调用新的X()操作符。
  2. 已分配内存,否则抛出异常。
  3. 调用构造函数
  4. new()操作符返回给调用者。

问题是为什么设计师这样决定?

用构造函数的类来命名构造函数是一个由来已久的惯例,至少可以追溯到20世纪80年代c++的早期,可能是它的前身Simula。

构造函数与类名相同的约定是为了编程方便、构造函数链化和语言的一致性。

例如,考虑一个您想使用Scanner类的场景,现在如果JAVA开发人员将构造函数命名为xyz怎么办?

那么你怎么知道你需要写:

Scanner scObj = new xyz(System.in);

可能会很奇怪,对吧!或者,更确切地说,您可能不得不引用一个巨大的手册来检查每个类的构造函数名称,以便创建对象,如果您可以通过仅命名与类相同的构造函数来解决问题,那么这也是没有意义的。

其次,构造函数本身是由编译器创建的,如果你没有显式地提供它,那么构造函数的最佳名称可以由编译器自动选择,所以程序员很清楚!显然,最好的选择是保持它与类的相同。

第三,你可能听说过构造函数链,那么当在构造函数之间链接调用时,编译器如何知道你给链接类的构造函数取了什么名字!显然,解决这个问题的方法还是一样的,保持构造函数的名称与类的名称相同。


创建对象时,通过在代码中使用new关键字(并在需要时传递参数)调用构造函数来调用它,然后通过链接调用最终给出对象的所有超类构造函数。