
| 🍕阿i索 | 个人主页 |
|---|---|
| 《C语言专栏》 | 《C++专栏》 |
| 《数据结构专栏》 | 《LaTeX专栏》 |
| 《软件配置问题》 | 《Linux 专栏》 |
| 待更新... |
前言.
本章介绍C++ 标准库(STL)中的vector容器。
一、vector
1.定义
vector 是C++ 标准模板库 STL 提供的序列容器 ,使用时必须引入头文件 <vector>。它是类模板 ,书写格式为 vector<存储类型>,可以存放任意相同数据类型(内置类型 int、string,自定义结构体、类对象等)。
vector 对外封装了一套完整的增删改查接口,开发者无需手动开辟、释放数组内存,系统会自动处理内存分配、扩容、销毁工作,因此也被称为动态变长数组,用来替代固定长度的静态数组。
2.本质
vector本质是基于堆上连续原生数组封装而成的STL序列容器 ,对外提供统一操作接口、自动管理内存,底层内存布局和普通数组一致,且能自动扩容实现动态变长。底层完全依赖堆上一段连续的内存空间存储所有元素,内存地址从头到尾紧密相连,不存在空隙。 容器内部维护三个关键指针,完整管控这块连续内存:
_start:指向整块连续内存的起始地址,代表数组头部;_finish:指向最后一个有效元素的后一个位置,_finish - _start的结果就是size(),代表当前真实存放元素的数量;_end_of_storage:指向整块已分配内存空间的末尾边界,_end_of_storage - _start的结果就是capacity(),代表容器当前一共能容纳多少元素。
二、vector的使用
1. vector 三种打印方式
// 打印函数,加const引用避免拷贝,只读不修改容器
void Print(const vector<int>& v)
{
// 方式1:下标遍历,最简单,vector支持随机下标访问
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
// 方式2:范围for循环,简洁,自动取容器每个元素
//for (auto e : v)
//{
// cout << e << " ";
//}
//cout << endl;
// 方式3:const迭代器遍历,只读容器时推荐
//vector<int>::const_iterator it = v.begin();
//while (it != v.end())
//{
// cout << *it << " "; // *解引用,取出迭代器指向的元素
// ++it;
//}
//cout << endl;
}
- vector 没有重载
<<运算符,无法直接cout << vector,必须手动封装打印; - 形参
const vector<int>& v:引用传参节省拷贝开销,const 保证不会修改容器; v.size():获取容器当前有效元素个数;begin()指向首元素,end()指向末尾元素下一位 ,区间左闭右开[begin, end);- const 迭代器只能读取数据,不能修改元素。
2. vector 多种构造初始化
void test_vector1()
{
vector<int> v1; // 默认无参构造,空容器,size=0
vector<int> v2(10,1); // 创建10个值为1的元素
vector<int> v3(v2.begin(), v2.end()); // 迭代器区间拷贝构造,复制v2全部元素
string s1("xxxxxx");
vector<int> v4(s1.begin(), s1.end()); // 可接收其他容器/字符串的迭代器构造
vector<int> v5(v3); // 拷贝构造,用已有容器直接复制
// initializer_list列表初始化,C++11及以上支持
//vector<int> v6({ 1,2,3,4,5 });
vector<int> v6 = { 1,2,3,4,5 };
vector<int> v7 = { 1,2,3,4,5,9,0,7,8 };
//打印
Print(v2);
Print(v4);
Print(v6);
Print(v7);
}
- 迭代器构造通用性极强,只要容器有
begin/end就可以拿来初始化 vector; - 列表初始化写法简单,刷题、日常定义容器最常用;
- 拷贝构造会完整复制另一个 vector 所有元素,两个容器内存互相独立。
拓展:initializer_list
std::initializer_list<T> 是 C++11 新增的模板类,专门用来包裹大括号{}逗号分隔的一组同类型常量 ; 当代码写{a,b,c}时,编译器会自动隐式生成一个initializer_list临时对象,不需要手动创建。
#include <initializer_list>
#include <vector>
int main()
{
// 编译器自动把 {1,2,3,4} 转换成 std::initializer_list<int> 临时对象
vector<int> v{1, 2, 3, 4};
return 0;
}
作用:
作为编译器和容器 / 自定义类之间的统一桥梁,把代码里大括号{}包裹的一组同类型数值自动封装成只读临时对象,让类能统一接收任意数量的初始化值,实现{元素1,元素2...}批量初始化语法。
它底层只存一段数组的首尾指针,不拷贝数据、开销极低,同时保证所有元素类型统一,相比 C 语言变长参数更安全。
3. vector 容量、扩容、reserve 与 resize
void test_vector2()
{
vector<int> v1;
// reserve(n):预分配内存,只扩容容量capacity,不创建有效元素size
//v1.reserve(100);
size_t old = v1.capacity();
for (size_t i = 0; i < 100;i++)
{
v1.push_back(i);
// 打印每次扩容后的容量
if (old != v1.capacity())
{
cout << v1.capacity() << endl;
old = v1.capacity();
}
}
vector<int> v2;
v1.resize(100, 1); // resize修改有效元素数量size
Print(v2);
}
size():当前存了多少元素;capacity():容器总内存容量;- 扩容规则:VS 编译器 1.5 倍扩容,g++(Linux) 2 倍扩容;扩容会开辟新内存、拷贝数据、释放旧内存,开销大;
reserve(n):仅提前分配空间,不会增加元素,已知数据量时使用,避免多次扩容;resize(n, val):修改有效元素个数:- n > 原 size:新增元素填充 val;
- n < 原 size:直接截断尾部多余元素。
4. vector 插入、删除
void test_vector3()
{
vector<int> v1 = { 1,2,3,4,5 };
v1.push_back(6); // 尾插6,效率最高,无需挪动数据
Print(v1);
v1.insert(v1.begin(), 0); // 头插0,在迭代器位置插入元素
Print(v1);
v1.insert(v1.begin()+3, 0); // 下标3对应位置插入元素
Print(v1);
v1.erase(v1.begin()); // 头删,删除迭代器指向元素
Print(v1);
v1.erase(v1.begin()+3); // 删除指定位置元素
Print(v1);
}
push_back尾插最优;中间、头部 insert/erase 会挪动后方所有元素,效率低;- insert 接收迭代器作为插入位置,
begin()+数字等价按下标定位; - erase 删除指定迭代器元素,删除后后面元素向前补齐;
- 插入删除操作可能触发扩容,会让原有迭代器全部失效。
5. emplace_back 和 push_back 对比
struct AA
{
int _a1 = 1;
int _a2 = 1;
// 构造函数
AA(int a1 = 1, int a2 = 1)
:_a1(a1)
,_a2(a2)
{ }
};
void test_vector4()
{
AA aa1 = { 0,0 };
// 列表初始化结构体vector
vector<AA> v = { aa1,{1,1},{2,2},{3,3} };//外层{}是initializer_list,内层{}是多参数构造的隐式类型转换
// 迭代器遍历结构体容器
auto it = v.begin();
while (it != v.end())
{
cout << it->_a1 << ":" << it->_a2 << endl;//自定义结构体需要访问成员
++it;
}
cout << endl;
v.push_back(aa1); // push_back:先构造临时AA,再拷贝进容器
v.emplace_back(aa1); // emplace_back:直接在容器内存原地构造对象,无拷贝
/*emplace_back 的作用:直接在 vector 容器内部已分配好的堆内存上原地构造对象,不需要创建临时对象再拷贝;而 push_back 是先在外部造出完整对象(临时 / 已有对象),再把它拷贝 / 移动到容器里,多一次拷贝开销*/
//差异:
v.push_back({2,2});//push_bacl只能传AA对象
v.emplace_back(1,1); //emplace可以传AA对象,也可以传构造AA对象的参数
it = v.begin();
while (it != v.end())
{
cout << it->_a1 << ":" << it->_a2 << endl;
++it;
}
cout << endl;
}
- 存储自定义结构体时,迭代器用
it->_成员名访问内部变量; - push_back:传完整对象,会发生对象拷贝,复杂结构体开销大;
- emplace_back:支持直接传入构造参数,原地构造,无拷贝,推荐优先使用;
{1,1}是结构体临时对象,可直接用于列表初始化和 push_back。
6.练习(杨辉三角)
C语言:



对于二维数组 int** 可以考虑使用指针数组来动态开辟二维数组:
int** aa = (int**)malloc(sizeof(int*) * numRows);
for (int i = 0; i < numRows; ++i)
{
aa[i] = (int*)malloc(sizeof(int) * (i + 1));
}
return aa;
//后两个参数
// 给输出参数赋值:外层总行数
*returnSize = numRows;
// 分配存储「每行长度」的数组,必须malloc给外部调用者
*returnColumnSizes = malloc(sizeof(int) * numRows);
returnSize 是外部传进来的int*一级指针,*returnSize 代表修改指针指向的外部变量; 我们把杨辉三角总行数numRows赋值给它,主函数调用完generate后,读取这个变量就能知道一共返回了多少行。
returnColumnSizes 是int**二级指针,*returnColumnSizes等价于外部的int*行长度数组;malloc(sizeof(int)*numRows):在堆上申请一块数组,用来存每一行有多少个数字 (第 0 行 1 个、第 1 行 2 个...... 第 n 行 n+1 个);把刚分配好的堆数组地址赋值给*returnColumnSizes,让外部调用者拿到这块内存,遍历读取每行长度。
C++:


class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv;
//行
vv.resize(numRows,vector<int>());//开一个数组出来应该有numRows个vector<int>对象
//列
for(size_t i=0;i<numRows;i++)
{
vv[i].resize(i+1,1);
}
//遍历
for(size_t i=2;i<vv.size();++i)
{
for(size_t j=1;j<vv[i].size()-1;++j)
{
vv[i][j]=vv[i-1][j]+vv[i-1][j-1];//上一行同下标位置的值和前一个值相加
}
}
return vv;
}
};

三、vector的模拟实现
vector.h文件
#pragma once
#include <cstring>// 提供memcpy、memset等内存拷贝/初始化函数
#include<assert.h>
#include <initializer_list>// 支持{}列表初始化 vector<int> v={1,2,3};
#include<iostream>
//声明和定义在同一个文件
namespace asuo
{
template<class T>
class vector
{
public:
// 迭代器重定义:原生指针作为vector迭代器
using iterator = T*;// 等同于typedef T* iterator;
using const_iterator = const T*;// 只读迭代器,不能修改容器内元素
// 获取容器第一个元素迭代器(可读可写)
iterator begin()
{
return _start;
}
// 获取末尾后一位迭代器,代表有效数据边界
iterator end()
{
return _finish;
}
// const容器调用,只读begin
const_iterator begin()const
{
return _start;
}
// const容器调用,只读end
const_iterator end()const
{
return _finish;
}
//构造
//初始化:初始化列表显式写了用初始化列表,没有显式写看缺省值,都没有也要走初始化列表(初始化列表一定要走)
// 无参构造,三个指针成员默认初始化为nullptr(类内缺省初始化)
vector()
{}
// initializer_list构造,支持{}初始化容器
vector(std::initializer_list<T> il)
{
// 提前开辟对应大小空间,避免多次扩容
reserve(il.size());
// 范围for遍历初始化列表,逐个尾插
for (const auto& e : il)//语法糖
{
push_back(e);
}
}
//析构
~vector()
{
// 防止空指针delete崩溃
if (_start)
{
// 释放堆上动态数组空间
delete[] _start;
// 三指针置空,防止野指针
_start = _finish = _end_of_storage = nullptr;
}
}
////传统写法构造:v2(v1)
////vector(const vector<T>& v)
////{
//// reserve(v.capacity());
//// for (const auto& e: v)//使用新迭代器的引用
//// {
//// push_back(e);
//// }
////}
//// v1=v3
//// v0=v1=v3
////vector<T>& operator=(const vector<T>& v)
////{
//// if (this!= &v)
//// {
//// clear();
//// reserve(v.capacity());
//// for (const auto& e : v)
//// {
//// push_back(e);
//// }
//// }
//// return *this;
////}
//n个val构造
//构造优先会匹配模板
// size_t版本重载,处理无符号数值传参场景
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
//解决:提供一个重载
// int版本重载,区分迭代器模板构造,防止歧义匹配
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
//函数模板,迭代器不一定是vector迭代器,也可以是其他容器的迭代器
// 接收任意容器输入迭代器区间 [first, last) 构造vector
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
//reserve(last - first);
// 遍历迭代器区间,逐个尾插元素
while (first != last)
{
push_back(*first);
++first;
}
}
//现代写法:拷贝构造,拷贝其他对象的
vector(const vector<T>& v)
{
// 用迭代器区间构造临时对象,完成深拷贝
vector<T> tmp(v.begin(), v.end());
// 交换临时对象与当前对象三指针,资源转移,出函数tmp自动析构释放旧空间
swap(tmp);
}
//v1=v3 拷贝赋值重载(现代交换写法)
vector<T>& operator=(vector<T>& tmp)
{
// 交换双方指针,tmp接收当前对象旧资源,函数结束自动释放
swap(tmp);
return *this;
}
// 交换两个vector底层三块指针,高效交换容器数据,不拷贝元素
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
// 清空有效数据,不释放底层容量空间
void clear()
{
_finish = _start;
}
// 判断容器是否为空(无有效元素)
bool empty()const
{
return _start == _finish;
}
// 扩容函数:开辟更大新空间,迁移旧数据,释放旧空间
void reserve(size_t n)
{
// 仅当目标容量大于现有容量时才扩容
if (n > capacity())
{
// 记录当前有效元素个数
size_t sz = size();
// 开辟n大小的T类型数组新空间
T* tmp = new T[n];//开辟新空间
if (_start)//_start不为空就把旧空间的内容拷贝出来
{
//memcpy(tmp, _start, sizeof(T) * sz);
// memcpy浅拷贝,自定义类型如string会浅拷贝导致析构双重释放,废弃不用
for (size_t i = 0; i < sz; i++)
{
//tmp[i] = _start[i];//法一:如果是string,调用string的赋值深拷贝
std::swap(tmp[i], _start[i]);//法二:如果是string,调用string的交换,交换资源指向,效率更高
}
// 释放旧动态数组空间
delete[] _start;
}
// 更新三块指针指向新空间
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
/*缺省参数一般使用:自变量常量,全局变量/类的静态成员,匿名对象 */
//这里T()就是使用了匿名对象,创建T类型默认值填充
// 修改有效元素个数,扩容/缩减size,容量不缩减
void resize(size_t n, T val = T())
{
if (n < size())
{
//删除数据:直接前移_finish截断有效数据,不析构元素
_finish = _start + n;
}
else
{
//空间不够就扩容到n
reserve(n);
//补充新增位置,全部填充默认值val
while (_finish < _start + n)//再插入数据
{
*_finish = val;
++_finish;
}
}
}
// 获取容器总容量(最大可存元素数,包含未使用空间)
size_t capacity()const
{
return _end_of_storage - _start;
}
// 获取当前有效元素个数
size_t size()const
{
return _finish - _start;
}
// 下标访问,可读可写
T& operator[](size_t i)
{
// 断言防止下标越界访问
assert(i < size());
return _start[i];
}
// const容器下标只读访问
const T& operator[](size_t i)const
{
assert(i < size());
return _start[i];
}
//尾插:在容器末尾新增元素
void push_back(const T& x)
{
//满了:有效数据占满全部容量,需要扩容
if (_finish == _end_of_storage)
{
// 空容器初始分配4个空间;非空扩容2倍
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
// 写入新元素,有效尾指针后移
*_finish = x;
++_finish;
}
//尾删:删除最后一个有效元素
void pop_back()
{
// 空容器禁止尾删,断言报错
assert(!empty());
// 有效尾指针前移,逻辑删除
--_finish;
}
// 在pos迭代器位置插入元素x,返回新元素迭代器
iterator insert(iterator pos, const T& x)
{
// 校验迭代器合法性,不能超出容器边界
assert(pos >= _start);
assert(pos <= _finish);
//扩容
/*扩容引起的迭代器失效:当_finish == _end_of_storage(容量已满)时,reserve会开辟新堆内存、释放旧内存,原来的 pos 迭代器指向已销毁的旧空间,变成野指针,直接使用会崩溃。*/
if (_finish == _end_of_storage)
{
// ① 先记录pos相对容器起始的偏移量,扩容后重建迭代器
size_t len = pos - _start;//
reserve(capacity() == 0 ? 4 : capacity() * 2); // 扩容,旧内存释放,原pos失效
pos = _start + len; // ② 用偏移量在新内存上重建合法pos迭代器
}
//挪动插入:pos及之后元素全部向后挪一位,腾出pos位置
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
// 插入目标元素
*pos = x;
// 有效元素数量+1
++_finish;
return pos;
}
// 删除pos位置元素,返回后一个元素迭代器
iterator erase(iterator pos)
{
// 校验迭代器合法,不能是end()
assert(pos >= _start);
assert(pos < _finish);
// pos后所有元素向前覆盖一位,逻辑删除pos元素
iterator it = pos + 1;
while (it != _finish)
{
*(it - 1) = *it;
++it;
}
// 有效元素数量-1
--_finish;
// 返回原pos位置(此时存储原下一个元素)
return pos;
}
private:
iterator _start = nullptr; // 动态数组起始指针
iterator _finish = nullptr; // 有效数据末尾下一位指针
iterator _end_of_storage = nullptr; // 整个容量空间末尾下一位指针
};
}
test.cpp测试文件
#define _CRT_SECURE_NO_WARNINGS
#include"vector.h"
#include<iostream>
using namespace std;
namespace asuo
{
// 打印vector所有元素,const引用避免拷贝,只读容器
void Print(const vector<int>& v)
{
// 范围for遍历,依赖begin/end迭代器实现
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//for (size_t i = 0; i < v.size(); i++)
//{
// cout << v[i] << " ";
//}
//cout <<endl;
}
void test_vector1()
{
asuo::vector<int> v;
// 尾插5个元素
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
Print(v);
// 头插0,测试insert扩容迭代器失效处理
v.insert(v.begin(), 0);
Print(v);
// 删除首元素
v.erase(v.begin());
Print(v);
////删除所有偶数
////迭代器失效:erase后itt失效,不能再被访问,访问结果未定义
//auto itt = v.begin();
//while (itt != v.end())
//{
// if (*itt % 2 == 0)
// {
// v.erase(itt);
// }
// ++itt;
//}
//Print(v);
//解决:
//删除所有偶数
//迭代器失效:erase后itt失效,不能再被访问,访问结果未定义,需要重新赋值迭代器
auto itt = v.begin();
while (itt != v.end())
{
if (*itt % 2 == 0)
{
// erase返回有效迭代器,重新接收,修复迭代器失效
itt = v.erase(itt);
}
else
{
// 当前元素不删除,迭代器后移
++itt;
}
}
Print(v);
auto it = v.begin() + 3;
//insert后it迭代器失效(底层指向野指针),不能再被使用,需要重新赋值迭代器
// insert返回新迭代器,覆盖原失效it
it = v.insert(it, 30);
Print(v);
// 缩小size到3,截断末尾元素
v.resize(3);
Print(v);
// 扩大size到23,自动扩容并用默认值填充空位
v.resize(23);
Print(v);
}
void test_vector2()
{
asuo::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
Print(v1);
// 拷贝构造测试,现代swap写法深拷贝
asuo::vector<int> v2(v1);
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
//asuo::vector<int> v3({ 10,20,30,40 });
// initializer_list列表初始化
asuo::vector<int> v3 = { 10,20,30,40 };//多参数构造需要用到initializer_list,需要添加头文件
// 拷贝赋值重载测试
v1 = v3;
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
// n个val构造测试
asuo::vector<int> v4(10, 1);
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
}
void test_vector3()
{
// 测试自定义类型string,验证reserve中swap拷贝规避浅拷贝问题
asuo::vector<string> v1;
v1.push_back("1111111111111");
v1.push_back("1111111111111");
v1.push_back("1111111111111");
v1.push_back("1111111111111");
v1.push_back("1111111111111");
for (auto& e : v1)
{
cout << e << " ";
}
cout << endl;
}
}
int main()
{
asuo::test_vector3();
int i = 0;
int j = int();
int k = int(1);
//C++11提供{}初始化
int z = {};
int y = { 1 };
int l{ 2 };
return 0;
}
1. 构造函数二义性问题
问题描述
模板迭代器构造 template<InputIterator> vector(first, last) 和 vector(size_t n, const T& val) 发生匹配歧义: 当传入 vector(10, 1),10 是 int,编译器会优先匹配模板,把 10、1 当成两个迭代器,编译报错。
// 迭代器模板构造,会匹配两个int入参
template <class InputIterator>
vector(InputIterator first,InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
解决方案
重载两个版本:
-
vector(size_t n, const T& val)无符号数值版本 -
vector(int n, const T& val)有符号 int 版本 区分数值传参与迭代器区间传参,消除重载歧义。//n个val构造
//size_t无符号版本
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(n);
}
}
//解决歧义:int有符号重载,优先匹配数字参数
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(n);
}
}
2. 拷贝构造 / 拷贝赋值浅拷贝、内存泄漏、双重释放问题
问题描述
传统直接赋值写法会共用底层数组指针,析构时两个对象释放同一块堆内存,触发崩溃;析构旧资源会内存泄漏。
////传统写法拷贝构造(浅拷贝)
//vector(const vector<T>& v)
//{
// reserve(v.capacity());
// for (const auto& e: v)
// {
// push_back(e);
// }
//}
////传统赋值重载(未处理自赋值、旧内存泄漏)
//vector<T>& operator=(const vector<T>& v)
//{
// if (this!= &v)
// {
// clear();
// reserve(v.capacity());
// for (const auto& e : v)
// {
// push_back(e);
// }
// }
// return *this;
//}
解决方案:现代 swap 交换法
-
拷贝构造 :用源容器迭代器区间构造临时对象 tmp(自动完成深拷贝),调用
swap(tmp)交换当前对象与 tmp 的三块底层指针;函数结束 tmp 析构,自动释放当前对象旧空间。 -
拷贝赋值:传值接收临时对象 tmp,直接 swap 交换指针,tmp 生命周期结束释放旧资源。 优势:无需手动 clear、手动释放旧空间,代码简洁、异常安全。
//现代拷贝构造
vector(const vector& v)
{
vectortmp(v.begin(), v.end());
swap(tmp);
}
//拷贝赋值重载(传值构造临时对象,交换指针)
vector& operator=(vector & tmp)
{
swap(tmp);
return *this;
}
//交换底层三指针,O(1)交换资源
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
3. reserve 扩容时 memcpy 浅拷贝,自定义类型(string)双重释放崩溃
问题描述
memcpy 按字节拷贝,string 内部存储堆字符串,浅拷贝后两个 string 指向同一块字符堆内存,析构两次释放同一块内存程序崩溃。
//memcpy(tmp, _start, sizeof(T) * sz);
解决方案
放弃 memcpy,循环std::swap(tmp[i], _start[i])迁移元素: 调用自定义类型自身 swap,仅交换内部资源指针,不重复开辟堆内存,规避浅拷贝风险。
void reserve(size_t n)
{
if (n > capacity())
{
size_t sz = size();
T* tmp = new T[n];
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
//std::swap仅交换对象内部指针,规避浅拷贝
std::swap(tmp[i], _start[i]);
}
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_end_of_storage = _start + n;
}
}
4.迭代器失效问题
(1)insert 扩容导致迭代器失效(野指针)
问题描述
容量满时 insert 触发 reserve 扩容:开辟新空间、delete 旧数组,原 pos 迭代器指向已释放的旧内存,成为野指针,访问崩溃。
解决方案
扩容前记录pos - _start偏移量,扩容完成后用_start + 偏移量重建合法迭代器 pos,保证插入位置正确。
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
//①记录偏移
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
//②用偏移重建迭代器
pos = _start + len;
}
//元素后移逻辑省略...
return pos;
}
(2)erase 循环删除元素迭代器失效
问题描述
erase 删除当前元素后,原迭代器立刻失效,直接++itt访问野指针,程序未定义行为(崩溃 / 乱码)。
//auto itt = v.begin();
//while (itt != v.end())
//{
// if (*itt % 2 == 0)
// {
// v.erase(itt);
// }
// ++itt;
//}
解决方案
接收 erase 返回值:itt = v.erase(itt),erase 返回删除位置下一个有效迭代器,不执行 ++;不删除才迭代器后移。
auto itt = v.begin();
while (itt != v.end())
{
if (*itt % 2 == 0)
{
//erase返回下一个有效迭代器,覆盖失效迭代器
itt=v.erase(itt);
}
else
{
++itt;
}
}
5. 支持 C++11 {} 列表初始化语法
问题描述
原生 vector 支持vector<int> v = {1,2,3},自实现容器无对应构造无法使用该写法。
解决方案
增加vector(std::initializer_list<T> il)构造函数,遍历 initializer_list 批量 push_back,支持大括号初始化。
vector(std::initializer_list<T> il)
{
reserve(il.size());
for (const auto& e : il)
{
push_back(e);
}
}
//测试调用
asuo::vector<int> v3 = {10,20,30,40};
6. 容器交换效率低(逐元素拷贝)
问题描述
直接循环拷贝两个容器所有元素交换,数据量大时性能极差。
解决方案
实现swap(vector& v)成员函数,仅交换_start/_finish/_end_of_storage三个指针,O (1) 复杂度,无元素拷贝。
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
7. 无参构造指针野指针、析构 delete 空指针崩溃
问题描述
不初始化指针,成员变量随机垃圾值,析构执行delete[] _start触发空指针 / 野指针崩溃。
解决方案
类内缺省初始化 _start = nullptr; _finish = nullptr; _end_of_storage = nullptr;,析构前判断if(_start)再释放空间。
private:
iterator _start=nullptr;
iterator _finish = nullptr;
iterator _end_of_storage = nullptr;
//析构增加判空
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
8. resize 两种场景处理(缩小 / 扩大容量)
问题描述
修改有效元素数量需要区分两种场景:缩小 size 截断数据、扩大 size 填充默认值,扩容需自动调用 reserve。
解决方案
分支判断:
-
n <size ():直接前移_finish 截断有效数据
-
n > size ():先 reserve 扩容到 n,循环填充 T () 默认值至_finish == _start+n
void resize(size_t n, T val = T())
{
if (n < size())
{
//缩小:直接截断有效数据
_finish = _start + n;
}
else
{
//扩大:先扩容,再填充默认值
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
++_finish;
}
}
}
9. 范围 for 循环无法遍历自定义 vector
问题描述
C++ 范围 for 底层依赖begin()、end()迭代器接口,未实现则不能使用for(auto e : v)。
解决方案
提供重载begin()/end()(普通版本 + const 版本),返回首尾迭代器指针,兼容范围 for 与只读 const 容器。
using iterator = T*;
using const_iterator = const T*;
iterator begin(){return _start;}
iterator end(){return _finish;}
const_iterator begin()const{return _start;}
const_iterator end()const{return _finish;}
//测试使用
for (auto e : v)
{
cout << e << " ";
}
10. 迭代器只读 / 读写区分
问题描述
const 修饰 vector 无法修改内部元素,若只提供 T * 迭代器,const 容器也能修改元素,违反 const 语义。
解决方案
定义const_iterator = const T*,重载 const 版本 begin/end,const 容器返回只读迭代器,禁止修改元素。
using iterator = T*;
using const_iterator = const T*;
//普通对象返回可修改迭代器
iterator begin(){return _start;}
//const对象返回只读迭代器,禁止修改元素
const_iterator begin()const{return _start;}