目录
1. vector的遍历
int main()
{
vector<int>v1;
vector<int>v2(10, 1);
vector<int>v3(++v2.begin(), --v2.end());
//vector的遍历:
for (size_t i = 0; i < v3.size(); i++)
{
cout << v3[i] << " ";
}
cout << endl;
vector<int>::iterator it = v3.begin();
while (it != v3.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : v3)
{
cout << e << " ";
}
cout << endl;
return 0;
}
2. vector扩容机制
当向vector中插入元素,且当前可用容量不足时,会触发扩容,在 vs下是按大约1.5倍扩容,而g++下是按标准2倍扩的。
3. reserve
在C++标准中,vector::reserve(n)的行为明确规定:
- 若n大于当前容量,则调整当前容量为n。
- 若n的值小于等于当前容量,则容量不发生变化,容器不会释放空间。
#include<iostream>
#include<vector>
using namespace std;
int main()
{
vector<int> v;
v.reserve(100); //初始容量100
cout << v.capacity() << endl;
v.reserve(50);
cout << v.capacity() << endl; //n=50 < 当前容量100,不缩容,容量仍为100
v.reserve(150);//大于当前容量,扩容到150
cout << v.capacity() << endl;
return 0;
}
在vs和g++中编译运行,第二个输出结果均为100,不会因reserve(50)而缩小。
4. resize
vector::resize用于调整容器中元素的数量,同时可能改变其容量。
void resize (size_type n); void resize (size_type n, const value_type& val);
- 当n<size()时,容量不变,减少元素数量到n,删除多余元素。
- 当n>size()时,在容器末尾添加元素直到n个,若未指定val,新元素默认初始化(如int为0,类类型调用默认构造函数);若指定val,新元素初始化为val。
若n<=capacity(),容量不变,仅增加元素。
若n>capacity(),触发扩容。
int main()
{
vector<int> v{ 1,2,3 };
cout << v.size() << endl;
cout << v.capacity() << endl << endl;
//n>size()
v.resize(5, 10);//新增两个元素,值为10
cout << v.size() << endl;
cout << v.capacity() << endl << endl;
//n<size()
v.resize(2);//保留前2个元素,删除后3个
cout << v.size() << endl;
cout << v.capacity() << endl << endl;//容量不变
return 0;
}
运行结果:
5. insert
vector::insert是用于在指定位置插入元素的成员函数,支持插入单个元素、多个元素或其它容器的元素。
int main()
{
vector<int> v(5, 0);
//插入多个相同元素
v.insert(v.begin(), 2, 8);
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//插入单个元素
v.insert(v.begin()+3, 6);//在位置3插入6
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
//插入其它容器的元素
vector<int> another = { 10,20,30,40,50 };
v.insert(v.end(), another.begin(), another.end());//末尾插入another的元素
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
return 0;
}
运行结果:
6. vector 的底层逻辑
在C++中,std::vector是一种动态数组容器,底层实现逻辑基于连续的内存空间和动态内存管理。
vector内部通过三个关键指针管理数据:
- 指向内存起始位置的指针(start):指向数组中第一个元素。
- 指向当前有效元素末尾的指针(finish):指向最后一个元素的下一个位置,用于标记实际存储的元素范围。
- 指向内存块末尾的指针(end_of_storage):指向整个已分配内存块的末尾,用于标记当前容量。
这三个指针定义了vector的大小(size)和容量(capacity):
- 大小(size()):finish - start,即当前实际存储元素的数量。
- 容量(capacity()):end_of_storage - start,即当前内存块可容纳的最大元素数量。
7. 小小疑惑
1. 为什么const对象需要const_iterator?
const对象的核心特性是:其内部数据不能被修改。
vector类中,iterator是T*(指向的元素可以修改,比如*it = 10;),而const_iterator是constT*(指向的元素不可修改*it = 10; 会报错)。
当用const vector<int> v;时,v是只读的,它的迭代器必须是const_iterator(确保不能通过迭代器修改元素),如果返回iterator,就可能通过迭代器修改const对象的数据,就会违反const语义,所以const对象必须调用返回const_itrator的begin()、end()。
typedef T* iterator;
typedef const T* const_iterator;
//非const版本迭代器
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
//const版本迭代器(供const对象使用)
const_iterator begin() const
{
return _start;
}
const_iterator end() const
{
return _finish;
}
这里要注意const版本迭代器const成员函数一定要返回const_iterator,如果写成:
typedef T* iterator; //非const版本迭代器 iterator begin() { return _start; } iterator end() { return _finish; } //const版本迭代器(供const对象使用) iterator begin() const { return _start; } iterator end() const { return _finish; }
那么进行下面操作:
const vector<int> v; // 常量容器,元素不可修改 iterator it = v.begin(); // begin() const返回iterator(int*) *it = 10; // 就能通过迭代器修改const容器的元素,彻底违背const语义!
这里的v是const对象,但通过v.begin()返回的普通指针,依然能修改其指向的数据,这就是逻辑漏洞。这种情况编译器不会报错,但会导致程序逻辑错误(意外修改const对象),这比编译错误更危险,会在运行时产生难以调试的bug。
解析:上面通过const对象间接修改了数据,看似违背了“const对象不应被修改”的原则,但编译器不认为这是错误的,因为编译器检查的是:begin()函数内部有没有修改v的成员比变量(比如_start = new int[10] ),const对象的自身状态没有被修改,被修改的是指针指向的数据,不在const语义的保护范围内,所以语法上合法,编译不报错。
注:const成员函数限制的是“通过this指针修改成员变量本身”(比如_start = new int[10] 这种修改指针本身的操作),但不限制“通过成员变量的指针修改其指向的数据”。
正确的写法(返回const_iterator)会让编译器强制检查,确保const容器的“只读”特性,这才是const关键字的设计初衷。此时*it=10会直接触发编译错误(const int* 不允许修改数据),从语法上阻止对const对象的修改。
正确的写法可以实现const vector_iterator v 要达到的目的:
- 不能修改成员变量本身的值,例如:v内部的_start 指针不能被重新指向其他地址(即vector自身状态不能被修改);
- 调用v的成员函数(如operator[]、begin()、end()等)时,会自动匹配 const版本重载,返回const T&(常量引用)或const_iterator(常量迭代器),限制通过v访问的“指针指向空间”(即元素)不能被修改。
const同时限制的这两部分共同确保了常量容器v既不能修改自身结构,也不能通过它修改内部存储的元素。const给容器加了一层“保护锁”,既锁死了容器的“框架”,也锁死了通过容器对“内容”的修改入口。
8. 打印任意类型的vector容器数据
如果容器是vector<double>等其它类型的,我们就需要需将其定义为函数模板,通过模板参数接收vector中元素的类型即可打印任何类型的vector。具体实现如下:
//使用模版,打印多种类型的数据 template<class T> void print_vector(const vector<T>& v) { typename vector<T>::const_iterator it = v.begin(); //编译器不知道T的具体类型,默认把它当作静态成员变量处理,当模版被实例化时,发现const_iterator实际是类型名,没有找到静态成员const_iterator,触发成员不存在而报错。 //模板中访问依赖于参数的类型时,必须用typename声明,否则编译器会误判为非类型成员,导致实例化时找不到对应成员而报错。 //auto it = v.begin(); //方便写法 while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; for (auto e : v) { cout << e << " "; } cout << endl; }
如果我们想要打印其它容器(如数组、list等),那么我们将容器定义成模版参数,具体实现如下:
template<class Container> void print_container(const Container& v) { typename Container::const_iterator it = v.begin(); //auto it = v.begin(); //方便写法 while (it != v.end()) { cout << *it << " "; ++it; } cout << endl; for (auto e : v) { cout << e << " "; } cout << endl; }