一、文章来由
本人现在用c++更多,但是以前Java也写过不少,Java和c++很像,但是深入挖一些,Java跟c++的区别很大,就拿刚刚发的另一篇博文虚函数与多态小览来说,里面就感觉有很多不同了,至少“重写”在这两个语言里面的理解就不同了~~跟基友一番讨论,决定把这个问题彻底捋一捋,因为这个是探讨,所以有不同想法欢迎提出和评论。
二、关于“重写”Override的定义
“重写”多么常见的一个概念。
1、首先说明“重写”(Override)完全等于“覆盖”,可能因为翻译的问题,是个很坑爹的问题
2、而且 Override 的定义在c++和Java中却不同~~
c++中这样定义“重写”:
Override 是指派生类函数覆盖基类函数:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同,参数不同都不称之为重写,根本就是两个函数了;
(4)基类函数必须有virtual 关键字。
特别留意最后一点~~这是区别于隐藏的关键点
Java中这样定义“重写”:
(1)在子类中可以根据需要对从基类中继承来的方法进行重写,方法重写又称方法覆盖。
(2)重写的方法和被重写的方法必须具有相同方法名称、参数列表和返回类型。
(3)重写方法不能使用比被重写的方法更严格的访问权限。如需父类中原有的方法,可使用super关键字,该关键字引用了当前类的父类。
有没有发现什么,就是Java中根本没有virtual关键字,所以没有“重写”和“隐藏”的区别,Java中重写一定隐藏了父类的方法,要访问就要用super关键字就可以了,所以Java里面只有 override。
三、深入理解c++中的 Hidden 与 Override
看了定义就已经发现不同了,现在来自己看看c++中 Hidden 与 override 的实现机制。
谈到c++的 override 就一定要谈到 hidden,他们在c++中是要绝对区分开的。
3.1 Hidden
c++隐藏的定义(派生类的函数屏蔽了与其同名的基类函数):
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载混淆)
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
这是上一篇博客也提到的类容,那么究竟实现机制是什么?
在调用一个类的成员函数的时候,编译器会沿着类的继承链逐级的向上查找函数的定义,如果找到了那么就停止查找了,所以如果一个派生类和一个基类都有同一个同名(暂且不论参数是否相同)的函数,而编译器最终选择了在派生类中的函数,那么我们就说这个派生类的成员函数”隐藏”了基类的成员函数,也就是说它阻止了编译器继续向上查找函数的定义。。。。
回到隐藏的定义中,前面已经说了有virtual关键字并且分别位于派生类和基类的同名,同参数函数构成覆盖的关系,因此隐藏的关系只有如下的可能:
1)必须分别位于派生类和基类中
2)必须同名
3)参数不同的时候本身已经不构成覆盖关系了,所以此时是否是virtual函数已经不重要了
当参数相同的时候就要看时候有virtual关键字了,有的话就是覆盖关系,没有的时候就是隐藏关系了
3.2 Override
覆盖指的是派生类的虚拟函数覆盖了基类的同名且参数相同的函数。
既然是和虚拟函数挂钩,说明了这个是一个多态支持的特性,所谓的覆盖指的是用基类对象的指针或者引用时访问虚拟函数的时候会根据实际的类型决定所调用的函数,因此此时派生类的成员函数可以”覆盖”掉基类的成员函数。
注意: 唯有同名且参数相同还有带有virtual关键字并且分别在派生类和基类的函数才能构成虚拟函数,这个也是派生类的重要特征。
而且,由于是和多态挂钩的,所以只有在使用类对象指针或者引用的时候才能使用上。
总之一句话:覆盖函数都是虚函数,反之不然~~
四、代码实例
口说无凭,上代码~~
Java代码如下:
public class PolyTest
{
public static void main(String[] args)
{
//向上类型转换
Cat cat = new Cat();
Animal animal = cat;
animal.sing();
//向下类型转换
Animal a = new Cat();
Cat c = (Cat)a;
c.sing();
c.eat();
//编译错误
//用父类引用调用父类不存在的方法
//Animal a1 = new Cat();
//a1.eat();
//编译错误
//向下类型转换时只能转向指向的对象类型
//Animal a2 = new Cat();
//Cat c2 = (Dog)a2;
}
}
class Animal
{
public void sing()
{
System.out.println("Animal is singing!");
}
}
class Dog extends Animal
{
public void sing()
{
System.out.println("Dog is singing!");
}
}
class Cat extends Animal
{
public void sing()
{
System.out.println("Cat is singing!");
}
public void eat()
{
System.out.println("Cat is eating!");
}
}
跑出来结果如下:
可见,Java即便第二组向下转换【父类强转子类】,输出任然是子类的东西,并不像c++的晚绑定。
c++代码如下:
#include<iostream>
using namespace std;
class Animal
{
public:
virtual void sing()
{
printf("Animal is singing!\n");
}
};
class Cat : public Animal
{
public:
void sing()
{
printf("Cat is singing!\n");
}
void eat()
{
printf("Cat is eating!\n");
}
};
int main(void)
{
Animal a;
Cat *cp = (Cat *)&a;
cp->sing();
cp->eat();
//编译错误
// Cat c;
// Animal a = c;
// a.eat();
return 0;
}
运行结果如下:
c++这里就良好体现了晚绑定的特性~~~即使强制转换了,指向的任然是基类对象的地址,所以通过虚函数列表还是可以找到基类的实现~
五、小结
Java与c++在隐藏、重写的不同就介绍到这里,刚刚在线Markdown没有保存,就打开了上一篇的编辑,差点以为要冲掉这篇了,码了好多字啊,吓傻了。。。看来以后要多导入到本地~~哈哈哈
—END—
参考文献
[1] http://bbs.chinaunix.net/thread-643467-1-1.html
[2] http://www.cnblogs.com/mengdd/archive/2012/12/25/2832288.html
版权声明:欢迎转载,注明出处就好!如果不喜欢请留言说明原因再踩哦,谢谢,我也可以知道原因,不断进步!!