在 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++那种畏首畏脚的做法才是不自然的,不彻底的面相对象设计思路