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);
}
}