当前位置: 首页 > news >正文

【C++重载操作符与转换】容器与继承

目录

一、容器基础回顾

1.1 容器分类

1.2 容器的共性

1.3 容器的内存管理

二、继承基础回顾

2.1 继承的基本概念

2.2 多态与虚函数

2.3 抽象类与纯虚函数

三、容器中存储基类指针

3.1 为什么需要存储基类指针

3.2 使用智能指针管理动态内存

3.3 容器中存储基类指针的优缺点

四、对象切片问题

4.1 什么是对象切片

4.2 对象切片的危害

4.3 避免对象切片的方法

五、容器与继承的性能考虑

5.1 虚函数调用开销

5.2 内存布局与缓存

5.3 优化建议

六、容器与继承的实际应用案例

6.1 图形库实现

6.2 游戏角色系统 

6.3 事件系统  

七、容器与继承的常见问题及解决方案

7.1 动态内存管理问题

7.2 线程安全问题

7.3 容器选择问题

7.4 对象生命周期管理

八、总结


在 C++ 编程中,容器(如vectorlistmap等)和继承是两个强大的语言特性。当它们结合使用时,可以构建出高度灵活且可扩展的系统。然而,这种组合也带来了许多复杂性和潜在的陷阱。

一、容器基础回顾

1.1 容器分类

C++ 标准库提供了多种容器,主要分为以下几类:

  • 顺序容器vectordequelistforward_list
  • 关联容器setmultisetmapmultimap
  • 无序容器unordered_setunordered_multisetunordered_mapunordered_multimap
  • 容器适配器stackqueuepriority_queue

1.2 容器的共性

所有容器都支持一些基本操作,如:

  • 插入元素push_back()insert()
  • 删除元素erase()pop_back()
  • 访问元素[]at()front()back()
  • 迭代器操作begin()end()

1.3 容器的内存管理

不同容器有不同的内存管理策略:

  • vector:连续内存,动态扩展
  • list:双向链表,非连续内存
  • map:红黑树,动态平衡

二、继承基础回顾

2.1 继承的基本概念

继承是面向对象编程的核心概念之一,它允许一个类(派生类)继承另一个类(基类)的属性和方法。

class Shape {
public:virtual double area() const = 0;
};class Circle : public Shape {
private:double radius;
public:Circle(double r) : radius(r) {}double area() const override { return 3.14 * radius * radius; }
};

2.2 多态与虚函数

通过虚函数和动态绑定,基类指针可以根据实际对象类型调用相应的函数。 

void printArea(const Shape& shape) {std::cout << "Area: " << shape.area() << std::endl;
}Circle circle(5.0);
printArea(circle); // 动态调用 Circle::area()

2.3 抽象类与纯虚函数

包含纯虚函数的类称为抽象类,不能实例化,只能作为基类。 

class Shape {
public:virtual double area() const = 0; // 纯虚函数
};

三、容器中存储基类指针

3.1 为什么需要存储基类指针

考虑一个图形库,需要存储多种不同类型的图形: 

#include <vector>
#include <memory>class Shape {
public:virtual double area() const = 0;virtual ~Shape() {}
};class Circle : public Shape {// ...
};class Rectangle : public Shape {// ...
};// 存储基类指针的容器
std::vector<Shape*> shapes;
shapes.push_back(new Circle(5.0));
shapes.push_back(new Rectangle(3.0, 4.0));

3.2 使用智能指针管理动态内存

为避免内存泄漏,推荐使用智能指针: 

std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(3.0, 4.0));// 遍历容器并调用虚函数
for (const auto& shape : shapes) {std::cout << "Area: " << shape->area() << std::endl;
}

3.3 容器中存储基类指针的优缺点

  • 优点
    • 实现多态,可存储不同派生类对象
    • 统一接口,简化代码
  • 缺点
    • 手动内存管理容易出错(使用智能指针可解决)
    • 切片问题(见下文)
    • 性能开销(虚函数调用)

四、对象切片问题

4.1 什么是对象切片

当将派生类对象直接赋值给基类对象时,派生类的特有部分会被 “切掉”,只保留基类部分。 

Circle circle(5.0);
Shape shape = circle; // 对象切片,只复制基类部分

4.2 对象切片的危害

对象切片会导致多态失效: 

void printArea(Shape shape) { // 按值传递,发生切片std::cout << "Area: " << shape.area() << std::endl;
}Circle circle(5.0);
printArea(circle); // 调用的是 Shape::area(),而非 Circle::area()

4.3 避免对象切片的方法

  • 使用基类指针或引用: 
void printArea(const Shape& shape) { // 按引用传递,避免切片std::cout << "Area: " << shape.area() << std::endl;
}
  • 使用容器存储指针或智能指针: 
std::vector<std::shared_ptr<Shape>> shapes;
shapes.push_back(std::make_shared<Circle>(5.0));

五、容器与继承的性能考虑

5.1 虚函数调用开销

虚函数通过虚函数表实现,会有一定的性能开销:

  • 额外的内存访问(虚表指针)
  • 运行时查找

5.2 内存布局与缓存

存储指针的容器可能导致内存碎片化,影响缓存命中率: 

// 内存碎片化示例
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Rectangle>(3.0, 4.0));
// 每个对象可能位于不同的内存位置

5.3 优化建议

  • 对于性能敏感的应用,考虑使用静态多态(模板)代替动态多态
  • 使用内存池或对象池减少内存分配开销
  • 对容器进行预分配(reserve())减少重新分配次数

六、容器与继承的实际应用案例

6.1 图形库实现

#include <iostream>
#include <vector>
#include <memory>
#include <string>// 手动实现 make_unique
#if __cplusplus < 201402L
namespace std {template<typename T, typename... Args>std::unique_ptr<T> make_unique(Args&&... args) {return std::unique_ptr<T>(new T(std::forward<Args>(args)...));}
}
#endifclass Shape {
public:virtual double area() const = 0;virtual std::string name() const = 0;virtual ~Shape() {}
};class Circle : public Shape {
private:double radius;
public:Circle(double r) : radius(r) {}double area() const override { return 3.14 * radius * radius; }std::string name() const override { return "Circle"; }
};class Rectangle : public Shape {
private:double width, height;
public:Rectangle(double w, double h) : width(w), height(h) {}double area() const override { return width * height; }std::string name() const override { return "Rectangle"; }
};int main() {std::vector<std::unique_ptr<Shape>> shapes;shapes.push_back(std::make_unique<Circle>(5.0));shapes.push_back(std::make_unique<Rectangle>(3.0, 4.0));for (const auto& shape : shapes) {std::cout << shape->name() << " area: " << shape->area() << std::endl;}return 0;
}

6.2 游戏角色系统 

#include <iostream>
#include <vector>
#include <memory>class Character {
public:virtual void attack() = 0;virtual void defend() = 0;virtual ~Character() {}
};class Warrior : public Character {
public:void attack() override { std::cout << "Warrior attacks with sword!" << std::endl; }void defend() override { std::cout << "Warrior defends with shield!" << std::endl; }
};class Mage : public Character {
public:void attack() override { std::cout << "Mage casts fireball!" << std::endl; }void defend() override { std::cout << "Mage uses magic barrier!" << std::endl; }
};int main() {std::vector<std::shared_ptr<Character>> party;party.push_back(std::make_shared<Warrior>());party.push_back(std::make_shared<Mage>());for (const auto& character : party) {character->attack();character->defend();}return 0;
}

6.3 事件系统  

#include <iostream>
#include <vector>
#include <memory>// 手动实现 make_unique
#if __cplusplus < 201402L
namespace std {template<typename T, typename... Args>unique_ptr<T> make_unique(Args&&... args) {return unique_ptr<T>(new T(std::forward<Args>(args)...));}
}
#endifclass Event {
public:virtual void process() = 0;virtual ~Event() {}
};class MouseEvent : public Event {
private:int x, y;
public:MouseEvent(int x, int y) : x(x), y(y) {}void process() override {std::cout << "Processing mouse event at (" << x << ", " << y << ")" << std::endl;}
};class KeyEvent : public Event {
private:char key;
public:KeyEvent(char k) : key(k) {}void process() override {std::cout << "Processing key event: " << key << std::endl;}
};class EventManager {
private:std::vector<std::unique_ptr<Event>> events;
public:void addEvent(std::unique_ptr<Event> event) {events.push_back(std::move(event));}void processAllEvents() {for (auto& event : events) {event->process();}events.clear();}
};int main() {EventManager manager;manager.addEvent(std::make_unique<MouseEvent>(100, 200));manager.addEvent(std::make_unique<KeyEvent>('A'));manager.processAllEvents();return 0;
}

七、容器与继承的常见问题及解决方案

7.1 动态内存管理问题

  • 问题:手动管理指针容易导致内存泄漏
  • 解决方案:使用智能指针(std::unique_ptrstd::shared_ptr

7.2 线程安全问题

  • 问题:多线程环境下访问容器和对象可能导致竞态条件
  • 解决方案:使用同步机制(如std::mutex)或原子操作

7.3 容器选择问题

  • 问题:选择不适合的容器类型影响性能
  • 解决方案
    • 频繁随机访问:使用vector
    • 频繁插入删除:使用list
    • 键值对存储:使用mapunordered_map

7.4 对象生命周期管理

  • 问题:容器中的对象生命周期管理不当导致悬空指针
  • 解决方案
    • 确保对象生命周期长于容器
    • 使用智能指针自动管理生命周期

八、总结

容器与继承是 C++ 中两个强大的语言特性,它们的结合可以构建出高度灵活且可扩展的系统。但在使用过程中,需要注意以下几点:

  1. 优先使用智能指针:避免手动内存管理,减少内存泄漏风险
  2. 警惕对象切片:始终使用指针或引用存储对象,避免按值传递
  3. 考虑性能开销:虚函数调用和内存碎片化可能影响性能
  4. 选择合适的容器:根据实际需求选择最适合的容器类型
  5. 注意线程安全:在多线程环境中使用适当的同步机制

通过合理运用容器与继承的组合,可以设计出更加优雅、高效的 C++ 程序。


http://www.xdnf.cn/news/465409.html

相关文章:

  • Excel函数使用介绍-分组求和SUMIF
  • 达利欧:“交易的艺术”与“背后的力量”
  • OpenCv高阶(4.0)——案例:海报的透视变换
  • 【杂谈】-AI 重塑体育营销:从内容管理到创意释放的全面变革
  • C#发送文件到蓝牙设备
  • 使用 `perf` 和火焰图(Flame Graph)进行性能分析
  • 25.5.15
  • MySQL读写分离
  • 深入解析C++模板:从基础到高级应用
  • LeetCode 热题 100 437. 路径总和 III
  • 运维职业发展思维导图
  • 建筑兔零基础人工智能自学记录92|类脑智能与脑机接口-7
  • vue3搭建脚手架前的前置知识
  • 【Unity】给出两个旋转角度,判断是应该左转还是右转
  • QT设置MySQL驱动
  • 已解决(亲测有效!):安装部署Docker Deskpot之后启动出现Docker Engine Stopped!
  • 11 web 自动化之 DDT 数据驱动详解
  • 文件目录与检索综合练习题
  • 面试 Linux 运维相关问题
  • 基于SpringBoot的家政服务系统设计与实现(源码+文档+部署讲解)
  • 20、鸿蒙学习——OAID、AAID、ODID
  • openEuler24.03 LTS下安装MySQL8.0.42
  • 气动排渣煤粉炉专用V型球阀——法兰连接耐磨阀门生产厂家解析-耀圣
  • 详解 Zephyr RTOS:架构、功能与开发指南
  • Function Calling
  • 106. 从中序与后序遍历序列构造二叉树
  • 【206】VS2022 C++ 实现无符号32位整数和IP地址字符串互相转换
  • element-ui的el-cascader增加全选按钮实现(附源码)
  • DB-GPT扩展自定义app配置说明
  • 【网络编程】九、详解 HTTPS 加密原理