本篇核心知识:多态(静态 / 动态)、虚函数、析构虚化、重写、final 关键字、纯虚函数与抽象类、成员函数指针、函数模板、类模板、模板友元、模板重载匹配规则
一、多态基础(静态多态 & 动态多态)
概念
多态:同一个调用形式,根据场景执行不同代码。
静态多态(编译期多态):编译阶段确定调用版本,代表:函数重载、运算符重载。
动态多态(运行期多态):运行时依据对象真实类型选择函数,依托继承 + 虚函数 + 基类指针 / 引用实现。
特性
静态多态:编译绑定,效率高;
动态多态:运行绑定,灵活,三大必要条件:公有继承、子类重写虚函数、基类指针 / 引用指向派生对象;
普通同名函数(非虚):按指针本身类型调用;虚函数:按指针指向的实际对象类型调用。
代码示例
#include <iostream> using namespace std; class Animal{ public: void speak(){cout<<"动物鸣叫"<<endl;} }; class Pig:public Animal{ public: void speak(){cout<<"小猪哼哼"<<endl;} }; int main(){ Animal* p = new Pig; p->speak(); // 普通函数:输出动物鸣叫 delete p; return 0; } //改成虚函数实现动态多态 class Animal{ public: virtual void speak(){cout<<"动物鸣叫"<<endl;} }; class Pig:public Animal{ public: void speak() override{cout<<"小猪哼哼"<<endl;} }; int main(){ Animal* p = new Pig; p->speak(); // 动态多态:小猪哼哼 delete p; }相似概念比较
静态多态:编译决议;动态多态:运行决议。
二、虚函数 virtual
概念
基类用 virtual 修饰的成员函数,派生类(子类)可重写函数,是实现动态多态核心。
特性
仅非静态成员函数(不加static)可做虚,构造、全局、友元、static 不能定义虚函数;
派生类重写同名同参函数自动为虚,可加 override 标注;
虚函数底层依靠虚表、虚指针实现运行查找。
如果在类内定义,类外实现,类外不需要再使用virtual。
代码示例
class Base{ public: virtual void func(){cout<<"基类虚函数"<<endl;} }; class Son:public Base{ public: void func() override{cout<<"子类重写"<<endl;} };拓展:override 关键字,强制检查是否合法重写。
三、虚析构函数
概念
基类析构加 virtual,通过基类指针释放派生对象时,先调用派生析构再基类析构,防止派生内存泄漏。
特性
无虚构造函数(构造不能 virtual);
有派生设计的基类,析构建议一律 virtual;
非虚析构:基类指针删子类,仅执行基类析构,子类资源泄漏。
代码示例
class Animal{ public: virtual ~Animal(){cout<<"基类析构"<<endl;} }; class Pig:public Animal{ int* p = new int[5]; public: ~Pig(){delete[] p;cout<<"子类析构"<<endl;} // 如果不写析构释放,子类不会释放内存 }; int main(){ Animal* p = new Pig; delete p; // 先子类再基,正常释放 }四、final 关键字
概念
修饰类 / 虚函数:final 修饰函数→禁止子类重写;final 修饰类→该类不能被继承。
特性
1 final 虚函数:后续派生类无法重写;
2 final 类:断绝一切继承;
3 final 不能修饰普通非虚函数。
代码示例
class A{ public: virtual void show() final{}; // 不能重写 // virtual void show() = 0 final; // =0 和 final会冲突,不能一起用 }; //class B:public A{}; // 报错 final class C{}; // C不能被继承五、纯虚函数 & 抽象类
概念
纯虚:virtual 函数() = 0;,无函数体;包含至少一个纯虚的类叫抽象类。
特性
抽象类不能实例化创建对象,仅能定义指针 / 引用;
子类会继承纯虚函数,必须全部实现父类所有纯虚函数,否则派生仍是抽象类;
用于定义接口规范。
代码示例
class Animal{ public: virtual void speak() = 0; // 纯虚函数 }; class Pig:public Animal{ // 未实现父类纯虚函数 }; class Dog:public Animal{ // 实现父类纯虚函数 public: void speak(){cout<<"狗叫"<<endl;} }; int main(){ // Animal a; // 报错,抽象类不能创建对象 // Animal* p = new Pig; // 报错,子类未实现父类纯虚函数,也是抽象类不能创建对象 Animal* d = new Dog; // 可执行 d->speak(); }六、成员函数指针
概念
专门存储类成员函数地址的指针,区别于普通全局函数指针,它是实现 “动态调用成员函数” 的唯一工具。
特性
1 定义格式:返回值 (类名::*指针)(形参);
2 赋值:指针 = &类名::函数名;
3 调用:(对象.*指针)(实参)/(对象指针->*指针)(实参);
4 不能跨类指向其他类成员。
作用
1动态调用不同成员函数
2构建函数表、消息映射(框架底层大量用)
3让成员函数作为回调函数
代码示例
class Test{ public: int add(int a,int b){return a+b;} }; int main(){ Test t; Test *pTest = &t; t.add(1,2); int (Test::*p)(int,int) = &Test::add; (t.*p)(3,4); pTest->add(5,6); (pTest->*p)(7,8); }七、模板总论(泛型编程)
概念
模板:把数据类型做参数,一套代码适配多种类型,分函数模板、类模板,编译期根据实例类型生成对应代码。
特性
1 关键字:template<class T>/template<typename T>等价,一般模板函数:typename,模板类:class;
2 模板本身不是可执行代码,实例化后生成实体;
3 模板声明 + 实现建议放同一头文件(分离编译易出错)。
八、函数模板
概念
生成通用函数,任意合法类型均可调用。模板函数不是一个实体函数(缺少数据类型)
特性
1 调用:实参可自动推导类型,推导失败需<类型>显式指定;
2 调用优先级:普通函数 > 模板函数(参数完全匹配优先普通,少传参);
3 支持重载、模板特例化。
代码示例
template<typename T> // template<typename U,typename V,typename K ...> T add(T a,T b){ return a+b; } //模板特例化 template<> MyData add<MyData>(MyData obj1,MyData obj2){ MyData obj; obj.data = obj1.data + obj2.data; return obj; } template<typename T,typename U,typename V> // 可以给默认参数 例如typename V = U 但一般不写 V add1(T a,U b){ return a+b; } int main(){ cout<<add(1,2); // 标准写法add<>() cout<<add(1.5,2.5);// 如果可以根据参数推断出参数类型即可省略<T> cout<<add<double>(3,3.14); cout<<add1<int,double,double>(3,3.14); }相似比较
普通函数:固定类型;函数模板:类型可变。
拓展:模板与普通函数共存规则
1 参数精准匹配普通→优先普通;
2 普通无法隐式转换匹配→启用模板;
3 强制使用模板:add<int>(3,4)。
九、类模板
概念
模板化类,成员类型由模板参数决定,实例化必须显式指定<类型>。
特性
1 格式template<class T> class XXX{};
2 类外成员实现需携带模板头;
3 模板类做函数参数:1 固定类型;2 参数写成模板函数。
代码示例
template<class T> class MyVal{ public: T data; MyVal(T d):data(d){} T getData(){return data;} void setData(T v){data = v;}; }; // 模板类的对象作为函数参数 void test1(MyVal<int> &obj){} // 写法一(只能传int型) template<typename T> // 写法二(可以指定Val的对象) void test2(MyVal<T> &obj){} template<typename T> // 写法二(可以指定任何类型) void test3(T &obj){} int main(){ MyVal<int> obj1(10); MyVal<double> obj2(3.14); }十、模板的友元
概念
模板类内声明友元,分类内实现、类外全局实现两种写法。
特性
1 友元写在类体内:写法最简,不需要额外声明,每个模板实例化都会生成对应友元;
2 类外实现:需要前置类声明 + 模板声明,真正全局函数,代码分离符合工程规范。
代码示例
类内实现:
template <class T> class Test { T val; public: Test(T v) : val(v) {} // 友元函数:直接在类内实现 friend void show(Test<T> &t) { // 可以直接访问私有成员 val cout << t.val << endl; } };类外实现:
// 1. 提前声明模板类 template <class T> class Test; // 2. 提前声明模板友元函数 template <class T> void show(Test<T> &t); // 3. 模板类定义 template <class T> class Test { T val; public: Test(T v) : val(v) {} // 4. 声明【模板友元函数】 注意中间有<> friend void show<>(Test<T>& t); }; // 5. 类外全局实现友元 template <class T> void show(Test<T>& t) { cout << t.val << endl; }十一、模板特例化(拓展)
概念
对特定类型单独定制模板实现,优先级高于通用模板。
特性
通用模板无法适配某类型(自定义类无 + 运算符),可单独特例。