目录
vector<>中是模版,模版可使用多种类型,所以vector可以传多种类型:
头插push_back()、vector大小size()、vector容量capacity():
插入函数insert()、扩容reserve()(无初始化)、扩容resize()(会初始化)
vector (size_type n, const value_type& val = value_type())
编辑initializer_list,支持迭代器,本质是可认为x是一个常量数组,其中有两个指针,分别指向开始begin()和结束end()编辑
编辑erase()更新:查看文档:erase()会返回删除元素的下一个元素的迭代器。编辑
首先先简单的讲解vector的几个方法:
构造:
示例:
push_back();
int main() { vector<int> v; //v.push_back(1); //v.push_back(2); //v.push_back(3); //v.push_back(4); //v.push_back(5); vector<int> v(10, 1); for (size_t i = 0;i < v.size();++i) { cout << v[i] << " "; } cout << endl; vector<int>::iterator it = v.begin(); while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; for (auto e : v) { cout << e << " "; } cout << endl; return 0; }
扩容机制:
void test_vector2() { vector<int> v; size_t sz = v.capacity(); //v.reserve(100); // 提前将容量设置好,可以避免一边插入一边扩容 cout << "making bar grow:\n"; for (int i = 0; i < 100; ++i) { v.push_back(i); if (sz != v.capacity()) { sz = v.capacity(); cout << "capacity changed: " << sz << '\n'; } } vector<int> a; //开好空间想直接初始化,可以用resize() a.resize(10,0); for (auto e : a) { cout << e << " "; } cout << endl; }
vs下一般1.5倍取整,linux一般2倍
插入insert():
想在某个数字前插入,那需要知道这个数字所处位置,但是vector没有find(),因此我们用到算法库中的find()
算法库头文件:
#include<algorithm>
void test_vector3() { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5); v.push_back(6); //这里打印一下整个区间 for (auto e : v ) { cout << e << " "; } cout << endl; //在这个数组空间内寻找3 //vector<int>::iterator pos = find(v.begin(), v.end(), 3); auto pos = find(v.begin(), v.end(), 3); if (pos != v.end()) { v.insert(pos, 30); } cout << endl; for (auto e : v) { cout << e << " "; } cout << endl; }void test_vector3() { vector<int> v; v.push_back(1); v.push_back(2); v.push_back(3); v.push_back(4); v.push_back(5); v.push_back(6); //这里打印一下整个区间 for (auto e : v ) { cout << e << " "; } cout << endl; //在这个数组空间内寻找3 //vector<int>::iterator pos = find(v.begin(), v.end(), 3); auto pos = find(v.begin(), v.end(), 3); if (pos != v.end()) { v.insert(pos, 30); } cout << endl; for (auto e : v) { cout << e << " "; } cout << endl; }
想要头插
v.insert(v.begin(), 7); for (auto e : v) { cout << e << " "; } cout << endl;
指定下标位置插入:
//插入到第二个位置 v.insert(v.begin() + 1, 0); for (auto e : v) { cout << e << " "; } cout << endl;
插入迭代器区间:
可以不插入自己的迭代器区间,可以插入其他容器的,只要数据类型匹配就可以(会隐式类型转换)。
//插入迭代器区间 string s("abcd"); //插入到末尾 v.insert(v.end(), s.begin(), s.end()); for (auto e : v) { cout << e << " "; } cout << endl;
结果:
为什么vector可以用算法的find(),但是string我们要自己写find()?
因为string需要支持子串的查找,和vector ,list是不一样的。
vector<>中是模版,模版可使用多种类型,所以vector可以传多种类型:
void test_vector()
{
//前面的都是整形数组,而这里是一个对象数组
vector<string> v;
//每个对象是一个vector
string s1("苹果");
//有名对象
v.push_back(s1);
//匿名对象
v.push_back(string("香蕉"));
//隐式类型转换---生成临时对象
v.push_back("草莓");
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
这句代码是什么意思?
vector<vector<int>> vv;
如图:相当于这个对象的_a 类型是vector<int>*,这个_a中的_a的类型是int*,类似于二维数组,二维数组内,就有许多一维数组
如何访问vector的第i行的第j个数据?
vv[i][j] ---->返回vector<int> 对象:vv.operator()[i] ---> vv.operator()[i].operator()[j] 返回int对象。
即可访问
int a[5][10] ----> a[i][j]本质转换成指针解引用( *(*(a+i)+j) )
几个例题:
1、只出现一次的数字:
class Solution {
public:
int singleNumber(vector<int>& nums) {
int value = 0;
for(auto e : nums)
{
value ^= e;
}
return value;
}
};
解释:
交换律:
a ^ b = b ^ a
结合律:
a ^ (b ^ c) = (a ^ b) ^ c
自反性:
a ^ a = 0
与 0 的异或:
a ^ 0 = a
代码的工作原理:
初始化:
value
被初始化为 0。遍历数组:对于数组中的每个元素
e
,执行value ^= e
,即将value
与e
进行异或操作。
如果
e
是第一次出现,value
会变成e
。如果
e
是第二次出现,value
会变成value ^ e
,由于e ^ e = 0
,所以value
会变回 0。最终结果:由于数组中只有一个数字出现一次,其他数字都出现两次,最终
value
会存储那个只出现一次的数字。
2、杨辉三角
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> vv;
//开空间并且初始化
//reserve(),并没有初始化
vv.resize(numRows);//开辟行数的空间
for(size_t i = 0;i < vv.size();++i)
{
vv[i].resize(i+1,0);//开辟列数的空间,第0行有1个数据,第1行2个数据所以要加1,默认值为0
//vv[i].size() - 1 = i
vv[i][0] = vv[i][vv[i].size() - 1] = 1;
}
//遍历
for(size_t i = 0; i < vv.size(); ++i)
{
for(size_t j = 0 ; j < vv[i].size(); ++j)
{
if(vv[i][j] == 0)
{
vv[i][j] = vv[i-1][j] + vv[i-1][j-1];
}
}
}
return vv;
}
};
代码逐段解析
初始化二维数组:
vector<vector<int>> vv; vv.resize(numRows); // 创建numRows行 for(size_t i = 0; i < vv.size(); ++i) { vv[i].resize(i+1, 0); // 每行有i+1列,初始化为0 vv[i][0] = vv[i][i] = 1; // 首尾置1 }
每行的长度递增(
i+1
),初始化为全0。每行的首(
vv[i][0]
)和尾(vv[i][i]
)元素设为1。填充中间元素:
for(size_t i = 0; i < vv.size(); ++i) { for(size_t j = 0; j < vv[i].size(); ++j) { if(vv[i][j] == 0) { // 中间未填充的元素 vv[i][j] = vv[i-1][j] + vv[i-1][j-1]; } } }
遍历所有元素,若值为0(即中间元素),则通过上一行(
i-1
)的当前列(j
)和前一个列(j-1
)之和填充。示例
以生成3行杨辉三角为例:
初始化:
行0:
[1]
行1:
[1, 1]
行2:
[1, 0, 1]
填充中间值:
行2的中间元素:
0 → 1+1=2
,结果为[1, 2, 1]
。复杂度
时间复杂度:O(n²),需要遍历所有元素填充。
空间复杂度:O(n²),存储整个杨辉三角。
边界情况处理
numRows=0:返回空数组。
numRows=1:返回单行
[[1]]
。
模拟实现vector精简版(有瑕疵)注重了解周边知识
普通构造vector()
头插push_back()、vector大小size()、vector容量capacity():
#include<iostream>
#include<assert.h>
namespace bit
{
template<class T>
class vector
{
public:
typedef T* iterator;vector()
{}size_t size()
{
return _finish - _start;
}size_t capacity()
{
return _endofstorage - _start;
}T& operator[](size_t pos)
{
assert(pos < size());
return _start[pos];
}
void push_back(const T& val)
{
if (_finish == _endofstorage)//满了就需要扩容
{
//需要知道当前的空间大小就需要增加两个函数size、capacitysize_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
T* tmp = new T[newcapacity];
memcpy(tmp, _start, size() * sizeof(T));
delete[] _start;
_start = tmp;
// 运行到这里的时候_start已经被释放并且重新赋值
// 指向了tmp所指向的位置,而_finish还是处于原来的内存空间,所以
// 这时候计算size(),即_finish - _start,会出现指针算术错误,因为两者不属于同一块内存。
_finish = tmp + size();
_endofstorage = tmp + newcapacity;
}
*_finish = val;
++_finish;}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
};void test_vector1()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);for (size_t i = 0; i < v.size();++i)
{
cout << v[i] << " ";
}
cout << endl;
}
}
问题1:
通过分析阅读以上代码,可以知道在释放_start的时候,应该先保存旧size()的大小,因此我们需要设置一个新的变量来存旧size()的大小
void push_back(const T& val)
{
if (_finish == _endofstorage)//满了就需要扩容
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
size_t old_size = size();
T* tmp = new T[newcapacity];
memcpy(tmp, _start, old_size * sizeof(T));
delete[] _start;
_start = tmp;
_finish = tmp + old_size;
_endofstorage = tmp + newcapacity;
}
*_finish = val;
++_finish;
}
~vector()析构函数
~vector()
{
delete[] _start;
_finish = _endofstorage = _start = nullptr;
}
iterator begin()、end()迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
测const vector容器
#include<iostream>
#include<assert.h>
namespace bit
{
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()
{}
~vector()
{
delete[] _start;
_finish = _endofstorage = _start = nullptr;
}
size_t size() const
{
return _finish - _start;
}
size_t capacity() const
{
return _endofstorage - _start;
}
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& val)
{
if (_finish == _endofstorage)//满了就需要扩容
{
size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
size_t old_size = size();
T* tmp = new T[newcapacity];
memcpy(tmp, _start, old_size * sizeof(T));
delete[] _start;
_start = tmp;
_finish = tmp + old_size;
_endofstorage = tmp + newcapacity;
}
*_finish = val;
++_finish;
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
};
}
测试函数print_vector()
需要用到模版,以便于使用到不同的类型
//template<typename T> //定义模版
template<class T> //定义模版 -- 新版//template<typename T> //定义模版 template<class T> //定义模版 -- 新版 void print_vector(const vector<T>& v) { for (size_t i = 0; i < v.size();++i) { cout << v[i] << " "; } cout << endl; //vector<int>::const_iterator it = v.begin(); auto it = v.begin(); while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; for (auto e : v) { cout << e << " "; } cout << endl; }
请注意:
出现这种问题的原因是因为
编译器不认识这一块的类型就会出现这样的报错,而这里出错的原因在于:
模版出现在一个地方只有已经被实例化了才具备意义,此时vector<T>还未被实例化,
一个类里面的内嵌类型或者静态成员变量,由于vector<T>不具备意义编译器不会去找,因此不能区分const_iterator是类型还是静态成员变量:解决办法:
//typename表明后面是一个类型,编译会先通过,等在后面实例化了再取,再去找
typename vector<T>::const_iterator it = v.begin();所以用auto很方便,编译器直接知道这里是一个类型。
尾删pop_back()、判空empty()
void pop_back() { assert(empty()); --_finish;//不用去删数据直接--尾部位置就可以,为空不能--,所以需要函数判空empty() } bool empty() { return _start == _finish; }
插入函数insert()、扩容reserve()(无初始化)、扩容resize()(会初始化)
可看见这里实际上就是原来写push_back()的代码,所以push_back()的代码可以改为:
void push_back(const T& val) { if (_finish == _endofstorage)//满了就需要扩容 { reserve(capacity() == 0 ? 4 : capacity() * 2); } *_finish = val; ++_finish; }
void insert(iterator pos, const T& val) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _endofstorage) { reserve(capacity() == 0 ? 4 : capacity() * 2); } iterator it = _finish - 1;//类似最后一个位置的下标 while (it >= pos) { *(it + 1) = *it; --it; } *pos = val; ++_finish; }
想一想如果传参是void insert(size_t pos, const T& val),在头插的时候就会出现问题,pos = 0,那么it 是size_t 是不会等于-1的就会出错,但是这里用了iterator就不会出现这样的问题,因为pos是迭代器,是一个指针,指针是不会为0的。
但此时代码还存在问题:
v2.insert(v2.begin() + 2, 5.11); print_vector(v2); v2.insert(v2.begin() + 2, 5.11); print_vector(v2); v2.insert(v2.begin() + 2, 5.11); print_vector(v2); v2.insert(v2.begin() + 2, 5.11); print_vector(v2);
当我多次重复插入
运行代码:
这是为什么,怎么出现了这样的表达式?
原因因为扩容的问题:原来的空间因为扩容已经被释放,pos所处的内存位置还是旧空间上,但是由于被释放,成为了一个野指针。---> 这就是迭代器失效,pos失效,pos所指向的空间已经释放,所以得让pos指向新位置。
所以如果扩容了要更新pos --->:
先算好pos所处的相对位置,即算出他与首位的差值,从而便于修改。
void insert(iterator pos, const T& val) { assert(pos >= _start); assert(pos <= _finish); if (_finish == _endofstorage) { size_t len = pos - _start;//算pos所处的相对位置,便于扩容时,pos一并跟着修改 reserve(capacity() == 0 ? 4 : capacity() * 2); pos = _start + len; } iterator it = _finish - 1;//最后一个位置的下标 while (it >= pos) { *(it + 1) = *it; --it; } *pos = val; ++_finish; }
输出结果正确:
因而push_back()还可以修改为:
void push_back(const T& val) { insert(end(), val); }
删除某个位置erase()
挪动数据覆盖的问题,将it置于需删除数据的下一个位置,依次++it,直到最后一个数移动完毕
void erase(iterator pos) { assert(pos >= _start); assert(pos < _finish); iterator it = pos + 1; while (it < _finish) { *(it - 1) = *it; ++it; } --_finish; }
popback()函数又可以复用这个函数
void pop_back() { erase(--end()); //尾删 }
指定大小并且resize()
void resize(size_t n,const T& val = T()) { }
resize()需要给一个缺省值,不能给0,因为val可以是各种类型,因此可以用上匿名对象T(),无参匿名对象。如果T不是自定义类型,而是内置类型,内置类型没有构造函数,但是现在有了,因为c++ 模版出来后,对内置类型升级,添加了构造函数。
如图:
void resize(size_t n,const T& val = T()) //如果容量比我大,我就扩容,容量比我小,我就缩容 { if (n > size()) { reserve(n); //插入 while (_finish < _start + n) { *_finish = val; ++_finish; } } else { //删除 _finish = _start + n;//相当于只保留n个数据 } }
测试代码:
void test_vector2() { vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(5); print_vector(v1); v1.resize(10); print_vector(v1); }
运行结果:
拷贝构造vector(const vector<T>& v)
vector(const vector<T>& v) //v = v1
{
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
赋值vector::operator=、交换swap()
具体可以看我的string章节的内容怎么用现代写法来写赋值
//赋值 v1 = v3 现代写法,所有深拷贝的类都可以用这个写法
vector<T>& operator=(vector<T> v) //v = v3,注意不要写成引用了,会改变v3
{
swap(v);
return *this;
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
迭代器区间构造:template <class InputIterator>
vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type()); |
//迭代器区间构造,容器一般都会支持一个迭代器区间
template <class InputIterator>
//类模版的成员函数可以是函数模版
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
类模板与成员函数模板:
类模板(如
vector
)允许用通用类型参数定义类,实例化时指定具体类型(如vector<int>
)。成员函数模板指类模板中的成员函数自身也可以是模板,拥有独立于类的模板参数。此处构造函数使用
InputIterator
作为模板参数,允许接受任意迭代器类型。构造函数分析:
template <class InputIterator> vector(InputIterator first, InputIterator last)
是一个模板构造函数。它接受两个迭代器
first
和last
,遍历并将元素添加到容器中(通过push_back
)。该构造函数不依赖类模板的元素类型
T
,只要迭代器解引用后的类型可转换为T
,就能构造vector<T>
。
vector (size_type n, const value_type& val = value_type())
当我们屏蔽掉上面的第一个构造函数,下面的函数编译通过,这是为什么?因为他可以匹配这两个函数,编辑器也选择了第一个函数,后面的类型不同就不会匹配第一个函数
解决办法通过查看源代码,
写一个重载版本:
c++11支持的用法:
initializer_list,支持迭代器,本质是可认为x是一个常量数组,其中有两个指针,分别指向开始begin()和结束end()看看底层:
如图:
因此为了支持vector<int> v1 = {1,2,3,4,5,6,7,8,9},我们需要再写一个
initializer_list 的构造函数
//vector<int> v1 = { 1,2,3,4,5,6,7,8,9 }; vector(initializer_list<T> il) { reserve(il.size()); for (auto& e : il) { push_back(e); } }
c++11也支持去掉括号的直接构造(有点融合Python):
修复扩容的bug
实际上在扩容上有bug,如图:
当我在插入string时,多插入几个后程序出现崩溃
调试代码,确认好像问题出现在析构上,但是实际上析构不会出现问题
实际上问题出现在memcpy(tmp , _start , old_size * sizeof(T))上,
请清楚memcpy是如何拷贝的:
memcpy只把数据拷贝下来了,但是没有拷贝到指向的空间,我们期望的是拷贝到新开的空间,释放上面的_start空间就不会有影响。如何解决?
在C++中,
memcpy(void*)
函数按字节复制内存,而不会调用对象的拷贝构造函数或赋值运算符。当类型T
需要深拷贝时(例如管理动态内存、文件句柄等资源),直接使用memcpy
会导致以下问题:
浅拷贝问题:
memcpy
会直接复制对象的二进制内容。如果T
包含指针或资源句柄,新旧对象的指针将指向同一块内存。当旧内存被释放(delete[] _start
)时,新对象中的指针会变成悬垂指针,后续访问将引发未定义行为(如崩溃、数据损坏)。绕过对象的语义:
如果T
的拷贝构造函数或赋值运算符包含关键逻辑(如引用计数、资源分配),memcpy
会绕过这些逻辑,导致资源泄漏或状态不一致。析构函数重复释放:
旧对象被销毁时,其析构函数可能会释放资源(如delete
指针)。由于新对象的指针与原对象指向同一资源,当新对象最终销毁时,会再次释放同一资源,导致双重释放错误。正确做法:使用循环逐个拷贝
通过循环
tmp[i] = _start[i]
,实际调用的是T
的赋值运算符(或拷贝构造函数,取决于实现)。这确保了:
每个元素的深拷贝逻辑(如动态内存复制)被正确执行。
对象的生命周期管理(构造/析构)符合C++规则。
示例说明
假设
T
是std::string
:
memcpy
:复制后,新旧string
的指针指向同一内存。旧内存释放后,新string
成为悬垂指针。循环赋值:调用
std::string::operator=
,执行深拷贝,新旧string
独立管理各自的字符数组。结论
memcpy
仅适用于平凡可复制类型(如基本类型、结构体且无资源管理)。对于需要深拷贝或非平凡类型的对象,必须通过循环逐个拷贝,确保语义正确性和资源安全性。
修改后代码,使用赋值的办法:
void reserve(size_t n) { if (n > capacity()) { T* tmp = new T[n]; size_t old_size = size(); //memcpy(tmp, _start, old_size * sizeof(T)); for (size_t i = 0; i < old_size; i++) { tmp[i] = _start[i]; } delete[] _start; _start = tmp; _finish = tmp + old_size; _endofstorage = tmp + n; } }
insert()导致迭代器失效
迭代器失效,扩容后迭代器还指向旧空间(c++貌似已经修复了这个问题,但是这里还是讲一下不能实现的原因),实际上我们的vector对象已经有了新空间:
可以加引用,但是传值会使用其他办法,所以我们不使用引用。而且临时对象具有常性,不能使用引用。库里也没有解决insert失效,因为这里本来就不能使用insert,我们需要在外面重新更新。
重新更新:
erase()会不会导致迭代器失效?会,不能使用迭代器
vector<int>::iterator it = v1.begin(); //删除偶数 while (it != v1.end()) { if (*it % 2 == 0) { v1.erase(it);//会不会导致迭代器失效 } ++it; } print_vector(v1); cout << endl; cout << *it << endl;
第一种:
输出:
输入这样的测试代码
vector<int> v1; v1.push_back(1); v1.push_back(2); v1.push_back(3); v1.push_back(4); v1.push_back(5); v1.push_back(4);
最后出现这样的报错,程序崩溃:
这都是为什么呢?
第一种:erase中有挪用数据后++,这里的循环也有++,因此会跳过某些数,因此4没被删掉。
第二种:因为插入的个数是偶数,删除4之后,end()指向空,it又往后++,就会一直错开,错过判断条件。
在vs下使用erase()之后就不能再使用迭代器。linux可以
erase()更新:查看文档:erase()会返回删除元素的下一个元素的迭代器。
使用重新更新的方法:
更新后代码:
iterator erase(iterator pos) { assert(pos >= _start); assert(pos < _finish); iterator it = pos + 1; while (it < _finish) { *(it - 1) = *it; ++it; } --_finish; return pos; }
结语:
随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。
在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。 你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。