STL学习


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 第二级配置器

    1. 维护16个自由链表,负责16种小型区块的配置能力,内存池,内存不足时,调用第一级配置器
    2. 需求块大于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一样
  • 迭代器中包含:
    1. 指向中控器的指针
    2. 指向缓冲区头的指针
    3. 指向缓冲区尾的指针
    4. 指向缓冲区中当前数据的指针
  • 拥有两个配置器
  • 一个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的处理措施:

    1. 当源内存的首地址等于目标内存的首地址时,不进行任何拷贝
    2. 当源内存的首地址大于目标内存的首地址时,实行正向拷贝
    3. 当源内存的首地址小于目标内存的首地址时,实行反向拷贝
  • 与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开始向前复制
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值