- 容器
- 支持泛型
- Vector
- 常用成员函数
- 示例
- 迭代器操作
- 插入和删除操作
- 与find 配合:
- vector 一些复杂操作
- Vector 的内存管理策略
- 压入对象
- 需要无参构造.
- 压入对象指针
- 高效插入和删除
- 迭代器失效
- 代码优化:
- 迭代器失效
- 二维及多维空间生成
容器
支持泛型
vector<int> vi;
vector<double> vd;
vector<string> vs;
list<int> li;
list<double> ld;
list<string> ls;
map<int,string> mis;
map<string,int> msi;
STL容器优劣对比:
---------------------------------------------------------------------------------
| 容器类型 | 优点 | 缺点 | 适用场景 |
|-------------------|------------------------------------------------|------------------------------------------------|----------------------------------------|
| `std::vector` | 1. 随机访问速度快,O(1) | 1. 在中间插入/删除元素慢,O(n) | 1. 需要频繁随机访问元素的场景 |
| | 2. 在末尾插入/删除元素快,O(1)(平均情况) | 2. 内存分配可能不连续,导致缓存不友好 | 2. 元素数量变化不大的场景 |
| `std::list` | 1. 在任意位置插入/删除元素快,O(1) | 1. 随机访问慢,O(n) | 1. 需要频繁在中间插入/删除元素的场景 |
| | 2. 内存分配连续,缓存友好 | 2. 存储开销大,每个元素需要额外存储指针 | |
| `std::deque` | 1. 随机访问速度快,O(1) | 1. 在中间插入/删除元素慢,O(n) | 1. 需要频繁在两端插入/删除元素的场景 |
| | 2. 在两端插入/删除元素快,O(1) | 2. 内存分配可能不连续,导致缓存不友好 | |
| `std::set` | 1. 元素唯一,自动排序 | 1. 插入/删除/查找元素慢,O(log n) | 1. 需要元素唯一且有序的场景 |
| | 2. 红黑树实现,平衡性好 | 2. 存储开销大,每个元素需要额外存储指针 | |
| `std::map` | 1. 键值对存储,键唯一,自动排序 | 1. 插入/删除/查找元素慢,O(log n) | 1. 需要键值对存储且键有序的场景 |
| | 2. 红黑树实现,平衡性好 | 2. 存储开销大,每个元素需要额外存储指针 | |
| `std::unordered_set` | 1. 元素唯一,无序 | 1. 插入/删除/查找元素慢,O(1)(平均情况) | 1. 需要元素唯一且无序的场景 |
| | 2. 哈希表实现,插入/删除/查找快 | 2. 哈希冲突可能导致性能下降 | |
| `std::unordered_map` | 1. 键值对存储,键唯一,无序 | 1. 插入/删除/查找元素慢,O(1)(平均情况) | 1. 需要键值对存储且键无序的场景 |
| | 2. 哈希表实现,插入/删除/查找快 | 2. 哈希冲突可能导致性能下降 | |
---------------------------------------------------------------------------------
Vector
std::vector是一个非常常用的容器,用于存储一组有序的元素。它类似于动态数组,能够自动调整大小,并且提供了许多方便的成员函数来操作和管理这些元素。
头文件:
#include
初始化:
std::vector<int> vec1; // 空vector
std::vector<int> vec2(5); // 包含5个默认初始化的元素
std::vector<int> vec3(5, 10); // 包含5个值为10的元素
std::vector<int> vec4 = {1, 2, 3, 4, 5}; // 使用初始化列表
添加元素:
vec1.push_back(10);
vec1.push_back(20);
访问元素:
可以使用下标运算符[]或at函数来访问元素:
int firstElement = vec1[0]; // 访问第一个元素
int secondElement = vec1.at(1); // 访问第二个元素
获取大小:
使用size函数可以获取vector中元素的数量:
size_t size = vec1.size();
遍历元素:
使用循环来遍历vector中的所有元素:
for (size_t i = 0; i < vec1.size(); ++i) {
std::cout << vec1[i] << " ";
}
// 或者使用范围for循环(C++11及以上)
for (int elem : vec1) {
std::cout << elem << " ";
}
常用成员函数
push_back(value): 在末尾添加一个元素。
pop_back(): 移除末尾的元素。
at(index): 访问指定位置的元素,带边界检查。
operator[](index): 访问指定位置的元素,不带边界检查。
size(): 返回元素的数量。
empty(): 检查vector是否为空。
clear(): 清空所有元素。
begin(): 返回指向第一个元素的迭代器。
end(): 返回指向最后一个元素之后位置的迭代器。
示例
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <string>
using namespace std;
class A
{
public:
A()
{
cout<<"无参构造函数"<<this<<endl;
}
A(int i):_data(i)
{
cout<<"有参构造函数"<<this<<endl;
}
A(const A & other)
{
cout<<"拷贝构造 "<<this<<" from "<<&other<<endl;
}
A& operator=(const A & other)
{
cout<<"拷贝赋值 "<<this<<" from "<<&other<<endl;
}
A(const A && other)
{
cout<<"移动构造 "<<this<<" from "<<&other<<endl;
}
A& operator=(const A && other)
{
cout<<"移动赋值 "<<this<<" from "<<&other<<endl;
}
~A()
{
cout<<"析构函数"<<this<<endl;
}
private:
int _data;
};
int main()
{
vector<A> va;//定义一个vector容器,元素类型为A
A a;
va.push_back(a); //调用了 拷贝构造 0x1f6621e1760 from 0x632efff88c
//存储了a的副本
va.push_back(A());//有移动构造就优先调用移动构造 调用了 移动构造 0x1c4b9e41760 from 0x8f7edffcac
return 0;
}
迭代器操作
//todo 迭代器操作
# include <iostream>
# include <vector>
using namespace std;
int main(){
vector<int> vi;
vi={1,2,3,4,5};
// vector<int> ::iterator it;
// for(it=vi.begin();it!=vi.end();it++){
// cout<<*it<<" ";
// }
//begin和end
for(auto it=vi.begin();it!=vi.end();it++){
cout<<*it<<" ";
}
cout<<endl;
//rbegin和rend 反向
for(auto it=vi.rbegin();it!=vi.rend();it++){
cout<<*it<<" ";
}
cout<<endl;
// cbegin和cend 不可改 const
for(auto it=vi.cbegin();it!=vi.cend();it++){
cout<<*it<<" ";
}
cout<<endl;
// crbegin和crend 不可改 且 反向
for(auto it=vi.crbegin();it!=vi.crend();it++){
cout<<*it<<" ";
}
return 0;
}
插入和删除操作
//todo 插入和删除操作
# include <iostream>
# include <vector>
using namespace std;
int main1(){
vector<int> vi;
vi={1,2,3,4,5};
vi.push_back(6); //在末尾插入元素
for(auto &i:vi){
cout << i << " ";
}
cout << endl;
while (!vi.empty()){//empty()检查容器是否为空
cout << vi.back() << " "; //输出最后一个元素
vi.pop_back(); //删除最后一个元素
}
}
int main(){
vector<int> vi;
vi={1,2,3,4,5};
//在开头插入元素
vi.insert(vi.begin(),1000);
for(auto &i:vi){
cout << i << " ";//1000 1 2 3 4 5
}
cout << endl;
//在中间插入元素
vi.insert(vi.begin()+2,2000);
for(auto &i:vi) {
cout << i << " ";//1000 1 2000 2 3 4 5
}
cout << endl;
//在末尾插入元素
vi.insert(vi.end(),3000);
for(auto &i:vi) {
cout << i << " ";//1000 1 2000 2 3 4 5 3000
}
cout << endl;
//插入多个元素
vi.insert(vi.begin()+2,5,777);
for(auto &i:vi) {
cout << i << " ";//1000 1 777 777 777 777 777 2000 2 3 4 5 3000
}
cout << endl;
//插入一个范围
int arr[]={11,22,33};
vi.insert(vi.begin(),arr,arr+3);
for(auto &i:vi) {
cout << i << " ";//11 22 33 1000 1 777 777 777 777 777 2000 2 3 4 5 3000
}
cout << endl;
//插入一个范围
vi.insert(vi.begin(), {999,999});
for(auto &i:vi) {
cout << i << " ";//999 999 11 22 33 1000 1 777 777 777 777 777 2000 2 3 4 5 3000
}
cout << endl;
//删除元素
vi.erase(vi.begin(),vi.begin()+2);
for(auto &i:vi) {
cout << i << " ";//11 22 33 1000 1 777 777 777 777 777 2000 2 3 4 5 3000
}
cout << endl;
vi.erase(vi.begin()+3);
for(auto &i:vi) {
cout << i << " ";//11 22 33 1 777 777 777 777 777 2000 2 3 4 5 3000
}
cout << endl;
//resize 调整容器大小
//vi.resize(0);//相当于清空容器
vi.resize(5);//调整容器大小,只剩下前5个元素
cout<<"resize"<<endl;
for(auto &i:vi) {
cout << i << " ";//11 22 33 1 777
}
cout << endl;
vi.resize(10);//调整容器大小 ,扩展到10个元素,补0
for(auto &i:vi) {
cout << i << " ";//11 22 33 1 777 0 0 0 0 0
}
cout << endl;
vi.resize(15,4);//调整容器大小 ,扩展到15个元素,补4
for(auto &i:vi) {
cout << i << " ";//11 22 33 1 777 0 0 0 0 0 4 4 4 4 4
}
cout << endl;
vi.clear();
return 0;
}
与find 配合:
int main(){
vector<int> vi;
vi={1,2,3,4,5};
auto itr= find (vi.begin(), vi.end(), 3); //查找元素3的位置
if(itr!= vi.end()){//如果找到元素3
cout << "元素的位置为:" << distance(vi.begin(),itr) << endl; //输出元素3的位置
vi.erase(itr);//删除元素3
}else{
cout << "元素不存在" << endl;
}
for(auto &i:vi){
cout << i << " ";//1 2 3 4 5
}
return 0;
}
vector 一些复杂操作
//todo vector 一些复杂操作
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iterator>
using namespace std;
int main()
{
// 创建一个存储字符串的向量
vector<string> sentence;
// 预留5个元素的容量,避免频繁的内存分配
sentence.reserve(5);
// 向向量中添加第一个字符串
sentence.push_back(string("Hello,"));
// 使用initializer_list一次性插入多个字符串到向量的末尾
sentence.insert(sentence.end(), {"how", "are", "you", "?"});
// 使用copy算法将向量中的内容复制到输出流,以空格分隔
copy(sentence.cbegin(), sentence.cend(), ostream_iterator<string>(cout, " "));
cout << endl;//Hello, how are you ?
// 输出向量的最大容量、当前大小和当前容量
cout << " max_size(): " << sentence.max_size() << endl; //max_size(): 288230376151711743
cout << " size(): " << sentence.size() << endl; //size(): 5
cout << " capacity(): " << sentence.capacity() << endl; //capacity(): 5
// 交换向量中第二个和第四个元素的位置
swap(sentence[1], sentence[3]);
// 在找到"?"的位置前插入"always"
sentence.insert(find(sentence.begin(), sentence.end(), "?"), "always");
// 将向量的最后一个元素修改为"!"
sentence.back() = "!";
// 再次输出向量中的内容
copy(sentence.cbegin(), sentence.cend(), ostream_iterator<string>(cout, " "));
cout << endl;//Hello, you are how always !
// 输出修改后的向量大小和容量
cout << " size(): " << sentence.size() << endl;// size(): 6
cout << " capacity(): " << sentence.capacity() << endl; // capacity(): 10
// 移除向量的最后两个元素
sentence.pop_back();
sentence.pop_back();
// 释放未使用的容量,使容量等于当前大小
sentence.shrink_to_fit();
// 输出最终的向量大小和容量
cout << " size(): " << sentence.size() << endl; // size(): 4
cout << " capacity(): " << sentence.capacity() << endl; // capacity(): 4
return 0;
}
Vector 的内存管理策略
引例:
//todo vector 内存管理
# include <iostream>
# include <vector>
using namespace std;
int main()
{
vector<int> vi; // 构造一个包含10个元素的vector
for(int i=0; i<10; i++) {
vi.push_back(i); // 向vector中添加元素
cout<<"size:"<<vi.size()<<endl; // 输出当前vector的大小
cout<<"capacity:"<<vi.capacity()<<endl; // 输出当前vector的容量
}
return 0;
}
输出:
size:1
capacity:1
size:2
capacity:2
size:3
capacity:4
size:4
capacity:4
size:5
capacity:8
size:6
capacity:8
size:7
capacity:8
size:8
capacity:8
size:9
capacity:16
size:10
capacity:16
频繁分配内存,导致效率低下。
有必要优化
# include <iostream>
# include <vector>
using namespace std;
int main()
{
vector<int> vi; // 构造一个包含10个元素的vector
vi.reserve(16); // 预留16个元素的容量,避免频繁的内存分配
for(int i=0; i<10; i++) {
vi.push_back(i); // 向vector中添加元素
cout<<"size:"<<vi.size()<<endl; // 输出当前vector的大小
cout<<"capacity:"<<vi.capacity()<<endl; // 输出当前vector的容量
}
vi.shrink_to_fit(); // 释放未使用的容量,使容量等于当前大小
cout<<"size:"<<vi.size()<<endl; // 输出当前vector的大小
cout<<"capacity:"<<vi.capacity()<<endl; // 输出当前vector的容量
return 0;
}
输出:
size:1
capacity:16
size:2
capacity:16
size:3
capacity:16
size:4
capacity:16
size:5
capacity:16
size:6
capacity:16
size:7
capacity:16
size:8
capacity:16
size:9
capacity:16
size:10
capacity:16
size:10
capacity:10
resize: resize变大等价于调用 push_back(),变小等于 pop_back()
# include <iostream>
# include <vector>
using namespace std;
int main(){
vector<int> vi={1,2,3,4,5};
cout<<"size:"<<vi.size()<<endl; // size:5
cout<<"capacity:"<<vi.capacity()<<endl; // capacity:5
//resize 变大等价于调用 push_back(),变小等于 pop_back() , 与reserve不同
vi.resize(16);
cout<<"size:"<<vi.size()<<endl; // size:16
cout<<"capacity:"<<vi.capacity()<<endl; // capacity:16
vi.resize(3);
cout<<"size:"<<vi.size()<<endl; // size:3
cout<<"capacity:"<<vi.capacity()<<endl; // capacity:16
vi.resize(100);
vi.shrink_to_fit(); // 释放未使用的容量,使容量等于当前大小
cout<<"size:"<<vi.size()<<endl; // size:100
cout<<"capacity:"<<vi.capacity()<<endl; //capacity:100
return 0;
}
压入对象
- :
#include <iostream>
#include <vector>
using namespace std;
class A
{
public:
A()
{
cout<<"无参构造函数"<<this<<endl;
}
A(int i):_data(i)
{
cout<<"有参构造函数"<<this<<endl;
}
A(const A & other)
{
cout<<"拷贝构造"<<this<<" from "<<&other<<endl;
}
A& operator=(const A & other)
{
cout<<"拷贝赋值"<<this<<" from "<<&other<<endl;
}
~A()
{
cout<<"析构函数"<<this<<endl;
}
private:
int _data;
};
int main()
{
vector<A> va;
va.reserve(10);
va.push_back(A()); //拷贝构造0x1e7bc841760 from 0x31467ffcec
va.resize(2);
return 0;
}
输出:
无参构造函数0xbb57ffdec
拷贝构造0x169393e1760 from 0xbb57ffdec
析构函数0xbb57ffdec
无参构造函数0x169393e1764
析构函数0x169393e1760
析构函数0x169393e1764
- :
int main()
{
vector<A> va;
va.reserve(10);
va.push_back(A()); //拷贝构造0x1e7bc841760 from 0x31467ffcec
va.resize(3);
{
va.resize(2); //本质是pop_back
}
cout<<"===";
return 0;
}
输出:
无参构造函数0x4384bff77c
拷贝构造0x1c74af71760 from 0x4384bff77c
析构函数0x4384bff77c
无参构造函数0x1c74af71764
无参构造函数0x1c74af71768
析构函数0x1c74af71768
===析构函数0x1c74af71760
析构函数0x1c74af71764
- :
int main()
{
vector<A> va;
A a;
va.push_back(a);
va.push_back(a);
va.push_back(a);
return 0;
}
构造了7次 ,因为没预留空间 , push_back空间不足 , 会重复申请空间 ,将旧的数据拷贝进新空间 1 2 4 8 16 ...
无参构造函数0x8a77bff9ec
拷贝构造0x24e6e4b1760 from 0x8a77bff9ec
拷贝构造0x24e6e4b1784 from 0x8a77bff9ec
拷贝构造0x24e6e4b1780 from 0x24e6e4b1760
析构函数0x24e6e4b1760
拷贝构造0x24e6e4b1768 from 0x8a77bff9ec
拷贝构造0x24e6e4b1760 from 0x24e6e4b1780
拷贝构造0x24e6e4b1764 from 0x24e6e4b1784
析构函数0x24e6e4b1780
析构函数0x24e6e4b1784
析构函数0x8a77bff9ec
析构函数0x24e6e4b1760
析构函数0x24e6e4b1764
析构函数0x24e6e4b1768
- :
int main()
{
vector<A> va;
va.reserve(10);//预留空间
A a;
va.push_back(a);
va.push_back(a);
va.push_back(a);
return 0;
}
无参构造函数0xa11c9ffb4c
拷贝构造0x2030a9f1760 from 0xa11c9ffb4c
拷贝构造0x2030a9f1764 from 0xa11c9ffb4c
拷贝构造0x2030a9f1768 from 0xa11c9ffb4c
析构函数0xa11c9ffb4c
析构函数0x2030a9f1760
析构函数0x2030a9f1764
析构函数0x2030a9f1768
需要无参构造.
va.resize() 调用的是无参构造函数
当向容器中压的是类对象成员时,最好要保证无参构造器的存在,类似于数组中的类对象一样。
在必要的情况下,要自实现拷贝构造和拷贝赋值。
压入栈中的对象元素在堆中,所申请的内存己托管,无需再次打理。
压入对象指针
容器有内存托管的功能,但仅限于,被压入的实体元素。
若元素为指针类型,则指针被托管。而不是指针所指向的空间被托管。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 定义一个类A,包含构造函数、拷贝构造函数、拷贝赋值运算符和析构函数
class A
{
public:
// 无参构造函数
A()
{
cout << "无参构造函数 " << this << endl;
}
// 有参构造函数,初始化成员变量_data
A(int i) : _data(i)
{
cout << "有参构造函数 " << this << endl;
}
// 拷贝构造函数
A(const A & other)
{
cout << "拷贝构造 " << this << " from " << &other << endl;
}
// 拷贝赋值运算符
A& operator=(const A & other)
{
cout << "拷贝赋值 " << this << " from " << &other << endl;
return *this;
}
// 析构函数
~A()
{
cout << "析构函数 " << this << endl;
}
private:
int _data; // 私有成员变量
};
int main()
{
// 动态分配三个A类的对象,使用无参构造函数
A *p1 = new A;
A *p2 = new A;
A *p3 = new A;
// 创建一个存储A类指针的向量
vector<A *> vap;
// 将动态分配的A类对象指针添加到向量中
vap.push_back(p1);
vap.push_back(p2);
vap.push_back(p3);
// 遍历向量,释放动态分配的A类对象
for (auto & itr : vap)
delete itr;
// 程序结束,返回0
return 0;
}
输出
无参构造函数 0x28206f61760
无参构造函数 0x28206f61780
无参构造函数 0x28206f61ad0
析构函数 0x28206f61760
析构函数 0x28206f61780
析构函数 0x28206f61ad0
高效插入和删除
尾部的插入和删除是高效的 O(1),其它位置则是低效的 O(n)
故对于 vector 而言,提倡使用 push_back()和 pop_back(),仅量少用 insert 和 erase
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class A
{
public:
A()
{
cout<<"无参构造函数"<<this<<endl;
}
A(int i):_data(i)
{
cout<<"有参构造函数"<<this<<endl;
}
A(const A & other)
{
cout<<"拷贝构造"<<this<<" from "<<&other<<endl;
}
A& operator=(const A & other)
{
cout<<"拷贝赋值"<<this<<" from "<<&other<<endl;
}
~A()
{
cout<<"析构函数"<<this<<endl;
}
private:
int _data;
};
int main()
{
vector<A> va;
va.reserve(100);
for(int i=0; i<10; i++)
{
va.push_back(A());
}
va.insert(va.begin(),100);//在头部插入会带来大量的拷贝
//如下的插入才是高效的
va.push_back(A());
va.insert(va.end(),A());
va.erase(va.begin());//在头部删除会带来大量的拷贝
//如下的删除才是高效的
va.pop_back();
va.erase(va.end()-1);
return 0;
}
迭代器失效
迭代器,实际是一个对象,一个智能指针,重载了operator*和operator->;
//在遍历过程中删除元素可能会导致迭代器失效,这是一个潜在的问题,但在本例中由于每次删除后都立即退出循环,所以没有引发问题。在实际开发中,处理这种情况时需要格外小心。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vi;
int data[10] = {1,3,5,7,9,2,4,6,8,10};
vi.assign(data,data+10);
vector<int>::iterator itr;
for(itr=vi.begin();itr != vi.end(); ++itr)
{
if(*itr%2==0)
vi.erase(itr);
else
{
cout<<*itr<<endl;
}
}
return 0;
}
vector 是一个顺序容器,在内存中是一块连续的内存,当删除一个元素后,内存
中的数据会发生移动,以保证数据的紧凑。所以删除一个数据后,其他数据的地址发生
了变化,之前获取的迭代器根据原有的信息就访问不到正确的数据。
代码优化:
安全地遍历和修改vector中的元素。
通过assign方法将数组中的数据赋值给向量,然后使用迭代器遍历向量中的每个元素。
如果元素是偶数,则使用erase方法从向量中删除该元素,并更新迭代器到下一个有效位置;
如果元素是奇数,则输出该元素,并手动递增迭代器以处理下一个元素。
这种方式避免了在遍历过程中删除元素导致的迭代器失效问题,确保了程序的稳定性和正确性。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// 创建一个存储整数的向量
vector<int> vi;
// 定义一个包含10个整数的数组
int data[10] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 10};
// 使用assign方法将数组中的数据赋值给向量
// data是数组的首地址,data+10是数组的末尾地址(最后一个元素之后的位置)
vi.assign(data, data + 10);
// 定义一个迭代器,用于遍历向量
vector<int>::iterator itr;
// 遍历向量中的每个元素
for (itr = vi.begin(); itr != vi.end(); )
{
// 如果当前元素是偶数,则从向量中删除该元素
// 注意:erase方法会返回下一个有效的迭代器位置,因此需要将返回值赋给itr
if (*itr % 2 == 0)
itr = vi.erase(itr);
else
{
// 如果当前元素是奇数,则输出该元素
cout << *itr << endl;
// 手动递增迭代器,处理下一个元素
++itr;
}
}
// 程序结束,返回0
return 0;
}
二维及多维空间生成
//todo vector 嵌套
# include <iostream>
# include <vector>
using namespace std;
int main1(){
vector<int> t(5);
vector<vector<int>> tt(5,t);
cout<<tt.size()<<endl; // 5
cout<<tt[0].size()<<endl; // 5
cout<<tt[1].size()<<endl; // 5
cout<<tt[2].size()<<endl; // 5
cout<<tt[3].size()<<endl; // 5
cout<<tt[4].size()<<endl; // 5
for(int i=0;i<5;i++){
for(int j=0;j<5;j++){
cout<<(tt[i][j]=100)<<" ";
}
cout<<endl;
}
return 0;
}
int main(){
vector<int> t;
vector<vector<int>> tt(5,t);
srand(time(NULL));
for(int i=0;i<5;i++){
for(int j=0;j<rand()%20;j++){
tt[i].push_back(rand()%100);
}
}
for(int i=0;i<5;i++){
for(int j=0;j<tt[i].size();j++){
cout<<tt[i][j]<<" ";
}
cout<<endl;
}
}