C++学习笔记——抽象类与多态
C++ 抽象类、纯虚函数与多态性:核心概念与生动解析
1. 抽象类与纯虚函数:制定“通用行动纲领”
想象一下,你是一个项目经理,要制定一个“通用行动纲领”,让所有团队成员(派生类)都必须完成某个任务,但具体怎么完成,由每个团队成员自己决定。
- 纯虚函数 (Pure Virtual Function):
- 它就像项目经理在“行动纲领”里写下的一个“待办事项”:
virtual void 完成任务() = 0;
。 = 0
的意思是:我只列出这个任务,具体怎么做(函数体),你们自己去想办法!它没有具体的实现。
- 它就像项目经理在“行动纲领”里写下的一个“待办事项”:
- 抽象类 (Abstract Class):
- 如果一个“项目经理”(类)的“行动纲领”里有至少一个这样的“待办事项”(纯虚函数),那这个“项目经理”就是个“抽象项目经理”(抽象类)。
- 特点:你不能直接“雇佣”一个“抽象项目经理”(不能创建抽象类的对象),因为他还有没具体落实的任务。
- 作用:抽象项目经理存在的意义,就是让他的“下属团队”(派生类)去继承他,然后把那些“待办事项”都具体化(实现纯虚函数)。这样,所有团队成员就都得完成这个任务了!
生动示例:
1 | // 抽象的“动物”类:它规定了所有动物都得“叫” |
2. override
关键字:我的专属“任务完成报告”!
- 当你看到
void makeSound() override {
这样的代码时,override
就像一个“任务完成报告”上的特殊标记。 - 作用:它告诉编译器和读代码的人:“看,我这个
makeSound()
函数,是特意去重写(覆盖)了基类(Animal
)里那个makeSound()
虚函数!” - 好处:
- 防错:如果你不小心把
makeSound()
写成了makeSoundd()
,或者参数写错了,编译器会立刻报错,提醒你“基类里没有这个虚函数让你重写啊!”。这能帮你避免很多低级错误。 - 清晰:一眼就能看出这个函数是重写的,代码意图更明确。
- 防错:如果你不小心把
- 建议:虽然不写
override
也能跑,但为了代码更健壮、更易读,强烈建议你每次重写虚函数时都加上它!
3. 内存管理:栈和堆,你的“工作台”和“仓库”
想象一下,你的电脑内存就是个大空间,里面有不同的区域。
栈 (Stack):
- 就像你的“临时工作台”:空间有限,放的东西都是临时的。
- 特点:你处理短期任务(函数执行)时,用的草稿纸(局部变量)就放在这儿。任务完成(函数结束),草稿纸就自动扔了(内存自动释放)。
- 优点:速度快,不用你操心。
- 缺点:空间小,东西不能久放。
堆 (Heap):
- 就像你的“大型仓库”:空间大,可以放很多东西,但需要你手动管理。
- 特点:你想长期保存的东西(动态创建的对象),就得放这儿。你得自己去“申请”仓库空间(
new
),用完了还得自己去“清理”(delete
),不然仓库就会堆满垃圾(内存泄漏)。 - 优点:空间大,东西可以放很久。
- 缺点:速度相对慢,需要你手动管理,容易忘记清理(内存泄漏)。
new
操作符:就是去仓库“申请”一块空间,并把东西放进去。delete
操作符:就是去仓库“清理”你之前申请的空间。
所以,Animal* myPet = new Dog();
这句话的意思就是:在你的“大型仓库”(堆)里,养了一只“狗”,然后用一个“动物”类型的“标签”(指针 myPet
)贴在它身上。
4. 指针与多态性基础:一个“通用遥控器”,控制多种动物!
- 指针:就像一个“标签”,它不存储具体的东西,只存储东西的“地址”(在哪儿)。
- **向上转型 (Upcasting)**:
- C++ 有个很酷的规定:一个“动物”类型的标签,可以贴在“狗”身上,也可以贴在“猫”身上,只要“狗”和“猫”都是“动物”的子类就行。
- 示例:
Animal* myPet = new Dog();
- 为什么可以? 因为“狗”是一种“动物”。
- 好处:这样你就可以用一个统一的“标签”(
Animal*
)来操作各种不同的动物,而不用管它具体是狗还是猫。这就是多态性的魅力!
5. 静态绑定与动态绑定:什么时候决定“谁来执行任务”?
想象一下,你有一个“通用遥控器”(指针),可以控制不同的动物。
静态绑定 (Static Binding) / 编译时多态:
- 非虚函数:如果遥控器上的按钮(函数)不是“智能”的(非虚函数),那么在你买遥控器(编译)的时候,就已经决定了按这个按钮会控制哪种动物。
- 问题:如果你用“动物”的遥控器去控制一只“狗”,但遥控器上的“吃饭”按钮不是智能的,它可能只会发出“动物”默认的“吃饭”指令,而不会发出“狗”特有的“吃饭”指令。
- 析构函数:如果析构函数(对象销毁时自动调用的函数)不是虚的,那么当你用“动物”的遥控器去“销毁”一只“狗”时,它只会执行“动物”的销毁步骤,而不会执行“狗”特有的销毁步骤,这可能导致“狗”内部的一些资源(内存)没被清理干净,造成内存泄漏。
动态绑定 (Dynamic Binding) / 运行时多态:
- 虚函数:如果遥控器上的按钮是“智能”的(虚函数),那么在你按下按钮(运行时)的时候,遥控器会根据它实际控制的是“狗”还是“猫”,来发出对应的“叫”指令。
- 好处:这就是多态的精髓!用一个统一的接口(基类指针),可以实现不同的行为。
6. 虚析构函数的重要性:安全“送走”动物!
- 作用:当你的“动物”遥控器(基类指针)指向一只“狗”(派生类对象),并且你想“送走”这只“狗”(
delete
对象)时,如果“动物”的析构函数是虚的,那么它会确保先调用“狗”的“送走”步骤,再调用“动物”的“送走”步骤。 - 目的:防止内存泄漏!确保所有派生类特有的资源都能被正确清理。
- 调用顺序:从最具体的动物(最底层派生类)开始“送走”,一步步往上“送走”到最抽象的动物(基类)。
7. 虚函数(非析构函数)的调用顺序:只听最具体的指令!
- 当你的“动物”遥控器(基类指针)指向一只“狗”(派生类对象),并按下“智能”按钮(调用虚函数
makeSound()
)时,它只会执行“狗”特有的“汪汪汪”指令。 - 它不会先执行“动物”的“叫”,再执行“狗”的“叫”。它直接跳到最具体的那个“叫”指令去执行。
我的学习总结
虚函数只触发最底层的,虚析构函数从底向顶依次触发。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 iBlog!