public static void main(String[] args) {
class Parent {
Parent() {
print();
}
void print() {
System.out.println(10);
}
}
class Child extends Parent {
void print() {
System.out.println(20);
}
}
Parent parent = new Child();
}
上面这段代码输出是 20.
感觉这样很奇怪,为什么父类的构造函数调用 print 给调用到子类了呢?这样难道不是一种反直觉的结果吗?
1
liuhan907 2022-11-17 13:52:20 +08:00
如果不是这样的话,那要怎么实现 abstract 方法呢?
|
2
llzzll1234 2022-11-17 13:54:32 +08:00 7
因为你实际 new 的是 Child 的对象啊,那当然调用实际对象所拥有的方法实现。
Parent parent = new Child();这里实际上是向上造型,父类的引用指向了子类的对象, 子类的初始化先调用父类的构造,所以先调用 Parent#print ,但是因为实际上初始化的对象是 Child ,print 方法由子类 override 了,所以输出的是 20. |
3
potatowish 2022-11-17 13:58:47 +08:00 via iPhone
调用了父类的构造函数,但是 print 方法被子类重写了啊
|
4
msg7086 2022-11-17 13:58:54 +08:00
否则子类怎么重写父类的方法呢?
|
5
movq OP C++的继承是这样的:
```c++ class Parent { public: Parent() { print(); } void print() { std::cout << 10 << std::endl; } }; class Child : public Parent { public: Child() { print(); } void print() { std::cout << 20 << std::endl; } }; int main() { Parent *p = new Child(); return 0; } ``` 输出结果为 10 20 也就是说父类就调用父类的 print ,子类就调用子类的 print |
6
movq OP 觉得 C++的更符合直觉一些。
|
7
msg7086 2022-11-17 14:03:05 +08:00 1
补充一句。在 C++里,父类方法可以选择是否为虚函数,如果是虚函数,那么总是被子类重写,否则才是像你说的调用父类实现。在 Java 里,所有的方法都相当于是虚函数,也就是说所有的方法都会向下查找到子类重写的版本。你只能用 final 去禁止重写,但是做不到你说的这个要求。
|
8
leonshaw 2022-11-17 14:47:37 +08:00 1
C++:
When a virtual function is called directly or indirectly from a constructor or from a destructor, including during the construction or destruction of the class’s non-static data members, and the object to which the call applies is the object (call it x) under construction or destruction, the function called is the final overrider in the constructor’s or destructor’s class and not one overriding it in a more-derived class. 我觉得这里 Java 更符合直觉 |
9
leonshaw 2022-11-17 14:49:22 +08:00
有个问题是这里子类是没初始化的,需要很小心
|
10
wenzhoou 2022-11-17 14:52:06 +08:00 via Android 25
举个例子。
class 人 method 自我介绍 说:我叫 xxx class 哑巴 extends 人 method 自我介绍 说:阿巴阿巴 人 张三 = new 哑巴() 张三.自我介绍() 这样结果是什么自然而然就知道了吧。 |
11
Jooooooooo 2022-11-17 14:55:07 +08:00
new Child() 等于这是一个 Child, 调用的方法也自然是 Child 里的.
|
12
byte10 2022-11-17 15:06:10 +08:00 4
其实省略了一个 this ,代码中 print() 等同 this.print() 写法。 这里 this 代表实际当前对象引用,也就是子类 Child ,这样解释是否能更好理解点。
|
13
movq OP @leonshaw 父类调用一个他认为自己已经有的方法,结果调用到子类去了。假设父类的这个方法里面的逻辑和父类本身的内容有关,这个方法将不会达到预期效果。所以 C++的方式更好。这里面没有说抽象方法。如果是抽象方法那都应该是调用 overrider
|
14
whileFalse 2022-11-17 15:15:51 +08:00
@movq 那请问子类覆盖父类方法的时候是干什么吃的?
|
15
movq OP @whileFalse C++里面用父类指针调用方法调用的结果是子类的方法,但是在父类内部调用被重写的方法,调用的仍然是父类自身的方法
|
16
leonshaw 2022-11-17 15:26:29 +08:00
@movq Java 的方式让方法在构造函数和平时保持一致,如果父类不希望 override ,可以声明为 final 或者 private ?
C++ 的逻辑是在子类构造开始之前,它还不是一个子类对象,这时子类的函数不可用。个人认为 C++ 更严谨,Java 更直觉。 |
17
coderge 2022-11-17 15:33:30 +08:00
https://www.bilibili.com/video/BV1fh411y7R8?p=310&vd_source=9ca206602067ee299613c675960feb79 等号左边是编译类型, 限制了可以调用哪些函数; 等号右边是运行类型, 运行 parent.print 时是调用的子类的 print 函数.
|
18
wangxiaoaer 2022-11-17 15:36:18 +08:00
10 楼答案简单明了,建议楼主多看几遍。
|
19
msg7086 2022-11-17 15:45:07 +08:00 5
顺便一提,你说的直觉并不一定是直觉,可能只是一个 C++用户的直觉。
非 C++用户和 C++用户会有不同的直觉。 你用 C++所以你觉得和 C++行为相同的行为就是直觉的,不同的行为就是反直觉的。 如果你只是想强制调用父类里的方法,不想去调用子类的,为什么不设置成 final 或者 private 呢? |
20
fzdwx 2022-11-17 15:48:06 +08:00
你都`new Child();`了,而且也重写了`print()`方法,这个结果没有任何问题。
|
21
GuuJiang 2022-11-17 15:59:47 +08:00
@movq #15 的结论不成立,你用 virtual 方法来测试将会得到和 Java 完全一样的结果,你把 Java 的所有方法都当成 virtual 的就能理解了
|
22
wetalk 2022-11-17 16:04:53 +08:00
不要用你 C++直觉,猜测 Java 语言的特性,每个语言本身特性不尽相同
|
23
cpstar 2022-11-17 16:10:11 +08:00
我好像提过类似的问题。我当时的问题是想办法外部调用的时候,调用 Parent 的而不是 Child 的。
但这确实就是 JAVA 的继承机制,重写 overwrite 父类方法。从 bytecode 看,Child 的实例,也就是 parent 相应的 method 入口在 Child.print 上,除非反射,否则类加载器永远无法找到 Parent.print 。 另外这里还有修饰符,print 的没有显式写,所以默认是 public ,那 Child 就 overwrite 了 print ,如果修饰符为 private ,那就不会 overwrite ,本级调用本级,次级调用次级,不会互相串。 |
24
cpstar 2022-11-17 16:17:35 +08:00
如果想要知道怎么做到实例 parent 嗲用了 Child.print 的话,可以了解一下 JAVA 虚拟机以及 bytecode
如果想要知道为什么要这么做的话,那就得问 Bjarne Stroustrup 和 James Gosling 了。🤣 |
25
wolfie 2022-11-17 16:17:55 +08:00
this.print()
this = child |
26
itning 2022-11-17 16:24:20 +08:00
披着羊皮的狼
|
27
kaedeair 2022-11-17 16:24:50 +08:00
java 并没有 C++这样继承体系,他的继承是基于类型擦除的
|
28
movq OP @GuuJiang 我说的是哪一步不成立呢?
```cpp class Parent { public: Parent() { print(); } void virtual print() { std::cout << "parent" << std::endl; } }; class Child : public Parent { public: Child() { print(); } void print() { std::cout << "child" << std::endl; } }; int main() { Parent *p = new Child(); p->print(); return 0; } ``` 执行结果 parent child child |
29
movq OP @wangxiaoaer 他都没看懂我在问什么,有什么好多看几遍的。
|
30
s1mpleo 2022-11-17 16:37:05 +08:00 via Android
我居然没弄懂,这要是面试了可咋办🤯
|
31
zoharSoul 2022-11-17 16:48:48 +08:00
10 楼解释的非常清楚了
|
32
hsfzxjy 2022-11-17 16:52:22 +08:00 via Android
所以你想问什么呢?你想问 (1) 为什么 java 不像 c++ 那样 还是 (2) java 怎么才能像 c++ 那样 还是 (3) java 这个机制底层是怎么实现的
|
33
x1aoYao 2022-11-17 16:53:53 +08:00
在 c++或者 go 里面确实是按照 op 预期的那样;但是在 java 里不同,所有的类方法都是动态分发的。
|
34
jawe001 2022-11-17 16:56:51 +08:00
其实 Java 中调用实例变量和实例方法时,前面省略了 this 。可以理解为 this.print()。而你 new 的是 Child(),自然这个 this 是指向 Child 这个实例的。因此 this.print() 调用的是 Child 类里面的 print() 实例方法
|
35
gaara 2022-11-17 16:59:13 +08:00
是不是搞错了,c++只是在父类的`构造函数`里调用被子类 override 的方法才是调用父类的(因为子类方法这会还没被初始化),其他情况父类指针指向(引用)子类对象调用的还是子类对象 override 的方法
|
36
lisongeee 2022-11-17 17:05:48 +08:00
如果按 op 的设想, Java 继承岂不是废了一半?
```java public class MyClass { public static class Child { public String toString() { System.out.println(10); return "Child"; } } public static void main(String args[]) { Object obj = new Child(); System.out.println(obj.toString()); } } ``` ![image]( https://user-images.githubusercontent.com/38517192/202402992-fe1beb78-cbc4-4f84-a5c0-accf37c2d3d0.png) |
37
listenerri 2022-11-17 17:08:52 +08:00
不同语言的底层实现自然有差异,这也不失为是人家的“特色”,记住就完了呗。就 OP 举的例子表现出的差异来讲,Java 和 C++ 双方选手各执一词,也各有各的优点,我倒没感觉有什么“反直觉”的问题,只是习惯或者惯性思维问题。
|
40
yazinnnn 2022-11-17 17:24:27 +08:00
你可以尝试用 unsafe 去修改字节码来实现 c++的效果,但是这需要一定的 jvm 功底
|
41
zsdroid 2022-11-17 17:26:09 +08:00
笑死,op 都没理解多态的意思,就说 10 楼是错的。实际上 10 楼是正解
|
42
liprais 2022-11-17 17:29:12 +08:00
我还以为干啥来着,原来是拿 cpp 踩 java 来了
二十年前就玩烂了好么 |
45
pkoukk 2022-11-17 18:10:26 +08:00
因为里氏替换不是削足适履
|
46
Vtwoguest 2022-11-17 18:16:00 +08:00 via iPhone 2
this.print(),本质上是因为子类对象创建时会默认调用 Super()访问父类构造方法,所以此时父类构造方法里的 this 是子类引用
|
47
geelaw 2022-11-17 18:44:11 +08:00 9
设计不同而已。
Java:对象在任何时刻的类型都是最终类,无论何时,调用虚方法的效果都是最终重写。 C++:对象在构造、析构期间的类型是当前类,可能不是最终类,此时直接或者间接调用虚函数会得到当前类的重写。此时也不可通过 this 访问兄弟类,即使整个对象的兄弟类子对象的构造函数已经运行。 C++ 的设计理念基于非祖先类子对象状态的不确定性。 你可以认为 C++ 里面祖先类的构造函数在子类构造函数之外,而 Java 里面祖先类的构造函数在子类的构造函数之内,且处于开头。 也就是说,考虑 A : B 以及 B::B() { B_ctor_body; } A::A() { A_ctor_body; } 以及 new A(),则 Java 认为发生的事情是 { /* vtable = A_vtable */ { B_ctor_body; } // B::B A_ctor_body; } // A::A 而 C++ 认为发生的事情是 { /* vtable = B_vtable */ B_ctor_body; } // B::B { /* vtable = A_vtable */ A_ctor_body; } // A::A |
48
aliveyang 2022-11-17 18:50:59 +08:00
你这样 Parent parent = new Child();其实跟 Child child= new Child();没有任何区别,就是换了张皮
|
49
psycho9631 2022-11-17 18:58:28 +08:00
对象先创建 再执行构造方法 构造方法的执行 也只是函数的调用而已
并不是说构造函数执行完毕后 才存在实例(是这样吗 我也不太清楚?) 所以在父类构造方法调用完成前 子类就已经实例化于内存中了 所以 this.print()就调用实例对象的方法 有大佬指点下 我的想法吗 |
50
dreamist 2022-11-17 18:59:28 +08:00
这不就是面向对象最核心的一个特性“多态”吗?如果现象打出来不是 20 ,那面对象还搞个啥
|
51
helloworld1024 2022-11-17 19:06:54 +08:00
这个问题太 low
|
52
movq OP @helloworld1024 完全赞同你的观点
|
53
movq OP @psycho9631 C++在父类构造函数完成之前子类对象还不存在,所以和 Java 不一样
|
54
jiangzm 2022-11-17 19:30:28 +08:00
没两把刷子就别那啥
|
55
dqzcwxb 2022-11-17 19:41:46 +08:00
梦回 Java 第一课,继承和多态
|
57
movq OP @jiangzm 哦,你是不是想说我没水平?那你的证据呢?——还是说,凡是你说的都是对的,所以不需要证据?
|
58
movq OP @jiangzm 关于你说的这句话,“没两把刷子”有可能是对的,因为我确实正在学 Java ,正在学当然水平不高了。但你说的“那啥”想表达什么呢?我发这个帖子是怎么着恶心你了吗?不能问问题是吧?不知道哪来的恶意
|
59
ediron 2022-11-17 20:26:02 +08:00
可以把构造方法当作是声明了一段初始化代码,也就是说这里父类的构造方法只是表明了需要执行一下 print() 方法,而子类调用构造方法时必须执行父类的构造方法,这里子类对象执行到 print() 这一行时发现有自己的 print() 方法,当然就执行自己的 print() 方法了。本质上就是多态的特性,构造器由上向下调用,成员由下向上调用。
|
60
movq OP @ediron 你还是把 Java 多态解释了一遍。但这里问题的核心是构造函数调用虚函数时 Java 和 C++的区别。你可以看 47 楼怎么说的。
|
61
fkdog 2022-11-17 20:50:28 +08:00
你既然都已经覆盖掉父类的方法了,如果父构造器还在调用自身的方法,那你就不怕程序出 bug 么。。
|
62
ShukeShuke 2022-11-17 21:04:59 +08:00
建议看一下 java 的多态性
|
63
ajaxgoldfish 2022-11-17 21:13:18 +08:00
封装、继承、多态,去搜搜“Java 使用多态的的好处示例”这个关键词,学到后边就会明白了,spring 中的设计模式很大一部分就是用的这个特性
|
64
AerithLoveMe 2022-11-17 21:13:37 +08:00
我猜楼主应该是觉得创建了父类对象( new 父类),就应该调用父类自己的方法吧,但子类只是调用了父类的构造方法,是由子类调用的,所以用的是子类的 print 方法
|
65
maninfog 2022-11-17 21:28:06 +08:00
楼主这个贴其实很有意思。如果用 Kotlin 的话,在父类构造函数内掉非 final 方法,IDE 还会给你一个 lint 提示,因为字类如果重写了这个方法,并且在方法内访问了字类自己的成员变量,那么这个方法的调用其实会出问题,因为这个时候字类的成员变量是还没有初始化的。确实就当是特性就好了,习惯了也会避免去这样做。
|
66
ajaxgoldfish 2022-11-17 22:07:46 +08:00
@geelaw yes 是这个意思,兄弟你表达能力太强了。
|
67
haya 2022-11-17 22:08:31 +08:00
父类构造方法里的 this 都指向的是 (你 new 出来的)(未初始化完成的)子类对象
|
68
movq OP @AerithLoveMe
我觉得父类不应该调用子类重写的方法,是因为我觉得这就把父类子类搅和在一起,破坏类的封装性了。 构造函数是给自己初始化的,父类他又不知道别的类写的是什么代码,怎么敢在构造函数里使用别的类的方法来初始化自己? 不过 Java 确实是这样的特性,因为它初始化父类时子类已经存在了,虚函数表里面的函数就是子类的函数。 |
69
LeegoYih 2022-11-17 22:39:29 +08:00
同一个方法,因为调用的对象不同,导致结果有多种,这样才是反直觉的吧?难道不觉得这种方式很恐怖吗?
|
70
littlewing 2022-11-17 22:54:29 +08:00
看了一遍回复,有接近一半的人不审题,或是大概瞄了一眼就开始回复了,连楼主想问什么都没搞清楚
|
72
mind3x 2022-11-17 23:50:13 +08:00 10
写了 20 多年 Java ,也写过 JVM ,我来尝试解释一下。
首先 Java 这个行为和 C#是一样的:在子类尚未完成初始化时,父类的构造函数就已经能调用在子类中重载的函数。这意味着不注意的话很容易跑出 NPE 和别的毛病来。这个问题在 stackoverflow 上也经常有人问。 要理解这个行为,可以看一下 JVM spec 里对 invokevirtual 这个字节码的解释: https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual 注:invokevirtual 是 JVM bytecode 调用在 class 中定义的 virtual 函数时所使用的指令。 简单的说,调用虚方法时,查找的是当前实例(this)的类的方法表,也就是你的 Child 的方法表。 这里 Java 和 C++的区别是,C++的虚函数表(vtable)的建立,在逻辑上是动态的,相当于每一层实例构造完以后,更新一次 vtable 。当然实际上 C++编译器不会这么没效率,就把构造函数里调用的函数当作非虚函数在编译期直接 resolve 完事。 而 Java 的类的方法表就那么一张,每个类在加载验证 link 完成以后,方法表就在那里不动了。而基类构造函数的调用是在初始化实例时动态发生的,调虚方法时查的表也是 Child 的表,自然会调用到 Child 中重载的函数,即使此时 Child 的数据成员并未初始化。 这样做在逻辑上确实有难以理解的地方:Child 整个实例都还没处于一个合法的状态,其方法就被调用了。 但是,C++这种做法也有其局限性:确实有场景是需要基类能在构造函数里调用子类重载的虚函数,只要子类的实现不依赖子类的数据成员即可。打个比方: class Bike { Bike () { frontWheel = makeWheel(); rearWheel = makeWheel(); } Wheel makeWheel(); } class TitaniumBike { Wheel makeWheel() { return new TitaniumAllyWheel(); }; } 这样子类就可以正确产生一辆拥有钛合金狗眼(划掉)轮子的自行车。这里不讨论此种设计模式的优劣,只是举个例子。我本人反正是不会这么写。 |
73
geelaw 2022-11-18 00:11:40 +08:00 via iPhone 1
@mind3x #72
> 当然实际上 C++编译器不会这么没效率,就把构造函数里调用的函数当作非虚函数在编译期直接 resolve 完事。 很多时候不能这样做,因为构造函数、析构函数可以调用其他成员函数或者把 this 传入其他地方,在其他成员函数里或者通过复制的 this 调用虚函数必须仍然得到正在被构造的类的版本,而且对 this 所指向的对象用 typeid 也必须得到正在被构造的类。安全的做法是反复改变虚函数表指针。 |
75
qwertyegg 2022-11-18 04:31:49 +08:00
爪哇 101:多态性
|
76
mortalbibo 2022-11-18 07:57:22 +08:00
java 里称之为动态绑定机制...
|
77
duanguyuan 2022-11-18 09:12:41 +08:00 1
10 楼回答清楚明了,op 说这是打虚空打拳?
退一步讲,先不论别人回答对不对,别人花时间回答你的问题(从语气来看,并未讥讽、引战),你是给钱了还是怎么的,对别人就这么大脾气…… |
78
movq OP @duanguyuan 我有啥脾气呢?你再看看我问的是什么,然后再看看 10 楼?我说他没看懂我在说什么,是在陈述事实。我跟你这种争论,如果你看懂了题,根本就不该发生。所以我说虚空打拳也是事实——你跟我争论的东西根本就是因为你在和我讨论与本帖毫无关联的内容。
|
79
movq OP @duanguyuan 真正清楚明了的内容,是本帖里面 mind3x 和 geelaw 的回答,而不是 10 楼这种根本没看懂题还被同样没看懂题的人强行说好的回答。
|
80
movq OP @duanguyuan 一群看不懂题的人不要在这虚空打拳和我争论浪费大家的时间,浪费版面
|
81
WhiteDragon96 2022-11-18 09:44:42 +08:00
编译看左,运行看右
|
82
duanguyuan 2022-11-18 09:49:32 +08:00 1
这里有几层逻辑不敢苟同。
( 1 )你在题目中说 java 这种继承行为反直觉,10 楼举例“哑巴不能说话”,java 的行为反而是符合直觉的。这个例子很直观,所以这么多人给 10 楼送感谢。 ( 2 )不管哪个论坛社区,有规定看不懂题目的人不能回答问题吗?而且“看不懂题目”这还是个主观论断,或许题目不清楚,或许别人的回答是对的,只是你没 get 到 ( 3 )关于浪费版面。在大佬看来,开这个贴就是浪费版面。但有人这么说你吗?还真有,说这个问题很 low 。那你心里爽吗?你不爽。所以,别双标。我是菜鸡,我认真回个帖还是浪费版面了? |
83
llzzll1234 2022-11-18 09:50:08 +08:00
别吵了,这个 OP 不知道为什么就一股迷之傲慢,根本不是想和你们讨论问题的样子。
|
85
movq OP @duanguyuan
@duanguyuan ( 1 )首先,10 楼说的内容和我的帖子没有任何关系。我是在问为什么构造函数里面调用被重写的函数,会调用到子类,然后我附了一个 C++的代码,说 C++里面,父类构造函数调用被重写的函数,调用到的是父类的方法。 10 楼回复的,是在说用父类静态类型,子类动态类型,调用静态类型被重写的方法,可以调用到子类里被重写的方法。和我说的完全没有关系。 不懂这个区别的人建议闭嘴,没必要一直争论。错就是错了,不会因为你们看不懂题目的人一直说就变成对的。 ( 2 )我没说不能回答问题,我说 10 楼没看懂题。其它没也没看懂题的人不要一直来骚扰楼主,说 10 楼是对的。讨论也是要朝着正确的方向来进行的,而不是不能指出某些人带偏了方向。 ( 3 )“在大佬看来,开这个贴就是浪费版面”。不要主观臆断,你说的没有根据。为什么楼里面 mind3x 和 geelaw 这两个大佬给出了专业性强,内容丰富的回答,而且没说这个帖子 low ,反而而有些根本没看懂题的人才会说本帖子 low 呢?是不是这些人太自恋了?还是说我指出他们看不懂题,说到他们的痛处了?真是黄钟毁弃,瓦釜雷鸣 ( 4 )“别吵了,这个 OP 不知道为什么就一股迷之傲慢,根本不是想和你们讨论问题的样子。” 你说的根本就不属实。我在和能看懂题目的人讨论问题,和他们讨论是平和的讨论的。你所谓的傲慢,是因为有些人没看懂题还来攻击我。对于这些人,我对他们回应的态度是正常的。自己错了就是错了,还逼逼赖赖的攻击对的人。 |
86
movq OP 统一回复:85 楼已经说的很清楚了,看不懂题的人建议不要继续在这回复我,建议去检查一下自己的认知能力。没看懂题就在这 diss 我的人我不会再回复。
|
88
Maiiiiii 2022-11-18 10:10:38 +08:00
啊对对对
|
89
movq OP 有个猜想, 不知道是不是有些人智商不够,看不懂长文,也看不懂专业回答,所以不看本楼里面 mind3x 和 geelaw 这两个大佬发的真正专业的回答,而只能看 10 楼这种写给小学生看的东西,还奉为圭臬。
从给他们的点赞也能看出来,真正专业的回答只有一两个赞,10 楼这种毫无意义的回答却点赞很多。 |
90
ccppgo 2022-11-18 10:34:12 +08:00
楼主估计是看到别人回答的东西简单明了, 然后看客都赞同, 他觉得看客们可能会因此觉得他水平很低 所以恼羞成怒了, 有点搞笑的
|
91
JerryV2 2022-11-18 10:35:12 +08:00 1
|
92
hez2010 2022-11-18 10:36:29 +08:00
因为 Java 中的方法默认都是 virtual 的,于是 Child 的 print 把 Parent 的 print 重写了;而 C++ 的方法则默认不是 virtual 的。
|
93
duanguyuan 2022-11-18 10:41:28 +08:00 2
抱歉,确实智商不够,看了 72 层 mind3x 的回答才明白 op 要问的是什么,10 楼回答确实文不对题。
打个比方,一个知识点,大家被考了 N 遍了,一遇到类似的题就知道怎么答,惯性思维。但这次的题看起来一样,问的却是另一个更深入的东西,不仔细审题自然答错。 ps: 楼主说话是真难听。 |
94
movq OP @duanguyuan 专门难听给那些没看懂题就说难听话 diss 我的人看的
|
96
reallittoma 2022-11-18 10:48:22 +08:00 1
这帖子看下来,我认为 Java 程序员更加傲慢,绝不接受别人指出的半点 [看似] 是在说 Java [有毛病] 的言论。
|
97
movq OP @reallittoma 而且我也没说 Java 是劣质语言,我只是说在这一点上我觉得不符合我的直觉,结果就有些人逼逼赖赖的说什么我在秀 C++优越
|
98
hez2010 2022-11-18 10:59:10 +08:00
@movq 其实 OOP 语言里也几乎只有 Java 没有写非 virtual 方法的能力。
像 C++、C# 的方法默认都是非 virtual 的,因此调用时不需要去查找虚表,反而有性能优势,只有需要 virtual 的时候才手动标记上 virtual 允许方法被重写。 |
99
Opportunity 2022-11-18 11:16:16 +08:00
你说的这个反直觉的结果其实是因为你类比的有问题。
可以先从 C#/Java 的“析构”来看。C# 里用的是 `~Foo(){}`,但是可以用 `GC.SuppressFinalize(Object)` 方法来阻止这个函数被调用。Java 里这个函数就直接叫“finalize()”了。可见,这些托管语言里是不存在真正的析构函数( Destructor )的,或者说,不允许用户自定义析构的逻辑。替代品是所谓的终结器( finalizer )。 同样的,这些语言的“构造函数”其实也只是初始化器( inintializer ),虚表这些在 VM 内真正的构造阶段已经完成了,调用这些 inintializer 的时候在 VM 看来你使用的已经是一个完整的对象了。 要用 cpp 类比的话你也应该保持构造和析构函数是 trival 的,使用 inintializer 和 finalizer 来模拟构造和析构: ```cpp class Parent { public: void virtual inintialize() { print(); } void virtual print() { std::cout << "parent" << std::endl; } }; class Child : public Parent { public: void virtual inintialize() { Parent::inintialize(); print(); } void print() { std::cout << "child" << std::endl; } }; int main() { Parent *p = new Child(); p->inintialize(); p->print(); return 0; } ``` 输出 child child child 和 Java/C# 表现一致 |
100
TArysiyehua 2022-11-18 12:12:57 +08:00
首先构造方法一定会调用父类的,所以 new Child () 的时候就会调用父类的没毛病,因为构造方法没有虚的,所以必须调用,不然没法继承父类的特征。
其次方法就不是,你不手动调父类的方法就相当于是覆盖了父类的方法,那自然输出的是子类的 |