为什么构造函数总是具有与类相同的名称,以及如何隐式调用它们
Why constructors will always have same name as of class and how they are invoked implicitly?
我想知道为什么构造函数的名称总是与类名相同,以及当我们创建该类对象时如何隐式调用它。有人能解释一下这种情况下的执行流程吗?
我想知道为什么构造函数的名字总是和类的名字一样
因为这个语法不需要任何新的关键字。除此之外,没有什么好的理由。
为了尽量减少新关键字的数量,我没有使用这样的显式语法:
class X { constructor(); destructor(); }
相反,我选择了一种声明语法,它反映了构造函数的使用。
class X { X(); ~X();
这可能太聪明了。[c++的设计与演变,3.11.2构造函数符号]
谁能解释一下这种情况下的执行流程?
对象的生命周期可以总结如下:
- <
- 调用构造函数/gh>
- 使用对象
- 调用析构函数/终结器
- 释放内存
在Java中,步骤1总是从堆中分配。在c#中,类也是从堆中分配的,而结构体的内存已经可用(在未捕获的局部结构体的情况下在堆栈上或在它们的父对象/闭包中)。请注意,了解这些细节通常不是必要的,也不是很有帮助。在c++中,内存分配是非常复杂的,所以我不会在这里深入讨论。
步骤5取决于内存的分配方式。一旦方法结束,堆栈内存就会自动释放。在Java和c#中,堆内存在不再需要后的某个未知时间由垃圾收集器隐式释放。在c++中,堆内存是通过调用delete
释放的。在现代c++中,很少手动调用delete
。相反,您应该使用RAII对象,如std::string
、std::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++中最简单的情况,一个没有基类的类,编译器只是调用创建该对象的代码指定的构造函数,即与提供的任何参数匹配的构造函数。一旦你有了几个虚拟基地,情况就会变得更加复杂。
我想知道为什么构造函数的名字总是一样的类名
以便可以明确地将其标识为构造函数。
以及在创建该类对象时如何隐式调用
编译器调用它是因为它的命名方案已经明确地标识了它。
谁能解释一下这种情况下的执行流程?
- 调用新的X()操作符。
- 已分配内存,否则抛出异常。
- 调用构造函数
- new()操作符返回给调用者。
问题是为什么设计师这样决定?
用构造函数的类来命名构造函数是一个由来已久的惯例,至少可以追溯到20世纪80年代c++的早期,可能是它的前身Simula。
构造函数与类名相同的约定是为了编程方便、构造函数链化和语言的一致性。
例如,考虑一个您想使用Scanner类的场景,现在如果JAVA开发人员将构造函数命名为xyz怎么办?
那么你怎么知道你需要写:
Scanner scObj = new xyz(System.in);
可能会很奇怪,对吧!或者,更确切地说,您可能不得不引用一个巨大的手册来检查每个类的构造函数名称,以便创建对象,如果您可以通过仅命名与类相同的构造函数来解决问题,那么这也是没有意义的。
其次,构造函数本身是由编译器创建的,如果你没有显式地提供它,那么构造函数的最佳名称可以由编译器自动选择,所以程序员很清楚!显然,最好的选择是保持它与类的相同。
第三,你可能听说过构造函数链,那么当在构造函数之间链接调用时,编译器如何知道你给链接类的构造函数取了什么名字!显然,解决这个问题的方法还是一样的,保持构造函数的名称与类的名称相同。
创建对象时,通过在代码中使用new关键字(并在需要时传递参数)调用构造函数来调用它,然后通过链接调用最终给出对象的所有超类构造函数。
- 调用类模板中隐式删除的复制构造函数
- 为什么这不是"调用隐式删除的'QQmlElement'的默认构造函数"中的默认构造函数
- 调用隐式删除的复制构造函数
- 编译器是否在由 new 初始化的对象上调用隐式析构函数
- 为什么用户定义的转换不会在调用对象上隐式发生
- 当使用来自 std::atomic 的方法时隐式调用 load() 吗?
- 错误:使用 auto 调用隐式删除的 unique_ptr 复制构造函数
- 调用 printf 时隐式转换为字符*
- C :另一个构造函数的构造函数的隐式调用
- 为什么隐式转换为 std::string 不适用于运算符<<被调用
- 对隐式删除的复制构造函数的 gmock 调用
- 在隐式转换后的智能指针上删除函数调用中的歧义
- 带有非类型参数的调用模板函数明确和类型参数隐式
- 带有模板的链接列表:调用到隐式删除的默认构造函数
- 为什么在调用隐式类型转换构造函数之后直接是驱动器
- 在复制初始化中,对复制构造函数的调用是显式的还是隐式的
- 初始化对象后,用隐式转换而不是赋值运算符调用构造函数有什么意义
- 通过"this->member"访问 c++ 成员类是否比隐式调用"member"更快/更慢
- 显式构造函数由派生类隐式调用
- 如何隐式调用 c 样式的清理器函数