用c++写一个简单vector

设计思路

使用 ::operator new 分配原始内存,然后通过 placement new 在指定地址构造对象,并显式调用析构函数来销毁对象。这与真实 STL 采用 std::allocator 的原理一致,但代码更直观。

真实 std::vector 的关键特性:

连续内存:元素存储在堆(heap)中,逻辑地址连续。

动态扩容 :当 size == capacity 时分配更大内存(通常 capacity *= 2)。

分离容量与大小size 表示实际元素个数,capacity 表示已分配内存能容纳的元素数。

只构造已使用的元素:未使用的内存保持未初始化状态,避免不必要的构造开销。

迭代器 :直接使用指针(T*)即可。

完整代码:

cpp 复制代码
#include <iostream>
#include <algorithm>   // for std::swap
#include <stdexcept>   // for std::out_of_range

template <typename T>
class SimpleVector {
private:
    T* data;          // 指向动态数组的指针
    size_t sz;        // 当前元素个数
    size_t cap;       // 当前容量(可容纳元素数)

    // 核心工具函数:分配新内存,将旧元素拷贝过去,释放旧内存
    void reallocate(size_t new_cap) {
        // 1. 分配原始内存(不构造对象)
        T* new_data = static_cast<T*>(::operator new(new_cap * sizeof(T)));

        // 2. 用拷贝构造将旧元素逐个放置到新内存
        for (size_t i = 0; i < sz; ++i) {
            new (new_data + i) T(data[i]);   // placement new
        }

        // 3. 析构旧元素(释放对象资源)
        for (size_t i = 0; i < sz; ++i) {
            data[i].~T();
        }

        // 4. 释放旧内存
        ::operator delete(data);

        // 5. 更新指针和容量
        data = new_data;
        cap = new_cap;
    }

public:
    // ---------- 构造函数 / 析构函数 ----------
    SimpleVector() : data(nullptr), sz(0), cap(0) {}

    // 拷贝构造
    SimpleVector(const SimpleVector& other)
        : data(nullptr), sz(other.sz), cap(other.sz) {
        if (sz > 0) {
            data = static_cast<T*>(::operator new(sz * sizeof(T)));
            for (size_t i = 0; i < sz; ++i) {
                new (data + i) T(other.data[i]);
            }
        }
    }

    // 拷贝赋值(copy-and-swap 惯用法)
    SimpleVector& operator=(SimpleVector other) {
        swap(*this, other);   // 这里需要下面的 friend swap
        return *this;
    }

    // 析构
    ~SimpleVector() {
        clear();              // 析构所有元素
        ::operator delete(data); // 释放原始内存
    }

    // ---------- 容量相关 ----------
    size_t size()     const { return sz; }
    size_t capacity() const { return cap; }
    bool empty()      const { return sz == 0; }

    // 预分配容量(若新容量大于当前容量)
    void reserve(size_t new_cap) {
        if (new_cap > cap) {
            reallocate(new_cap);
        }
    }

    // 改变大小(若小于当前大小则截断,否则用 value 填充)
    void resize(size_t new_size, const T& value = T()) {
        if (new_size < sz) {
            // 截断:析构多出的元素
            for (size_t i = new_size; i < sz; ++i) {
                data[i].~T();
            }
            sz = new_size;
        } else if (new_size > sz) {
            reserve(new_size);   // 保证容量足够
            for (size_t i = sz; i < new_size; ++i) {
                new (data + i) T(value);   // 构造新元素
            }
            sz = new_size;
        }
    }

    // ---------- 元素访问 ----------
    T& operator[](size_t index) { return data[index]; }
    const T& operator[](size_t index) const { return data[index]; }

    T& at(size_t index) {
        if (index >= sz) throw std::out_of_range("index out of range");
        return data[index];
    }
    const T& at(size_t index) const {
        if (index >= sz) throw std::out_of_range("index out of range");
        return data[index];
    }

    // ---------- 修改器 ----------
    void push_back(const T& value) {
        if (sz == cap) {
            size_t new_cap = (cap == 0) ? 1 : cap * 2;
            reallocate(new_cap);
        }
        new (data + sz) T(value);   // 在末尾构造
        ++sz;
    }

    void pop_back() {
        if (empty()) throw std::out_of_range("pop_back on empty vector");
        --sz;
        data[sz].~T();   // 析构最后一个元素
    }

    void clear() {
        for (size_t i = 0; i < sz; ++i) {
            data[i].~T();
        }
        sz = 0;
    }

    // ---------- 迭代器(直接使用指针) ----------
    T* begin() { return data; }
    T* end()   { return data + sz; }
    const T* begin() const { return data; }
    const T* end()   const { return data + sz; }

    // ---------- 交换(用于拷贝赋值和移动) ----------
    friend void swap(SimpleVector& a, SimpleVector& b) noexcept {
        using std::swap;
        swap(a.data, b.data);
        swap(a.sz,   b.sz);
        swap(a.cap,  b.cap);
    }
};

关键点解析

1. 内存管理(堆区与逻辑地址)

::operator new 分配的是 原始内存 (未构造对象),返回 void*,我们强转为 T*。这块内存位于进程的 堆区(heap),与你之前学习的进程地址空间完全对应。

使用 placement new new (address) T(args) 在已分配的内存上构造对象,而不是重新申请内存。这避免了对未使用区域调用默认构造函数,提高了效率(对复杂类型尤其重要)。

析构时,我们 显式调用析构函数 data[i].~T() 来释放对象内部资源(如动态分配的成员),然后再用 ::operator delete 释放整个原始内存块。

2. 扩容策略(reallocate

size == capacity 时,触发扩容。新容量通常翻倍(或从 1 开始),这是为了平摊 push_back 的时间复杂度到 O(1)。

reallocate 会:

分配新内存。将旧元素 拷贝构造 到新内存(如果类型支持移动,可进一步优化为移动构造)。析构并释放旧内存。更新指针和容量。

注意 :如果 T 的拷贝构造函数可能抛出异常,上述 reallocate 不是异常安全的(旧数据可能丢失)。真实 STL 会先复制到新内存,成功后再释放旧内存。为简化,此处忽略异常安全,但你可以在此基础上改进。

3. 拷贝赋值(Copy-and-Swap)

operator= 接受参数为 值传递 ,这会在参数构造时调用拷贝构造或移动构造。然后通过 swap 交换当前对象和临时对象,临时对象析构时释放原来的资源。这种写法简洁且自动处理了自赋值和异常安全。

4. 迭代器与边界检查

begin() / end() 返回 T*,这正是连续内存的优势 ------ 指针就是天然迭代器。

operator[] 不检查边界,at() 会抛异常,与标准库行为一致。

下面是测试用例:

cpp 复制代码
int main() {
    SimpleVector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);

    std::cout << "size = " << v.size() << ", capacity = " << v.capacity() << '\n';
    for (auto it = v.begin(); it != v.end(); ++it) {
        std::cout << *it << ' ';
    }
    std::cout << '\n';

    v.pop_back();
    std::cout << "after pop: size = " << v.size() << '\n';

    v.resize(5, 0);
    std::cout << "after resize(5,0): ";
    for (size_t i = 0; i < v.size(); ++i) {
        std::cout << v[i] << ' ';
    }
    std::cout << '\n';

    try {
        std::cout << v.at(10) << '\n';
    } catch (const std::out_of_range& e) {
        std::cerr << "caught: " << e.what() << '\n';
    }

    return 0;
}

输出结果:

cpp 复制代码
size = 3, capacity = 4
1 2 3 
after pop: size = 2
after resize(5,0): 1 2 0 0 0 
caught: index out of range
相关推荐
不听话坏1 小时前
Ignition篇(下 一) 动态执行前的事情
开发语言·前端·javascript
likeyi071 小时前
require 和 import的区别
开发语言·前端
远离UE42 小时前
UE5 compute shader 原子加
开发语言·c++·ue5
C+-C资深大佬2 小时前
C++ 显式类型转换详解:static_cast、dynamic_cast、const_cast、reinterpret_cast
开发语言·c++
码少女3 小时前
数据结构——哈希表的基础
数据结构·散列表
KaMeidebaby3 小时前
卡梅德生物技术快报|抗体亲和力成熟工业化调控新机制:差异性浆细胞增殖工艺优化思路
java·开发语言·人工智能·算法·机器学习·架构·spark
luj_17683 小时前
心形曲线轨迹控制三大关键技术
c语言·开发语言·c++·经验分享·算法
取地址符3 小时前
C++学习笔记(基于learn-cxx)(1)
c++·经验分享·笔记·学习
Mininglamp_27184 小时前
Claude Code 封禁中国开发者之后:本地 AI 编程工具的替代方案实测
开发语言·人工智能·windows·开源软件·ai-native
思麟呀4 小时前
C++17(三)if constexpr+折叠表达式
开发语言·c++