目录
-
- 为什么学C++一定要亲手实现vector?🧐
- 一、vector的"三驾马车":模板如何定义动态数组结构?🛠️
-
- [1.1 用模板参数T打造"万能容器"](#1.1 用模板参数T打造"万能容器")
- [1.2 三个指针管理内存:动态数组的"骨架"](#1.2 三个指针管理内存:动态数组的"骨架")
- 二、构造函数家族:模板如何实现多场景初始化?🚀
-
- [2.1 填充构造:处理不同参数类型的"小技巧"](#2.1 填充构造:处理不同参数类型的"小技巧")
- [2.2 迭代器区间构造:模板的"泛型搬运工"](#2.2 迭代器区间构造:模板的"泛型搬运工")
- [2.3 拷贝构造:深拷贝的"防坑指南"](#2.3 拷贝构造:深拷贝的"防坑指南")
- 三、容量管理:模板容器的"动态伸缩术"📦
-
- [3.1 reserve扩容:避免频繁搬家的"智慧"](#3.1 reserve扩容:避免频繁搬家的"智慧")
- [3.2 扩容策略:不同编译器的"性格差异"](#3.2 扩容策略:不同编译器的"性格差异")
- 四、增删查改:模板让接口"一劳永逸"🔧
-
- [4.1 迭代器接口:容器与算法的"桥梁"](#4.1 迭代器接口:容器与算法的"桥梁")
- [4.2 insert插入:处理迭代器失效的"细节控"](#4.2 insert插入:处理迭代器失效的"细节控")
- [4.3 erase删除:迭代器失效的"重灾区"](#4.3 erase删除:迭代器失效的"重灾区")
- 五、完整实现代码:模板vector的"最终形态"✨
- 六、学习心得:模板让vector"活"起来的秘密🔑

🌟个人主页 :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,我深刻体会到模板的三大核心价值:
- 类型无关性:同一套代码支持int、string等所有类型
- 代码复用:避免为每种类型重复编写容器代码
- 泛型编程:容器与算法解耦,实现"一次编写,到处使用"
最关键的收获是:不要害怕实现标准库容器!这个过程能帮你彻底理解模板、内存管理和迭代器等C++核心概念。
如果你也在学习模板,不妨从实现vector开始,相信我,当你看到自己写的容器能像STL一样工作时,那种成就感是刷多少题都换不来的!💪