本文总结了http://blog.csdn.net/hackbuteer1/article/details/7475622和http://blog.csdn.net/zoopang/article/details/14071779、http://blog.csdn.net/haoel/article/details/1948051/几位老师的博客
首先声明:博客是对自己学习知识的总结,避免遗忘方便以后学习。为了避免误导读者,如果哪位发现有描述不对的地方,希望告诉我,谢谢。
一、什么是多态
关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议
C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。
备注:重写、隐藏、重载的概念
重写:子类重新定义父类的做法称为覆盖(override),或者称为重写,只有重写了虚函数的才能算作是体现了C++多态性。
隐藏:如果重写的是父类的成员函数,则称为隐藏
重载:重载指得是同一个类内部,有多个同名函数,同名函数里面的参数类型或者参数个数不同(和返回值没有关系),???这里有个疑问,不知道程序是在编译的时候还是在运行的时候知道要调用的函数的真正地址,我感觉是编译的时候。
早晚绑定的问题:多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。
二 如何实现多态
最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。
#include<iostream>
using namespace std;
class A
{
public:
void foo()
{
printf("1\n");
}
virtual void fun()
{
printf("2\n");
}
};
class B : public A
{
public:
void foo()
{
printf("3\n");
}
void fun()
{
printf("4\n");
}
};
int main(void)
{
A a;
B b;
A *p = &a;
p->foo();
p->fun();
p = &b;
p->foo();
p->fun();
//直接赋值无法实现多态
a = b;
a.foo(); //仍然输出的是A的函数
a.fun();
return 0;
}
第一个p->foo()和p->fuu()都很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。
第二个输出结果就是1、4。p->foo()和p->fuu()则是基类指针指向子类对象,正式体现多态的用法,p->foo()由于指针是个基类指针,指向是一个固定偏移量的函数,因此此时指向的就只能是基类的foo()函数的代码了,因此输出的结果还是1。而p->fun()指针是基类指针,指向的fun是一个虚函数,由于每个虚函数都有一个虚函数列表,此时p调用fun()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fun()函数的地址,因此输出的结果也会是子类的结果4。
三、虚函数表
后面会继续补充