本篇核心知识:多态(静态 / 动态)、虚函数、析构虚化、重写、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;
}
十一、模板特例化(拓展)
概念
对特定类型单独定制模板实现,优先级高于通用模板。
特性
通用模板无法适配某类型(自定义类无 + 运算符),可单独特例。