手写vector容器:C++模板实战指南(从0到1掌握泛型编程)

目录

🌟个人主页 :L_autinue_Star

🌟当前专栏 :c++进阶


为什么学C++一定要亲手实现vector?🧐

作为刚接触C++泛型编程的小白,我曾经以为STL容器是"只可远观"的黑科技。直到尝试用模板实现vector后才发现:原来那些看似复杂的容器,本质上是由简单的模板语法搭建而成!今天这篇文章,我会从大学生视角出发,手把手带你用模板实现一个功能完整的vector容器,全程穿插代码解析和学习心得,让你彻底搞懂模板在容器实现中的核心应用!

一、vector的"三驾马车":模板如何定义动态数组结构?🛠️

1.1 用模板参数T打造"万能容器"

vector之所以能存储任意类型数据,全靠模板参数T的"神助攻":

cpp 复制代码
template<class T>  // T就是类型占位符
class vector {
public:
    typedef T* iterator;  // 迭代器本质是T类型指针
private:
    iterator _start;      // 指向数据起始位置
    iterator _finish;     // 指向有效数据末尾
    iterator _endOfStorage; // 指向容量末尾
};

💡 通俗理解

T就像"类型变量",当我们声明vector<int>时,编译器会自动生成一个专存int的容器;声明vector<string>时,又会生成专存string的容器。这种"一次编写,多类型适配"的特性,正是模板赋予vector的超能力!

1.2 三个指针管理内存:动态数组的"骨架"

这三个指针的关系堪称动态数组的灵魂:

cpp 复制代码
size_t size() const { return _finish - _start; }        // 元素个数 = 已用空间长度
size_t capacity() const { return _endOfStorage - _start; }  // 容量大小 = 总空间长度

把vector比作可伸缩的行李箱

  • _start → 行李箱的左端(内存起始地址)
  • _finish → 当前衣物堆到的位置(已用空间)
  • _endOfStorage → 行李箱的右端(总容量)

二、构造函数家族:模板如何实现多场景初始化?🚀

2.1 填充构造:处理不同参数类型的"小技巧"

实现vector<int> v(10, 5)这种初始化时,需要特别处理参数类型:

cpp 复制代码
// 版本1:处理无符号整数n(如size_t)
vector(size_t n, const T& value = T()) {
    reserve(n);
    while (n--) push_back(value);
}

// 版本2:处理有符号整数n(如int)
vector(int n, const T& value = T()) {
    reserve(n);
    while (n--) push_back(value);
}

😱 踩坑提示

如果只写size_t版本,当传入vector<int> v(10, 5)时(10是int类型),编译器会错误匹配迭代器构造函数!添加int重载版本才能完美适配各种整数参数。

2.2 迭代器区间构造:模板的"泛型搬运工"

这个构造函数能接收任何容器的迭代器,堪称模板泛型能力的典范:

cpp 复制代码
template<class InputIterator>  // 另一个模板参数!
vector(InputIterator first, InputIterator last) {
    while (first != last) {
        push_back(*first);  // 不管什么容器,只要有迭代器就能复制
        ++first;
    }
}

2.3 拷贝构造:深拷贝的"防坑指南"

直接复制指针会导致"浅拷贝灾难",正确的深拷贝实现:

cpp 复制代码
vector(const vector<T>& v) {
    reserve(v.capacity());  // 先开辟相同大小的空间
    const_iterator cit = v.cbegin();
    while (cit != v.cend()) {
        push_back(*cit);  // 逐个拷贝元素(调用T的拷贝构造)
        ++cit;
    }
}

三、容量管理:模板容器的"动态伸缩术"📦

3.1 reserve扩容:避免频繁搬家的"智慧"

vector自动扩容的实现逻辑:

cpp 复制代码
void reserve(size_t n) {
    if (n > capacity()) {
        size_t oldSize = size();
        T* temp = new T[n];  // 申请新空间
        
        if (_start) {
            // 深拷贝元素(关键!避免浅拷贝)
            for (size_t i = 0; i < oldSize; ++i)
                temp[i] = _start[i];  // 调用T的赋值运算符
            delete[] _start;  // 释放旧空间
        }
        
        _start = temp;
        _finish = temp + oldSize;
        _endOfStorage = _start + n;
    }
}

🚫 禁忌操作

绝对不能用memcpy拷贝元素!如果T是string等带资源管理的类型,memcpy会导致双重释放。循环赋值才是王道!

3.2 扩容策略:不同编译器的"性格差异"

实测发现不同编译器的扩容倍数不同:

  • VS:1.5倍扩容(1→2→3→4→6→9...)
  • GCC:2倍扩容(1→2→4→8→16...)

我们实现GCC风格的2倍扩容:

cpp 复制代码
void push_back(const T& x) {
    if (_finish == _endOfStorage) {
        size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
        reserve(newCapacity);
    }
    *_finish = x;
    ++_finish;
}

四、增删查改:模板让接口"一劳永逸"🔧

4.1 迭代器接口:容器与算法的"桥梁"

简单两行代码,让vector兼容所有STL算法:

cpp 复制代码
iterator begin() { return _start; }
iterator end() { return _finish; }

用法示例:

cpp 复制代码
vector<int> v{3,1,4,2};
sort(v.begin(), v.end());  // 直接使用STL排序算法

4.2 insert插入:处理迭代器失效的"细节控"

插入元素时要特别注意扩容可能导致的迭代器失效:

cpp 复制代码
iterator insert(iterator pos, const T& x) {
    assert(pos >= _start && pos <= _finish);
    
    if (_finish == _endOfStorage) {
        size_t offset = pos - _start;  // 记录偏移量
        reserve(capacity() == 0 ? 4 : capacity() * 2);
        pos = _start + offset;  // 重置迭代器
    }
    
    // 从后往前移动元素
    iterator end = _finish - 1;
    while (end >= pos) {
        *(end + 1) = *end;
        --end;
    }
    *pos = x;
    ++_finish;
    return pos;
}

4.3 erase删除:迭代器失效的"重灾区"

删除元素后必须返回新迭代器:

cpp 复制代码
iterator erase(iterator pos) {
    assert(pos >= _start && pos < _finish);
    
    iterator it = pos + 1;
    while (it != _finish) {
        *(it - 1) = *it;  // 元素前移
        ++it;
    }
    --_finish;
    return pos;  // 返回新位置迭代器
}

💡 正确用法

cpp 复制代码
auto it = v.begin();
while (it != v.end()) {
    if (*it % 2 == 0)
        it = v.erase(it);  // 用返回值更新迭代器
    else
        ++it;
}

五、完整实现代码:模板vector的"最终形态"✨

整合所有模块,一个功能完整的模板vector实现:

cpp 复制代码
#include<iostream>
#include <assert.h>
using namespace std;

template<class T>
class vector {
public:
    // 迭代器定义
    typedef T* iterator;
    typedef const T* const_iterator;

    // 迭代器接口
    iterator begin() { return _start; }
    iterator end() { return _finish; }
    const_iterator cbegin() const { return _start; }
    const_iterator cend() const { return _finish; }

    // 构造函数家族
    vector() : _start(nullptr), _finish(nullptr), _endOfStorage(nullptr) {}

    vector(size_t n, const T& value = T()) {
        reserve(n);
        while (n--) push_back(value);
    }

    vector(int n, const T& value = T()) {
        reserve(n);
        while (n--) push_back(value);
    }

    template<class InputIterator>
    vector(InputIterator first, InputIterator last) {
        while (first != last) {
            push_back(*first);
            ++first;
        }
    }

    vector(const vector<T>& v) {
        reserve(v.capacity());
        const_iterator cit = v.cbegin();
        while (cit != v.cend()) {
            push_back(*cit);
            ++cit;
        }
    }

    // 赋值运算符
    vector<T>& operator=(vector<T> v) {
        swap(v);
        return *this;
    }

    // 析构函数
    ~vector() {
        if (_start) {
            delete[] _start;
            _start = _finish = _endOfStorage = nullptr;
        }
    }

    // 容量接口
    size_t size() const { return _finish - _start; }
    size_t capacity() const { return _endOfStorage - _start; }
    bool empty() const { return _start == _finish; }

    void reserve(size_t n) {
        if (n > capacity()) {
            size_t oldSize = size();
            T* temp = new T[n];
            if (_start) {
                for (size_t i = 0; i < oldSize; ++i)
                    temp[i] = _start[i];
                delete[] _start;
            }
            _start = temp;
            _finish = temp + oldSize;
            _endOfStorage = _start + n;
        }
    }

    void resize(size_t n, const T& value = T()) {
        if (n < size()) {
            _finish = _start + n;
        } else {
            reserve(n);
            while (_finish != _start + n) {
                *_finish = value;
                ++_finish;
            }
        }
    }

    // 元素访问
    T& operator[](size_t pos) {
        assert(pos < size());
        return _start[pos];
    }

    const T& operator[](size_t pos) const {
        assert(pos < size());
        return _start[pos];
    }

    // 修改接口
    void push_back(const T& x) {
        if (_finish == _endOfStorage) {
            size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
            reserve(newCapacity);
        }
        *_finish = x;
        ++_finish;
    }

    void pop_back() {
        assert(!empty());
        --_finish;
    }

    void swap(vector<T>& v) {
        std::swap(_start, v._start);
        std::swap(_finish, v._finish);
        std::swap(_endOfStorage, v._endOfStorage);
    }

    iterator insert(iterator pos, const T& x) {
        assert(pos >= _start && pos <= _finish);
        if (_finish == _endOfStorage) {
            size_t offset = pos - _start;
            size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
            reserve(newCapacity);
            pos = _start + offset;
        }
        iterator end = _finish - 1;
        while (end >= pos) {
            *(end + 1) = *end;
            --end;
        }
        *pos = x;
        ++_finish;
        return pos;
    }

    iterator erase(iterator pos) {
        assert(pos >= _start && pos < _finish);
        iterator it = pos + 1;
        while (it != _finish) {
            *(it - 1) = *it;
            ++it;
        }
        --_finish;
        return pos;
    }

private:
    iterator _start;
    iterator _finish;
    iterator _endOfStorage;
};

// 测试代码
void TestVector() {
    vector<int> v;
    v.push_back(1); v.push_back(2); v.push_back(3);
    cout << "size:" << v.size() << " capacity:" << v.capacity() << endl;
    
    v.insert(v.begin(), 0);
    for (auto e : v) cout << e << " ";  // 0 1 2 3
    cout << endl;
    
    v.erase(v.begin() + 2);
    for (auto e : v) cout << e << " ";  // 0 1 3
    cout << endl;
}

int main() {
    TestVector();
    return 0;
}

六、学习心得:模板让vector"活"起来的秘密🔑

通过亲手实现vector,我深刻体会到模板的三大核心价值:

  1. 类型无关性:同一套代码支持int、string等所有类型
  2. 代码复用:避免为每种类型重复编写容器代码
  3. 泛型编程:容器与算法解耦,实现"一次编写,到处使用"

最关键的收获是:不要害怕实现标准库容器!这个过程能帮你彻底理解模板、内存管理和迭代器等C++核心概念。

如果你也在学习模板,不妨从实现vector开始,相信我,当你看到自己写的容器能像STL一样工作时,那种成就感是刷多少题都换不来的!💪

相关推荐
basketball61616 分钟前
Linux C 管道文件操作
linux·运维·c语言
future141220 分钟前
游戏开发日记
数据结构·学习·c#
今天背单词了吗98022 分钟前
算法学习笔记:17.蒙特卡洛算法 ——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·笔记·考研·算法·蒙特卡洛算法
Dcs25 分钟前
从 C 到 Rust:一位开发者的 `tmux` 全面移植之旅
java
ydm_ymz41 分钟前
C语言初阶4-数组
c语言·开发语言
Maybyy42 分钟前
力扣242.有效的字母异位词
java·javascript·leetcode
presenttttt1 小时前
用Python和OpenCV从零搭建一个完整的双目视觉系统(六 最终篇)
开发语言·python·opencv·计算机视觉
逐花归海.1 小时前
『 C++ 入门到放弃 』- 多态
开发语言·c++·笔记·程序人生
棱镜研途2 小时前
学习笔记丨卷积神经网络(CNN):原理剖析与多领域Github应用
图像处理·笔记·学习·计算机视觉·cnn·卷积神经网络·信号处理
卜锦元2 小时前
Go中使用wire进行统一依赖注入管理
开发语言·后端·golang