对于同一个行为对于不同的对象,有不同的表现,是面向对象编程三大特性之一(封装、继承、多态)在c++,一般针对一个行为只会有一个名称,是对类的行为在抽象,主要作用在于统一行为的接口.提高方法的通用性。
多态在c++中分为静态多态与动态多态
静态多态是基于函数重载与泛型编程实现的。动态多态是基于虚函数实现的
定义:
静态绑定是指程序编译结束后就已经确定了需要调用的函数
动态绑定是指在运行时才确定具体需要调用的函数
作用:把不同派生类对象都当做基类对象来看,可以屏蔽不同子类之间的差异,提高程序的通用性来适应需求的不断变化
在C++中允许父类的指针或者引用指向子类的对象。
虚函数
使用virtual关键字声明的函数,是动态多态实现的基础
非类的成员函数不能定义为虚函数
类的静态成员函数不能定义为虚函数
构造函数不能定义为虚函数,但可以将析构函数定义为虚函数
当将基类的某一成员函数声明成虚函数后,派生类的同名函数自动成为虚函数
动态多态的实现
1.创建两个类,并且是继承关系
2.基类中的函数声明称virtual 函数,也就是虚函数
3.派生类继承基类并且重写基类中的虚函数
4.通过基类的指针或者引用访问基类的对象或者派生类的对象
成员函数覆盖(override,也称为重写)
定义:派生类重新实现基类的虚函数
特点:1.不同作用域(分别位于基类和派生类)2.函数名相同 3.参数相同 4.返回值相同 5.基类必须有virtual关键字,不能有static 6.重写函数的权限访问限定符可以不同
函数重载(overload)
定义:同意作用域中的函数名相同,参数不同的多个函数间构成重载,常成员函数与非常成员函数也可以构成重载。
特点:1.同一个作用域 2.函数名相同 3.参数相同 4.返回值类型可相同可不同 5.virtual关键字可有可无。
成员函数的隐藏(hiding)
定义:基类与派生类有同名函数,调用的时候总是调用子类函数,此时服了已成员函数被隐藏
特点:1. 不在同意作用域(分别位于派生类与基类)2.函数名相同 3.返回值可相同可不同 4.参数不同时,不论有无virtual 关键字,基类函数都将被隐藏 4.参数相同时,但基类没有virtual 关键字,基类的函数被隐藏。
建议将基类的析构函数设置为是虚函数,基类的析构函数声明为虚函数,则派生类的析构函数自动为虚函数。当基类的指针指向派生类对象时,如果析构函数不是虚函数,则不会发生动态多态,而导致只会调用基类的析构函数,造成内存泄漏。
原理:C++能够在运行时确定调用的函数,是因为引入了虚函数一旦类中引入了虚函数,在程序编译期间(<深度探索c++对象模型4.2>)就会创建虚函数表,表中每一项数据都是虚函数的入口地址,为了将对象与虚函数表关联起来.编译器会在对象中会增加一个指针成员用于存储虚函数表的位置
基类的指针指向派生类对象时就是通过虚函数表的指针来找到实际应该调用的函数。
虚函数表原理分析:
基类与派生类都维护自己的虚函数表如果派生类重写基类的虚函数,则虚函数表存储的是派生类的函数的地址没有重写的虚函数则保存的是基类的虚函数表,当要获取虚函数表的地址以及虚函数表的内容(虚函数的地址),可以使用一下代码进行验证
父类:
#include
using namespace std;
class A
{
public:
virtual void func1()
{
cout << "A func1" << endl;
}
virtual void func2()
{
cout << "A func2" << endl;
}
virtual void func3()
{
cout << "A func3" << endl;
}
};
子类:
class B : public A
{
public:
virtual void func1()
{
cout << "B func1" << endl;
}
virtual void func2()
{
cout << "B func2" << endl;
}
virtual void func3()
{
cout << "B func3" << endl;
}
};
main函数:
typedef void (*pnf)();
int main(int argc, const char *argv[])
{
B b;
A &a = b;
cout << "指向虚函数表指针的地址:" << (long *)&a << endl;
cout << "虚函数表的地址:" << (long *)*(long *)&a << endl;
cout << "虚函数内第二个元素的地址:" << (long *)*(long *)&a + 1 << endl;
cout << "虚函数内第三个元素的地址:" << (long *)*(long *)&a + 2 << endl;
cout << "虚函数内第三个元素的值:" << (long *)*((long *)*(long *)&a + 2) << endl;
pnf p = (pnf)*((long *)*(long *)&a + 2);
p();
return 0;
}
运行结果:
定义:含纯虚函数的类,称为抽象类(纯虚函数:指定函数接口规范,而不做具体的实现,实现部分有继承它的子类去实现)
抽象类的特点:抽象类中只声明函数接口.不能有具体的实现抽象类不能创建对象,可以定义指针与引用,派生类继承基类.并且必须要实现基类中的所有纯虚函数,否则派生类也是抽象类
应用场景:某些情况下父类只知道其子类应该包含怎样的方法,但无法准确知道子类如何实现这些方法。此时我们会在父类中只声明相应的方法,而不去具体实现,让子类根据自己实际情况去实现相应的方法,从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为子类的模板,避免子类设计随意性
示例:建立一个四边形Quadrangle ,派生出菱形Rhombus 和矩形Rect
Quadrangle类:
#include
using namespace std;
class Quadrangle
{
public:
virtual ~Quadrangle() {}
virtual double area() = 0;
virtual double peri() = 0;
};
Rhombus类:
class Rhombus : public Quadrangle
{
public:
Rhombus(double a = 0,double h = 0) : a(a),h(h)
{
}
double area()
{
return a*h;
}
double peri()
{
return 4*a;
}
private:
double a;
double h;
};
Rect类:
class Rect : public Quadrangle
{
public:
Rect(double a = 0,double b = 0) : a(a),b(b)
{
}
double area()
{
return a*b;
}
double peri()
{
return 2*a+2*b;
}
private:
double a;
double b;
};
main函数:
void ap(Quadrangle &h)
{
cout << "area " << h.area() << endl;
cout << "peri " << h.peri() << endl;
}
int main(int argc, const char *argv[])
{
Rhombus a(1,2);
Rect b(3,4);
ap(a);
ap(b);
运行结果: