目录
一、OOP 四大核心特性:
多态
1. 静态多态(编译多态)
2. 动态多态(运行多态)
5. 纯虚函数 & 抽象类
6. 重写 (override)、重载 (overload)、重定义 (hide) 三者区别
二、模板(泛型编程)+ 模板编译原理
1. 模板是什么?
2. 函数模板
3. 类模板
4. 模板特化
一、OOP 四大核心特性:
封装、继承、多态、抽象
抽象:提取事务的共性,行为共性和属性共性
封装:将对象的属性和行为捆绑在一起,并控制外部访问权,捆绑 + 访问控制 + 对外统一接口
继承:公有继承,私有继承,保护继承
构造顺序:先构造父类再构造子类
析构顺序:先析构子类再析构父类
重要坑:父类析构必须加 virtual(多态前提)
如果父类析构不加 virtual,父类指针指向子类对象,delete 时只会调用父类析构,子类资源泄漏。
同名隐藏(重定义)
子类定义和父类同名函数(无 virtual),会隐藏父类所有同名重载函数,叫静态重定义,不构成多态。
继承分类
- 单继承:一个子类只继承一个父类
- 多继承:一个子类继承多个父类(C++ 支持,Java 不支持)
- 问题:菱形继承(钻石继承)带来二义性、数据冗余
- 解决方案:虚继承 virtual public,共享一份父类成员。
- 多层继承:A→B→C
多态
同一个接口,不同对象表现不同行为。一句话:一个接口,多种实现。
分类:静态多态,动态多态
1. 静态多态(编译多态)
编译器在编译阶段就能确定调用哪个函数。两种实现:
- 函数重载:同一作用域,函数名相同,参数列表不同(个数 / 类型 / 顺序),返回值不能区分重载。
- 运算符重载特点:早绑定,编译期确定地址,效率高。
2. 动态多态(运行多态)
运行时才能确定调用哪个函数,必须同时满足 3 个条件:
- 公有继承(public 继承)
- 父类中有虚函数 virtual,子类重写该虚函数(重写 override)
- 使用父类指针 / 引用指向子类对象调用函数
4. 动态多态底层原理
虚函数表 vtable、虚函数指针 vptr
- 含有虚函数Base的类会生成一张虚函数表vtable,这个虚表里有所有的虚函数的地址,这个类的对象头部会生成一个虚指针指向这个虚表
- 在son子类继承Base的时候,son也会生成一张属于自己的虚函数表vtable,vtable中有已经被重写的父类虚函数,未被重写的父类虚函数,和自己新增的虚函数。也会在子类生成的对象头部生成一个虚指针指向虚函数表。
- 程序运行时,会根据指针拿到子类对象的内存起始地址,再由内存起始地址找到vptr(因为vptr在对象的头部),在找到自己的虚表,再读出虚表中函数的真实地址,最后执行该函数。
5. 纯虚函数 & 抽象类
- 纯虚函数:virtual void func() = 0;
- 包含纯虚函数的类叫抽象类,不能实例化,只能被继承。
- 作用:定义接口规范,强制子类必须重写所有纯虚函数,否则子类仍是抽象类。
6. 重写 (override)、重载 (overload)、重定义 (hide) 三者区别
特性 | 重载 overload | 重写 override | 重定义 hide |
作用域 | 同一个类 | 父子类 | 父子类 |
virtual | 不需要 | 必须父类 virtual | 无 virtual |
参数 | 参数必须不同 | 参数完全一致 | 名字相同即可 |
绑定 | 编译期静态 | 运行期动态 | 编译静态 |
二、模板(泛型编程)+ 模板编译原理
1. 模板是什么?
泛型编程:编写与类型无关的代码,一套代码适配多种数据类型,消除重复逻辑。
两种模板:函数模板、类模板
2. 函数模板
template<typename T> // template<class T> 等价 T add(T a, T b){ return a + b; } // 使用 add(1,2); // T=int add(1.5,2.2); // T=double- typename/class 无区别,推荐 typename 区分类类型。
- 模板参数可以多个:template<T1,T2>
3. 类模板
template<class T> class Stack{ private: T arr[100]; public: void push(T val){} }; // 实例化必须指定类型 Stack<int> st1; Stack<string> st2;4. 模板特化
1)全特化:为某一个特定类型单独实现模板
template<> class Stack<char>{ // char类型专属逻辑 };2)偏特化:部分模板参数固定(多参数模板适用)
5. 模板编译原理(重要)
1. 模板两阶段编译(C++ 标准)
阶段 1:模板定义检查(只检查语法,不实例化)
编译器读取模板代码,只做基础语法校验(少分号、关键字错误),不校验类型相关逻辑。哪怕 T 类型没有 + 运算符,只要语法没错,这一阶段不会报错。
阶段 2:模板实例化时检查(使用模板时)
当代码调用add<int>、Stack<string>,编译器触发隐式实例化:根据传入的实参 类型,生成一份对应类型的真实函数 / 类代码。
- 模板本身不生成代码;只有实例化才会生成二进制代码。
- 不同类型实例化,生成多份独立代码:add<int>、add<double>是两个完全不同函数。
2. 实例化分类
- 隐式实例化:使用模板自动生成代码(日常开发)
- 显式实例化:手动强制生成指定类型代码
template int add<int>(int,int);3. 模板代码分离问题
错误写法:模板声明放.h,实现放.cpp问题:编译.cpp 时无实例化,不会生成对应类型代码;主函数链接时找不到函数定义,报链接错误。解决方案二选一:
- 模板声明与实现全部写在头文件.h 中(工程最常用)
- .cpp 末尾添加显式实例化,指定所有需要用到的类型。
————————————————————————-———————————————
本文讲解到处结束,有问题欢迎给我留言