本期重点:空间配置器,容器
空间配置器:把内存的开辟和对象的构造分开。把对象的析构和内存的释放分开。
空间配置器的实现如下:
// 给容器实现空间配置器Allocator
template<typename T>
struct Allocator
{
// allocate开辟内存
T* allocate(size_t size)
{
return (T*)malloc(size);
}
// deallocate释放内存
void deallocate(T *ptr)
{
free(ptr);
}
// construct构造对象
void construct(T *ptr, const T &val)
{
new (ptr)T(val);
}
//destroy析构对象
void destroy(T *ptr)
{
ptr->~T();
}
};
template<typename T, typename allocator = Allocator<T>>
class Vector
{
public:
Vector(int size = 10)
:mCur(0), mSize(size)
{
mpVec = alloc.allocate(size*sizeof(T));
}
~Vector()
{
for (int i = 0; i < mCur; ++i)
{
alloc.destroy(mpVec + i);
}
alloc.deallocate(mpVec);
mpVec = NULL;
}
Vector(const Vector &src)
{
mpVec = alloc.allocate(src.mSize*sizeof(T));
mSize = src.mSize;
mCur = src.mCur;
for (int i = 0; i < mCur; ++i)
{
alloc.construct(mpVec + i, src.mpVec[i]);
}
}
Vector&operator=(const Vector &src)
{
if (this == &src)
{
return *this;
}
for (int i = 0; i < mCur; ++i)
{
alloc.destroy(mpVec + i);
}
alloc.deallocate(mpVec);
mpVec = alloc.allocate(src.mSize);
for (int i = 0; i < mCur; ++i)
{
alloc.construct(mpVec + i, src.mpVec[i]);
}
return *this;
}
int GetCur(){ return mCur; }
int GetSize(){ return mSize; }
void push_back(const T &val) // 从末尾给向量容器添加元素
{
if (mCur == mSize)
{
reSize();
}
alloc.construct(mpVec + (mCur++), val);
}
void pop_back() // 从末尾删除向量容器的元素
{
alloc.destroy(--mCur);
}
T& operator[](int index)
{
return mpVec[index];
}
// 给向量容器Vector实现迭代器
class iterator
{
public:
iterator(T *pos) :_ptr(pos){}
bool operator!=(const iterator&src)
{
return this->_ptr != src._ptr;
}
bool operator==(const iterator&src)
{
return this->_ptr == src._ptr;
}
iterator &operator++()
{
_ptr++;
return iterator(_ptr);
}
iterator &operator++(int)
{
return iterator(_ptr++);
}
iterator operator--()
{
_ptr--;
return iterator(_ptr);
}
iterator &operator--(int)
{
return iterator(_ptr--);
}
T& operator*(){ return *_ptr; }
private:
T*_ptr;
};
iterator begin() { return iterator(mpVec); }// 返回首元素迭代器
iterator end() { return iterator(mpVec + mCur); }// 返回末尾后继位置的迭代器
void show()
{
for (int i = 0; i < mCur; i++)
{
cout << mpVec[i] << " ";
}
}
private:
T *mpVec;
int mSize; // 扩容的总大小
int mCur; // 当前元素的个数
allocator alloc;
void reSize()// 向量容器扩容函数,默认2倍扩容
{
T *tmp = alloc.allocate(2 * mSize *sizeof(T));
for (int i = 0; i < mCur; ++i)
{
alloc.construct(tmp + i, mpVec[i]);
}
for (int i = 0; i < mCur; ++i)
{
alloc.destroy(mpVec + i);
}
alloc.deallocate(mpVec);
mpVec = tmp;
mSize *= 2;
}
friend ostream& operator<<(ostream&out, const Vector<T>&src)
{
for (int i = 0; i < src.mCur; i++)
{
cout << src.mpVec[i] << " ";
}
return out;
}
friend Vector operator+(const Vector<T>&src, const Vector<T>&srv);
};
template<typename T>
Vector<T>operator+(const Vector<T>&src, const Vector<T>&srv)
{
int size = src.mCur + srv.mCur;
while (src.mSize < size)
{
reSize();
}
int j = 0;
for (int i = src.mpVec; i < size; i++)
{
src[i] = srv[j++];
}
}
容器
在数据存储上,有一种对象类型,它可以持有其它对象或指向其它对像的指针,这种对象类型就叫做容器。很简单,容器就是保存其它对象的对 象,当然这是一个朴素的理解。
容器还有另一个特点是容器可以自行扩展。在解决问题时我们常常不知道我们需要存储多少个对象,也就是说我们不知道应该创建多大的内存空间来保存我们的对象。 显然,数组在这一方面也力不从心。容器的优势就在这里,它不需要你预先告诉它你要存储多少对象,只要你创建一个容器对象,并合理的调用它所提供的方法,所 有的处理细节将由容器来自身完成。它可以为你申请内存或释放内存,并且用最优的算法来执行您的命令。
通用容器的分类
STL 对定义的通用容器分三类:顺序性容器、关联式容器和容器适配器。
顺序性容器 是 一种各元素之间有顺序关系的线性表,是一种线性结构的可序群集。顺序性容器中的每个元素均有固定的位置,除非用删除或插入的操作改变这个位置。这个位置和 元素本身无关,而和操作的时间和地点有关,顺序性容器不会根据元素的特点排序而是直接保存了元素操作时的逻辑顺序。比如我们一次性对一个顺序性容器追加三 个元素,这三个元素在容器中的相对位置和追加时的逻辑次序是一致的。
关联式容器 和 顺序性容器不一样,关联式容器是非线性的树结构,更准确的说是二叉树结构。各元素之间没有严格的物理上的顺序关系,也就是说元素在容器中并没有保存元素置 入容器时的逻辑顺序。但是关联式容器提供了另一种根据元素特点排序的功能,这样迭代器就能根据元素的特点“顺序地”获取元素。
关联式容器另一个显著的特点是它是以键值的方式来保存数据,就是说它能把关键字和值关联起来保存,而顺序性容器只能保存一种(可以认为它只保存关键字,也可以认为它只保存值)。这在下面具体的容器类中可以说明这一点。
容器适配器(这些容器有没有自己的底层数据结构) 是一个比较抽象的概念, C++的 解释是:适配器是使一事物的行为类似于另一事物的行为的一种机制。容器适配器是让一种已存在的容器类型采用另一种不同的抽象类型的工作方式来实现的一种机制。其实仅是发生了接口转换。那么你可以把它理解为容器的容器,它实质还是一个容器,只是他不依赖于具体的标准容器类型,可以理解是容器的模版。或者把它 理解为容器的接口,而适配器具体采用哪种容器类型去实现,在定义适配器的时候可以由你决定。
下表列出STL 定义的三类容器所包含的具体容器类:
顺序型容器各自的特点
vector和list的使用场景的区别如下:
vector: 随机访问 内存是连续的,方便排序,二分搜索
list : 增加,删除 多的时候deque和list比较:
头插和尾插 deque list O(1)
在中间进行插入删除 list O(1) vector deque O(n)双端队列:动态开辟的二维数组
初始开辟两个第一维的数组。里面保存第二维数组的地址
第二维数组的大小:4096/sizeof(T)扩容是第一维数组的二倍扩容。第二维数组放到第一维数组的中间
扩容的时候deque比vector效率高,因为deque一维数组保存的是第二维的地址,不需要拷贝数据
reserve 和 resize 的区别
reserve 只开辟内存,不添加元素(一般用这个,在刚开始的时候,开辟出多余的空间)
resize 开辟内存,还添加元素
概念都讲完了,现在我们来实际应用一下
先实现一下功能函数(库里面有,但是我们也写一遍)
template<typename InputIterator, typename Compare>
void mysort(InputIterator first,InputIterator last, Compare &comp)
{
InputIterator i, j;
int k = 0;
InputIterator::value_type tmp;
for (i = first; i != last - 1; ++i, ++k)
{
for (j = first; j != last - k - 1; ++j)
{
if (comp(*j, *(j + 1))) // 回调函数
{
tmp = *j;
*j = *(j + 1);
*(j + 1) = tmp;
}
}
}
}
template<typename InputIterator, typename _Ty>
InputIterator myfind(InputIterator first, InputIterator last, const _Ty &val)
{
for (; first != last; ++first)
{
if (*first == val)
{
return first;
}
}
return last;
}
template<typename InputIterator, typename Compare>
InputIterator myfind_if(InputIterator first,
InputIterator last, Compare &comp)
{
for (; first != last; ++first)
{
if (comp(*first)) // comp(*first)
return first;
}
return last;
}
template<typename Container>
void showContainer(Container &con)
{
Container::iterator it = con.begin();
for (; it != con.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
template<typename T>
bool compareGreater(T a,T b)
{
return a > b;
}
template<typename T>
bool compareLess(T a, T b)
{
return a < b;
}
template<typename T>
class mygreat
{
public:
bool operator()(T a, T b)
{
return a>b;
}
};
template<typename T>
class myless
{
public:
bool operator()(T a, T b)
{
return a<b;
}
};
运用库函数实现一个小功能(求大数据的top k的问题,在上面含有100 0000个元素的vec中,找到值最大的前10个元素 在O(n)时间内 n * O(log2n))
#include <list>
#include <ctime>
#include <algorithm>
#include <functional>
#include <stack>
#include <queue>
using namespace std;
int main(int argc, _TCHAR* argv[])
{
vector<int> vec;
srand(time(NULL));
for (int i = 0; i < 1000000; ++i)
{
vec.push_back(rand()%1000000 + 1);
}
priority_queue<int, vector<int>, greater<int>> queue;
for (int i = 0; i < 10 ; ++i)
{
queue.push(vec[i]);
}
for (int i = 10; i < vec.size(); ++i)
{
if (vec[i] > queue.top())
{
queue.pop();
queue.push(vec[i]);
}
}
while (!queue.empty())
{
cout << queue.top() << " ";
queue.pop();
}
cout << endl;
}
// 把40这个元素,按顺序插入到vec里面,打印
vector<int>::iterator it2 = find_if(vec.begin(),vec.end(), bind2nd(greater<int>(), 40));
if (it2 != vec.end())
{
vec.insert(it2, 40);
}
for (it1 = vec.begin(); it1 != vec.end(); ++it1)
{
cout << *it1 << " ";
}
cout << endl;
参考资料:
C/C++STL常用容器用法总结 https://blog.csdn.net/weixin_41162823/article/details/79759081
浅谈C++容器 https://www.cnblogs.com/xkfz007/articles/2534249.html