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

C++基础:(十一)vector深度剖析:底层原理与模拟实现

目录

前言

一、 vector 的底层结构

二、 vector 的模拟实现(核心接口)

2.1 模拟实现的头文件(bit_vector.h)

2.2 模拟实现的测试代码(main.cpp)

三、 模拟实现中的关键问题:为什么不能用 memcpy 拷贝元素?

3.1 问题分析:memcpy 浅拷贝的危害

3.2 解决办法:使用赋值运算符或拷贝构造进行深拷贝

四、 动态二维数组:vector 的嵌套使用

4.1 动态二维数组的原理

4.2 动态二维数组实现杨辉三角

总结


前言

        本期博客我们将继续上期对vector容器的学期,为大家介绍vector的底层原理与模拟实现,话不多说,让我们现在开始吧!


一、 vector 的底层结构

        vector 的底层由三个核心指针维护:

  • _start:指向底层数组的第一个元素;
  • _finish:指向底层数组中最后一个有效元素的下一个位置(即_start + size);
  • _end_of_storage:指向底层数组的最后一个位置(即_start + capacity)。

        三者的关系如下所示:

[ _start          _finish          _end_of_storage )
[ 元素0, 元素1, 元素2, 空, 空, 空 ]
size = _finish - _start;
capacity = _end_of_storage - _start;

        基于这三个指针,vector 的核心接口可以通过简单的指针运算实现:

  • size()return _finish - _start;
  • capacity()return _end_of_storage - _start;
  • empty()return _start == _finish;
  • operator[](n)return *(_start + n);

二、 vector 的模拟实现(核心接口)

        下面我来基于 C++ 标准,模拟实现一个简化版的 vector(命名为bit::vector),涵盖构造、析构、迭代器、空间管理、增删查改等核心接口,帮助大家深入理解 vector 的底层逻辑。

2.1 模拟实现的头文件(bit_vector.h)

#ifndef BIT_VECTOR_H
#define BIT_VECTOR_H#include <algorithm> // for copy
#include <cassert>   // for assert
#include <iostream>namespace bit {template <class T>
class vector {
public:// 迭代器(原生指针)typedef T* iterator;typedef const T* const_iterator;// 1. 构造函数vector(): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {}// 构造n个valvector(size_t n, const T& val = T()): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {reserve(n);while (n--) {push_back(val);}}// 适配int作为n的情况(如vector<int> v(5, 3); 5是int类型)vector(int n, const T& val = T()): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {reserve(n);while (n--) {push_back(val);}}// 迭代器构造template <class InputIterator>vector(InputIterator first, InputIterator last): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {while (first != last) {push_back(*first);++first;}}// 拷贝构造(深拷贝)vector(const vector<T>& v): _start(nullptr), _finish(nullptr), _end_of_storage(nullptr) {reserve(v.capacity());// 遍历v的元素,逐个拷贝到当前vectorfor (const auto& e : v) {push_back(e);}}// 赋值运算符重载(现代写法:利用拷贝构造实现深拷贝)vector<T>& operator=(vector<T> v) {swap(v);return *this;}// 析构函数~vector() {if (_start) {delete[] _start; // 释放底层数组_start = _finish = _end_of_storage = nullptr;}}// 2. 迭代器接口iterator begin() { return _start; }iterator end() { return _finish; }const_iterator begin() const { return _start; }const_iterator end() const { return _finish; }// 3. 空间管理接口size_t size() const { return _finish - _start; }size_t capacity() const { return _end_of_storage - _start; }bool empty() const { return _start == _finish; }void reserve(size_t n) {if (n > capacity()) {size_t old_size = size();// 1. 分配新空间T* new_start = new T[n];// 2. 拷贝旧空间的元素到新空间(注意:若T是自定义类型,需调用拷贝构造)if (_start) {// 不能用memcpy!因为memcpy是浅拷贝,自定义类型(如string)会出问题for (size_t i = 0; i < old_size; ++i) {new_start[i] = _start[i]; // 调用T的赋值运算符}// 3. 释放旧空间delete[] _start;}// 4. 更新指针_start = new_start;_finish = _start + old_size;_end_of_storage = _start + n;}}void resize(size_t n, const T& val = T()) {if (n < size()) {// 缩小size:直接移动_finish_finish = _start + n;} else {// 扩大size:先reserve足够空间,再补元素if (n > capacity()) {reserve(n);}while (_finish < _start + n) {*_finish = val; // 调用T的赋值运算符++_finish;}}}// 4. 增删查改接口void push_back(const T& val) {// 检查容量:若满则扩容(默认扩2倍,若原容量为0则扩1)if (_finish == _end_of_storage) {size_t new_cap = capacity() == 0 ? 1 : capacity() * 2;reserve(new_cap);}// 插入元素,更新_finish*_finish = val;++_finish;}void pop_back() {assert(!empty()); // 断言:vector不为空--_finish;}iterator insert(iterator pos, const T& val) {assert(pos >= _start && pos <= _finish); // 断言:pos在有效范围内// 检查容量if (_finish == _end_of_storage) {// 计算pos在旧空间中的偏移量(扩容后pos会失效,需重新计算)size_t offset = pos - _start;size_t new_cap = capacity() == 0 ? 1 : capacity() * 2;reserve(new_cap);// 扩容后,更新pos为新空间的对应位置pos = _start + offset;}// 移动元素:从_finish-1到pos,依次后移1位iterator end = _finish;while (end > pos) {*end = *(end - 1);--end;}// 插入元素,更新_finish*pos = val;++_finish;return pos; // 返回指向插入元素的迭代器}iterator erase(iterator pos) {assert(pos >= _start && pos < _finish); // 断言:pos在有效范围内// 移动元素:从pos+1到_finish,依次前移1位iterator it = pos + 1;while (it < _finish) {*(it - 1) = *it;++it;}// 更新_finish--_finish;return pos; // 返回指向删除元素下一个元素的迭代器}void swap(vector<T>& v) {// 交换三个核心指针(O(1)高效交换)std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}T& operator[](size_t n) {assert(n < size()); // 断言:n在有效范围内return _start[n];}const T& operator[](size_t n) const {assert(n < size());return _start[n];}private:iterator _start;          // 指向数组首元素iterator _finish;         // 指向数组尾元素的下一个位置iterator _end_of_storage; // 指向数组末尾
};// 重载operator<<,用于打印vector
template <class T>
std::ostream& operator<<(std::ostream& os, const bit::vector<T>& v) {for (size_t i = 0; i < v.size(); ++i) {os << v[i] << " ";}return os;
}} // namespace bit#endif // BIT_VECTOR_H

2.2 模拟实现的测试代码(main.cpp)

#include <iostream>
#include "bit_vector.h"
using namespace std;
using namespace bit;int main() {// 测试1:构造函数与push_backvector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);cout << "v1: " << v1 << "(size=" << v1.size() << ", capacity=" << v1.capacity() << ")" << endl; // 1 2 3 (3,4)// 测试2:迭代器构造int arr[] = {4,5,6};vector<int> v2(arr, arr+3);cout << "v2: " << v2 << endl; // 4 5 6// 测试3:拷贝构造与赋值vector<int> v3(v2);vector<int> v4;v4 = v3;cout << "v3: " << v3 << ", v4: " << v4 << endl; // 4 5 6, 4 5 6// 测试4:reserve与resizev4.reserve(10);cout << "v4 reserve(10): size=" << v4.size() << ", capacity=" << v4.capacity() << endl; // 3,10v4.resize(5, 7);cout << "v4 resize(5,7): " << v4 << "(size=" << v4.size() << ")" << endl; //4 5 6 7 7 (5)// 测试5:insert与eraseauto it = v4.begin() + 2;v4.insert(it, 60);cout << "v4 insert(60): " << v4 << endl; //4 5 60 6 7 7it = v4.begin() + 2;v4.erase(it);cout << "v4 erase(60): " << v4 << endl; //4 5 6 7 7// 测试6:自定义类型(如string)vector<string> v5;v5.push_back("hello");v5.push_back("world");v5.push_back("vector");cout << "v5: ";for (auto& s : v5) {cout << s << " ";}cout << endl; // hello world vectorreturn 0;
}

三、 模拟实现中的关键问题:为什么不能用 memcpy 拷贝元素?

        在reserve接口的实现中,我们提到 “不能用memcpy拷贝元素”,这是因为memcpy二进制浅拷贝,仅适用于 “无资源管理的类型”(如 int、double 等内置类型);对于涉及资源管理的自定义类型(如 string、vector 等),memcpy会导致 “二次释放” 或 “内存泄漏”。

3.1 问题分析:memcpy 浅拷贝的危害

        以bit::vector<bit::string>为例,假设使用memcpy拷贝元素:

  1. 插入元素v.push_back("1111")v.push_back("2222")后,vector 的_start指向一块容量为 2 的空间,每个string对象的_str指向堆内存(存储 "1111"、"2222")。
  2. 扩容触发:当插入第三个元素"3333"时,容量不足,触发reserve(4)
  3. memcpy 拷贝:用memcpy将旧空间的两个string对象拷贝到新空间,此时新空间的string对象的_str与旧空间的_str指向同一块堆内存(浅拷贝)。
  4. 释放旧空间:删除旧空间时,会调用旧空间中string对象的析构函数,释放_str指向的堆内存。
  5. 二次释放:当新空间的string对象析构时,会再次尝试释放_str指向的堆内存(已被释放),导致 “二次释放”,程序崩溃。

3.2 解决办法:使用赋值运算符或拷贝构造进行深拷贝

        对于自定义类型,应通过 “逐个元素赋值” 或 “拷贝构造” 的方式进行深拷贝,确保每个元素的资源被正确复制,而不是简单的二进制拷贝。在模拟实现的reserve接口中,我们使用了:

for (size_t i = 0; i < old_size; ++i) {new_start[i] = _start[i]; // 调用T的赋值运算符(深拷贝)
}

        对于string类型,new_start[i] = _start[i]会调用string的赋值运算符,分配新的堆内存并拷贝字符串内容,避免浅拷贝问题。

四、 动态二维数组:vector 的嵌套使用

        vector 支持嵌套使用(如vector<vector<int>>),可以用来表示动态二维数组(每行的长度可以不同),这比 C 语言的静态二维数组(int arr[3][4])更灵活。

4.1 动态二维数组的原理

    vector<vector<int>> vv的底层结构是:

  • 外层 vector(vv)的每个元素是一个内层 vector(vector<int>);
  • 外层 vector 的_start指向一个数组,数组中的每个元素是内层 vector 的三个指针(_start_finish_end_of_storage);
  • 每个内层 vector 维护自己的元素和容量,因此每行的长度可以不同。

4.2 动态二维数组实现杨辉三角

        下面我们以构建杨辉三角为例,来进一步了解动态二维数组的初始化、赋值和遍历:

#include <iostream>
#include <vector>
using namespace std;// 生成n行杨辉三角
vector<vector<int>> generateYangHui(int n) {vector<vector<int>> vv(n); // 外层vector有n个元素(每行一个vector)// 初始化每行的size,并将首尾元素设为1for (int i = 0; i < n; ++i) {vv[i].resize(i + 1, 1); // 第i行有i+1个元素,默认值为1}// 填充中间元素:第i行第j列 = 第i-1行第j-1列 + 第i-1行第j列for (int i = 2; i < n; ++i) {for (int j = 1; j < i; ++j) {vv[i][j] = vv[i-1][j-1] + vv[i-1][j];}}return vv;
}int main() {int n = 5;vector<vector<int>> yangHui = generateYangHui(n);// 遍历杨辉三角cout << "杨辉三角(" << n << "行):" << endl;for (int i = 0; i < n; ++i) {for (int j = 0; j < yangHui[i].size(); ++j) {cout << yangHui[i][j] << " ";}cout << endl;}// 输出:// 1 // 1 1 // 1 2 1 // 1 3 3 1 // 1 4 6 4 1 return 0;
}

        上面的代码构造了一个vv动态二维数组,vv中总共有n个元素,每个元素都是vector类型的,每行没有包含任何元素,如果n为5时如下所示:

        vv中的元素填充完成之后,如下图所示:


总结

        本期博客我们学习了vector容器的底层原理和模拟实现。下期博客我们将继续深入学习STL中的list容器,请大家多多关注哦!

http://www.dtcms.com/a/469900.html

相关文章:

  • 【自用】request.ts 封装,带 token 过期后自动刷新 token 的功能
  • 成都定制网站建设服合肥公司注册地址
  • 分布式事务在前后端分离场景下的最终一致性实现
  • 农产品电子商务网站建设要求锦州网站建设公司
  • SSH命令建立隧道
  • [GazeTracking] 依赖项管理 | Docker化执行环境
  • uniapp web-view相互通信方法
  • (2)Kafka架构原理与存储机制
  • uniapp学习【项目创建+项目结构解析】
  • 虚拟机所需的硬件功能在目标主机上不受支持或已禁用:*长模式:对于支持64位客户机操作系统而言是必需的。
  • Uniapp微信小程序开发:http请求封装。
  • 个人可以做商城网站吗合肥制作网站价格
  • 网站制作的前期主要是做好什么工作网站的构思
  • java每小时调动一次,生成任务,基于corn表达式动态调动任务执行
  • 网站模板兼容手机端市场推广是做什么的
  • 企业微信防封防投诉拦截系统:从痛点解决到技术实现
  • vue的组件通信
  • 掌握PINN:从理论到实战的神经网络进阶!!
  • wordpress thremeseo推广排名软件
  • 安平县哪里做网站建立公司网站视频
  • PostgreSql ALL 与 ANY 区别
  • 解决 husky > pre-commit hook failed (add --no-verify to bypass)
  • 模拟器抓包
  • (数据结构)线性表(下):链表分类及双向链表的实现
  • 阿里云短信服务配置说明
  • Java+SpringBoot+Dubbo+Nacos快速入门
  • 【开题答辩全过程】以 办公管理系统为例,包含答辩的问题和答案
  • 天创网站做网站 数据标准
  • 做除尘骨架的网站网页一般用什么语言编写
  • SciPy 常量模块