容器是C++ STL(标准模板库)六大组件之一,为我们代码的实现提供了诸多便利。
容器的分类及相关内容有顺序容器、容器适配器、关联容器
一、顺序容器
顺序容器:是一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序容器中的每个元素均有固定的位置,除非用插入或者删除的操作来改变这个位置。这个位置和元素本身无关,而和操作的时间地点有关。顺序容器不会根据元素的特点排序而是直接保存了元素操作时的逻辑顺序。
主要关注:
容器底层数据结构
内存增长方式
常用的增删查改的方法
顺序容器1---vector
1、定义:Vector是内存可2倍扩容的数组,采用连续存储空间来存储元素。也是一个类模板,可以用来定义多种数据类型。
我们把vector称为容器,一个容器内必须是同一种类型对象的集合,头文件:#include<vector>
2、值的初始化
3、vector中循环的判断条件用 != 来判断vector的下标值是否越界
- 如果没有指定元素的初始化式,那么标准库将自行提供一个元素初始值进行值的初始化
- int--->标准库用0值初始化
- string--->标准库将用string类型的默认构造函数创建元素初始化式
- 没有定义任何构造函数的类类型-->标准库产生一个带初值的对象,让这个对象的每个成员进行值的初始化
vector下标操作不能进行添加元素,而且仅能对确知已存在的元素进行下标操作。
4、vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。因此, vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
注意:vector底层采用的数据结构:线性连续空间。当分配空间被占满而仍然需要添加元素时,vector便会进行空间重新配置。一旦引起空间重新配置,之前指向vector的所有迭代器就都失效了。
5、vector常用方法
reserve预留空间,并不增加元素个数,主要用来解决vector容器初始的内存使用效率低的问题
resize分配空间并构造,增加相应个数的元素
vector的insert比quque的insert效率高,因为vector内存连续;vector使用[]进行随机访问的速度也快于deque使用[]进行随机访问,因为deque内部处理堆跳转deque也有保留空间。 所以vector适用于随机访问。
6、用vector开辟二维数组:
vector<vector<int>> array;
array.resize(10);一维空间开辟
array[0].resize(10);扩展二维
7、vector常用方法
//swap实现是靠first,last,end三个指针交换,不做任何数据的交换,所以效率很高
8、实现不带空间配置器的vector
#include<iostream>
using namespace std;
template<typename T>
class Vector
{
public:
// 按指定size进行构造,size个空间,没有元素
Vector(int size = 0):mCur(0),mSize(size)
{
if(size == 0)
{
mpVec = nullptr;
}
else
{
mpVec = new T[mSize];
}
}
// 按指定size进行构造,添加size个元素,元素值是val
Vector(int size, const T &val = T()):mCur(size),mSize(size)
{
mpVec = new T[mSize];
for(int i = 0;i<mSize;++i)
{
mpVec[i] = val;
}
}
// 按[first, last)区间的元素来构造Vector
Vector(T *first, T *last)
{
int size = last - first;
mSize = size;
mpVec = new T[mSize];
for(mCur = 0;first <last;++first)
{
mpVec[mCur++] = *first;
}
}
~Vector()
{
delete []mpVec;
};
// 从末尾添加元素
void push_back(const T &val)
{
if(full())
{
//满了扩容
resize();
}
mpVec[mCur++] = val;
}
// 从末尾删除元素
void pop_back()
{
if(empty())
{
return;
}
--mCur;
}
bool full()const
{
return mCur == mSize;
}
bool empty()const
{
return mSize == 0;
}
// 返回容器元素的个数
int size()const
{
return mCur;
}
// Vector的迭代器
class iterator
{
public:
iterator(T *p = nullptr):itVec(p){}
bool operator!=(const iterator &it)
{
return itVec != it.itVec;
}
void operator++()
{
itVec++;
}
T& operator*()
{
return *itVec;
}
private:
T *itVec;
};
iterator begin()//返回容器0号元素的迭代器
{
return iterator(mpVec);
}
iterator end()//返回容器最后一个元素后继位置的迭代器
{
return iterator(mpVec + mCur);
}
private:
T *mpVec;
int mCur;
int mSize;
// 容器内存2倍扩容
void resize()
{
if(0 == mSize)
{
mpVec = new T[1];
mCur = 0;
mSize = 1;
}
else
{
T *newVec = new T[mSize*2];
for(int i = 0;i<mSize;++i)
{
newVec[i] = mpVec[i];
}
delete []mpVec;
mpVec = newVec;
mCur = mSize;
mSize *= 2;
}
}
};
int main()
{
Vector<int> vec1; // 底层不开辟空间
//vec1.push_back(10); // 0 - 1 - 2 - 4 - 8 - 16 - 32 - 64 - 128
//vec1.push_back(20);
for (int i = 0; i < 20; ++i)
{
vec1.push_back(rand() % 100 + 1);
}
cout << vec1.size() << endl;
// 用通用的迭代器遍历方式,遍历vec1,并打印容器中所有的元素值
Vector<int>::iterator it1 = vec1.begin();
for(;it1 != vec1.end();++it1)
{
cout << *it1 << " ";
}
cout << endl;
Vector<int> vec2(10, 20);
Vector<int>::iterator it2 = vec2.begin();
for(;it2 != vec2.end();++it2)
{
cout << *it2 << " ";
}
cout << endl;
int arr[] = { 12,4,56,7,89 };
Vector<int> vec3(arr, arr + sizeof(arr) / sizeof(arr[0]));
Vector<int>::iterator it3 = vec3.begin();
for(;it3 != vec3.end();++it3)
{
cout << *it3 << " ";
}
cout << endl;
return 0;
}
9、容器的空间配置器allocator
目的:把对象的内存开辟和对象的构造分开
把对象的析构,和内存释放分开
construct:构造 如何在一个存在的内存上构造
destroy:析构
allocate:开辟内存 malloc
deallocate:释放内存 free
// 实现容器的空间配置器
template<typename T>
class Allocator
{
public:
T* allocate(size_t size) // 开辟内存
{
return (T*)malloc(size);
}
void deallocate(void *ptr) // 释放内存
{
free(ptr);
}
void construct(T *ptr, const T &val) // 构造
{
new (ptr) T(val);//会调用拷贝构造
}
void destroy(T *ptr) // 析构
{
ptr->~T();
}
};
实现带空间配置器的vector
#include<iostream>
using namespace std;
// 实现容器的空间配置器
template<typename T>
class Allocator
{
public:
T* allocate(size_t size) // 开辟内存
{
return (T*)malloc(size);
}
void deallocate(void *ptr) // 释放内存
{
free(ptr);
}
void construct(T *ptr, const T &val) // 构造
{
new (ptr) T(val);
}
void destroy(T *ptr) // 析构
{
ptr->~T();
}
};
template<typename T,
typename allocator = Allocator<T>>
class Vector
{
public:
// 按指定size进行构造,size个空间,没有元素
Vector(int size = 0)
:mCur(0), mSize(size)
{
if (size == 0)
{
mpVec = nullptr;
}
else
{
//mpVec = new T[mSize];
mpVec = mAllocator.allocate(mSize * sizeof(T));
}
}
// 按指定size进行构造,添加size个元素,元素值是val
Vector(int size, const T &val)
:mCur(size), mSize(size)
{
mpVec = new T[mSize];
for (int i = 0; i < mSize; ++i)
{
mpVec[i] = val;
}
}
// 按[first, last)区间的元素来构造Vector
Vector(T *first, T *last)
{
int size = last - first;
mSize = size;
mpVec = new T[mSize];
for (mCur=0; first < last; ++first)
{
mpVec[mCur++] = *first;
}
}
~Vector()
{
//delete[]mpVec;
// 析构有效的对象
for (int i = 0; i < mCur; ++i)
{
mAllocator.destroy(mpVec+i);
}
// 释放内存
mAllocator.deallocate(mpVec);
}
// 从末尾添加元素
void push_back(const T &val)
{
if (full())
resize();
//mpVec[mCur++] = val;
mAllocator.construct(mpVec+mCur, val);
mCur++;
}
// 从末尾删除元素
void pop_back()
{
if (empty())
return;
--mCur;
mAllocator.destroy(mpVec+mCur);
}
bool full()const { return mCur == mSize; }
bool empty()const { return mCur == 0; }
// 返回容器元素的个数
int size()const { return mCur; }
// Vector的迭代器
class iterator
{
public:
iterator(T *p = nullptr) :_ptr(p) {}
bool operator!=(const iterator &it)
{
return _ptr != it._ptr;
}
void operator++() { _ptr++; }
T& operator*() { return *_ptr; }
private:
T *_ptr;
};
iterator begin() { return iterator(mpVec); }
iterator end() { return iterator(mpVec + size()); }
private:
T *mpVec;
int mCur;
int mSize;
allocator mAllocator; // 存储容器的空间配置器
// 容器内存2倍扩容
void resize()
{
if (0 == mSize)
{
mCur = 0;
mSize = 1;
mpVec = mAllocator.allocate(mSize * sizeof(T));
}
else
{
T *ptmp = mAllocator.allocate(2 * mSize * sizeof(T));
for (int i = 0; i < mSize; ++i)
{
mAllocator.construct(ptmp + i, mpVec[i]);
}
for (int i = 0; i < mSize; ++i)
{
mAllocator.destroy(mpVec + i);
}
mAllocator.destroy(mpVec);
mpVec = ptmp;
mSize *= 2;
}
}
};
class A
{
public:
A() :p(new int[2]) { cout << "A()" << endl; }
A(const A &src) { cout << "A(const A&)" << endl; }
~A() { cout << "~A()" << endl; }
private:
int *p;
};
int main()
{
A a1, a2, a3;
cout << "------------" << endl;
// 这里只需要空间,不需要构造对象 malloc
Vector<A> vec(100);
vec.push_back(a1);
vec.push_back(a2);
vec.pop_back();
vec.push_back(a3);
}
10、vector的几种遍历方式
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// vector容器使用示例
vector<int> vec;
for (int i = 0; i < 20; ++i)
{
vec.push_back(rand() % 100);
}
// 定义迭代器遍历
vector<int>::iterator it1 = vec.begin();
for (; it1 != vec.end(); ++it1)
{
cout << *it1 << " ";
}
// 使用auto来得到指针类型,然后遍历
auto it2 = vec.begin();
for (; it2 != vec.end(); ++it2)
{
cout << *it2 << " ";
}
// foreach遍历,foreach底层也是通过容器的迭代器实现的容器元素遍历
for (int val : vec)
{
cout << val << " ";
}
// 下标遍历
for (int i = 0; i < vec.size(); ++i)
{
cout << vec[i] << " ";
}
return 0;
}
11、通过泛型算法对vector容器进行搜索
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
// vector容器使用示例
vector<int> vec;
for (int i = 0; i < 20; ++i)
{
vec.push_back(rand() % 100);
}
// find演示vector中搜索20是否存在,如果存在则删除第一个20
auto it1 = find(vec.begin(), vec.end(), 20);
if (it1 != vec.end())
{
vec.erase(it1);
}
// 打印vector容器
for_each(vec.begin(),
vec.end(),
[](int val)->void
{
cout << val << " ";
});
// 找第一个大于50的数字,打印出来
auto it2 = find_if(vec.begin(), vec.end(),
[](int val)->bool {return val > 50; });
if (it2 != vec.end())
{
cout << "第一个大于50的数字是:" << *it2 << endl;
}
return 0;
}
12、迭代器
前面一直在说迭代器,可是迭代器到底是什么?
迭代器
①定义:迭代器(iterator)是一种检查容器内元素并遍历元素的数据类型。
②迭代器对所有的容器都适用。
③操作:
- begin返回迭代器指向的第一个元素
- end返回迭代器指向vector的末端元素的下一个。它返回的并不是一个实际的元素。只是起到一个哨兵的作用,表示已处理完vector中所有元素。
- * --> 解引用
- 前置++ 把容器中的迭代器向前移动一个位置(对于对象,前置++效率更高,因为不会产生临时对象)
- == 和 != 如果两个迭代器对象指向同一个元素,则它们相等,否则不相等。包括end位置
④、迭代器的范围:[begin,end)
⑤、迭代器的种类:
除了为容器提供的用于访问数据提供的迭代器之外,标准库还提供了其它几种迭代器。
- 插入迭代器:绑定在容器上,可以向容器内插入数据;它接受一个数据,生成一个迭代器,当通过insert iterator进行赋值的时候,迭代器调用容器操作来向容器指定位置插入一个元素。
- 流迭代器:绑定在输入或输出流上,用来遍历关联的IO流。
- 反向迭代器:迭代器向后移动,虽然也使用++运算符,但是是向后移动的。
- 移动迭代器:用来移动元素。
两个不同容器的迭代器能否进行比较?(两个迭代器比较必须作用于同一个容器)
不行,迭代器在进行比较的时候,需要判断:
①是否是同一个容器的迭代器
②迭代的容器的元素长度是否一样,迭代过程中容器元素经过改动,一定要及时更新迭代器
迭代器失效:
任何改变vector长度的操作都会使已存在的迭代器失效。所以每次操作之后都要更新迭代器。