目录
1. C/C++中的数组
1.1. C语言中的数组
1.2. C++中的数组
2. vector的接口
2.1. vector的迭代器
2.2. vector的初始化与销毁
2.3. vector的容量操作
2.4. vector的访问操作
2.5. vector的修改操作
💓 博客主页:C-SDN花园GGbond
⏩ 文章专栏:玩转c++
1. C/C++中的数组
1.1. C语言中的数组
在 C 语言中,数组是一组相同类型元素的有序集合。与字符串类似,它的大小在编译时就已经确定,不可更改。
//大小为5的整型数组
int arr1[5] = { 1,2,3,4,5 };
//大小为5的浮点型数组
double arr2[5] = {0.0};
1.2. C++中的数组
同样与string
类似,C++为了更加方便就引入了一个支持可动态大小数组的序列容器vecotr
。其特点如下:
vector
是可变大小的序列容器,采用连续存储空间存储元素,可通过下标高效访问。- 与数组不同,
vector
大小可动态改变,由容器自动处理。vector
本质上用动态分配数组存储元素,插入新元素时可能重新分配空间,即分配新数组并移动全部元素,此操作时间代价高,但不是每次插入都重新分配。vector
会分配额外空间适应增长,不同库策略不同,但重新分配通常是对数增长间隔,使末尾插入元素能在常数时间完成。- 与
deque
、list
和forward_list
相比,vector
访问元素及末尾添加和删除元素更高效,非末尾的删除和插入操作效率低,且统一的迭代器和引用更好。
//整型数组
vector<int> v1;
//浮点型数组
vector<double> v2;
使用vector
都需要包含头文件#include<vector>
。并且vector
是一个模版类,所以在使用时需要显示实例化。
2. vector的接口
介绍一些vector
的常见接口,因为很多接口的作用都与string
的接口非常类似,所以很多就不在详细说明,具体也可以参考vector文档。
下面我们开始研究他的使用,为了能够更好的测试,我们先实现一个打印容器元素的函数,vector底层是数组,所以有三种访问方式:下标访问、迭代器访问、范围for(本质也是迭代器)
void Printf(vector<int>& v)
{
//下表遍历
cout << "下表遍历:";
for (size_t i = 0; i < v.size(); i++)
{
cout << v[i];
}
cout << endl;
//迭代器遍历
cout << "迭代器遍历:";
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it;
it++;
}
cout << endl;
cout << "范围for遍历:";
for (auto e : v)
{
cout << e;
}
cout << endl;
}
int main()
{
vector<int> v = { 1,2,3,4,5,6,7,8,9 };
Printf(v);
//Test01();
return 0;
}
2.1. vector的迭代器
同样的vector中也存在迭代器iterator,因为定义在vector类中,所以其需要通过域作用限定符访问——vector<类型>::iterator
下面将介绍的begin(),end(),rbeign(),rend()的使用访问方法与string中的几乎一摸一样,我们直接上实例演示:
#include<iostream>
#include<vector>
using namespace std;
void Test01()
{
vector<int> v = { 1,2,3,4,5,6,7,8,9 };
cout << "顺序遍历:";
vector<int>::iterator it = v.begin();
while (it != v.end())
{
cout << *it;
it++;
}
cout << endl;
cout << "逆序遍历:";
vector<int>::reverse_iterator rit = v.rbegin();
while (rit != v.rend())
{
cout << *rit;
rit++;
}
cout << endl;
}
int main()
{
Test01();
return 0;
}
当然vector
也支持const_iterator
,用法也类似,这里就不在赘述。
2.2. vector的初始化与销毁
(1) 空容器构造函数(默认构造函数)
构造一个没有元素的空容器。
(2) 填充构造函数
构造一个包含n个元素的容器。每个元素都是val的副本。
(3) 范围构造器
构造一个包含与范围[first,last)一样多的元素的容器,每个元素都按照相同的顺序从该范围内的相应元素构造而成。
(4) 复制构造函数
构造一个容器,其中包含x中每个元素的副本,顺序相同。
同样的vector
也支持多种构造函数,拷贝构造以及赋值运算符重载。
void Test02()
{
//无参构造
vector<int> v1;
Print(v1);
//有参构造,n个位置初始化
vector<int> v2(5, 2);
Print(v2);
//有参构造,n个位置调用T类型的默认构造
vector<int> v3(5);
Print(v3);
//拷贝构造
vector<int> v4(v3);
Print(v4);
//迭代器区间构造(传string的迭代器区间)
string s("hello world");
vector<int> v5(s.begin(), s.end());
Print(v5);
//迭代器区间构造(传vctor的迭代器区间)
vector<int> v6(v5.begin(), v5.end());
Print(v6);
//赋值重载
v1 = v6;
cout << &v1 << " " << &v6 << endl;//深拷贝
Print(v1);
//特殊的赋值方式
vector<int> v7{ 1,2,3,4,5,6,7,8 };
Print(v7);
}
2.3. vector的容量操作
在vector
类中,同样可以通过size()
容器的有效长度;capacity()
返回容器的容量大小。
void Test03()
{
vector<int> v1;
cout << v1.size() << endl;
cout << v1.capacity() << endl;
vector<int> v2 = { 0,1,2,3,4,5 };
cout << v2.size() << endl;
cout << v2.size() << endl;
}
在初始化时,vecotr
中的size
与capacity
一般相同。这时我们也可以通过以下程序探究一下其扩容机制:
void Test04()
{
size_t sz;
vector<int> v;
sz = v.capacity();
cout << "making v grow" << endl;
for (int i = 0; i < 100; i++)
{
v.push_back(i);
if (sz != v.capacity())
{
sz = v.capacity();
cout << "capacity changed " << v.capacity() << endl;
}
}
}
int main()
{
Test04();
return 0;
}
在VS环境下,vector
一般是以1.5倍扩容。但是在Linux环境下一般就以2倍扩容。
要求容器减少容量以适应其大小。
该请求是非绑定的,容器实现可以自由地进行优化,使向量的容量大于其大小。
这可能会导致重新分配,但对向量大小没有影响,也不能改变其元素。
有效长度与容量操作
vector
中的resize()
与reserve()
。其实他们的用法与特点也是与string
类中的相同
当n<sz时,reserve并不会发生任何改变,resize会删除有效字符到指定大小。
当sz<n<capcity时,reserve并不会发生任何改变,resize会补充有效字符(默认为0)到指定大小。
当n>capacity时,reserve会发生扩容,resize会补充有效字符(默认为0)到指定大小。
void Test06()
{
vector<int> v1 = { 1,2,3,4,5 };
cout << "v1的有效长度为:" << v1.size() << endl;
cout << "v1的容量大小为:" << v1.capacity() << endl;
v1.reserve(10);
cout << "reserve(10)后:" << endl;
cout << "v1的有效长度为:" << v1.size() << endl;
cout << "v1的容量大小为:" << v1.capacity() << endl;
cout << endl;
v1.resize(8, 10);
for (auto& e : v1)
{
cout << e << " ";
}
cout << endl;
cout << "v1的有效长度为:" << v1.size() << endl;
cout << "v1的容量大小为:" << v1.capacity() << endl;
v1.resize(3);
for (auto& e : v1)
{
cout << e << " ";
}
cout << "v1的有效长度为:" << v1.size() << endl;
cout << "v1的容量大小为:" << v1.capacity() << endl;
}
int main()
{
Test06();
return 0;
}
2.4. vector的访问操作
void Test07()
{
vector<int> v = { 1,2,3,4,5 };
for (int i = 0; i < v.size(); i++)
{
cout << v.at(i) << " ";
}
cout << endl;
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
cout << "front:" << v.front() << endl;
cout << "back:" << v.back() << endl;
}
int main()
{
Test07();
return 0;
}
2.5. vector的修改操作
首先先介绍最简单的四个函数push_back()
,pop_back()
,assign()
,swap()
。
void Test7()
{
vector<int> v = { 1,2,3,4,5,6 };
cout << "back:" << v.back() << endl;
//尾插
v.push_back(7);
//尾删
cout << "back:" << v.back() << endl;
v.pop_back();
cout << "back:" << v.back() << endl;
vector<int> vv = { 6,5,4,3,2,1 };
//n个val赋值给原数组
vv.assign(3, 2);
for (int i = 0; i < vv.size(); i++)
{
cout << vv[i] << " ";
}
cout << endl;
vv.swap(v);
for (int i = 0; i < v.size(); i++)
{
cout << v[i] << " ";
}
cout << endl;
for (int i = 0; i < vv.size(); i++)
{
cout << vv[i] << " ";
}
}
介绍insert()
与·earse()
的用法,这两个函数的用法就与string
中的有所不同。首先是insert()
函数:
指定位置插入,要注意的是这里不再像string一样,用的size_t 的pos,vector虽然也可以用下标访问,但是为了承接后面STL其他不支持下标访问的容器,所以这边的pos用的是迭代器类型
void Test8()
{
vector<int> myvector(3, 100);
vector<int>::iterator it = myvector.begin();
//1.向指定位置插入一个元素
it = myvector.insert(it, 200);
cout << "myvector contains:";
for (it = myvector.begin(); it < myvector.end(); it++)
cout << ' ' << *it;
cout << endl;
//2.向指定位置插入n个元素
myvector.insert(it, 2, 300);
cout << "myvector contains:";
for (it = myvector.begin(); it < myvector.end(); it++)
cout << ' ' << *it;
cout << endl;
//3.向指定位置插入一段迭代器区间
it = myvector.begin();
vector<int> anothervector(2, 400);
cout << "myvector contains:";
for (it = myvector.begin(); it < myvector.end(); it++)
cout << ' ' << *it;
cout << endl;
it = myvector.begin();
myvector.insert(it + 2, anothervector.begin(), anothervector.end());
//4.向指定位置插入一段迭代器区间
int myarray[] = { 501,502,503 };
myvector.insert(myvector.begin(), myarray, myarray + 3);
cout << "myvector contains:";
for (it = myvector.begin(); it < myvector.end(); it++)
cout << ' ' << *it;
cout << endl;
}
erase()
,
删除单个元素: 当你需要删除向量中的某个特定元素时,可以使用 erase 函数,并传递一个指向该元素的迭代器。这将删除该元素,并使后续元素向前移动一个位置。
删除多个元素: erase 函数还可以接受两个迭代器参数,表示要删除的元素范围(左闭右开区间)。这允许你一次性删除多个连续的元素。
无论是哪种形式,erase 的返回值都是一个迭代器,指向被删除元素之后的位置(即第一个未被删除的元素)。如果删除的是容器中的最后一个元素或所有元素,则返回 vector::end() 迭代器。
void Test9()
{
//1.删除迭代器所指元素
vector<int> myvector;
for (int i = 1; i <= 10; i++)
myvector.push_back(i);
vector<int>::iterator it = myvector.erase(myvector.begin() + 5);
it = myvector.erase(it);
//2.删除一段迭代器区间
it = myvector.erase(myvector.begin(), myvector.begin() + 3);
cout << "myvector contains:";
for (int i = 0; i < myvector.size(); ++i)
cout << ' ' << myvector[i];
cout << endl;
}
虽然看起来vector的insert()和erase()与string的没有什么区别,但是仔细观察就可以发现我们每次使用完迭代器之后都会更新,这是为什么呢?
主要还是因为我们每次插入数组都可能发生扩容,而扩容分为就地扩容与异地扩容。如果发生的异地扩容,这时的迭代器就不在指向原来的空间,而就指向一块释放的内存,我们一旦继续访问就会报错,这种现象我们称为迭代器失效。为了避免出现这种情况,所以我们在使用完迭代器之后需要更新。