STL学习
tags: 数据结构
配置器
- SGI STL采用的和标准规范不同,名称为alloc,不接受任何参数
- 标准配置器效率不佳,只对::operator new和::opeartor delete 做了简单封装
操作 | 第一步 | 第二步 |
---|---|---|
new | 调用::operator new 配置内存 | 调用对象构造函数 |
delete | 调用对象析构函数 | 调用::operator delete 释放内存 |
* STL 将这两个阶段分开称为:
* 内存配置: alloc::allocate()
和alloc::deallocate()
* 构造和析构: ::construct()
和::destroy()
template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
new(p) T1(value);
}
template <class T>
inline void destroy(T* pointer) {
pointer->~T();
}
- destroy中用到了_type_traits<>来求取最适当措施。还有针对迭代器为char* 和wchar_t* 的特化版
- destroy有两个版本,第一版接受一个指针,第二版接受两个迭代器
- std::alloc考虑了多线程状态
- SGI正是以malloc()和free()完成对内存的配置和释放
当配置区块超过128bytes时,调用第一级配置器,否则调用第二级配置器
为了降低额外负担,采用了memory pool整理方式(采用typedef把alloc定义为第一级配置器,或者第二级配置器,由_USE_MALLOC决定)
- SGI还为alloc包装了个接口simple_alloc
SGI STL 第二级配置器
- 维护16个自由链表,负责16种小型区块的配置能力,内存池,内存不足时,调用第一级配置器
- 需求块大于128bytes转调用第一级配置器
为方便管理,SGI二级配置器会自动把内存需求量上调为8的整数倍,并维护16个链表,大小为8, 16, …., 128bytes
//链表节点(使用共用体只是为了节省内存开销)
union obj {
union obj * free_list_link;
char client_data[1];
};
//内存池起始位置 static char* start_free;
//内存池结束位置 static char* end_free;
- 第二级配置器,当free list中没有可用区块时,就调用refill()。新的空间取自内存池,缺省取得20个新节点,如果内存池不足,获得节点数可能小于20
//内存池思想:
if (内存池水量足) {
直接返回20个区块给free list.
} else if (足够供应一个以上的区块) {
直接供应出去
} else { //一个都无法供应
从对中配置内存,为内存池中注入水,新水量等于需求量的两倍
if(配置失败) { //堆中都没有空间了
寻找尚未使用的足够大的区块。
if(找到) {
挖出一块,交出
} else {
调用第一级配置器(有out-of-memory机制)
}
} else { //配置成功
一个交出,19个连接到free list上。剩余20个放入内存池
}
}
STL定义了五个全局函数,作用域未初始化的空间上
- 3, 4, 5包含自
<memory>
*POD 标量型别,为_true_type,批量构造,否则单个构造
construct()
template <class T1, class T2>
inline void construct (T1* p, const T2& value) {
new(p) T1(value);
}
destroy()
template <class T>
inline void destroy(T* ptr) {
ptr->~T();
}
uninitialized_copy() –> copy()
template <class InputIterator , class ForwardIterator>
ForwardIterator uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator resut);
//输出目的地: [result, result+(last-first)];
//针对(first, last)范围内的每个对象都会产生一份复制品放入输出范围中,调用construct(&*(result+(i-first)), *i)
//要么构造所有的元素,要么不构造东西
uninitialized_fill() –> fill()
template<class ForwardIterator , class T>
void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x);
//[first, last]范围内的每个迭代器都指向未初始化的内存,都会在该范围内产生x的复制品,调用construct(&*i, x)
uninitialized_fill_n() –> fill_n()
template <class ForwardIterator, class Size, class T>
ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n, const T& x);
//[first, first+n]指向为初始化内存,每个迭代器都会产生x的复制品,调用construct(&*i, x)
//要么构造所有的元素,要么不构造东西
迭代器
- 思想: 将容器和算法分开,彼此独立设计
- 迭代器是一种行为类似指针的对象,最重要的是对
operator*
和operator->
进行重载 - 为了符合规范,任何迭代器都应提供五个内嵌型别:iterator_Category, value_type, difference_type, pointer, reference
萃取技术
iterator_traits
- 在算法中运用迭代器很可能会用到其型别
- template参数推导机制推导的知识参数,无法推导函数的返回值型别。需要其他方法,:在迭代器中声明内嵌型别
- 并不是所有的迭代器都是class type,比如原生指针。使用偏特化
- 就是在所有class中声明内嵌型别,然后定义一个类iterator_traits对所有的类型进行萃取(推导)
traits可以拥有特化版本
_type_traits
- SGI 将这种技术进步扩大有了_type_traits,负责萃取型别type的特性,以便我们在对这个型别进行构造,析构,拷贝,赋值等操作时采用最有效率的措施
- 提供了一种机制在编译使其完成函数派送决定
_type_traits<T>::has_trivial_default_constructor
_type_traits<T>::has_trivial_copy_constructor
_type_traits<T>::has_trivial_assignment_opertator
_type_traits<T>::has_trivial_destructor
_type_traits<T>::is_POD_type
//希望上述式子响应我们真假,但不应该是一个bool值,应该是一个有着真假性质的对象,可以利用响应结果进行参数推导
struct _type_type {};
struct _type_false {};
template <class type>
struct _type_traits {
typedef _true_type this_dummy_member_must_be_first;
typedef _false_type has_trivial_default_constructor
typedef _false_type has_trivial_copy_constructor
typedef _false_type has_trivial_assignment_opertator
typedef _false_type has_trivial_destructor
typedef _false_type is_POD_type
}; //全部设置为_false_type为了保守
迭代器型别
value_type
- 迭代器对象所指型别
difference_type
- 表示两个迭代器之间的距离
reference_type
- 传回左值
pointer_type
- 传回所指之物的地址
iterator_category
- 根据移动特性与实施操作,迭代器被分为
名称 | 说明 |
---|---|
InputIterator | 只读 |
OuputIterator | 只写 |
FowardIterator | 允许读写 |
BidirectionalIterator | 可双向移动 |
RandomAccessIterator | 涵盖所有指针的运算能力 |
容器
vector
- vector 的实现技术,关键在于
其对大小的控制
,重新配置时的数据移动效率
- vector内部定义了,使用空间的头iterator start, 和尾iterator finish,目前可用空间的尾end_of_stroage
- 可使用空间的尾end_of_storage
- vector支持随机存取,所以vector是Random Access Iterators
- 增加新元素时,如果原大小为0,则配置1,否则,容量会扩充至两倍
- 对vector的任何操作一但引起空间重新配置,指向原vector的所有迭代器就都失效了。
操作函数
vector(arr, arr+sizeof(arr)/sizeof(int)); //初始化数组
vector();
vector(int nSize);
vector(int nSize, const T& t);
vector(cosnt vector&);
void push_back(const T& x );
iterator insert(iterator it, const T& x);
void insert(iterator it, int n, const T& x);
void insert(iterator it, const_iterator first, const_iterator last);
iterator erase(iterator it);
iterator erase(iterator first, iterator last)
void pop_back();
void clear();
reference at(int pos);
referenc front();
reference back();
iterator begin();
iterator end();
reverse_iterator rbegin();
reverse_iterator rend();
bool empty() cons;
int size()const ;
int capacity()const ; //当前能容纳的最大元素的个数
int max_size() const ;
void swap(vector&);
void assign(int n, const T& x); //替换
void assign(const_iterator first, const_iterator last); //向量[first, last]中元素设置成当前向量元素
void resize(size_type n, value_type val=value_type());
// resize(),设置大小(size);
// reserve(),设置容量(capacity);
list
- list提供的是Bidrectional Iterator
- 双向环状链表,只需要一个指针(node)就可以表现整个链表
- node指向可以置于尾端的一个空白节点,就能符合STL对于‘前闭后开’区间的要求成为last迭代器
- list插入和删除操作都不会造成原有的迭代器失效
迭代器设计
- 迭代器中由一个普通指针指向list的节点
函数
list<Elem> c;
list<Elem> c1(c2);
list<Elem> c(n); //创建n个元素,默认值由默认构造函数确定
list<Elem> c(n, elem);
list<Elem> c(begin, end);
int size() const;
bool empty*() const;
void push_back(cont T& x);
void push_front(const T& x);
void pop_back();
void pop_front();
void remove(const T& x); //删除链表中所有元素为x的元素
void clear();
iterator insert(iterator it, const T& x=T()); it前插入x,返回x的迭代器指针
void insert(Iterator it, size_type n, const T& x=T());
void insert(Iterator it, const_iterator first, const_iterator last);
iterator erase(iterator it);
iterator erase(iterator first, iterator last);
iterator begin();
iterator end();
reverse_iterator rbegin();
reverse_iterator rend();
reference front();
reference back();
void sort(); //默认升序
template <class Pred> void sort(Pred pr); //判定函数Pr
void swap(list& str);
void unique(); //相邻元素如有重复的仅保留一个
void splice(iterator it, list& x); //队列合并,队列x所有元素插入迭代器指针it前,x变成空队列
void splice(iterator it, list& x, iterator first)
void splice(iterator it, list& x, iterator first, iterator last)
void reverse(); //反转容器中元素
deque
- 双向开口的连续线性空间
- deque和vector最大差异:deque允许常数时间内对头端进行插入或者移除操作,deque没有所谓的容量概念
- 有分段的连续空间组成
- 提供Random Access Iterator
- 对deque排序为了效率,可将deque完整复制到vector,然后排序再考回deque
- 采用一块所谓的map()作为中控
- SGI STL允许指定缓冲区大小,默认0表示将使用512bytes缓冲区
- map中每个节点都指向一个缓冲区,map的维护和vector一样
- 迭代器中包含:
- 指向中控器的指针
- 指向缓冲区头的指针
- 指向缓冲区尾的指针
- 指向缓冲区中当前数据的指针
- 拥有两个配置器
- 一个map最少管理8个节点,最多管理‘所需节点数+2’(前后各与被一个扩充时准备)
函数
deque()
dque(int nSize);
deque(int nSize, const T& t);
deque(const deque&);
void push_front(const T& x);
void push_back(const T& x);
iterator insert(iterator it, const T& x);
void insert(iterator it, const_iterator first, const_iterator last);
iterator erase(iterator it);
iterator erase(iterator first, iterator last);
void pop_back();
void pop_front();
void clear();
reference at(int pos);
reference front();
reference back();
iterator begin();
iterator end();
reverse_iterator rbegin();
reverse_iterator rend();
bool empty() const;
int size() const;
int max_size() const;
void swap(vector&);
void assign(int n, const T& x);
void assign(const_iterator first, const_iterator last);
bitset
函数
bitset()
bitset(const bitset&);
bitset(unsigned long val);
bitset(const string& str, size_t pos = 0, size_t n = -1); //由字符串创建为容器
bitset& operator=(const bitset&)
bitset& operator&=(const bitset&);
bitset& operator|=(const bitset&);
bitset& oeprator^=(const bitset&);//返回两个容器的异或后的引用,修改第一个位容器的值
bitset& operator<<=(size_t);
bitset& operator>>=(size_t);
bitset operator<<(size_t n)const; //返回左移后的备份
bitset operator>>(size_t n)const ;
bitset operator&(const bitset&, const bitset&);
bitset operator|(const bitset&, const bitset&);
bitset operator^(cosnt bitset&, const bitset&);
string toString();
size_t size() const; //返回容器大小
size_t count() const; //返回设置1位个数
bool any() const; //是否有位设置1
bool none() const; //是否没有为设置1
bool test(size_t n)const; //测试某位是否为1
bool operator[](size_t n) const; //随机访问位元素
unsigned long to_ulong() const; //若没有溢出异常,返回无符号长整形数
bitset& set(); //位容器所有位置1
bitset& flip(); //所有位反转
bitset& reset(); //所有位置0
bitset& set(size_t n, int val=1); //设置某位为1或0,默认为1
bitset& reset(size_t n); //复位某位为0
bitset flip(size_t n) ; //反转某位
C++特性相关
c++ new handler机制
- 你可以要求系统在内存配置需求无法被满足时,调用一个你自己所指定的函数
typename用法:
1. 它的作用同class一样表明后面的符号为一个类型
2. 使用嵌套依赖类型(nested depended name)
class MyArray
{
public:
typedef int LengthType;
static LengthType GetLength;
};
MyArray::LengthType MyArray::GetLength = 4;
template<class T>
void MyMethod(T myarr)
{
typedef typename T::LengthType LengthType;
//这个时候typename的作用就是告诉c++编译器,typename后面的字符串为一个类型名称,而不是成员函数或者成员变量
LengthType length = myarr.GetLength;
cout << length << endl;
}
void test() {
MyArray m;
MyMethod<MyArray>(m); //cout << 4;
}
模板特化
template <class T>
class Pair {
T value1, value2;
public:
Pair(T first, T second){
value1=first;
value2=second;
}
T module() { return 0;}
};
//特殊化本身也是模板定义的一部分,我们必须在该定义开头写template<>
template <>
class Pair<int> {
int value1, value2;
public:
//当我们特殊化模板的一个数据类型的时候,同时还必须重新定义类的所有成员的特殊化实现,特殊化不会继承通用模板的任何一个成员。
Pair (int first, int second){
value1=first;
value2=second;
}
int module (){
return value1%value2;
}
};
类静态成员初始化
- 如果class 中含有const static integral data member, 那么可以在class 中直接初始化。可是static intergral data member 不能直接初始化
class Test {
static const int _datai = 2;
static int _dataj = 2; // 错误
};
- 函数指针无法持有自己的状态(局部状态),无法达到组件技术中的可是配性
函数
qsort()
void qsort(void* base, int nelem, int width, int (*fcmp)(const void*, const void* ) )
- 1: 待排序数组首地址
- 2: 数组中待排序元素数量
- 3: 各元素的占用空间大小
- 4: 指向函数的指针,用于确定排序的顺序
- 对二维数组排序
int a[1000][2]; //按照a[0]的大小进行整体排序
qsort(a, 1000, sizeof(int)*2, comp);
int comp(const void* a, const void* b) {
return ((int*)a)[0]-((int*)b)[0];
}
drand48()
- drand48: 产生[0,1]之间均匀分布的随机数,采用了线性同余法和48位整数运算来产生伪随机序列
- 函数产生一个48位的伪随机整数,取出此整数的高32位作为随机数,然后将这个32位的伪随机数规划到[0,1]之间,函数srand48来初始化drand48(),其只对于48位整数的高32位进行初始化,而其低16位被设定为随机值。
#define MNWZ 0x100000000
#define ANWZ 0x5DEECE66D
#define CNWZ 0xB16
#define INFINITY 0xFFFFFFFFF
int labelsize;
int dim;
static unsigned long long seed = 1;
double drand48(void)
{
seed = (ANWZ * seed + CNWZ) & 0xFFFFFFFFFFFFLL;
unsigned int x = seed >> 16;
return ((double)x / (double)MNWZ);
}
//static unsigned long long seed = 1;
void srand48(unsigned int i)
{
seed = (((long long int)i) << 16) | rand();
}
realloc
void* realloc(void* mem_address, unsigned int newsize)
- #include
find
template <class InputIterator, class T> InputIterator find(InputIterator fist, InputIterator last, const T& value)
- 查找
advanced()
- 函数内部将p累进n次
distance()
- 用来计算两个迭代器之间的距离
memcpy
void* memcpy(void* dest, const void* src, size_t n)
#include <string.h>
- 由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始地址的空间内。
- 函数返回一个指向dest的指针。
- 与strcpy相比,memcpy并不是遇到’\0’就结束,而是一定会拷贝完n个字节。
- memcpy用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度;
memmove
void* memmove(void* dest, const void* src, size_t count)
#include <string.h>
- 由src所指内存区域复制count个字节到dest所指内存区域。src和dest所指内存区域可以重叠,但复制后dest内容会被更改。函数返回指向dest的指针。
memmove的处理措施:
- 当源内存的首地址等于目标内存的首地址时,不进行任何拷贝
- 当源内存的首地址大于目标内存的首地址时,实行正向拷贝
- 当源内存的首地址小于目标内存的首地址时,实行反向拷贝
与memcpy的区别
- 唯一的区别是,当内存发生局部重叠的时候,memmove保证拷贝的结果是正确的,memcpy不保证拷贝的结果的正确。
- 实际上,memcpy只是memmove的一个子集。
- 这两个函数都是用汇编实现的。
- memcopy比memmove的速度要快一些
copy_backward()
template<class BidirectionalIterator1, class BidirectionalIterator2>
BidirectionalIterator2 copy_backward ( BidirectionalIterator1 first, BidirectionalIterator1 last, BidirectionalIterator2 result);
- copy_backward算法与copy在行为方面相似,只不过它的复制过程与copy背道而驰,其复制过程是从最后的元素开始复制,直到首元素复制出来。也就是说,复制操作是从last-1开始,直到first结束。
- 从result开始向前复制