C/C++ 基础笔记(十四)多态与模板编程

本篇核心知识:多态(静态 / 动态)、虚函数、析构虚化、重写、final 关键字、纯虚函数与抽象类、成员函数指针、函数模板、类模板、模板友元、模板重载匹配规则

一、多态基础(静态多态 & 动态多态)

概念

多态:同一个调用形式,根据场景执行不同代码。

静态多态(编译期多态):编译阶段确定调用版本,代表:函数重载、运算符重载。

动态多态(运行期多态) :运行时依据对象真实类型选择函数,依托继承 + 虚函数 + 基类指针 / 引用实现。

特性

  1. 静态多态:编译绑定,效率高;

  2. 动态多态:运行绑定,灵活,三大必要条件:公有继承、子类重写虚函数、基类指针 / 引用指向派生对象

  3. 普通同名函数(非虚):按指针本身类型 调用;虚函数:按指针指向的实际对象类型调用。

代码示例

复制代码
#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 修饰的成员函数,派生类(子类)可重写函数,是实现动态多态核心。

特性

  1. 非静态成员函数(不加static)可做虚,构造、全局、友元、static 不能定义虚函数;

  2. 派生类重写同名同参函数自动为虚,可加 override 标注;

  3. 虚函数底层依靠虚表、虚指针实现运行查找。

  4. 如果在类内定义,类外实现,类外不需要再使用virtual。

代码示例

复制代码
class Base{
public:
    virtual void func(){cout<<"基类虚函数"<<endl;}
};
class Son:public Base{
public:
    void func() override{cout<<"子类重写"<<endl;}
};

拓展:override 关键字,强制检查是否合法重写。

三、虚析构函数

概念

基类析构加 virtual,通过基类指针释放派生对象时,先调用派生析构再基类析构,防止派生内存泄漏。

特性

  1. 无虚构造函数(构造不能 virtual);

  2. 有派生设计的基类,析构建议一律 virtual;

  3. 非虚析构:基类指针删子类,仅执行基类析构,子类资源泄漏。

代码示例

复制代码
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;,无函数体;包含至少一个纯虚的类叫抽象类

特性

  1. 抽象类不能实例化创建对象,仅能定义指针 / 引用;

  2. 子类会继承纯虚函数,必须全部实现父类所有纯虚函数,否则派生仍是抽象类;

  3. 用于定义接口规范。

代码示例

复制代码
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;
}

十一、模板特例化(拓展)

概念

对特定类型单独定制模板实现,优先级高于通用模板。

特性

通用模板无法适配某类型(自定义类无 + 运算符),可单独特例。

相关推荐
Roann_seo%2 小时前
C++文件操作完全指南:从文本读写到二进制文件处理
开发语言·c++
坚果派·白晓明3 小时前
【鸿蒙PC】SDL3 适配:AtomCode + Skills 快速集成 NAPI 测试工具
c++·华为·ai编程·harmonyos·atomcode
凡人叶枫3 小时前
Effective C++ 条款17:以独立语句将 newed 对象置入智能指针
java·linux·开发语言·c++·算法
凡人叶枫5 小时前
Effective C++ 条款16:成对使用 new 和 delete 时要采取相同形式
开发语言·c++·effective c++
不吃土豆的马铃薯5 小时前
C++ 高性能网络缓冲区 Buffer 源码解析
linux·服务器·开发语言·网络·c++
caimouse5 小时前
Reactos 第1章 概述
c语言·开发语言·架构
.千余6 小时前
【C++】C++继承入门(下):友元、静态成员与菱形继承的底层逻辑
开发语言·c++·笔记·学习·其他
初中就开始混世的大魔王6 小时前
6 Fast DDS-传输层
开发语言·c++·中间件·信息与通信
啊森要自信6 小时前
【GUI自动化测试】控件、鼠标键盘操作与多场景自动化
c语言·开发语言·python·adb·ipython