- 静态多态:包括函数重载和运算符重载,复用函数名等;
- 动态多态:派生类和虚函数(什么是虚函数?);
->C++虚函数详解
- 静态多态的函数地址在编译阶段就已经确定了;
- 动态多态的函数地址在运行阶段确定。
满足条件:
- 类之间有继承关系;
- 子类要重写父类的虚函数;
重写:函数的返回值类型,形参列表均相同,关键在virtual;
父类的 指针或者引用 指向子类对象时,会进行如下判断:
父类中有虚函数:
- 子类中如果说重写了父类中的虚函数,则在全局函数中,形参是父类引用,但传入参数为子类时,执行子类的成员函数;
- 若没有重写,则调用父类中的成员函数;
父类中没有虚函数:则在上述情况下,执行父类的成员函数。
深入一下:举例说明:
- 每实例化一个类对象,都会将这个类中的虚函数地址保存到虚函数表中去,然后使用虚函数指针记录这个地址;
- 当子类继承时,首先从父类拷贝一份虚函数表,然后使用虚函数指针指向它,如若重写了父类虚函数,则使用重写的函数地址覆盖原来的虚函数地址,在调用时调用重写的函数;若没有重写,则仍旧调用原来的函数地址。
1. 父类无虚函数时的重写:class Base { public: void doSome() { cout << "基类函数调用" << endl; } }; class Derived :public Base { public: void doSome() { cout << "派生类函数调用" << endl; } }; ```查看类布局:运行截图:
2. 父类有虚函数时的重写:class Base { public: virtual void doSome() { cout << "基类函数调用" << endl; } }; class Derived :public Base { public: void doSome() { cout << "派生类函数调用" << endl; } };类布局:
运行截图:
3. 父类有虚函数时不重写:总结:class Base { public: virtual void doSome() { cout << "基类函数调用" << endl; } }; class Derived :public Base { };类布局:
运行截图:
归根到底,多态关键就是父类虚函数的重写与否。
在 不同子类 被 同父类的指针或者引用 使用时,执行不同的程序,表现出不同的形态,这就是多态。
多态意义在于使用一个指针(接口),可以根据不同的实例化对象执行不同的操作。
->值得注意的一点是,在多继承问题中,子类的虚函数表只有一个<-
利用多态的技术写一个计算器类。
多态优点:
- 代码组织结构清晰
- 可读性强
- 利益前期和后期的扩展及维护
#include3. 纯虚函数和抽象类using namespace std; // 计算器基类 class BaseCalculator { public: int num1; int num2; virtual int result() { return 0; } }; // 加法计算器 class Add: public BaseCalculator { public: int result() { return num1 + num2; } }; // 减法计算器 class Sub :public BaseCalculator { public: int result() { return num1 - num2; } }; // 乘法计算器 class mult :public BaseCalculator { public: int result() { return num1 * num2; } }; // 简单实现加法运算 void test1() { BaseCalculator* base = new Add; // 多态,即使用父类指针指向子类 cin >> base->num1; cin >> base->num2; cout << base->num1 << " + " << base->num2 << " = " << base->result() << endl; } int main() { test1(); system("pause"); return 0; }
在多态中,使用的基本都是子类重写的函数,父类的虚函数基本不会使用到。这时,我们可以将父类中的虚函数改写为纯虚函数。
语法: virtual 返回值类型 函数名(参数列表) = 0;
当类中有了纯虚函数,这个类也成为抽象类。
抽象类的特点:
- 无法实例化对象;
- 子类必须重写抽象类的纯虚函数,不然也属于抽象类;
- 这样做的意义是在于,可以实现多态;即要求子类必须要重写父类虚函数,不然这个多态无意义。
多态中,如果子类中有属性开辟到了堆区,那么父类指针在释放时无法调用子类的析构代码。
如:测试案例中,初始化Derived类base到堆区,使用delete释放,发现没有调用子类的析构函数,即没有将子类中申请的a释放。
class Base
{
public:
Base()
{
cout << "Base的构造函数调用" << endl;
}
~Base()
{
cout << "Base的析构函数调用" << endl;
}
virtual void doSome() = 0;
};
class Derived :public Base
{
public:
Derived(int input)
{
cout << "Derived的构造函数调用" << endl;
a = new int(input);
}
~Derived()
{
cout << "Derived的析构函数调用" << endl;
delete a;
a = NULL;
}
void doSome()
{
cout << "doSome执行,cout a = " << *a << endl;
}
int *a;
};
void test1()
{
Base* base = new Derived(10);
base->doSome();
delete base;
}
运行截图:
解决方法是将父类中的析构函数改写为虚析构或者纯虚析构函数。
对代码做出如下修改:
class Base
{
public:
Base()
{
cout << "Base的构造函数调用" << endl;
}
virtual ~Base() // 改写为虚析构后 // 纯虚析构结果也一样
{
cout << "Base的析构函数调用" << endl;
}
virtual void doSome() = 0;
};
运行截图:
特点是:
- 可以解决父类指针释放子类对象;
- 需要具体的函数实现。
语法和虚函数和纯虚函数类似,使用方法也类似。
但要注意,如若父类使用纯虚析构函数,需要在类外定义带有父类作用域的析构函数。
原因是父类的析构函数是无论如何都会执行的,需要有具体的代码实现,而纯虚析构仅仅相当于是一个函数的声明。
这个操作,相当于是成员函数的重写。
这是自己动手,根据视频学习写出来的一个案例:
–>多态案例–组装电脑<–
里面有点疑问,希望各位可以给点建议。。
6. 娱乐时刻



