vector介绍:
vector是表示可变大小数组的序列容器。
vector与数组的相似点与不同点:
相同点:都是采用连续存储空间来存储元素,这就意味着可以采用下标对vector进行访问,和数组一样高效
不同点:vector的大小是可以动态改变的,而且它的大小会被容器自动处理,vector 底层依靠一块动态分配的数组存储所有元素。 当插入新元素的时候且存储空间不够的时候,数组会自动重新分配一个更大的新数组,然后将所有元素都移到这个数组中,代价较高。 因此每当一个新的元素加入到容器的时候,vector并不会每次都重新分配内存。 总结:相较于数组,vector会占用更多的内存,以此换取自动内存管理,高效动态扩容的能力
vector分配空间策略:
vector会预先分配一部分额外内存,预留扩容空间,所以vector的容量(capacity)通常大于当前实际存储元素的大小(size)
注意:C++ 标准仅规定 vector 尾插均摊 O (1),未限定扩容倍数,各 STL 实现策略不同:
- Windows VS 的 PJ(MSVC)STL 采用 1.5 倍扩容,计算方式为旧容量 + 旧容量 / 2,内存碎片更少;但是增容次数更多,效率更低,因为每次增容都要付出代价
- Linux GCC 的 libstdc++ 基于经典 SGI STL 实现,采用 2 倍扩容,逻辑简单、扩容次数少,但是浪费的空间更多;
因此不能固化认为 vector 一定 2 倍扩容,扩容因子由标准库实现决定。同时,增多少是一种选择,各有利弊,均衡一点。
注意:
reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。 对size没有影响
resize在开空间的同时还会进行初始化为0,影响size,不会影响底层的容量capacity。
java
举例:
//n < 当前 size:截断尾部多余元素,销毁超出部分
//n > 当前 size:尾部新增元素,内置类型默认值初始化(int 补 0)
//resize(n, val):扩容时新增的元素统一用 val 填充
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> myvector;
// set some initial content:
for (int i=1;i<10;i++) myvector.push_back(i);
myvector.resize(5);
myvector.resize(8,100);
myvector.resize(12);
std::cout << "myvector contains:";
for (int i=0;i<myvector.size();i++)
std::cout << ' ' << myvector[i];
std::cout << '\n';
return 0;
}
//输出:myvector contains: 1 2 3 4 5 100 100 100 0 0 0 0
vector的简单使用
1.创建一个vector数组
java
vector<数据类型> 数组名;
2.向数组中插入元素
java
数组名.push_back(值);
3.返回vector中元素的个数
java
数组名.size();
4.遍历数组
假设数组名为v
4.1.for循环遍历:operator\[\] +size
java
for(size_t i = 0; i < v.size(); i++){
cout << v[i] << " ";
}
cout << endl;//换行
4.2.迭代器
java
vector<int>::iterator it = v.begin();
while(it != v.end())
{
cout << *it << " ";
it++;
}
cout << endl;
4.3.范围for(被编译器替换成迭代器方式遍历来支持)
java
for(auto e : v)
{
cout << e << " ";
}
cout << endl;
4.4.关于只读数组的遍历
java
vector<int>::const_iterator it = v.begin();
while(it != v.end()){
// *it = 1;会报错
cout << *it << " ";
it++;
}
cout << endl;
4.5.关于数组的逆置输出
java
//用到的迭代器函数:reverse_iterator rbegin();const_reverse_iterator rbegin() const;
vector<int>::reverse_iterator rit = v.rbegin();
while(rit != v.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
5.赋值运算符=的使用:
vector 赋值 operator=, 赋值会覆盖目标容器全部内容,目标 size、内容完全和右侧容器保持一致,capacity 会根据实现重新分配适配
java
#include <iostream>
#include <vector>
int main ()
{
std::vector<int> foo (3,0);//创建包含 3 个元素,每个元素值都为 0
std::vector<int> bar (5,0);//创建包含 5 个元素,每个元素值都为 0
bar = foo;//bar 丢弃原来 5 个 0,完全复制 foo 的内容,foo 不变,size 依旧是 3
foo = std::vector<int>();//std::vector<int>() 调用无参构造,生成一个空临时 vector(size=0,无任何元素)
std::cout << "Size of foo: " << int(foo.size()) << '\n';//Size of foo: 0
std::cout << "Size of bar: " << int(bar.size()) << '\n';//Size of foo: 3
return 0;
}
注意:
1.int(foo.size()) 强制转换原因size() 返回值类型是无符号整数size_t,直接输出没问题,这里强转 int 是为了避免无符号 / 有符号类型警告。
- vector 的=是**深拷贝**,两个容器内存完全独立,后续修改 foo 不会影响 bar
6.关于给数组赋值时访问越界时,不同访问方式可能出现的情况
6.1.用\[\]访问时
java
v[值下标] = 给定值;//会报断言错误,常使用
6.2.用at访问
java
v.at(值下标) = 给定值;//会报异常错误
7.插入数据
java
v.insert(v.begin(), 值);//头插
v.insert(v.end(),值);//尾插
8.删除数据
java
v.erase(v.begin());//头删
v.erase(v.end());//尾删
9.寻找数组中特定值的下标,使用find函数
java
vector<int>::iterator pos = find(v.begin(), c.end(), 值);
if(pos != v.end())//pos到了v.end()表示没找到
{
v.erase(pos);//删除pos下标对应的值
}
10.排序函数sort的使用
java
sort(v.begin(), v.end());
vector 迭代器失效问题:
迭代器失效,实际就是迭代器底层对应指针所指向的 空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃
举例:1,插入元素崩溃
java
//1.插入数据引起迭代器的失效,因为vector容量不够时会扩容,扩容后原来的空间会被释放
//而在打印时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的
//空间,而引起代码运行时崩溃。
//解决方式:在以上操作完成之后,如果想要继续通过迭代器操作vector中的元素,只需给it重新赋值即可;或将迭代器操作放在插入元素后计算
void test1() {
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>::iterator i1 = v.begin();
v.push_back(6);
v.push_back(7);
//迭代器遍历数组
while (i1 != v.end()) {
cout << *i1 << " ";
i1++;
}
cout << endl;
}
举例:2,删除元素崩溃及修正方法
java
//2.删除元素崩溃
void test2() {
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);
v.push_back(7);
//计算迭代器
vector<int>::iterator i1 = v.begin();
//删除容器中的所有偶数
/*while (i1 != v.end()) {
if (*i1 % 2 == 0) {
v.erase(i1);
}
i1++;
}
//删除i1后,i1就失效了,i1的位置不对了,再++不可以:vs下报错,编译检查出来的;gcc下可能会报错,可能正常运行,但可能有偶数没删掉
//这样写程序会崩溃
*/
//修正写法
while (i1 != v.end()) {
if (*i1 % 2 == 0) {
i1 = v.erase(i1);//erase会返回删除的i1的下一个位置的迭代器
}
else {
i1++;
}
}
//遍历
for (auto e : v) {
cout << e << " ";
}
cout << endl;
}
注意: resize、reserve、insert、assign、 push_back等 ,都有可能是迭代器失效。
memset的使用:按字节处理拷贝内容
java
memset(a, 0, sizeof(int)*10);//合理,每一位都是0
memset(a, 1, sizeof(int)*10);//不合理,int类型有4个字节,如果每一位都赋予00000001的话,总的结果不是原来的值
小知识:c++中变量初始化
java
int i = int();//i = 0
int j = int(1);//j = 1
double d = double();//d=0.0
double e = double(1.1);//e = 1.1
vector模拟实现
https://gitee.com/papaya-2/test.c.4/commit/efa1d4c71285a08dc8625104e0f969c2de6e7d46