1.多态的基本概念
多态性提法接口和具体实现之间的另一层隔离,多态改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性。
C++支持编译时多态(静态多态)和运行时多态(动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态
静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。如果函数调用,在编译阶段就可以确定函数调用的地址,并产生代码就是静态多态(编译时多态)。而如果函数的调用地址不能在编译期间确定,需要在运行时才能确定,这就是晚绑定(动态多态,运行时多态)。
2.动态联编和静态联编
静态联编
class animal {
public:
void speak()
{
cout << "动物发出了叫声" << endl;
}
};
class dog:public animal
{
public:
void speak() {
cout << "狗发出了叫声" << endl;
}
};
void dospeak(animal&an) {
//参数的类型已经确定是animal类型
//speak在编译的过程中就已经确定好了函数的地址
an.speak();
}
void test() {
dog dg;
//这里传入dg类型的时候编译器并不会报错
//原因是当发生继承关系的时候,编译器会发生类型的转换
dospeak(dg);
}
int main() {
test();
return 0;
}
//输出:动物发出了叫声
程序调用的是animal对应的成员函数,因为在编译的过程中函数dospea()就已经确定了参数的类似是animal,所以调用时也是调用的animal类的成员函数
动态联编
将dospeak()函数改为虚函数,在父类上定义虚函数,发生多态。
多态的核心思想:父类的引用或者指针指向子类对象
class animal {
public:
//使用关键字virtual改为虚函数
virtual void speak()
{
cout << "动物发出了叫声" << endl;
}
};
class dog:public animal
{
public:
virtual void speak() {
cout << "狗发出了叫声" << endl;
}
};
//方法:父类的引用指向子类
void dospeak(animal&an) {
an.speak();
}
void test() {
dog dg;
dospeak(dg);
}
//输出:狗发出了叫声
2.多态的原理剖析
class animal {
public:
void speak()
{
cout << "动物发出了叫声" << endl;
}
};
void test() {
animal an;
cout << sizeof(an) << endl;
};
//输出的结果为:1
class animal {
public:
virtual void speak()
{
cout << "动物发出了叫声" << endl;
}
};
void test() {
animal an;
cout << sizeof(an) << endl;
};
//输出的结果为:4
结果变为4的原因是此时存放的是一个虚函数指针。
class animal {
public:
virtual void speak()
{
cout << "动物发出了叫声" << endl;
}
};
class dog:public animal
{
public:
};
当发生多态时
class animal {
public:
virtual void speak()
{
cout << "动物发出了叫声" << endl;
}
};
class dog:public animal
{
public:
virtual void speak()
{
cout << "狗发出了叫声" << endl;
}
};
子类写speak()父类的虚函数,这种写法叫重写;
重写必须返回值,参数个数,类型,顺序都相同。
//发生了函数重写
//f
animal* an = new dog;
an->speak();
//输出:狗发出了叫声
//通过找函数的地址直接调用
void test() {
dog dg;
//*(int*)*(int*)&dg函数地址
//强转为int*的原因是指针的大小为4字节
//先拿到vfptr指针,通过解引用访问虚函数表
((void(*)()) * (int*)*(int*)&dg)();
((void(*)())(*((int*)*(int*)&dg + 1)))();
}
输出:
狗在吃东西
狗发出了叫声
3.计算器案例
class abstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
void setv1(int v)
{
this->val1 = v;
}
void setv2(int v)
{
this->val2 = v;
}
public:
int val1;
int val2;
};
//加法计算器
class PlusCalculator :public abstractCalculator
{
public:
virtual int getResult()
{
return val1 + val2;
};
};
class SubCalculator : public abstractCalculator
{
public:
virtual int getResult()
{
return val1 - val2;
};
};
class ChengCalculator :public abstractCalculator
{
public:
virtual int getResult()
{
return val1 * val2;
};
};
void test02()
{
abstractCalculator * abc ;
//加法计算器
abc = new PlusCalculator;
abc->setv1(10);
abc->setv2(20);
cout << abc->getResult() << endl;
delete abc;
abc = new SubCalculator;
abc->setv1(10);
abc->setv2(20);
cout << abc->getResult() << endl;
delete abc;
abc = new ChengCalculator;
abc->setv1(10);
abc->setv2(20);
cout << abc->getResult() << endl;
}
4.抽象类与纯虚函数
//纯虚函数的写法
virtual int getResult()=0;
class abstractCalculator
{
public:
//告诉编译器为函数保留一个位置,但是这个位置不存放地址
virtual int getResult()=0;
}
- 如果父类有纯虚函数,那么子类继承后就必须实现纯虚函数。
- 如果父类中有了纯虚函数,这个父类就无法实例化对象,相当于父类只是进行了声明。
- 如果父类有了纯虚函数,这个类就被称为抽象类
- 因此抽象类无法实例化
5.虚析构和纯虚析构函数
class animal
{
public:
virtual void speak() = 0;
~animal()
{
cout << "animal的析构函数" << endl;
}
};
class dog :public animal {
public:
virtual void speak() {
cout << "狗在叫" << endl;
}
dog(const char* name) {
//开空间
m_name = new char[strlen(name) + 1];
strcpy(m_name, name);
}
~dog() {
delete[] m_name;
cout << "dog的析构函数" << endl;
}
char * m_name;
};
void test() {
animal* an;
an = new dog("TOM");
delete an;
}
//输出:调用的是animal的析构函数
调用的是父类的析构函数,那么m_name的空间就没有释放,出现内存泄漏。
解决方案:虚析构/纯虚析构
virtual ~animal()
{
cout << "animal的析构函数" << endl;
}
virtual ~dog() {
delete[] m_name;
cout << "dog的析构函数" << endl;
}
输出:
调用dog的析构函数
调用aniaml的析构函数
使用纯虚析构
class animal
{
public:
animal() {
}
virtual void speak()
{
}
//纯虚函数,需要类内声明,内外实现。
virtual ~animal() = 0;
};
animal::~animal() {
cout << "animal的纯虚析构" << endl;
}
为什么虚析构需要写定义?因为子类无法继承父类的构造函数和析构函数,所以无法对析构和构造进行重写;在删除子类对象时需要调用父类的析构函数,所以父类的纯虚析构函数需要定义。
6.向上类型转换和向下类型转换
不发生多态
向下类型转换(基类转换为派生类):不安全,访问的范围变大
向上类型转换(派生类转换为基类):安全,访问的范围变小
发生多态
发生了多态,类型转换总是安全的