🌠 作者:@阿亮joy.
🎆专栏:《吃透西嘎嘎》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
目录
- 👉vector 的介绍👈
- 👉vector 的使用👈
- vector 的构造函数和遍历
- vector 空间增长问题
- 👉vector 动态二维数组的理解👈
- 👉vector 的增删查改👈
- 👉总结👈
👉vector 的介绍👈
- vector是表示可变大小数组的序列容器。
- 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素 进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。
- 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
- vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存 储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
- 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增 长。
- 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末 尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。
使用STL的三个境界:能用,明理,能扩展 ,那么下面学习vector,我们也是按照这个方法去学习。
👉vector 的使用👈
vector学习时一定要学会查看文档:vector的文档介绍,vector在实际中非常的重要,在实际中我们熟悉常见的接口就可以,下面列出了哪些接口是要重点掌握的。
vector 的构造函数和遍历
constructor构造函数声明 | 接口说明 |
---|---|
vector()(重点) | 无参构造 |
vector(size_type n, const value_type& val = value_type()) | 构造并初始化n个val |
vector (const vector& x); (重点) | 拷贝构造 |
vector (InputIterator first, InputIterator last); | 使用迭代器进行初始化构造 |
vector的构造和遍历代码演示
void vectorTest1()
{
vector<int> first; // empty vector of ints
vector<int> second(4, 100); // four ints with value 100
vector<int> third(second.begin(), second.end()); // iterating through second
vector<int> fourth(third); // a copy of third
// 用数组来初始化
// the iterator constructor can also be used to construct from arrays:
int myints[] = { 16,2,77,29 };
vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));
// []+下标遍历,只有string和vector支持这种方式
for (size_t i = 0; i < fifth.size(); ++i)
{
cout << fifth[i] << ' ';
}
cout << endl;
// 迭代器遍历
vector<int>::iterator it = fifth.begin();
while (it != fifth.end())
{
cout << *it << ' ';
++it;
}
cout << endl;
// 范围for遍历,底层为迭代器
for (auto e : fifth)
{
cout << e << ' ';
}
cout << endl;
}
vector iterator
iterator的使用 | 接口说明 |
---|---|
begin + end(重点) | 获取第一个数据位置iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator |
rbegin + rend | 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator |
为什么
vector<char> str
和string str
无法取代对方
- 网络上发送一段字符串,字符串需要以
'\0'
结束,而 string 无法被发送。- string 支持比较字符串大小,比较常见;而 vector 的比较大小不常见,也没有什么意义。
- 函数接口不完全相同。如 string 类有 +=,而 vector 没有 +=。
vector 空间增长问题
容量空间 | 接口说明 |
---|---|
size | 获取数据个数 |
capacity | 获取容量大小 |
empty | 判断是否为空 |
resize(重点) | 改变vector的size |
reserve (重点) | 改变vector的capacity |
reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
resize在开空间的同时还会进行初始化,影响size
reserve 函数接口不会使 vector 缩容,缩容一般是异地缩容,缩容比较大。
resize 的函数接口也能说明内置类型需要有构造函数。
注:在程序里看到的空间都是虚拟的进程空间,进程会提高页表映射和物理内存建立映射。只有在内核层通过页表才能找到物理地址。
注:reserve 只会修改capacity,不会影响size。
为什么上面的程序会报错呢?因为 reserve 函数将 capacity改成了10,而 size 还是0,所以就会越界访问,触发了 [ ]运算符重载的 assert 报错。
我们可以将上面的代码,改成下面代码的样子,就不会出现这个问题了。
void vectorTest3()
{
vector<int> v;
v.reserve(10);
// 或者将v.reserve(10)换成v.resize(10)
for (size_t i = 0; i < 10; ++i)
{
//v[i] = i;
v.push_back(i);
}
}
测试 vector 的默认扩容机制
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';
}
}
}
VS的运行结果
Linux的运行结果
capacity的代码在VS和g++下分别运行会发现,VS下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。VS是PJ版本STL,g++是SGI版本STL。
为什么扩容多数扩2倍
- 2 倍比较合适
- 每次扩少了,会导致频繁扩容
- 每次扩多了,用不完,存在空间浪费
如果已经确定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';
}
}
}
shift_to_fit缩容
释放空间由于内存管理的一些原因,不允许分段释放空间,只能整体释放,一般为异地缩容。
👉vector 动态二维数组的理解👈
LeetCode 杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
C语言题解
int** generate(int numRows, int* returnSize, int** returnColumnSizes)
{
int** ret = malloc(sizeof(int*) * numRows); // 指针数组
*returnSize = numRows; // 指针数组元素的个数
*returnColumnSizes = malloc(sizeof(int) * numRows); // returnColumnSizes为一级指针的地址
for (int i = 0; i < numRows; ++i)
{
ret[i] = malloc(sizeof(int) * (i + 1)); // 让指针数组里的指针都指向一块空间
(*returnColumnSizes)[i] = i + 1; // 每一行元素的个数
ret[i][0] = ret[i][i] = 1;
for (int j = 1; j < i; ++j)
{
ret[i][j] = ret[i - 1][j] + ret[i - 1][j - 1];
}
}
return ret;
}
C++题解
class Solution
{
public:
vector<vector<int>> generate(int numRows)
{
vector<vector<int>> vv;
vv.resize(numRows);
for(size_t i = 0; i < vv.size(); i++)
{
// vv[i]的类型为vector<int>
vv[i].resize(i + 1); // 开辟空间
vv[i][0] = vv[i][vv[i].size()-1] = 1;
}
for(size_t i = 0; i < vv.size(); i++)
{
for(size_t j = 1; j < i; j++)
{
vv[i][j] = vv[i-1][j-1] + vv[i-1][j];
}
}
return vv;
}
};
👉vector 的增删查改👈
vector增删查改 | 接口说明 |
---|---|
push_back(重点) | 尾插 |
pop_back (重点) | 尾删 |
find | 查找(注意这个是算法模块实现,不是vector的成员接口) |
insert | 在position之前插入val |
erase | 删除position位置的数据 |
swap | 交换两个vector的数据空间 |
operator[] (重点) | 像数组一样访问 |
assign | 给vector赋值 |
clear | 清空数据,size修改为0,capacity保持不变 |
operator[ ]和 at 的相同和区别
at() 函数和 [] 运算符的重载,两者都可以得到相应下标的值,并且两者都会去做边界检查。当发生越界行为时,at 是抛异常,operator[] 是报出 assert 错误。assert 断言在 Release 版本下会被优化掉,不会起作用。
补获异常
函数接口什么时候用 const 修饰,什么时候不需要用 const 修饰。
assign
void vectorTest4()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
v.assign(10, 1);
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
string s("hello world");
v.assign(s.begin(), s.end());
for (auto e : v)
{
cout << e << ' ';
}
cout << endl;
}
注:assign的迭代器赋值,迭代器可以是任意类型的迭代器。
insert
注:string 和 vector 可以用迭代器 + 数字的玩法,而 list 没有。因为 string 和 vector 的空间是连续的,而 list 的空间是不连续的。string 和 vector 的正向迭代器为指针,而 list 的迭代器不是指针。
find
注:find 函数是算法库里的函数,并不是 vector 的成员函数(因为每个容器有 find 函数,且功能都一样,所以就就设计成库函数了),使用时需要包algorithm
头文件。
那为什么 string 类不直接复用算法库的find函数,要自己设计呢?因为 string 类可能要查找一个子串。
swap
注:vector 有专门的模板交换函数,只要是避免使用std::swap这个函数,因为这个函数要3次深拷贝。
注:编译器会调用更加匹配的函数接口。
👉总结👈
以上就是 vector 的介绍和常见接口的使用,下一篇博客将会给大家讲解 vector 的模拟实现。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️