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

c++的容器和适配器究竟有什么差别?

在C++编程中,容器和适配器是两个重要概念,它们虽有联系,但在功能、用法和实现原理上存在显著差别。理解这些差别,能帮助整在从事开发的童鞋更高效地使用它们,比如我们可以清晰的理解容器和适配器的差别及关系,如:stack,queue与deque与vector他们的关系。

文章目录

    • 1. 容器和适配器概念与功能
      • 1.1 容器的概念
      • 1.2原生数组与容器相似的特性
      • 1.3原生数组与标准容器的区别
      • 1.4关于容器的总结
      • 1.5 适配器的概念
    • 2. 用法特点
      • 2.1 容器
      • 2.2 适配器
    • 3. 实现原理
      • 3.1 容器
      • 3.2 适配器

1. 容器和适配器概念与功能

1.1 容器的概念

首先我们可以明确的是容器就是一种数据结构,所以我们在学习数据结构的时候都会首先学习容器。详细的说容器是什么呢?容器是用于存储和管理数据元素的数据结构,它能直接容纳各种类型对象,并提供插入、删除、查找等操作方法或者说是接口。例如std::vector,它像动态数组,能在末尾高效插入和删除元素,还支持快速随机访问。比如:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    // 随机访问元素
    std::cout << "访问vec[2]:" << vec[2] << std::endl; 
    // 在末尾插入元素
    vec.push_back(6); 
    return 0;
}

再如std::deque,它是双端队列,支持在两端高效插入和删除元素,也支持随机访问。

#include <iostream>
#include <deque>

int main() {
    std::deque<int> deq = {1, 2, 3};
    // 从前端插入元素
    deq.push_front(0); 
    // 随机访问元素
    std::cout << "访问deq[2]:" << deq[2] << std::endl; 
    return 0;
}

我们看到deque和vector都支持下标访问,也就是对元素的高效访问,那么我们不禁会想起我们最初学习的固定数组的使用,也就是我们说的普通原生数组,那么它也属于容器吗?

在C++ 中,普通数组(原生数组)从严格意义上来说并不完全算是标准库中的容器,但它具有一些与容器相似的特性。

1.2原生数组与容器相似的特性

  • 存储数据:普通数组可以存储多个相同类型的数据元素,这与容器的基本功能一致,例如int arr[5] = {1, 2, 3, 4, 5};就创建了一个能存储5个整数的数组。
  • 支持迭代访问:可以通过指针算术运算来实现类似迭代器的功能,对数组中的元素进行遍历访问。例如,可以使用for循环和指针偏移来遍历数组元素:
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    for (int* p = arr; p < arr + 5; ++p) {
        std::cout << *p << " ";
    }
    return 0;
}

1.3原生数组与标准容器的区别

  • 没有统一的接口:C++标准容器都有统一的接口,如begin()end()size()push_back()等成员函数,但普通数组没有这些统一的成员函数接口,需要使用不同的语法和函数来操作,比如获取数组元素个数需要手动计算。
  • 缺乏自动管理机制:标准容器能自动管理内存,例如std::vector可以在添加或删除元素时自动调整大小,而普通数组的大小在定义时就固定了,无法在运行时自动改变大小。如果对普通数组进行越界访问或操作,可能会导致未定义行为,而标准容器通常会有一定的边界检查和错误处理机制。
  • 不是类模板:标准容器都是类模板,具有很强的通用性和可扩展性,可以存储各种类型的数据,通过模板参数来指定存储的数据类型。而普通数组是一种内置的数据类型,虽然也可以存储不同类型的数据,但它的定义和使用方式相对固定,不像标准容器那样灵活。

1.4关于容器的总结

到这里,我们应该已经清楚容器的特征,再次强调,我所总结的容器应该默认为标准容器也就是STL库中包涵的容器。它具备以下特征:
1、支持不同类型的数据存储,且支持迭代访问,包括对元素的高效访问。
2、必须有同意的接口或者称为方法实现对元素的操作。
3、可以动态自动的对容器的内存进行管理(而原生数组不可以)。
4、标准容器必须是模板类,可以灵活的进行扩展。

1.5 适配器的概念

适配器是对已有容器或其他类型的包装,不直接存储数据,通过包装现有容器提供不同功能或接口。像std::stackstd::queuestd::stack实现了后进先出(LIFO)的栈结构,std::queue实现了先进先出(FIFO)的队列结构。

2. 用法特点

2.1 容器

  • 元素访问:不同容器访问方式不同。std::vectorstd::deque支持[]运算符随机访问,std::list需用迭代器顺序访问。
  • 迭代器:容器都有迭代器遍历元素。std::vector迭代器支持随机访问,std::list迭代器只能顺序访问。例如遍历std::vector
#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec = {1, 2, 3};
    for (auto it = vec.begin(); it!= vec.end(); ++it) {
        std::cout << *it << " ";
    }
    return 0;
}

2.2 适配器

  • 接口限制:适配器只暴露特定操作接口,屏蔽底层容器其他操作。以std::stack为例,只提供push(入栈)、pop(出栈)、top(访问栈顶元素)等操作,不能直接访问栈中间元素。
#include <iostream>
#include <stack>

int main() {
    std::stack<int> stk;
    stk.push(1);
    stk.push(2);
    // 访问栈顶元素
    std::cout << "栈顶元素:" << stk.top() << std::endl; 
    stk.pop();
    return 0;
}
  • 依赖底层容器:使用时需指定或默认使用底层容器,功能实现依赖底层容器操作。std::queue默认基于std::deque实现,std::queuepush操作实际调用std::dequepush_back操作。
#include <iostream>
#include <queue>

int main() {
    std::queue<int> que;
    que.push(1);
    que.push(2);
    // 访问队首元素
    std::cout << "队首元素:" << que.front() << std::endl; 
    que.pop();
    return 0;
}

3. 实现原理

3.1 容器

  • 内存管理:不同容器内存管理方式不同。std::vector通常分配连续内存空间,空间不足时重新分配并复制元素;std::list通过节点存储元素,节点独立分配,用指针链接。
  • 数据结构特性:容器依自身数据结构特点实现操作。std::setstd::map通常基于红黑树实现,查找、插入和删除快,元素有序。

3.2 适配器

  • 包装与转发:适配器内部含底层容器对象,将外部操作转发到底层容器,并按需处理或限制。std::queuepush操作转发为底层dequepush_back操作,pop操作转发为pop_front操作。
  • 下面我们尝试自己写一个MyQueue来模仿queue的实现,以便让大家对适配器queue有一个清晰的了解。代码如下:
template <typename T, typename Container = std::deque<T>>
class MyQueue {
public:
    void push(const T& value) {
        c.push_back(value);
    }
    void pop() {
        c.pop_front();
    }
    T& front() {
        return c.front();
    }
    const T& front() const {
        return c.front();
    }
    bool empty() const {
        return c.empty();
    }
    typename Container::size_type size() const {
        return c.size();
    }
private:
    Container c;
};
  • 不改变底层容器结构:适配器不改变底层容器数据结构,只提供不同接口和行为。std::stack基于底层容器实现栈功能,但底层容器结构和操作仍在,只是限制了访问方式。这里我们同样自己来实现一个Mystack来看看stack的实现原理:
template <typename T, typename Container = std::deque<T>>
class MyStack {
public:
    void push(const T& value) {
        c.push_back(value);
    }
    void pop() {
        c.pop_back();
    }
    T& top() {
        return c.back();
    }
    const T& top() const {
        return c.back();
    }
    bool empty() const {
        return c.empty();
    }
    typename Container::size_type size() const {
        return c.size();
    }
private:
    Container c;
};

好了,本文就到此结束。希望通过概念和代码实现让您能够对容器和适配器有了清晰的认知。欢迎有兴趣讨论的童鞋留言。


http://www.kler.cn/a/521487.html

相关文章:

  • 统计安卓手机一段时间内进程中每个线程的平均CPU使用率
  • mysql 学习6 DQL语句,对数据库中的表进行 查询 操作
  • http跳转https
  • Prism--对话服务
  • 如何使用DDD 的思想规划对应的模块
  • arcgis短整型变为长整型的处理方式
  • window保存好看的桌面壁纸
  • 一文讲解JVM中的G1垃圾收集器
  • 什么是vue.js组件开发,我们需要做哪些准备工作?
  • 洛谷题目: P2188 小Z的 k 紧凑数 (本题较难,省选-难度)题解
  • 深度学习:基于MindNLP的RAG应用开发
  • Python Typing: 实战应用指南
  • 安装Office自定义项,安装期间出错
  • 力扣算法题——11.盛最多水的容器
  • Synology 群辉NAS安装(5)介绍一下NAS的体系和安装container manager
  • 第三节 MATLAB基本语法
  • SuperMap GIS基础产品FAQ集锦(20250127)
  • 美国公司有意收购TikTok(抖音)
  • Linux——冯 • 诺依曼体系结构
  • 834 数据结构(自用)