在使用面向对象语言中,最最重要的三个思想就是继承,封装,多态.封装使得代码更加模块化,继承可以扩展现有代码的功能,多态,可以实现接口重用,降低代码的耦合性.那么我们今天要讨论的问题就是在C++中是怎样实现多态这一机制的.
基础知识
-
多态指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。
- C++支持两种多态性:编译时多态性,运行时多态性。
a、编译时多态性:通过重载函数实现
b、运行时多态性:通过虚函数实现。
- C++支持两种多态性:编译时多态性,运行时多态性。
-
在父类中存在的方法,再在子类中实现相同的方法,只是在实现功能上或许有差异叫重写.
-
要是一个类A继承了一个类B,那么类B可以引用类A的对象.
-
C++ new 一个对象时,只为类中的成员变量分配空间,对象之间共享成员函数.
对于了解Java语言的都知道,在父类方法中可以直接使用父类来引用子类的实例.以下例子
class Parent{
void print() {
System.out.println("Parent method");
}
}
class Generation extends Parent{
void print() {
System.out.println("Generation method");
}
}
public class Main {
public static void print(Parent s) {
s.print();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Parent p = new Parent() ;
Generation g =new Generation();
print(p);
print(g);
}
}
运行结果:
Parent method
Generation method
Java多态的实现原理在此不做解释,参考Java多态实现原理
那么用类似的方法来简单实现一下C++的多态是否能相同呢?不妨试一下?OK
#include <iostream>
using namespace std ;
class Person{
public:
int a ;
Person(){}
~Person(){}
//virtual
void print(){
cout << "Person method" << endl ;
}
};
class Generation:public Person{
public:
int a;
Generation(){}
~Generation(){}
void print(){
cout << "Generation method" << endl ;
}
};
void print( Person *s ){
cout<<"address s:"<<s<<endl;
cout<<"address a:"<<&(s->a)<<endl;
s->print();
}
int main()
{
Person* p = new Person() ;
Generation* s = new Generation() ;
print(p);
print(s);
free(p);
free(s);
return 0;
}
未使用virtual运行结果:
changke@King:~/blog$ g++ 1.cpp
changke@King:~/blog$ ./a.out
address s:0x55ed01d50e70
address a:0x55ed01d50e70
Person method
address s:0x55ed01d50e90
address a:0x55ed01d50e90
Person method
使用virtual:
changke@King:~/blog$ g++ 1.cpp
changke@King:~/blog$ ./a.out
address s:0x55d292fb6e70
address a:0x55d292fb6e78
Person method
address s:0x55d292fb6e90
address a:0x55d292fb6e98
Generation method
以上在没有使用virtual的情况下,是不能实现多态的.C++通过virtual来修饰被子类重写的方法,这样在父类中就可以引用子类的实例对象实现统同一语句多种不同的表现形式.
以上程序中还打印出了两次对象的地址和对象中a的地址,我们之前也说过,C++中new一个对象,只给成员变量分配空间,各个对象共享类中的方法.在不使用virtual的情况下,两次打印的都是父类对象的首地址和a的地址,两者是相同的.
而在使用virtual之后,打印的父类对象的首地址和成员a的地址不同,第二次打印的是子类的对象首地址和其成员a的地址,两个值也不同,都差了8字节.
这八字节被用来存储它们各自的虚函数指针了.该指针指向该类的虚函数表.(64位机,指针占八个字节)
虚函数表
比如下面的单继承UML图及相应子类和父类的虚函数表:
以上是单继承父类和子类的虚函数表,要是多继承的话,会产生多个_vptr虚函数指针来指向不同的函数表.图中不难看出,要是子类重写了父类的方法func2
,那在子类虚函数表中,就会用子类中相同的方法将继承下来的父类方法覆盖掉.之后编译器会根据父类引用的对象来查询相应类的虚函数表,再调用重写的方法.
纯虚函数
在基类中定义的纯虚函数而不在基类中实现,继承该类的子类必须实现纯虚函数.存在纯虚函数的基类不能实例化对.
Java中有抽象类或接口这些概念,当然在C++中我们通过纯虚函数也可以实现,下面是接口的实现:
#include <iostream>
using namespace std;
class Parent{
public:
//纯虚函数的定义
virtual void func()=0 ;
};
class Generation : public Parent{
public:
Generation(){}
~Generation(){}
void func(){
cout << "Pure virtual func" << endl ;
}
};
int main()
{
//不能被实例化
//Parent p ;
Generation g;
g.func();
return 0;
}