系列文章目录
文章目录
前言
vector是可变大小数组的类
vector的介绍及使用
vector的介绍
- vector是表示可变大小数组的序列容器。
- 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
- 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。**其做法是,分配一个新的数组,然后将全部元素移到这个数组。**就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
- vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
- 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
- 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好
- vector和string的区别:vector里面给char,虽然他们底层都是数组中存char但是还是不一样的,s对象中指向的空间结尾有’\0’,string与vector都有自己专用的需求(接口),所以功能是不一样的。
vector的使用
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); | 使用迭代器进行初始化构造 |
void test_vector1()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
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 ch : v)
{
cout << ch << " ";
}
cout << endl;
vector<int> copy_v(v);
for (auto ch : copy_v)
{
cout << ch << " ";
}
cout << endl;
}
int main()
{
test_vector1();
return 0;
}
void test_vector2()
{
vector<int> v1(10, 1);
for (auto e : v1)
{
cout << e << " ";
}
cout << endl;
vector<int> v2(v1.begin(), v1.end());//迭代器区间左闭右开
for (auto e : v2)
{
cout << e << " ";
}
cout << endl;
string s1("hello world");
vector<int> s2(s1.begin(), s1.end());//可以传其他容器的迭代器
for (auto e : s2)
{
cout << e << " ";
}
cout << endl;
}
2. vector iterator 的使用
除了string和vector,剩下的容器都用迭代器
iterator的使用 | 接口说明 |
---|---|
begin +end(重点) | 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置 的iterator/const_iterator |
rbegin + rend | 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator |
void test_vector3()
{
vector<int> v;
//vector<int> ::reverse_iterator rit = v.rbegin();
auto rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
3. vector 空间增长问题
容量空间 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize(重点) | 改变vector的size |
reserve (重点) | 改变vector的capacity |
- capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
- reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
- resize在开空间的同时还会进行初始化,影响size。
- 尽量不要缩容,C++不支持原地缩容,一般会重新开辟一块空间,再将数据拷贝过来,最后释放原空间。
void test_vector4()
{
vector<char> v;
cout << v.max_size() << endl;
}
void TestVectorExpand()
{
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "making v 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';
}
}
}
//结果
making v grow:
capacity changed: 1
capacity changed: 2
capacity changed: 3
capacity changed: 4
capacity changed: 6
capacity changed: 9
capacity changed: 13
capacity changed: 19
capacity changed: 28
capacity changed: 42
capacity changed: 63
capacity changed: 94
capacity changed: 141
//g++运行结果:linux下使用的STL基本是按照2倍方式扩容
making foo grow:
capacity changed: 1
capacity changed: 2
capacity changed: 4
capacity changed: 8
capacity changed: 16
capacity changed: 32
capacity changed: 64
capacity changed: 128
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
void TestVectorExpandOP()
{
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';
}
}
}
4. vector 增删查改
vector增删查改 | 接口说明 |
---|---|
push_back(重点) | 尾插 |
pop_back (重点) | 尾删 |
find | 查找。(注意这个是算法模块实现,不是vector的成员接口) |
insert | 在position之前插入val |
erase | 删除position位置的数据 |
swap | 交换两个vector的数据空间 |
operator[] (重点) | 像数组一样访问 |
5. vector 的其他接口
可以给所有容器用
void test_vector6()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
vector<int>::iterator pos = find(v.begin(), v.end(), 3);
if (pos != v.end())
{
v.insert(pos, 20);
}
for (auto ch : v)
{
cout << ch << " ";
}
cout << endl;
pos = find(v.begin(), v.end(), 3);
//迭代器失效要重新找
v.erase(pos);
for (auto ch : v)
{
cout << ch << " ";
}
cout << endl;
}
迭代器的区间是左闭右开的
6. vector 在OJ中的使用
vector深度剖析
内置类型构造函数
内置类型也是有构造函数的,用于实现区间构造
template<class T>
void f()
{
T x = T();//调用默认构造
cout << x << endl;
}
void test_vector2()
{
//内置类型有构造函数
int i = int();
int j = int(1);
//int* p = int* ();
f<int>();
f<int*>();
f<double>();
}
//[first, last)
template <class InputIterator>//允许类的成员函数再是函数模板
vector(InputIterator first, InputIterator last)
{
while (first != last)
{
push_back(*first);
++first;
}
}
迭代器失效问题
inert()
void insert(iterator pos, const T& val)
{
assert(pos <= _finish);
assert(pos >= _start);
if (_finish == _end_of_storage)
{
//扩容
reserve(capacity() == 0 ? 4 : capacity() * 2);
//迭代器pos失效
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
}
void insert(iterator pos, const T& val)
{
assert(pos <= _finish);
assert(pos >= _start);
if (_finish == _end_of_storage)
{
//扩容
size_t = len = pos - _start;
reserve(capacity() == 0 ? 4 : capacity() * 2);
//扩容后更新,解决pos失效问题,让pos指向新空间的元素地址
pos = _start + len;
}
iterator end = _finish - 1;
while (end >= pos)
{
*(end + 1) = *end;
--end;
}
*pos = val;
++_finish;
}
void insert(iterator pos, const T& val)
{
//不能用引用来传入拷贝的临时对象,因为其具有常性
assert(pos <= _finish);
assert(pos >= _start);
if (_finish == _end_of_storage)
{
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;
}
void test_vector4()
{
vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (size_t i = 0; i < v1.size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
yyf::iterator pos = find(v1.begin(), v1.end(), 2);
v1.insert(pos, 6);
for (size_t i = 0; i < v1.size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
(*pos)++;//想让2+1,但是pos指向了新插入的元素6
for (size_t i = 0; i < v1.size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
}
iterator insert(iterator pos, const T& val)
{
//不能用引用来传入拷贝的临时对象,因为其具有常性
assert(pos <= _finish);
assert(pos >= _start);
if (_finish == _end_of_storage)
{
//扩容
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 = val;
++_finish;
return pos;
}
但最好不要这要做,所以insert以后我们认为pos失效了,不能再使用
erase()
void test_vector5()
{
std::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (size_t i = 0; i < v1.size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
auto pos = find(v1.begin(), v1.end(), 2);
if (pos != v1.end())
{
v1.erase(pos);
}
(*pos)++;
//vs下std的pos失效了,不能访问并且进行强制检查
for (size_t i = 0; i < v1.size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
}
void erase(iterator pos)
{
assert(pos <= _finish);
assert(pos >= _start);
iterator start = pos + 1;
while(start != _finish)
{
*(start - 1) = *start;
++start;
}
--_finish;
}
void test_vector6()
{
yyf::vector<int> v1;
v1.push_back(1);
v1.push_back(2);
v1.push_back(3);
v1.push_back(4);
v1.push_back(5);
for (size_t i = 0; i < v1.size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
// 要求删除所有的偶数
std::vector<int>::iterator it = v1.begin();
while (it != v1.end())
{
if (*it % 2 == 0)
{
it = v1.erase(it);//g++中std的erase会指向删除数据的后一个数据的新位置
}
else
{
++it;
}
}
for (size_t i = 0; i < v1.size(); ++i)
{
cout << v1[i] << " ";
}
cout << endl;
}
iterator erase(iterator pos)
{
assert(pos <= _finish);
assert(pos >= _start);
iterator start = pos + 1;
while(start != _finish)
{
*(start - 1) = *start;
++start;
}
--_finish;
return pos;
}
erase以后我们认为pos失效了,不能再使用
vs下会有强制检查,erase以后迭代器不能访问。
g++没有强制检查,会导致一系列结果未定义
匿名对象生命周期延长
vector(size_t n, const T& val = T())
{
}
- const T& val = T()就是创建了一个匿名对象,通过构造函数,然后再引用
- 匿名对象生命周期只在当前一行,因为这行之后就没人会用它了
- const引用会延长匿名对象的生命周期到引用对象域结束,因为引用就是起别名,就是这个匿名对象
对象 - 匿名对象和临时对象具有常性
vector 内存布局
深拷贝与浅拷贝
//拷贝构造
vector(const vector<T>& v)
{
reserve(v.capacity());
//memcpy(_start, v._start, sizeof(T) * v.size());
for (size_t i = 0; i < v.size(); ++i)
{
_start[i] = v._start[i];
}
_finish = _start + v.size();
_end_of_storage = _start + v.capacity();
}
//赋值重载
void swap(vector<T>& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
//赋值重载现代写法
vector<T>& operator=(vector<T> v)
{
//先构造拷贝了一个v,然后进行引用交换,最后析构掉原来的*this.
swap(v);
return *this;
}
//扩容
void reserve(size_t n)
{
if (n > capacity())
{
//提前记录size
size_t sz = size();
T* tmp = new T[n];
//开新空间
if (_start)
{
//memcpy(tmp, _start, sizeof(T) * size());
for (size_t i = 0; i < sz; ++i)
{
tmp[i] = _start[i];
}
//拷贝旧数据
delete[] _start;
}
_start = tmp;
_finish = tmp + sz;
_end_of_storage = tmp + n;
}
}
void test_vector11()
{
//类模板类型为自定义类型时要深拷贝
yyf::vector<std::string> v3(3, "111111111111111111111");
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
vector<std::string> v4(v3);
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
v4.push_back("2222222222222222222");
v4.push_back("2222222222222222222");
v4.push_back("2222222222222222222");
for (auto e : v4)
{
cout << e << " ";
}
cout << endl;
class Solution {
public:
vector<vector<int>> generate(int numRows) {
vector<vector<int>> nums;
nums.resize(numRows, vector<int>());
for (int i = 0; i < nums.size(); i++)
{
nums[i].resize(i + 1, 0);
nums[i][0] = nums[i][nums[i].size() - 1] = 1;
}
for (size_t i = 0; i < nums.size(); ++i)
{
for (size_t j = 0; j < nums[i].size(); ++j)
{
if (nums[i][j] == 0)
{
nums[i][j] = nums[i - 1][j] + nums[i - 1][j - 1];
}
}
}
return nums;
}
};
yyf::vector<vector<int>> ret = Solution().generate(5);
for (size_t i = 0; i < ret.size(); ++i)
{
for (size_t j = 0; j < ret[i].size(); ++j)
{
cout << ret[i][j] << " ";
}
cout << endl;
}
cout << endl;
}
memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
如果拷贝的是自定义类型的元素,memcpy即高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起野指针问题甚至程序崩溃。
总结
vector是可以变大小任意类型的数组的类。
会当凌绝顶,一览众山小。——杜甫