STL中string和deque的实现

1. string

1.1 存储管理
  1. 实际数据空间申请由_M_dataplus负责管理,其中指针_M_p指向存储位置。
  2. 当数据量较小时,数据存储在一个union_M_local_data数组中(char数组长度小于等于15,不包含’\0’标识符),并使用该结构体中的指针初始化_M_dataplus
    数据量较大时,union结构体中保存的是分配空间的容量
1.2 构造函数
  1. 初始化时,都是先用_M_local_data初始化,构造函数底层大多调用_M_construct
    小于等于15,则按照15为容量。如果需要的空间大于15,则调用分配器进行分配; _M_limit是限制最大值;
    堆区分配规则是:分配空间时,通过_M_create申请的空间大小为 max(2*old_capacity, __capacity) + 1,设置新的指针
    最后,调用assign进行赋值
    注意细节:调用_S_copy_chars拷贝时,不论是栈空间还是堆空间都并没有将’\0’标识符拷贝进来。但申请空间时也都额外多申请了一个,这个额外空间就是为了标识end。(栈区的大小为16,实际存储字符的为15,最后一个保留)

  2. 移动拷贝构造时,首先将原str的分配器移动拷贝过来
    如果小于等于15,需要调用copy进行复制
    否则,只需要把原分配器的指针复制一份即可。

  3. 列表初始化initializer_list,即使用花括号 {} 或圆括号 () 来提供初始化的值。

  4. 析构函数中调用_M_dispose,若局部存储足够则什么都不做,否则,释放申请的空间

  5. push_backappend(字符串拼接),如果超出当前容量,会调用_M_mutate重新分配大小(第3条的分配规则)。

1.3 常用函数
  1. substr调用构造函数实现

2. sort算法的实现

其参数为两个随机访问迭代器beginend

比较函数以greater为例,其返回值为a > b;

  1. 首先计算下,2×log(end - begin)确定最大的迭代深度,这里的乘2的含义是:假设是理想情况下,每次都能选取到区间中位数的情况,这样每一边都是log(end - begin),递归调用时也是每次只调用一边。

  2. __introsort_loop函数中
    (1)如果当前元素数量大于16,才会进行后续流程。否则,停止。
    (2)__unguarded_partition_pivot中选取的是第二个元素(__first + 1)、中间元素(mid),最后一个元素(__last - 1),这三个值的中间值作为枢轴,并移动到第一个元素位置处
    (3)__unguarded_partition执行的就是按照选取的枢轴,对元素进行排序。具体而言,是在左侧找比枢轴大的元素,右侧找比枢轴小的元素,进行交换。不断重复这个过程即可。左侧为>=,右侧为<
    (4)如果达到最大迭代步长,调用堆排序。

  3. 最后执行插入排序

3. pair实现

pair是一个struct,拥有两个成员变量first和second

4. initializer_list

可认为是一个只读视图,里面只存储迭代器,无法进行修改
initializer_list拷贝构造是浅拷贝,底层的对象只有一份

        initializer_list<string> s1 = { "aa", "bb", "cc", "dd" };
        initializer_list<string> s2 = s1;
        initializer_list<string> s3(s1);
        initializer_list<string> s4;
        s4 = s1;
        cout << &(*s4.begin()) << endl;					// 0x7ffe0e382fa0
        cout << &(*s2.begin()) << endl;					// 0x7ffe0e382fa0

5.deque的实现

  1. 实质上是指针数组,数组中的每个元素指向一块存储空间,__deque_buf_size表明每个空间存储几个对象
    该指针数组的初始大小为max(_S_initial_map_size, __num_nodes + 2),至少预留了两个空间

  2. _Deque_impl_M_map指向指针数组的起始位置;_M_start_M_finish为迭代器,指向第一个buffer和最后一个buffer;
    其中_Deque_iterator迭代器中包含,_M_cur_M_first_M_last_M_node,其中前三个为T*,后一个为T**

  3. push_front函数中
    (1)先判断_M_cur != _M_first,若满足,则说明此buffer中还有空间,向该buffer中添加元素,调整_M_cur ,直接结束。
    (2)若不满足,需要在其前面添加buffer。判断前面是否有剩余空间,执行第4步;若没有,则重新分配;执行第3步。
    (3)重新分配时,判断当前数组大小是否大于新结点数量的2倍,若满足,说明有空间,但当前位置太靠前,则只需要进行移位即可。若不满足,重新分配指针数组的大小。
    (4)重新申请一个buffer,将_M_cur设置为其最后一个元素的位置。

  4. pop_front函数中逻辑相对简单,析构对象,如果满足_M_cur == _M_last - 1,则将该buffer释放,修改_M_start即可。如不满足,调整_M_cur 即可。

  5. insert函数,先判断是否是插入最前面或最后面的情况,若是,则调用相应的push_front/back方法
    否则,是插入在中间位置。需要判断下哪边的元素数量较小,则移动哪边,调用带三个参数的move方法。移动之前为了确保有空间,先执行一个push_front/back方法。

迭代器失效情况总结

  1. 从上述过程可知,向头尾插入数据,因为可能会改变指针数组或改变指针指向,因此迭代器可能会失效,各buffer并未改变,因此对象的引用和指针仍然有效。
  2. 向中间插入数据时,迭代器、引用、指针都可能失效,因为涉及到buffer中对象的移动(理论上来说只会改变相对插入位置元素数量较小的一边的引用和指针,但在外部不好判断是哪一边)。

6.现代C++

std::move std::forward 只是执行类型转换(static_cast)的模板函数
对于const变量,调用move()转换的是const右值引用,仍然是采用的拷贝构造而非移动构造

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值