C++的大部分接口是互通的 详解已经在string类中写过了 这里重复的接口属性就不详细写了
文章目录
- [1.Member functions](#1.Member functions)
- 2.Iterators
- 3.Capacity
- [4.Element access](#4.Element access)
- [5. vector 增删查改](#5. vector 增删查改)
- [6. 例题板块](#6. 例题板块)
-
- [例题1.只出现一次的数字(^ 的用法)](#例题1.只出现一次的数字(^ 的用法))
- 例题2.杨辉三角(C和C++做法的比较)
- [7. vector模拟实现和实现过程中的一些重难点](#7. vector模拟实现和实现过程中的一些重难点)
-
- 7.1模拟实现完整代码
- 7.2迭代器失效问题
- [7.3 作为函数参数时 能否写成&的形式](#7.3 作为函数参数时 能否写成&的形式)
- [7.4 reserve的两个注意点](#7.4 reserve的两个注意点)
- [7.5 Tips](#7.5 Tips)
- [创作不易 求三连!](#创作不易 求三连!)
1.Member functions
C++提供的顺序表容器 在使用前建议先系统的学习过数据结构中的顺序表 详情请点
constructor

explicit 说明:防止隐式转换(vector v = 5 这是错误的 )

- 默认构造函数(无参构造)
功能:创建一个空的vector,不包含任何元素。
cpp
std::vector<int> v; // 创建空的 int 类型 vector
std::cout << v.size(); // 输出 0(无元素)
- 填充构造函数(指定大小和初始值)
功能:创建一个包含 n 个元素的 vector,每个元素的初始值为 val。
cpp
std::vector<int> v(3, 10); // 创建包含3个元素的vector,每个元素为10
// 结果:v = [10, 10, 10]
- 范围构造函数(用迭代器区间初始化)
功能:通过迭代器区间 [first, last) 中的元素初始化 vector,即复制该区间内的所有元素(包含 first 指向的元素,不包含 last 指向的元素)。
cpp
int arr[] = {1, 2, 3, 4};
std::vector<int> v(arr, arr + 3); // 复制arr[0]、arr[1]、arr[2]
// 结果:v = [1, 2, 3]
std::vector<int> v2(vec.begin(), vec.end()); // 复制另一个vector的所有元素
// 结果:v2 = [1, 2, 3]
- 拷贝构造函数(复制另一个 vector)
功能:创建一个新的 vector,并复制另一个 vector x 中的所有元素(深拷贝,两个容器后续修改互不影响)。
cpp
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2(v1); // 拷贝v1的元素
// 结果:v2=[1, 2, 3]
然后C++11中还引入了initializer_list(使用需要包含initializer_list的头文件)
cpp
#include<iostream>
#include<vector>
#include<initializer_list>
int main() {
// 假设 il 是一个整数类型的初始化列表
auto il = {1, 2, 3, 4, 5}; // 推断为 initializer_list<int>
// 输出 il 的类型名(不同编译器可能显示不同,通常为 "initializer_list<int>" 相关形式)
cout << typeid(il).name()<<endl;
// 遍历初始化列表中的元素并输出
for (auto e : il)
{
cout << e << " ";
}
cout<<endl; // 输出:1 2 3 4 5
// 用初始化列表直接初始化 vector(C++11 及以上支持)
vector<int> v1({10, 20, 30}); // v1 包含元素 10, 20, 30
return 0;
}
cpp
vector (initializer_list<value_type> il,
const allocator_type& alloc = allocator_type());
vector 的初始化:
vector<int> v1({10,20,30})或者vector<int> v1=({10,20,30}) (构造传参)是 C++11 引入的列表初始化方式,等价于更简洁的 vector<int> v1{10,20,30} 或者vector<int> v1={10,20,30}(走隐式类型转换 构造+拷贝构造 直接优化为构造),直接构造包含指定元素的向量。
注意:initializer_list 中的元素是常量,无法修改(如 vector[0] = 5 错误)。
而且 他的遍历是要通过迭代器的方式 但是放在别的容器中还可以让他像数组一样!

而initializer_list初始化的本质相当于 在范围for中 一直用push_back()插入数据。

std::vector::operator=
cpp
copy (1) vector& operator= (const vector& x);
赋值重载 将新内容分配给容器,替换其当前内容,并相应地修改其大小。
功能说明
- 作用:把 vector x 中的所有元素复制到当前 vector 中。
- 特性:这是一个深拷贝操作,赋值后两个 vector 各自拥有独立的元素,修改其中一个不会影响另一个。
- 行为:赋值前会先清空当前 vector 中的原有元素,再复制 x 的元素。
cpp
// vector assignment
#include <iostream>
#include <vector>
int main ()
{
vector<int> a (3,0);
vector<int> b (5,0);
b = a;
a = vector<int>();
cout << "Size of a: " << int(a.size()) << '\n';
cout << "Size of b: " << int(b.size()) << '\n';
return 0;
}
结果:Size of a: 0 ;Size of b: 3
2.Iterators
迭代器 没什么好说的 但是有一说一 vevtor容器是不支持直接像数组一样遍历 必须使用迭代器
其接口特性和string 基本一致 详情请转跳


3.Capacity


vector的容器部分 其效果基本也与string 相似 本质上是并没有太大的区别
详情请转跳
4.Element access

vector的容器部分 其效果基本也与string 相似 本质上是并没有太大的区别
详情请转跳
5. vector 增删查改
这里面会产生一些不同于string容器的变化 我这里会挑一些重点讲 
push_back&pop_back
cpp
void push_back (const value_type& val);
void push_back (value_type&& val);
cpp
vector<int> v1;
v1.reserve(100);//扩容到100 减少多次扩容
size_t old=v1.capacity();
cout<<old<<endl;
for (size_t i=0; i<100; i++)//尾插
{
v1.push_back(i);
if (v1.capacity()!=old)
{
cout<<v1.capacity()<<endl;
old=v1.capacity();
}
}

cpp
void pop_back();
Delete last element
vector::pop_back() 只会减少 vector 的元素数量(size),不会改变其容量(capacity)。
移除向量中的最后一个元素,实际上会使容器大小减少一个。
这会销毁被移除的元素。 这里就不演示了
insert&&find
- 插入单个元素
cpp
iterator insert (iterator position, const value_type& val);
与string不同 vector没有办法使用pos 而是要使用迭代器
返回值:指向新插入元素的迭代器(方便后续操作)
cpp
vector<int> v = {1, 3, 4};
// 在索引1位置插入元素2(通过迭代器 v.begin() + 1 指定位置)
v.insert(v.begin() + 1, 2);
// 结果:v = [1, 2, 3, 4],it 指向新插入的 2
- 插入 n 个重复元素
cpp
void insert (iterator position, size_type n, const value_type& val);
返回值:void
cpp
std::vector<int> v = {1, 4};
// 在开头插入 2 个元素 0
v.insert(v.begin(), 2, 0);
// 结果:v = [0, 0, 1, 4]
- 插入迭代器范围元素
cpp
template <class InputIterator>
void insert (iterator position, InputIterator first, InputIterator last);
返回值:void
cpp
std::vector<int> v1 = {1, 5};
std::vector<int> v2 = {2, 3, 4};
// 在 v1 的索引1位置插入 v2 的所有元素
v1.insert(v1.begin() + 1, v2.begin(), v2.end());
// 结果:v1 = [1, 2, 3, 4, 5]
虽然vector容器里面并没有现成的find 但是在std中是有的
cpp
template <class InputIterator, class T>
InputIterator find (InputIterator first, InputIterator last, const T& val);
返回一个迭代器,指向范围 [first, last) 中第一个与 val 比较相等的元素。如果未找到这样的元素,则函数返回 last。
cpp
auto it=find(v1.begin(), v1.end(), 5);//返回的也是迭代器
if (it!=v1.end())
{
v1.insert(it, 50);//在数字5的位置插入50
}
erase
cpp
iterator erase (iterator position);
iterator erase (iterator first, iterator last);
- 删除单个元素
cpp
vector<int> v = {1, 2, 3, 4};
// 删除索引1处的元素(值为2)
auto it = v.erase(v.begin() + 1);
// 结果:v = [1, 3, 4],it 指向元素3(被删除的下一个元素)
- 删除范围内的元素
cpp
vector<int> v = {1, 2, 3, 4, 5};
// 删除索引1到3(不包含3)的元素(即2和3)
auto it = v.erase(v.begin() + 1, v.begin() + 3);
// 结果:v = [1, 4, 5],it 指向元素4
emplace_back
这个接口因为目前知识深度的原因 没发讲全 但是也是可以讲一部分
cpp
vector<int> v1{10,20,30};//让其像数组一样
cout<<v1[1]<<endl;//输出 20
v1.push_back(1);
v1.emplace_back(1);//这么用可以理解成两者没有区别
push_back和emplace_back的区别
这个是我们自定义的结构体
cpp
struct A
{
A(int a1,int a2)
:_a1(a1)
,_a2(a2)
{
cout<<"A(int a1,int a2)"<<endl;
}
A(const A& aa)
:_a1(aa._a1)
,_a2(aa._a2)
{
cout<<"A(const A& aa)"<<endl;
}
int _a1;
int _a2;
};
cpp
vector<A> v2;
//push_back的三种方法
//1.有名对象
A aa1(1,1);
v2.push_back(aa1);
//2.匿名对象
v2.push_back(A(2,2));
//3.隐式类型转换
v2.push_back({3,3});
v2.emplace_back(aa1);
v2.emplace_back(A(2,2));
//v2.emplace_back({3,3)}; 不能这么用
//可以这么用
//传构造A的参数,效率比较高
v2.emplace_back(3,3);//直接构造
前两种可以理解二者没有区别 最后一种是采用直接的构造效率更高
自定义类的遍历(含结构化绑定知识点)
cpp
//A*
vector<A>::iterator it2=v2.begin();
while (it2!=v2.end())
{
// cout<<*it2<<" ";这么写是错误的 因为没有重载流插入
cout<<(*it2)._a1<<":"<<(*it2)._a2<<endl;
cout<<it2->_a1<<":"<<it2->_a2<<endl;//这么写也可以
++it2;
}
cout<<endl;
//当然肯定是范围for更香一点 c++11
for(auto& aa:v2)
{
cout<<aa._a1<<" "<<aa._a2<<endl;
}
//c++17 新出的语法糖 当容器是结构的时候就可以这么用
auto[x,y]=aa1;//取aa1的成员依次进行赋值
for(auto[x,y] :v2)//结构化绑定
{
cout<<x<<":"<<y<<endl;
}
for(auto&[x,y] :v2)//不想有拷贝构造函数
{
cout<<x<<":"<<y<<endl;
}

6. 例题板块
例题1.只出现一次的数字(^ 的用法)

cpp
class Solution {
public:
int singleNumber(vector<int>& nums) {
int x = 0; // 初始化为0
for (auto e : nums) { // 遍历数组中的每个元素
x ^= e; // 累积异或运算
}
return x; // 最终结果就是只出现一次的数字
}
};
核心原理:异或运算的特性
- 自反性:a ^ a = 0(任何数与自身异或结果为 0)
- 恒等性:a ^ 0 = a(任何数与 0 异或结果为其本身)
- 交换律和结合律:a ^ b ^ c = a ^ c ^ b(运算顺序不影响结果)
假设数组为 [2, 3, 2, 4, 4]
x = 0 ^ 2 → 2
x = 2 ^ 3 → 1(二进制 10 ^ 11 = 01)
x = 1 ^ 2 → 3(二进制 01 ^ 10 = 11)
x = 3 ^ 4 → 7(二进制 11 ^ 100 = 111)
x = 7 ^ 4 → 3(二进制 111 ^ 100 = 011)
最后结果就是 3
异或运算的自反性(a ^ a = 0)和恒等性(a ^ 0 = a)在此过程中起关键作用:
数组中出现两次的数字(如 2、4),经过两次异或后会相互抵消(结果为 0)。
最终剩下的数字就是只出现一次的数字(如 3),因为它只被异或了一次。
例题2.杨辉三角(C和C++做法的比较)

如果用C语言来写的话 你需要动态开辟二纬数组 释放的时候需要讲空间进行分别进行释放

如果你想用c写,你需要返回两个你不怎么认识的指针给测试者。
其中 *returnSize是让调用者知道有多少行,然后 *returnColumnSizes是让调用者知道为每一行分配的列数。
cpp
int** generate(int numRows, int* returnSize, int** returnColumnSizes) {
int** aa =(int*) malloc(numRows * sizeof(int*));//为指针数组分配内存,定义行数
//对程序结构没影响,但如果缺少则无法与出题者交互,进而不能通过测试
*returnSize = numRows;//让调用者知道有多少行
*returnColumnSizes = (int*)malloc(numRows * sizeof(int));//为每一行分配列数做准备【x,x,x,x,x...】
for (int i = 0; i < numRows; i++) {
triangle[i] =(int*) malloc((i+ 1) * sizeof(int));//为特定的行分配内存,定义了该行的具体元素数量
(*returnColumnSizes)[i] = i + 1; // 为每一行分配列数【1,2,3,4,5...】,让调用者知道
// 每行的第一个和最后一个元素为 1
aa[i][0] = 1;
aa[i][i] = 1;
// 计算当前行中间的元素
for (int j = 1; j < i; j++) {
aa[i][j] = aa[i - 1][j - 1] + aa[i - 1][j];
}
}
return aa; // 返回生成的杨辉三角
}
让我们看看C++是怎么做的
cpp
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv;
vv.resize(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.operator[](i).operator[](j)
vv[i][j]=vv[i-1][j]+vv[i-1][j-1];
}
}
return vv;
}
};
cpp
vv.resize(numRows, vector<int>());
这个操作将 vv 调整为有 numRows 行,每行是一个空的 vector。注意,vector() 是一个空的 int 类型的向量,表示每一行的初始状态为空

里面的vector<int>相当于 一个int*的数组 同时伴随容器的size和capacity,而外层的vector则是一个包含着一个vector<int>*数组的一个类。
- 外部 vector (vv): 存储的是指向内部 vector 的指针。
- 内部 vector (vector): 每个内部的 vector 实际上是一个独立的动态数组。
而这么做的目的也就是为开辟一个动态的二维数组。这也就是STL容器所带来的简便性!
7. vector模拟实现和实现过程中的一些重难点
_start:指向内存空间的起始位置,即第一个元素的地址。
_finish:指向当前已存储元素的下一个位置,即末尾元素的地址 + 1。
_endofstorage:指向已开辟内存空间的末尾位置(已开辟内存的最后一个字节的下一个位置)。
7.1模拟实现完整代码
cpp
#include<iostream>
#include <cassert>
using namespace std;
//模版 申明和定义不能分离定义到两个文件.h .cpp
namespace fcy
{
template<class T>
class vector
{
public:
typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
// vector():
// _start(nullptr)
// ,_finish(nullptr)
// ,_endofstorage(nullptr)
// {}
//因为有了缺省值所以还可以
// vector()
// {}
//c++11强制编译器生成默认构造
vector()=default;
//多参数拷贝构造
vector(initializer_list<T> il)
{
reserve(il.size());
for(auto& e:il)
{
push_back(e);
}
}
//迭代器区间初始化 但这么写只能传vector 的iterator
// vector(iterator start,iterator end)
// {
// while (start!=end)
// {
// push_back(*start);
// ++start;
// }
// }
template<class it>
//函数模版
//只要类型匹配 我可以用任意类型迭代器来进行初始化
vector(it first,it last)
{
while (first!=last)
{
push_back(*first);
first++;
}
}
//非法间接寻址
// vector(size_t n,T val=T())
// {
// resize(n,val);
// }
//改成int就没问题吗? 这只能解决一种情况 所以咱们就多搞两个
vector(int n,T val=T())
{
resize(n,val);
}
vector(size_t n,T val=T())
{
resize(n,val);
}
//v2(v1) 拷贝构造 写法1 这种写法必须要初始化 不然reserve鬼知道会发生什么 迭代器都是随机值 当然也可以用缺省值进行初始化
vector(const vector<T>& v)
// _start(nullptr)
//,_finish(nullptr)
//,_endofstorage(nullptr)
{
reserve(v.capacity());
for(auto& e:v)
{
push_back(e);
}
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
//v1=v7
vector<T>& operator=(vector<T> v)
{
swap(v);
return *this;
}
~vector()
{
if(_start)
{
delete[] _start;
}
_start=_finish=_endofstorage=nullptr;
}
//string 由_str _size _capacity 组成
//扩容
void reserve(size_t n)
{
if(n>capacity())
{
size_t old_size=size();//修正1
T* tmp=new T[n];
//拷贝旧空间数据到新空间
if (_start)
{
// memcpy(tmp,_start,sizeof(T)*size());
//string中的指针也会原样赋值 会导致str*指向同一空间
//然后空间释放时候导致tmp所指向的也一样释放了
//导致了浅拷贝
//所以建议用赋值来解决
for(size_t i=0;i<old_size;i++)
{
tmp[i]=_start[i];
}
//int这样的类型,赋值可以完成浅拷贝
//string这样的类型,赋值重载完成深拷贝
delete[] _start;
}
_start=tmp;
//_finish=_start+size();
//此时的size()是有问题的 因为此时
//size=_finish(原来的)-_start=0(因为初始化时候为0)-_start
//所以要提前更新 参考修正1
_finish=_start+old_size;
_endofstorage=_start+n;
}
}
void resize(size_t n,T val =T())//本质就是调用默认构造
{
if(n>size())
{
reserve(n);
while (_finish!=_start+n)
{
*_finish=val;
++_finish;
}
}
else
{
_finish=_start+n;
}
}
T& operator[](size_t i)
{
assert(i<size());
return _start[i];
}
const T& operator[](size_t i)const
{
assert(i<size());
return _start[i];
}
void clear()
{
_finish=_start;
}
size_t size() const
{
return _finish-_start;
}
size_t capacity() const
{
return _endofstorage-_start;
}
//尾插
void push_back(const T& x)
{
if (_finish==_endofstorage)
{
reserve(capacity()==0?4:capacity()*2);
}
*_finish=x;
_finish++;
}
//尾删
void pop_back()
{
assert(!empty());
--_finish;
}
bool empty() const
{
return _start==_finish;
}
iterator insert(iterator pos,const T& x)//不能用& 因为假设传的begin()调用函数iterator begin() 或者说 是表达式的形式 返回的是零时对象 具有常性
{
assert(pos>=_start);
assert(pos<=_finish);
if(_finish==_endofstorage)
{
size_t len=pos-_start;
reserve(capacity()==0?4:capacity()*2);//扩容会引起迭代器失效
//更新pos
pos=_start+len;
//指向新内存中插入的数值的位置,是有效的迭代器
}
iterator end=_finish-1;
while(end>=pos)
{
*(end+1)=*end;
--end;
}
*pos=x;
_finish++;
return pos;
}
iterator erase(iterator pos)
{
assert(pos>=_start);
assert(pos<_finish);
//挪动数据
iterator it=pos+1;
while (it!=_finish)
{
*(it-1)=*it;
it++;
}
_finish--;
return pos;//返回指向删除数据的下一个位置
//比如删除位置为2 结束后指向位置3
}
private:
iterator _start=nullptr;
iterator _finish=nullptr;//最后一个数据的下一个
iterator _endofstorage=nullptr;//已开辟内存的最后一个字节的下一个位置
};
}
7.2迭代器失效问题
问题解析
首先我们需要先了解迭代器是什么?
迭代器的本质:指向内存地址的指针
vector的迭代器本质是原生指针(或行为类似指针的对象),直接指向元素在内存中的地址。例如:
若pos迭代器指向内存地址0x1000(对应元素A),那么*pos就是访问0x1000地址上的数据。
为什么迭代器会失效呢?
迭代器的核心作用是 "指向特定元素"。删除后,pos原本指向的元素A已被移除,0x1000地址上的元素变成了B,pos不再指向 "原来的元素",语义上已失效。
cpp
//insert 部分
iterator insert(iterator pos,const T& x)
{
if(_finish==_endofstorage)
{
size_t len=pos-_start;
reserve(capacity()==0?4:capacity()*2);//扩容会引起迭代器失效
pos=_start+len;
}
这是扩容的部分 代码 引起迭代器失效的罪魁祸首就是扩容操作里的
cpp
T* tmp=new T[n];
//拷贝旧空间数据到新空间
if (_start)
{
for(size_t i=0;i<old_size;i++)
{
tmp[i]=_start[i];
}
delete[] _start;
}
_start=tmp;
这里delete空间释放是导致其出现迭代器失效的罪魁祸首。
cpp
//erase部分
iterator it=pos+1;
while (it!=_finish)
{
*(it-1)=*it;
it++;
}
_finish--;
而这里导致迭代器失效的主要原因就是因为 删除元素后 替他元素的前移 虽然指向的地址不变 但是指向的内容发生改变 所以迭代器失效哦。
解决方案
首先在std库中我们参考源代码
erase 提供了一个可以返回的 新的有效迭代器,指向 "被删除元素的下一个元素",供用户更新迭代器,避免使用失效的迭代器。
假设我们有一个 vector 存储元素 [10, 20, 30, 40],内存布局如下(地址为示例):

- 删除 pos 指向的元素(30)
执行 erase(pos) 时,vector 会将 pos 之后的元素(40)往前移动,覆盖 pos 位置的元素:
40 从 0x100C 移到 0x1008(原 30 的位置)。
最终元素变为 [10, 20, 40],_finish 指向 0x100C(尾后位置)。
- 原 pos 迭代器指向的内容
原 pos 迭代器本质是指针,仍指向 0x1008 这个内存地址,但该地址上的元素已从 30 变成了 40。
此时:若解引用原 pos(*pos),会得到 40(而非被删除的 30)。
原 pos 迭代器在语义上已失效:它本应指向 "被删除的 30",但现在指向的是 "原 30 后面的 40",与预期逻辑不符。
- erase 返回值指向的内容
erase 会返回 被删除元素的下一个元素的迭代器,即原 40 的位置(移动后 40 在 0x1008,下一个元素是尾后位置 0x100C)。
因此返回的迭代器指向 0x100C(新的尾后位置),是有效的。
而insert的解决方案 则是如果扩容 通过记录和更新pos的方式来阻止迭代器失效
cpp
if(_finish==_endofstorage)
{
size_t len=pos-_start;
reserve(capacity()==0?4:capacity()*2);//扩容会引起迭代器失效
//更新pos
pos=_start+len;
//指向新内存中插入的数值的位置,是有效的迭代器
}
这里提一嘴 insert的返回值 始终指向新插入的元素的位置,是唯一有效的迭代器,需用它更新外部迭代器(比如你在 1 3 4 数组的 1后插入个2 那迭代器返回的就是2 的位置和元素)
7.3 作为函数参数时 能否写成&的形式
iterator insert(iterator& pos,const T& x) 能否这么写?
答案是 NO!
这里举个例子
cpp
fcy::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.insert(v.begin(), 0);
当我们想在开头位置传入0时候 如果以引用的方式写 会发生报错。
为什么?
cpp
iterator begin()
{
return _start;
}
当调用 insert(v.begin(), 0) 时,begin() 返回的是一个临时迭代器对象(右值 具有常性质)。C++ 规定 "非 const 左值引用(iterator&)不能绑定到右值",此时编译会直接报错。
而这也就是我们常说的权限放大问题!
首先临时对象是具有常性的 具体为何这里会是临时对象 可以参考我以前写过的文章
7.4 reserve的两个注意点
大部分人一开始写这个功能都会这么写
cpp
void reserve(size_t n)
{
if(n>capacity())
{
T* tmp=new T[n];
//拷贝旧空间数据到新空间
if (_start)
{
memcpy(tmp,_start,sizeof(T)*size());
delete[] _start;
}
_start=tmp;
_finish=_start+size();
_endofstorage=_start+n;
}
}
乍一看没问题 其实漏洞百出
size()失效问题
cpp
size_t size() const
{
return _finish-_start;
}
问题就出现在了这三行代码中
cpp
_start=tmp;
_finish=_start+size();
_endofstorage=_start+n;
_finish = _start + size()中,size()的计算是_finish - _start,但此时_start已被更新为新空间的指针(tmp),旧的_finish指针已经失效,导致size()计算结果错误。
解决方案
记录原来的size()即可
cpp
size_t old_size=size();//提前记录原来的size
_start=tmp;
_finish=_start+old_size;
_endofstorage=_start+n;
浅拷贝导致的内存管理问题
问题出现在这里
cpp
memcpy(tmp,_start,sizeof(T)*size());
delete[] _start;
memcpy是字节级别的浅拷贝,仅适用于内置类型(如int、double)。对于自定义类型(如std::string、包含指针成员的类),memcpy会直接拷贝指针值,导致新空间和旧空间的元素共用同一块内存,析构时会触发双重释放(delete[] _start释放旧空间后,新空间的元素指针仍指向已释放的内存,后续析构新空间时再次释放)。
解决方案
用循环赋值的方法
if (_start)
{
// memcpy(tmp,_start,sizeof(T)*size());
//string中的指针也会原样赋值 会导致str*指向同一空间
//然后空间释放时候导致tmp所指向的也一样释放了
//导致了浅拷贝
//所以建议用赋值来解决
for(size_t i=0;i<old_size;i++)
{
tmp[i]=_start[i];
}
//int这样的类型,赋值可以完成浅拷贝
//string这样的类型,赋值重载完成深拷贝
delete[] _start;
}
7.5 Tips
模版赋值
cpp
template<class T>
vector(int n,T val=T())
模版赋值可以采用匿名对象的方式进行赋值
缺省赋值
cpp
iterator _start=nullptr;
iterator _finish=nullptr;//最后一个数据的下一个
iterator _endofstorage=nullptr;//已开辟内存的最后一个字节的下一个位置
缺省值是要走初始化列表的 这么写可以让初始化更方便
cpp
// vector():
// _start(nullptr)
// ,_finish(nullptr)
// ,_endofstorage(nullptr)
// {}
//因为有了缺省值所以还可以
vector()
{}
//c++11强制编译器生成默认构造
vector()=default;
多参数构造
才用初始化列表的方式
cpp
vector(initializer_list<T> il)
{
reserve(il.size());
for(auto& e:il)
{
push_back(e);
}
}