在 Think in Java 多态这章中为了讲解构造器多态的层次结构,举出了一个有意思的例子。我将这个Java例子“翻译”为C++,果然得到了不同的结果。从这种结果来看,C++比Java更加“严谨”。先来看看Bruce Eckel给出的例子:
package com.hankcs.polymorphism;//: polymorphism/PolyConstructors.java // Constructors and polymorphism // don't produce what you might expect. import static net.mindview.util.Print.*; class Glyph { void draw() { print("Glyph.draw()"); } Glyph() { print("Glyph() before draw()"); draw(); print("Glyph() after draw()"); } } class RoundGlyph extends Glyph { private int radius = 1; RoundGlyph(int r) { radius = r; print("RoundGlyph.RoundGlyph(), radius = " + radius); } void draw() { print("RoundGlyph.draw(), radius = " + radius); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } } /* Output: Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5 *///:~
原本程序员期望第二行输出
RoundGlyph.draw(), radius = 5
然后却输出了
RoundGlyph.draw(), radius = 0
书里面讲得很清楚,这是由于多态决定的。在Glyph构造的时候,draw()方法被子类覆盖,调用的是RoundGlyph::draw()。这是Java的“陷阱”,当然,这是Java的设计初衷。如果程序员足够警惕,它就是天经地义的了。
但是作者在括号里指出“C++语言会产生更合理的行为”,这不禁激起了我的好奇心,于是将这个示例程序“翻译为”C++语言:
#include <iostream> using namespace std; class Glyph { virtual void draw() { printf("Glyph.draw()\n"); } public: Glyph() { printf("Glyph() before draw()\n"); draw(); printf("Glyph() after draw()\n"); } }; class RoundGlyph: public Glyph { private: int radius; public: RoundGlyph(int r) { radius = r; printf("RoundGlyph.RoundGlyph(), radius = %d\n" , radius); } virtual void draw() { printf("RoundGlyph.draw(), radius = %d\n" , radius); } }; ///////////////////////////SubMain////////////////////////////////// int main(int argc, char *argv[]) { Glyph *pG = new RoundGlyph(5); // error C2248: “Glyph::draw”: 无法访问 private 成员(在“Glyph”类中声明) // pG->draw(); delete pG; system("pause"); return 0; } ///////////////////////////End Sub//////////////////////////////////
输出:
Glyph() before draw() Glyph.draw() Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5
看来,C++的多态似乎更加合理。
并没有不妥啊,反倒觉得c++的方式不妥,因为父类构造器先于子类构造器运行,这时候radius并未赋值嘛,至于为何父类要调子类的方法,那也是很自然的,我都覆写了,那么不掉覆写之后的调什么,覆写的结果应该就是所有调用的地方都使用覆盖后的版本,即使是父类中的调用,这样才是的覆写真正的做到全面覆盖,相比之下c++那种畏首畏脚的做法才是不自然的,不彻底的面相对象设计思路