文章目录
- 1. 主要结构
- 2. 构造函数与复制重载
- 3. 迭代器
- 4. 容量相关
- 1.容量读取
- 2.容量修改
- 5. 数据访问
- 6. 数据修改
- 1. 尾插尾删
- 2.任意位置的插入删除
- 7.其他接口
在之前我们学习了string的使用与模拟实现,在参考文档中可以发现,vector和string以及其他的容器的接口基本都相同,所以这里也就简要的介绍一下。
同样的,在这里还是要说一下,对于STL库的学习,不可能在一朝一夕之间学完,要学会去查看文档和多练习vector参考文档
1. 主要结构
vector没有像string一样进行类型重定义typedef basic_string<char> string
,所以在使用vector的时候要指定模板参数类型例如vector<int>
。
2. 构造函数与复制重载
可以看到vector的构造函数实现相较于string就简化了很多,一共分为四个,分别是默认构造,指定长度和值的构造,迭代器区间构造,拷贝构造
void Test_Construct()
{
vector<int> v1;//默认构造
vector<int> v2(10, 3);//指定长度和值的构造
string s("aaaaaaaaaaaaaa");
vector<char> v3(s.begin(), s.end());//迭代器区间构造
vector<char> v4(v3);//拷贝构造
}
通过监视查看四个对象的值如上。
3. 迭代器
由于vector也是通过动态数组实现的,所以原生指针就能很好的支持迭代器行为,所以在vector中的迭代器本质也是指针。
可以看到和string一样,vector的迭代器也分为普通迭代器,反向迭代器,const迭代器,const反向迭代器。
对于迭代器和反向迭代器的工作方式如上图。
4. 容量相关
1.容量读取
1. size
返回一个size_t类型的vector中数据个数
2. capacity
返回一个size_t类型的vector的容量。
使用范例:
void Test_Capacity()
{
vector<int> v;
v.reserve(10);
for (size_t i = 0; i < 5; ++i)
{
v.push_back(i);
}
cout << "size:" << v.size() << endl;
cout << "capacity:" << v.capacity() << endl;
}
运行结果为:
2.容量修改
1. reserve
扩大vector的容量到n,如果原来vector的容量大于等于n,不做任何操作
void Test_Capacity2()
{
vector<int> v(5, 1);
cout << "before reserve capacity:" << v.capacity() << endl;
v.reserve(10);
cout << "after reserve capacity:" << v.capacity() << endl;
}
2. resize
修改vector中的数据个数,如果n小于size,那就修改size;如果n大于size但是小于capacity,那就尾插数据直到size==n,如果n大于capacity,那就先扩容到n,再填充数据。
void Test_Capacity3()
{
vector<int> v(5, 1);
cout << "before resize capacity:" << v.size() << endl;
v.resize(10);
cout << "after resize capacity:" << v.size() << endl;
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
5. 数据访问
与string类似,在数据访问中也有很多接口,但是最常用的还是[]的重载,其余基本不常用,这里我们就不过多赘述
[]实际上是一个运算符重载,使vector也具有类似下标访问的能力,返回值是vector中存放元素类型的引用
对于[]的重载,在库里面支持了两种重载,分别是普通类型的和const类型的用于支持多样化的使用环境
怎么判断需要实现const或者非const版本?
- 对于只读接口,只实现const版本即可
- 对于只写接口,只实现非const版本即可
- 对于可读可写的接口,需要同时实现const版本和非const版本
void Test_Access()
{
vector<int> v(10);
for (size_t i = 0; i < v.size(); ++i)
{
v[i] = i;
}
for (size_t i = 0; i < v.size(); ++i)
{
cout << v[i] << " ";
}
cout << endl;
}
6. 数据修改
1. 尾插尾删
1. push_back
尾插数据,插入前会进行扩容操作,每次扩容的大小取决于编译器的实现
2. pop_back
如果vector中有数据的话,尾删一个数据,否则会由assert中断程序或者抛出异常,具体情况取决于编译器的实现
void Test_Modifity()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.pop_back();
v.pop_back();
v.pop_back();
v.pop_back();
v.pop_back();
v.pop_back();
}
2.任意位置的插入删除
1. insert
在传入的任意位置插入一个数据或者迭代器区间的数据
2. erase
删除任意位置的值或者任意一个迭代器区间的值
void Test_Modifity2()
{
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.insert(v.begin() + 3, 30);
v.erase(v.begin());
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
这里注意一下,vector中会出现迭代器失效的问题
❗❗==vector中的迭代器失效问题==❗❗
❓第一个问题:迭代器的出现原因是什么?
✅迭代器的设计模式,为了不暴露底层实现细节,提供统一的方式来访问容器,对于vector,其迭代器本质上就是原生指针T*,所以对vector来说,迭代器失效的原因就是原迭代器指向的空间被销毁,导致出现野指针。
❓第二个问题:导致迭代器失效的操作有哪些?
✅迭代器失效的原理是底层空间改变,导致产生野指针,所以所有的底层空间的改变都有可能使迭代器失效,例如:reserve,resize,push_back,insert等。
✅除了对底层空间的改变之外,erase也会导致迭代器失效。
❓第三个问题:按照我们上一个问题的逻辑,erase没有使底层空间发生改变啊,为什么也会使迭代器失效?
✅虽然erase没有改变底层空间,但是,如果erase删除的是vector中最后一个元素,刚好删除完之后pos位置是end指向的位置,那么迭代器就失效了。
✅就算删除的不是最后一个元素,那么迭代器指向的位置的值也是改变了的,严格意义上来说,也是不能实现我们所需要的功能的,所以也可以认为是迭代器失效了。
❓第四个问题:只有vector会遇到迭代器失效的的情况吗,string有没有迭代器失效的情况?
✅不只是vector有,string和其他的容器都有迭代器失效的情况。
❓第五个问题:迭代器失效的问题怎么解决呢?
✅在上文中,我们讲解了vector的主要接口,可以注意到,能够引起迭代器失效的接口函数的返回值都是迭代器,这里返回的迭代器就是我们所需要的有效的迭代器。所以,在使用前对迭代器进行重新赋值即可解决。
//关于迭代器失效解决的例题: //对于使用vector存储的数组{0,1,2,3,4,5,6,7,8,9},删除其中所有的偶数元素 int main() { vector<int> v(10); for (size_t i = 0; i < 10; ++i) { v[i] = i; } vector<int>::iterator it = v.begin(); while (it != v.end()) { if (*it % 2 == 0) { it = v.erase(it); } else { ++it; } } for (auto e : v) { cout << e << " "; } cout << endl; return 0; }
7.其他接口
1. clear
删除vector内部所有数据(本质上做的操作就是将vector的size置为0)
2. swap
vector内部实现的一个交换函数,提升效率。