本期重點:空間配置器,容器
空間配置器:把內存的開闢和對象的構造分開。把對象的析構和內存的釋放分開。
空間配置器的實現如下:
// 給容器實現空間配置器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