容器删除元素后迭代器失效_C 实现 STL · 迭代器与容器

上一篇定义了一个简单的泛型。有兴趣的老铁,头条搜索《C 实现 STL·泛型设计》。本篇,我们尝试把迭代器,和容器来实现一下。

迭代器的设计

迭代器(iterator)有时又称光标(cursor)是程序设计的软件设计模式,可在容器对象(container,例如链表或数组)上遍访的接口,设计人员无需关心容器对象的内存分配的实现细节。总的来说,迭代器就是有两个功能:移动和访问。于是设计如下:

typdef struct _iterator {    iterator_t (*move)(iterator_t, int step);    void* reference;    void* container;} iterator_t;
  • move 字段为函数指针。主要用于移动迭代器。它接受一个当前位置的迭代器,和移动的步数和方向作为参数,此函数由具体实现的容器提供,在 双向连表 List 的容器实现 这一章节中是看到如何实现。当 step 为正数时向后移动(next),而 step 为负数时向前移动(prev)。
  • reference 指向容器中节点的地址,容器节点的结构体设计必须要配合迭代器的这个字段。具体细节在下一章“双向连表 List 的容器实现”中会细说,这里不展开。
  • container 就是关联的容器的地址。
  • 迭代器的解引用(dereference),设计如下:
#define iterator_dereference(iter) (*((type_value_t*)iterator_reference(iter)))

这个与容器节点的设计有关,具体细节在下一章节双向连表 List 的容器实现中细说。

迭代器接口使用

我设计了一套宏,用于调用迭代器的接口:

  • 伪装的iterator构造函数的宏
#define __iterator(__refer, __container, __move)     ({         iterator_t it = {             .move = (__move),             .reference = (__refer),             .container = (__container),         };         it;     })
  • move 移动
// 此处 step 为正数时往前移动,负数时为往后移动。#define iterator_move(iter, step) iter.move(iter, step)
  • next 向后移动一步
#define iterator_next(iter) iterator_move(iter, 1)
  • prev 向前移动一步
#define iterator_prev(iter) iterator_move(iter, -1)
  • 解引用
#define iterator_dereference(iter) (*((type_value_t*)iterator_reference(iter)))
  • 判断连个迭代器是否相等
#define iterator_equal(iter1, iter2) (iterator_reference(iter1) == iterator_reference(iter2))
  • 判断迭代器是否是容器的边界 head
#define iterator_is_head(iter) iterator_equal(iter, container_head(iter.container))
  • 判断迭代器是否是容器的边界 tail
#define iterator_is_tail(iter) iterator_equal(iter, container_tail(iter.container))

容器的设计

容器(终于到了要设计大 boss 的时候了),可以在容器中存储基本类型或任何类类型的条目。容器行为一般有一下几个:获取第一个节点、获取最后一个节点、插入、删除、查找、排序。于是设计一个“基类”,包含以上的几种行为的接口,然后将这个“基类”放入到具体的容器实现当中,把具体实现的函数指针赋值到这个“基类”的接口中。这就解决 C 中的统一接口,不同实现。

typedef struct _container {    iterator_t (*first) (container_t* container_ptr);    iterator_t (*last) (container_t * container_ptr);    iterator_t (*search) (container_t* container_ptr, iterator_t offset, type_value_t find, int (*compare)(type_value_t, type_value_t));    int (*insert) (container_t* container_ptr, iterator_t iter, type_value_t data);    int (*remove) (container_t* container_ptr, iterator_t iter, void* rdata);    int (*sort) (container_t* container_ptr, int(*compare)(type_value_t, type_value_t));    pool_t* mem_pool;} container_t;
  • first 获取容器第一个元素迭代器。
  • last 获取容器最后一个元素迭代器。
  • search 搜索函数,接收一个 find 的参数,此参数包含你要在容器中搜索的节点的特征。另外需要一个比较函数compare,自行实现容器里面元素的比较逻辑,它第一个参数为容器里面的元素,第二个参数为存入的 find。例如:要找到名字为 “xxx“ 的元素。对比相等,compare 需要返回 0。不等可以返回任何值。
  • insert 在当前迭代器的位置前插入数据。
  • remove 移除当前迭代器所指的数据节点。
  • 容器遍历 一般是在 [first, tail) 或者 (head, last] 的范围中进行。其中这里 head 和 tail 为容器第一个元素 first前一个,而 tail 为容器 最后一个元素的后一个。大概是这个样子:
9a5bd3d524f410163ff15a572b11ba6a.png

head 与 tail 只做容器边界的辨识,不能存放数据。若是对边界的迭代器(iterator),做解引用(dereference),会发生未知错误。

容器接口使用

我设计了一套宏用于调用

  • container 的初始化
#define initialize_container(container_ptr, __first, __last, __search,  __insert, __remove) do {     ((container_t*)(container_ptr))->first  = (__first);                                            ((container_t*)(container_ptr))->last   = (__last);                                             ((container_t*)(container_ptr))->search = (__search);                                           ((container_t*)(container_ptr))->insert = (__insert);                                           ((container_t*)(container_ptr))->remove = (__remove);                                           ((container_t*)(container_ptr))->sort   = (__sort);                                         } while (0)
  • first 元素
#define container_first(container_ptr) (((container_t*)(container_ptr))->first((container_t*)(container_ptr)))
  • 最后元素
#define container_last(container_ptr) (((container_t*)(container_ptr))->last((container_t*)(container_ptr)))
  • head 边界,使用 iterator 的 prev 宏获取了容器 first 节点的前一个节点。
#define container_head(container_ptr) iterator_prev(container_first(container_ptr)) 
  • taile 边界,使用 iterator 的 next 宏获取容器 last 节点的下一个节点。
#define container_tail(container_ptr) iterator_next(container_last(container_ptr))
  • search 接口
#define container_search(container_ptr, offset, find, compare) (((container_t*)(container_ptr))->search(((container_t*)(container_ptr)), offset, find, compare))
  • insert 接口
#define container_insert(container_ptr, iter, data) (((container_t*)(container_ptr))->insert(((container_t*)(container_ptr)), iter, data))
  • remove 接口
#define container_remove(container_ptr, iter, rdata) (((container_t*)(container_ptr))->remove(((container_t*)(container_ptr)), iter, rdata))

双向连表 List 的容器实现

以上只是一套容器接口,需要有具体的实现,容器才能跑起来。现在我用双向连表来尝试容器实现。

List 的定义

typedef struct _list_node list_node_t;typedef struct _list_node{    /* 数据节点的data,要放在首段,否则会出现灾难性后果 */    type_value_t data;    list_node_t* prev;    list_node_t* next;};typedef struct _list {    container_t container;    list_node_t _sentinel;    size_t _size;} list_t;
  • list 定义第一个字段便是 container_t 的接口结构体,实现初始化阶段,我必须把 List 相关实现函数的指针赋值到此接口结构体。
  • 容器的节点 list_node_t,其第一个字段必须为 type_value_t。因为节点的访问是由 iterator_t 提供的。iterator_t 中的引用地址字段 reference 便是 list_node_t 的地址。当对 iterator_t 进行解引用(dereference) 操作:
iterator_dereference(iter); // ==> *((type_value_t*)iter.reference)

便可获取节点中 type_value_t 的值,而其他字段,例如 list_node_t 中的 prev 与 next 便不会暴露在接口外面。当获取到 type_value_t 后,便可利用上一篇中 type_value_t 转换的宏,还原到原来的基础数据类型。

  • 双向连表的定义中使用字段 _sentinel 作为容器的 head 和 tail。也就是 first 节点 的 prev 指向 _sentienl,last节点的 next 也是指向了 _sentinel。也就是大概这个样子: 反过来,_sentinel 的 next 便指向了容器第一个元素 first,_sentinel 的 prev 便指向了容器最后一元素 last 这就形成了一个完美的闭环。

List 容器接口实现

  • List 提供给 iterator 的 move 函数。此函数告诉 iterator 它在 List 容器中的移动方式。
static iterator_t _move(iterator_t it, int step){    list_node_t* node = iterator_reference(it);    // step 大于 0 往后走,step 小于 0 往前走。    for(int next = step; next; next = step > 0? --step:++step) {        if (next > 0) node = node->next;        else if (next < 0) node = node->prev;    }    it.reference = node;    return it;}
  • List 的 first 节点函数:
// __itreator 是伪装 iterator 构造函数的宏static iterator_t _list_first (container_t* plist){    return __iterator(plist->_sentinel.next, plist, _move);}
  • List 的 last节点函数
static iterator_t _list_last (container_t* plist){    return __iterator(list_last(plist), plist, _move);}
  • List 搜索函数
static iterator_t _list_search (container_t* container, iterator_t offset, type_value_t find, int(compare)(type_value_t data1, type_value_t data2)){    iterator_t first = offset;    iterator_t tail  = container_tail(container);    for(;!iterator_equal(first, tail); first = iterator_next(first)) {        // 当 compare() 返回 0 的时候,表示匹配成功,然后匹配节点的 iterator。        if (compare(iterator_dereference(first), find) == 0) {            return first;        }    }    // 返回边界的指针    return first;}
  • List 插入函数
static int _list_insert(container_t* container, iterator_t pos, type_value_t data){    list_node_t *node = iterator_reference(pos);    list_node_t *new = allocate(container_mem_pool(container), sizeof(list_node_t));    // 赋值 和 插入    pnew->data = data;    pnew->prev = node->prev;    pnew->next = node;    pnode->prev->next = new;    pnode->prev = new;    list_t *plist = container;    plist->_size++;    return 0;}
  • List 的移除函数
static int _list_remove(container_t* container, iterator_t pos, void* rdata){    // 删除    // 边界的东西不能移除    list_t* list = container;    list_node_t* node = iterator_reference(pos);    pnode->prev->next = node->next;    pnode->next->prev = node->prev;        // 将要删除的值返回出去。    if (rdata) *((type_value_t*)rdata) = iterator_dereference(pos);    // 回收    deallocate(container_mem_pool(container), pnode);    list->_size--;    return 0;}

此处的 rdata 的返回节点中 type_value_t 的值。用于在移除后的后续处理。例如可以是否申请的内存。

  • List 的 sort 函数
static int _list_sort(container_t* container, int(*compare)(type_value_t, type_value_t)){    return quick_sort(container_first(container), container_last(container), compare);}

使用的是 quick sort 算法。由于是统一了接口,所以 quick sort 也只需实现一遍,便可对多种容器进行排序。

  • List 的创建及初始化
container_t* list_create() {        list_t* list = (list_t*) malloc( sizeof(list_t));    pool_t* _mem_pool = alloc_create(0);    // initialize_container 是吧各个函数指针设置入 container_t 的中。    initialize_container(          list,        _list_first,        _list_last,        _list_search,        _list_insert,        _list_remove,        _list_sort,    );    list_first(list) = list_head(list);    list_last(list) = list_tail(list);    list->_size = 0;    list->_sentinel.data = int_vtype(-1);    return list;}

至此,用C写的完整的小型容器就实现完毕了。下面来测试一下。

小试菜刀

  • 测试代码如下:
    printf("n测试 list, 插入10个整数: ");    list_t* list = list_create();    container_insert_tail(list, int_vtype(1));    container_insert_tail(list, int_vtype(2));    container_insert_tail(list, int_vtype(3));    container_insert_tail(list, int_vtype(4));    container_insert_tail(list, int_vtype(5));    container_insert_tail(list, int_vtype(6));    container_insert_tail(list, int_vtype(7));    container_insert_tail(list, int_vtype(8));    container_insert_tail(list, int_vtype(9));    container_insert_tail(list, int_vtype(10));    for(iterator_t first = container_first(list); !iterator_equal(first, container_tail(list)); first=iterator_next(first)) {        type_value_t t = iterator_dereference(first);        printf(" %d ", vtype_int(t));    }    printf("删除第一和最后一个元素:");    container_remove_first(list, NULL);    container_remove_last(list, NULL);    for(iterator_t first = container_first(list); !iterator_equal(first, container_tail(list)); first=iterator_next(first)) {    // 将节点的值取出        type_value_t t = iterator_dereference(first);// 将 type_value_t 还原成 int。int v= vtype_int(t);        printf(" %d ", vtype_int(t));    }    list_destroy(list);    list_t* list2 = list_create();    printf("测试 list2, 插入10个浮点: ");    container_insert_tail(list2, float_vtype(1.1));    container_insert_tail(list2, float_vtype(2.2));    container_insert_tail(list2, float_vtype(3.3));    container_insert_tail(list2, float_vtype(4.4));    container_insert_tail(list2, float_vtype(5.5));    container_insert_tail(list2, float_vtype(6.6));    container_insert_tail(list2, float_vtype(7.7));    container_insert_tail(list2, float_vtype(8.8));    container_insert_tail(list2, float_vtype(9.9));    container_insert_tail(list2, float_vtype(10.11));    for(iterator_t first = container_first(list2); !iterator_equal(first, container_tail(list2)); first=iterator_next(first)) {        // 将节点的值取出        type_value_t t = iterator_dereference(first);       // 把 type_value_t 还原成浮点。        float v = vtype_float(t);        printf(" %f ", v);    }    printf(" 删除第一和最后一个元素:");    container_remove_first(list2, NULL);    container_remove_last(list2, NULL);    for(iterator_t first = container_first(list2); !iterator_equal(first, container_tail(list2)); first=iterator_next(first)) {        type_value_t t = iterator_dereference(first);        printf(" %f ", vtype_float(t));    }    list_destroy(list2);    list_t* list3 = list_create();    printf("测试 list3, 插入7个字符串 ");    container_insert_tail(list3, pointer_vtype("hello6"));    container_insert_tail(list3, pointer_vtype("hello5"));    container_insert_tail(list3, pointer_vtype("hello4"));    container_insert_tail(list3, pointer_vtype("hello3"));    container_insert_tail(list3, pointer_vtype("hello2"));    container_insert_tail(list3, pointer_vtype("hello1"));    container_insert_tail(list3, pointer_vtype("hello0"));    for(iterator_t first = container_first(list3); !iterator_equal(first, container_tail(list3)); first=iterator_next(first)) {    // 将节点的值取出。        type_value_t t = iterator_dereference(first);// 将 type_value_t 还原字符串 char*char* v = vtype_pointer(t);        printf(" %s ", vtype_pointer(t));    }    printf(" 删除第一和最后一个元素:");    container_remove_first(list3, NULL);    container_remove_last(list3, NULL);    for(iterator_t first = container_first(list3); !iterator_equal(first, container_tail(list3)); first=iterator_next(first)) {        type_value_t t = iterator_dereference(first);        printf(" %s ", vtype_pointer(t));    }    list_destroy(list3);    printf("");
  • 测试结果:
7d43ffbe56239b69614e7abff73d0270.png

项目地址

github:https://github.com/zuweie/boring-code

后记: 容器实现不只用双向链表list。还有连续存储空间的 vector。还有关联式容器红黑树,和哈希表。有兴趣的同学可以下载本项目慢慢学习~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值