最近查看有关多态的知识,感觉有些陌生,因此梳理一下c++多态的知识
多态:分为静多态和动多态,分别对应编译时期绑定的称为静多态(早绑定),非编译时期绑定的称为动多态(晚绑定)
静多态通过函数模板和重载实现.本质是接口的复用。
函数重载:
int add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
函数模板(
泛型编程):
template<typename T>
T add(T a, T b)
{
return a + b;
}
多态:为了实现代码的接口重用,无论传递过来那个对象,函数都能找到合适的实现方法。
class Base {
public:
void print()
{
cout << "Base::print()" << endl;
}
};
class Derived : public Base {
public:
//void print()
//{
// cout << "Derived::print()" << endl;
//}
};
void Test()
{
Base b;
Derived d;
b.print();//调用基类的打印Base::print()
d.print();//子类继承了基类的print()且子类本身没有print(),因此此处还是调用基类的print()打印 Base::print()
}
int main()
{
Test();
return 0;
}
我们取消派生类print()函数注释,
Base b;
Derived d;
b.print();//调用基类的print()打印Base::print()
d.print();//子类本身中print(),此时构成了重定义,即基类中的print()被隐藏,因此调用的是子类的print()打印 Derived::print()
d.Base::print();//若想调用基类的print()需要加类的作用域限定符,打印Base::print()
试试用指针来调用:
Base b;
Derived d;
Base* pb = &b;
Derived* pd = &d;
pb->print();//pb指向基类,打印Base::print()//call Base::fun(0DE14C9H)
pd->print();//pd指向子类,打印Derived::print()
pb = &d; pb->print();//pb指向子类,却打印Base::print()
引用也相同。
为什么基类对象引用派生类对象,打印的是基类函数?
因为在静多态时期,编译时期就将函数的实现和函数的地址绑定,无论指针还是引用都在编译时期就确定了基类对象,因此打印基类函数。为了避免这种情况,我们引入了动态多态。
什么叫多态?
基类指针(同引用)指向不同的派生类对象,调用派生类和基类的同名覆盖方法,基类指针指向哪个派生类对象,调用的就是它的覆盖方法!程序运行期间(非编译期)才能判断所引用对象的实际类型,根据其实际类型调用相应的方法。多态就是通过动态绑定来实现的 =》 动态绑定通过vfptr和vftable来实现的,继承+虚函数实现的。
实现格式:
基类成员函数加virtual,声明为虚函数,派生类重写该函数,编译器将实现动态绑定。
class Base {
public:
virtual void print()
{
cout << "print::fun()" << endl;
}
};
派生类保持不变
void Test()
{
Derived d;
Base* pb = &d;
pb->print();//基类指针pb指向子类,可以打印Derived::print()//
Base& rb = d;
rb.print();//基类对象pb引用子类,可以打印Derived::print()//lea eax,[d] mov dword ptr[rb],eax
}
实现动态绑定条件:虚函数,基类指针/引用调用虚函数。
1、不再同一作用域中,分别在父类和子类中
2、要求函数名相同,函数参数列表相同,返回值也相同
3、基类函数必须是虚函数(virtual)
4、访问修饰符可以不同
纯虚函数
在成员函数的形参后面写上=0,则成员函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象 。纯虚函数必须在派生类中重新定义以后,派生类才能实例化出对象。
class Person
{
virtual void Display() = 0;
protected:
string _name;
};
class Student : public Person
{
void Display()
{
cout << "Student" << endl;
}
};
int main()
{
//Person p;//不能实例化对象
Student stu;
}
抽象类 含有纯虚函数的类就成为抽象类。抽象类只是一种基本的数据类型,用户需要在这个基础上根据自己的需要定义处各种功能的派生类。抽象类的作用就是为一个类族提供一个公共接口。抽象类不能定义对象,一个水果类可以派生出橘子香蕉苹果等等,但是水果类本身定义对象并不合理也没有必要。但是可以定义指向抽象类的指针变量,通过这个指针变量可以实现多态。
1 只有类的非静态成员函数才能定义为虚函数,静态成员函数和友元函数不能定义为虚函数。
2 只有类的非静态成员函数才能定义为虚函数,静态成员函数和友元函数不能定义为虚函数。
3 最好将基类的析构函数声明为虚函数。(析构函数比较特殊,因为派生类的析构函数跟基类的析构
函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)。