目录
一、vector的介绍及使用
1.1 vector的介绍
std::vector 是表示可变大小数组的序列容器,它能够存储一组相同类型的元素,并且可以动态调整大小。
- 元素在内存中是连续存储的,因此支持高效的随机访问(通过下标访问)。
- 与其它动态序列容器相比(deque, list and forward_list), vector 在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起 list 和 forward_list 统一的迭代器和引用更好。
1.2 vector的使用
1.2.1 vector的定义
(constructor)构造函数声明 | 接口说明 |
vector() (重点) | 无参构造 |
vector(size_type n, const value_type& val = value_type()) | 构造大小为 n 的容器并都初始化为 val |
vector (const vector& x);(重点) | 拷贝构造 |
vector (InputIterator first, InputIterator last) | 使用迭代器进行初始化构造 |
#include <vector>
int main() {
// 创建一个空的 vector
std::vector<int> vec1;
// 创建包含 5 个元素的 vector,初始值为 0
std::vector<int> vec2(5);// [0,0,0,0,0]
// 创建包含 5 个元素的 vector,初始值为 10
std::vector<int> vec3(5, 10);// [10,10,10,10,10]
// 拷贝构造
std::vector<int> vec4(vec3);// [10,10,10,10,10]
// 迭代器构造
std::vector<int> vec5(vec4.begin() + 1, vec4.end() - 1);// [10,10,10]
// 使用初始化列表创建 vector
std::vector<int> vec6 = { 1, 2, 3, 4, 5 };
//使用数组初始化
int arr[] = { 1, 2, 3, 4, 5, 6 };
std::vector<int> vec7(arr, arr + 5); // 用数组的前 5 个元素初始化 [1,2,3,4,5]
//int可替换为其他类型,包括自定义类型
std::vector<char> v1;
std::vector<int*> v2;// 存储指针类型时,需要注意内存管理
std::vector<std::string> v3;
std::vector<vector<int>> v4;
return 0;
}
1.2.2 vector iterator 的使用
iterator的使用 | 接口说明 |
begin() + end()(重点) | 获取第一个数据位置的 iterator/const_iterator, 获取最后一个数据的下一个位置的 iterator/const_iterator |
rbegin + rend | 获取最后一个数据位置的 reverse_iterator,获取第一个数据前一个位置的reverse_iterator |
1.2.3 vector 的容量操作
容量空间 | 接口说明 |
size() | 获取数据个数 |
capacity() | 获取容量大小 |
empty() | 判断是否为空 |
resize (size_type n, value_type val = value_type())(重点) | 调整大小为 n ,多出的空间用 val 填充(默认用 value_type() 填充) |
reserve (size_type n) (重点) | 预留 n 的容量 |
max_size() | 返回容器可以容纳的最大元素数量 |
shrink_to_fit() | 缩容,使 capacity 尽可能接近 size |
- resize 在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
- reserve:不改变有效元素个数,当 reserve 的参数小于底层空间总大小时,reserve 不会改变容量大小(当数据量大时,用reserve预留好空间以免自动扩容次数过多)
capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。
1.2.4 vector 增删查改
函数 | 接口说明 |
push_back (const value_type& val)(重点) | 尾插 |
pop_back()(重点) | 尾删 |
insert | 在 pos 之前插入 val |
erase | 删除 pos 位置的数据 |
swap (vector& x) | 交换两个vector的数据空间 |
clear() | 删除所有元素 |
operator[](重点) | 返回 n 处元素的引用 |
insert 及 erase 用法:
std::vector<int> vec = { 1, 2, 3, 4, 5 };
// 在索引为2的位置插入元素99
vec.insert(vec.begin() + 2, 99);// [1,2,99,3,4,5]
// 删除索引为2的元素
vec.erase(vec.begin() + 2);
// 在索引为2的位置插入3个99
vec.insert(vec.begin() + 2, 3, 99);// [1,2,99,99,99,3,4,5]
// 删除索引为2到5的元素(不包括索引5)
vec.erase(vec.begin() + 2, vec.begin() + 5);
// 在索引为2的位置插入另一个容器的内容
std::vector<int> vec2 = { 99, 100, 101 };
vec.insert(vec.begin() + 2, vec2.begin(), vec2.end());// [1,2,99,100,101,3,4,5]
vec.erase(vec.begin() + 2, vec.begin() + 5);
// 在索引为2的位置插入初始化列表的内容
vec.insert(vec.begin() + 2, { 99, 100, 101 });// [1,2,99,100,101,3,4,5]
//删除所有满足条件的元素(结合迭代器)这里以删除99<=数字<=101的为例
vec.erase(std::remove_if(vec.begin(), vec.end(), [](int num) {
return num >= 99 && num <= 101;
}), vec.end());
查找find: std::vector
本身并没有提供 find 成员函数。但是,可以使用标准库中的算法函数 std::find
来在 std::vector
中查找元素。std::find
是定义在头文件 <algorithm>
中的一个通用算法,用于在范围中查找特定值。
示例:
#include <iostream>
#include <vector>
#include <algorithm> // 包含 std::find
int main() {
std::vector<int> vec = { 1, 2, 3, 4, 5 };
// 查找值为 3 的元素
auto it = std::find(vec.begin(), vec.end(), 3);
// 一般和!=end()搭配使用
if (it != vec.end()) {
std::cout << "目标值: " << *it << " 在索引: " << std::distance(vec.begin(), it) << std::endl;
//std::distance相当于
std::cout << "目标值: " << *it << " 在索引: " << it - vec.begin() << std::endl;
}
else {
std::cout << "Value not found!" << std::endl;
}
return 0;
}
1.2.5 vector 迭代器失效问题
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector 的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
对于 vector 可能会导致其迭代器失效的操作有:
- 会引起其底层空间改变的操作,都有可能是迭代器失效,比如:resize、reserve、insert、assign、push_back等。
- 指定位置元素的删除操作--erase
vector<int> v{1,2,3,4,5,6};
auto it = v.begin();
// 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容
v.resize(100, 8);
// 改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变
v.reserve(100);
// 插入元素期间,可能会引起扩容,而导致原空间被释放
v.insert(v.begin(), 0);
v.push_back(8);
// 给vector重新赋值,可能会引起底层容量改变
v.assign(100, 8);
// 出错
while(it != v.end())
{
cout<< *it << " " ;
++it;
}
以上操作,都有可能会导致 vector 扩容,也就是说 vector 底层原理旧空间被释放掉,而在打印时,it 还使用的是释放之间的旧空间,在对 it 迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。
// 使用find查找3所在位置的iterator
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
// 删除pos位置的数据,导致pos迭代器失效。
v.erase(pos);
cout << *pos << endl; // 此处会导致非法访问
erase 删除 pos 位置元素后,pos 位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效。但是,如果 pos 刚好是最后一个元素,删完之后 pos 刚好是 end 的位置,而 end 位置是没有元素的,那么 pos 就失效了。因此删除 vector 中任意位置上元素时,vs 就认为该位置迭代器失效了。
- Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。虽然可能运行,但结果也是不对的。
- 同样,string类型在以上类似操作之后,迭代器也会失效
迭代器失效解决办法:在使用前,对迭代器重新赋值
二、vector的模拟实现
#pragma once
#include <assert.h>
namespace vec
{
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(const vector<T>& v)// 或const vector& v
{
reserve(v.capacity());
for (auto& e : v)
{
push_back(e);
}
}
vector(size_t n, const T& val = T())
{
reserve(n);
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
}
vector(int n, const T& val = T())
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(val);
}
}
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_endofstorage, v._endofstorage);
}
vector<T>& operator=(vector<T> v)// 拷贝-交换模式,这里传值不传引用
{
swap(v);//swap 将*this中的旧资源交换到v
//而临时拷贝会自动析构释放掉旧资源
return *this;
}
//析构函数
~vector()
{
delete[] _start;
_start = _finish = _endofstorage = nullptr;
}
size_t capacity() const
{
return _endofstorage - _start;
}
size_t size() const
{
return _finish - _start;
}
void reserve(size_t n)
{
if (n > capacity())
{
T* tmp = new T[n];
size_t sz = size();
if (_start)
{
for (size_t i = 0; i < sz; i++)
{
tmp[i] = _start[i];// 利用了对象类型 T 的(operator=)
}
//memcpy(tmp, _start, sizeof(T) * size()); memcpy会导致浅拷贝问题,只会拷贝指针的值,指针指向的空间会被delete释放
delete[] _start;
}
_start = tmp;
_finish = _start + sz;
_endofstorage = _start + n;
}
}
void resize(size_t n, const T& val = T())
{
if (n <= size())
{
_finish = _start + n;
}
else {
reserve(n);
while (_finish < _start + n)
{
*_finish = val;
++_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& val)
{
//判断是否需要扩容
if (_finish == _endofstorage)
{
reserve(capacity() == 0 ? 4 : capacity() * 2);
}
*_finish = val;
++_finish;
}
void insert(iterator pos, const T& val)
{
assert(pos >= _start && pos <= _finish);
if (_finish == _endofstorage)
{
//扩容会导致迭代器pos失效,所以要更新pos
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
}
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 = nullptr;// 数据的起始位置
iterator _finish = nullptr;// 动态数组最后一个元素之后的位置
iterator _endofstorage = nullptr;// 动态数组分配的内存末尾
};
}
注意:如果对象中涉及到资源管理时,不能使用 memcpy 进行对象之间的拷贝,因为 memcpy 是浅拷贝,可能会引起内存泄漏甚至程序崩溃。