zoukankan      html  css  js  c++  java
  • C基础 万能动态数组

    引言 - 动态数组切入

      开发中动态类型无外乎list 或者 vector, 这里就是在C中实现vector结构容器部分.

    对于C中使用的数据结构, 可以参照下面感觉很不错框架源码学习 , 感觉是<<C接口与实现>>的标准Demo

    写的很清楚易懂, 给人一种铺面而来的美感.

    关于动态数组设计的总思路, 主要体现在下面的数组结构 struct array {}; 中

    struct array {
        void *        as;        /* 存储数组具体内容首地址 */
        unsigned    len;    /* 当前数组的长度 */
        unsigned    size;   /* 当前数组容量大小 */
        size_t        alloc;    /* 每个元素字节大小 */
    };

     as 保存动态数组的首地址, len保存当前动态数组中元素个数, size表示动态数组容量(内存总大小), alloc记录每次分配多大内存.

    是不是很简单, 数据结构一但确定什么都OK了. 在具体分析之前先扩展讲解一下 C11的内联函数. 内联函数是应用场景很多, 宏一般的性能, 并且还可以当函数调试!

    // 内联函数声明部分, 不需要加inline
    extern void heoo(void);
    
    // 内联函数定义部分
    inline void
    heoo(void) {
      ... 
    }

    上面就是内联函数的套路, 在定义的时候加上内联属性inline. 这种做法是为了VS2015 和 GCC5.3 对于inline 语法解析的统一. 

    具体看下面搜索到的老外给的资料 

        内联解析warning资料 http://www.avrfreaks.net/forum/declaring-function-extern-inline-header-file

    Computer science 还是老外厉害些, 国内最屌, 也只是国外一线的水准. 目前猜测的原因是, 祖国就一个, 国外太多了. O(∩_∩)O哈哈~

    前言  - 接口分析

      具体设计了下面几种接口, 基本够用了.也很好用.

    /*
     * 返回创建数组对象
     * size        : 创建数组的总大小个数
     * alloc    : 数组中每个元素的字节数
     *            : 返回创建的数组对象
     */
    extern array_t array_new(unsigned size, size_t alloc);
    
    /*
     * 销毁这个创建的数组对象
     * a        : 创建的数组对象
     */
    extern void array_delete(array_t a);
    
    /*
     * 重新构建一个数组对象
     * a        : 可变数组对象
     * size        : 新可变数组总长度
     */
    extern void array_newinit(array_t a, unsigned size);
    
    /*
     * 得到节点elem在数组中索引
     * a        : 可变数组对象
     * elem        : 查询元素
     *            : 返回查询到位置
     */
    extern unsigned array_idx(array_t a, void * elem);
    
    /*
     * 为可变数组插入一个元素, 并返回这个元素的首地址
     * a        : 可变数组对象
     *            : 返回创建对象位置
     */
    extern void * array_push(array_t a);
    
    /*
     * 弹出一个数组元素
     * a        : 可变数组对象
     *            : 返回弹出数组元素节点
     */
    extern void * array_pop(array_t a);
    
    /*
     * 按照索引得到数组元素
     * a        : 可变数组对象
     * idx        : 索引位置
     *            : 返回查询到数据
     */
    extern void * array_get(array_t a, unsigned idx);
    
    /*
     * 得到数组顶的元素
     * a        : 可变数组对象
     *            : 返回得到元素
     */
    extern void * array_top(array_t a);
    
    /*
     * 两个数组进行交换
     * a        : 数组a
     * b        : 数组b
     */
    extern void array_swap(array_t a, array_t b);
    
    /*
     * 数组进行排序
     * a        : 数组对象
     * compare    : 比对规则
     */
    extern void array_sort(array_t a, icmp_f compare);
    
    /*
     * 数组进行遍历
     * a        : 可变数组对象
     * func        : 执行每个结点函数, typedef flag_e    (* each_f)(void * node, void * arg);
     * arg        : 附加参数
     *            : 返回操作结果状态码
     */
    flag_e array_each(array_t a, each_f func, void * arg);

     围绕创建, 销毁, 添加元素, 删除元素, 交换, 遍历, 排序等操作. 具体参看 array.h 文件 

    #ifndef _H_SIMPLEC_ARRAY
    #define _H_SIMPLEC_ARRAY
    
    #include <stdlib.h>
    
    #define sm_free free
    
    typedef enum {
        RT_SuccessBase    = 00,                //结果正确的返回宏
        RT_ErrorBase    = -1,                //错误基类型, 所有错误都可用它, 在不清楚的情况下
        RT_ErrorParam    = -2,                //调用的参数错误
        RT_ErrorMalloc    = -3,                //内存分配错误
        RT_ErrorFopen    = -4,                //文件打开失败    
        RT_ErrorClose    = -5,                //文件描述符读取关闭, 读取完毕也会返回这个
    } flag_e;
    
    // icmp_f 最好 是 int cmp(const void * ln, const void * rn); 标准结构
    typedef int        (* icmp_f)();
    // 循环操作函数, arg 外部参数, node 内部节点
    typedef flag_e    (* each_f)(void * node, void * arg);
    
    struct array {
        void *        as;        /* 存储数组具体内容首地址 */
        unsigned    len;    /* 当前数组的长度 */
        unsigned    size;   /* 当前数组容量大小 */
        size_t        alloc;    /* 每个元素字节大小 */
    };
    
    // 定义可变数组类型 对象
    typedef struct array * array_t;
    
    /*
     * 在栈上创建对象 ##var
     * var        : 创建对象名称
     * size        : 创建对象总长度
     * alloc    : 每个元素分配空间大小
     */
    #define ARRAY_NEW(var, size, alloc) 
        struct array $__##var = { NULL, 0, 0, alloc }, * var = &$__##var;
        array_newinit(var, size)
    #define ARRAY_DELETE(var) 
        sm_free(var->as)
    
    /*
     * 返回创建数组对象
     * size        : 创建数组的总大小个数
     * alloc    : 数组中每个元素的字节数
     *            : 返回创建的数组对象
     */
    extern array_t array_new(unsigned size, size_t alloc);
    
    /*
     * 销毁这个创建的数组对象
     * a        : 创建的数组对象
     */
    extern void array_delete(array_t a);
    
    /*
     * 重新构建一个数组对象
     * a        : 可变数组对象
     * size        : 新可变数组总长度
     */
    extern void array_newinit(array_t a, unsigned size);
    
    /*
     * 得到节点elem在数组中索引
     * a        : 可变数组对象
     * elem        : 查询元素
     *            : 返回查询到位置
     */
    extern unsigned array_idx(array_t a, void * elem);
    
    /*
     * 为可变数组插入一个元素, 并返回这个元素的首地址
     * a        : 可变数组对象
     *            : 返回创建对象位置
     */
    extern void * array_push(array_t a);
    
    /*
     * 弹出一个数组元素
     * a        : 可变数组对象
     *            : 返回弹出数组元素节点
     */
    extern void * array_pop(array_t a);
    
    /*
     * 按照索引得到数组元素
     * a        : 可变数组对象
     * idx        : 索引位置
     *            : 返回查询到数据
     */
    extern void * array_get(array_t a, unsigned idx);
    
    /*
     * 得到数组顶的元素
     * a        : 可变数组对象
     *            : 返回得到元素
     */
    extern void * array_top(array_t a);
    
    /*
     * 两个数组进行交换
     * a        : 数组a
     * b        : 数组b
     */
    extern void array_swap(array_t a, array_t b);
    
    /*
     * 数组进行排序
     * a        : 数组对象
     * compare    : 比对规则
     */
    extern void array_sort(array_t a, icmp_f compare);
    
    /*
     * 数组进行遍历
     * a        : 可变数组对象
     * func        : 执行每个结点函数, typedef flag_e    (* each_f)(void * node, void * arg);
     * arg        : 附加参数
     *            : 返回操作结果状态码
     */
    flag_e array_each(array_t a, each_f func, void * arg);
    
    #endif // !_H_SIMPLEC_ARRAY
    View Code

    下面添加栈上声明部分, 采用宏设计

    // 定义可变数组类型 对象
    typedef struct array * array_t;
    
    /*
     * 在栈上创建对象 ##var
     * var        : 创建对象名称
     * size        : 创建对象总长度
     * alloc    : 每个元素分配空间大小
     */
    #define ARRAY_NEW(var, size, alloc) 
        struct array $__##var = { NULL, 0, 0, alloc }, * var = &$__##var;
        array_newinit(var, size)
    #define ARRAY_DELETE(var) 
        sm_free(var->as)

    是不是很有意思, 这些都是具体开发中的解决方案.

    此事我们的设计思路已经转成接口设计代码. 现在先写一些前测试代码 test_array.c

    下面主要测试栈上动态数组分配和使用

    #define LEN(arr) 
        (sizeof(arr)/sizeof(*(arr)))
    
    //简单结构
    struct dict {
        char * key;
        char * value;
    };
    
    // 单独遍历函数
    static flag_e _dict_echo(struct dict * node, void * arg)
    {
        printf("[%s]    => [%s]
    ", node->key, node->value);
        return RT_SuccessBase;
    }
    
    // 比较函数
    static int _dict_cmp(struct dict * ln, struct dict * rn) {
        return strcmp(ln->key, rn->key);
    }
    
    /*
     * 主要测试 array 动态数组模块代码
     */
    void test_array(void) {
        struct dict * elem;
    
        struct dict dicts[] = {
            { "zero"    , "" },
            { "one"        , "" },
            { "two"        , "" },
            { "three"    , "" },
            { "four"    , "" },
            { "five"    , "" },
            { "six"        , "" },
            { "seven"    , "" },
            { "eight"    , "" },
            { "night"    , "" },
            { "night"    , "" },
        };
    
        /* Step 1 : 测试堆上array对象 */
        int i = -1, len = LEN(dicts);
        array_t a = array_new(len, sizeof(struct dict));
    
        // 插入数据
        while (++i < len) {
            elem = array_push(a);
            *elem = dicts[i];
        }
    
        // 打印数据测试
        puts("----------- start data look at the following:");
        array_each(a, (each_f)_dict_echo, NULL);
    
        // 排序一下
        array_sort(a, _dict_cmp);
    
        // 打印数据测试
        puts("----------- sort data look at the following:");
        array_each(a, (each_f)_dict_echo, NULL);
    
        array_delete(a);
    
        exit(EXIT_SUCCESS);
    }

    为什么采用exit 结束操作, 先卖一个关子.  上面是围绕创建和遍历最后排序的测试. 从上面感受这些动态数组的api使用方式.

    软件设计套路很多但是对于C. 请参照下面套路.

      业务理解  -> 数据结构 -> 接口定义 -> | 接口实现

                       ->  |      -> 业务实现 -> 业务测试 -> 提交功能

                        -> | 接口测试

    标黑的特别重要. 当然了, 对于高级语言, 数据结构可以省略了, 主要是业务理解 -> 接口实现. 这也是软件设计界一种解放吧. 正文部分会具体分析实现部分.

    正文  - 接口实现

      好的接口定义, 是一切好的开始. 接口实现还是很容易的.  例如下面关于动态数组的 array.c 实现

    #include "array.h"
    #include <stdio.h>
    #include <assert.h>
    #include <string.h>
    
    // 导入需要的辅助函数
    static void * sm_malloc(size_t sz) {
        void * ptr = malloc(sz);
        if(NULL == ptr) {
            fprintf(stderr, "sm_malloc malloc sz = %ld is error!
    ", sz);
            exit(EXIT_FAILURE);
        }
        memset(ptr, 0, sz);
        return ptr;
    }
    
    static void * sm_realloc(void * ptr, size_t sz) {
        void * nptr = realloc(ptr, sz);
        if(NULL == nptr) {
            free(ptr);
            fprintf(stderr, "sm_realloc realloc ptr, sz = %p, %ld is error!
    ", ptr, sz);
            exit(EXIT_FAILURE);
        }
        if(NULL != ptr)
            memset(nptr, 0, sz);
        return nptr;
    }
    
    /*
     * 返回创建数组对象
     * size        : 创建数组的总大小个数
     * alloc    : 数组中每个元素的字节数
     *            : 返回创建的数组对象
     */
    array_t 
    array_new(unsigned size, size_t alloc) {
        struct array * a = sm_malloc(sizeof(struct array));
        if (size * alloc > 0)
            a->as = sm_malloc(size * alloc);
    
        a->size = size;
        a->alloc = alloc;
    
        return a;
    }
    
    /*
     * 销毁这个创建的数组对象
     * a        : 创建的数组对象
     */
    inline 
    void array_delete(array_t a) {
        sm_free(a->as);
        sm_free(a);
    }
    
    /*
     * 重新构建一个数组对象
     * a        : 可变数组对象
     * size        : 新可变数组总长度
     */
    inline void
    array_newinit(array_t a, unsigned size) {
        assert(NULL != a);
        a->as = sm_realloc(a->as, size * a->alloc);
        if (a->len > size)
            a->len = size;
        a->size = size;
    }
    
    /*
     * 得到节点elem在数组中索引
     * a        : 可变数组对象
     * elem        : 查询元素
     *            : 返回查询到位置
     */
    inline unsigned 
    array_idx(array_t a, void * elem) {
        unsigned char * p, * q;
        unsigned off;
    
        assert(NULL != a && elem >= a->as);
    
        p = a->as;
        q = elem;
        off = (unsigned)(q - p);
    
        assert(off % (unsigned)a->alloc == 0);
    
        return off / (unsigned)a->alloc;
    }
    
    /*
     * 为可变数组插入一个元素, 并返回这个元素的首地址
     * a        : 可变数组对象
     *            : 返回创建对象位置
     */
    void * 
    array_push(array_t a) {
        assert(NULL != a);
    
        if (a->len == a->size) {
            /* the array is full; allocate new array */
            a->size <<= 1;
            a->as = sm_realloc(a->as, a->size * a->alloc);
        }
    
        return (unsigned char *)a->as + a->alloc * a->len++;
    }
    
    /*
     * 弹出一个数组元素
     * a        : 可变数组对象
     *            : 返回弹出数组元素节点
     */
    inline void * 
    array_pop(array_t a) {
        assert(NULL != a && 0 != a->len);
        --a->len;
        return (unsigned char *)a->as + a->alloc * a->len;
    }
    
    /*
     * 按照索引得到数组元素
     * a        : 可变数组对象
     * idx        : 索引位置
     *            : 返回查询到数据
     */
    inline void * 
    array_get(array_t a, unsigned idx) {
        assert(NULL != a && idx < a->len);
        return (unsigned char *)a->as + a->alloc * idx;
    }
    
    /*
    * 得到数组顶的元素
    * a        : 可变数组对象
    *            : 返回得到元素
    */
    inline void * 
    array_top(array_t a) {
        assert(NULL != a && 0 != a->len);
        return (unsigned char *)a->as + a->alloc * (a->len - 1);
    }
    
    /*
     * 两个数组进行交换
     * a        : 数组a
     * b        : 数组b
     */
    inline 
    void array_swap(array_t a, array_t b) {
        struct array t = *a;
        *a = *b;
        *b = t;
    }
    
    /*
    * 数组进行排序
    * a        : 数组对象
    * compare    : 比对规则
    */
    inline void 
    array_sort(array_t a, icmp_f compare) {
        assert(NULL != a && 0 != a->len && NULL != compare);
        qsort(a->as, a->len, a->alloc, (int ( *)(const void *, const void *))compare);
    }
    
    /*
    * 数组进行遍历
    * a        : 可变数组对象
    * func        : 执行每个结点函数, typedef flag_e    (* each_f)(void * node, void * arg);
    * arg        : 附加参数
    *            : 返回操作结果状态码
    */
    flag_e 
    array_each(array_t a, each_f func, void * arg) {
        flag_e rt;
        unsigned char * s, * e;
    
        assert(NULL != a && NULL != func);
    
        s = a->as;
        e = s + a->alloc * a->len;
        while (s < e) {
            rt = func(s, arg);
            if (RT_SuccessBase != rt)
                return rt;
            s += a->alloc;
        }
    
        return RT_SuccessBase;
    }
    View Code

    观看array.c 中具体部分 下面三个函数是控制整个动态数组生命周期的

    /*
     * 返回创建数组对象
     * size        : 创建数组的总大小个数
     * alloc    : 数组中每个元素的字节数
     *            : 返回创建的数组对象
     */
    array_t 
    array_new(unsigned size, size_t alloc) {
        struct array * a = sm_malloc(sizeof(struct array));
        if (size * alloc > 0)
            a->as = sm_malloc(size * alloc);
    
        a->size = size;
        a->alloc = alloc;
    
        return a;
    }
    
    /*
     * 销毁这个创建的数组对象
     * a        : 创建的数组对象
     */
    inline 
    void array_delete(array_t a) {
        sm_free(a->as);
        sm_free(a);
    }
    
    /*
     * 重新构建一个数组对象
     * a        : 可变数组对象
     * size        : 新可变数组总长度
     */
    inline void
    array_newinit(array_t a, unsigned size) {
        assert(NULL != a);
        a->as = sm_realloc(a->as, size * a->alloc);
        if (a->len > size)
            a->len = size;
        a->size = size;
    }

    new 创建, delete销毁, newinit重新创建.  其中还有一个 api, 向动态数组加入元素也特别有意思

    /*
     * 为可变数组插入一个元素, 并返回这个元素的首地址
     * a        : 可变数组对象
     *            : 返回创建对象位置
     */
    void * 
    array_push(array_t a) {
        assert(NULL != a);
    
        if (a->len == a->size) {
            /* the array is full; allocate new array */
            a->size <<= 1;
            a->as = sm_realloc(a->as, a->size * a->alloc);
        }
    
        return (unsigned char *)a->as + a->alloc * a->len++;
    }

    返回插入元素的首地址, 需要自己初始化. 特别新潮的做法. (当然了对于C这种老古董,  这些也都是老掉牙的东西, 只是自娱自乐, 开心就好)

    实现部分到这里基本完毕了, 最好能看一遍其中具体文件, 设计思路非常好, 实现也是力求最快最简.

    对于 exit这个坑 , 主要 看 linux上代码, 例如 man select 会出现

    exit(EXIT_SUCCESS) ; 这个做法是为了, 解决在函数调用结束, 释放启动这个 main函数之前声明的资源.

    当然系统都会对 mian 函数特殊处理, 哪怕return 也能释放干净. 但是假如一个文件没有main函数, 启动了 新的入口函数,

    这是否return 是不会帮我们额外清理一些资源的. 这时候运行结束会崩溃. exit 就是为了解决退出时候资源释放问题.

    到这里我们给出测试部分了. 先看 Makefile 文件

    CC = gcc
    DEBUG = -ggdb3 -Wall
    RUN = $(CC) $(DEBUG) -o $@ $^
    RUNO = $(CC) -c -o $@ $<
    RUNMAIN = $(RUN) -nostartfiles -e $(*F)
    
    all:test_array.out test_array_stack.out
    
    %.o:%c
        $(RUNO)
        
    test_array.out:test_array.o array.o
        $(RUNMAIN)
    
    test_array_stack.out:test_array.o array.o
        $(RUNMAIN)
    
    # 清除命令
    .PHONY:clean
    clean:
        rm -rf *.i *.s *.o *.out *~ ; ls

    具体的测试文件 test_array.c

    #include "array.h"
    #include <stdio.h>
    #include <string.h>
    
    #define LEN(arr) 
        (sizeof(arr)/sizeof(*(arr)))
    
    //简单结构
    struct dict {
        char * key;
        char * value;
    };
    
    // 单独遍历函数
    static flag_e _dict_echo(struct dict * node, void * arg)
    {
        printf("[%s]    => [%s]
    ", node->key, node->value);
        return RT_SuccessBase;
    }
    
    // 比较函数
    static int _dict_cmp(struct dict * ln, struct dict * rn) {
        return strcmp(ln->key, rn->key);
    }
    
    /*
     * 主要测试 array 动态数组模块代码
     */
    void test_array(void) {
        struct dict * elem;
    
        struct dict dicts[] = {
            { "zero"    , "" },
            { "one"        , "" },
            { "two"        , "" },
            { "three"    , "" },
            { "four"    , "" },
            { "five"    , "" },
            { "six"        , "" },
            { "seven"    , "" },
            { "eight"    , "" },
            { "night"    , "" },
            { "night"    , "" },
        };
    
        /* Step 1 : 测试堆上array对象 */
        int i = -1, len = LEN(dicts);
        array_t a = array_new(len, sizeof(struct dict));
    
        // 插入数据
        while (++i < len) {
            elem = array_push(a);
            *elem = dicts[i];
        }
    
        // 打印数据测试
        puts("----------- start data look at the following:");
        array_each(a, (each_f)_dict_echo, NULL);
    
        // 排序一下
        array_sort(a, _dict_cmp);
    
        // 打印数据测试
        puts("----------- sort data look at the following:");
        array_each(a, (each_f)_dict_echo, NULL);
    
        array_delete(a);
    
        exit(EXIT_SUCCESS);
    }
    
    void test_array_stack(void) {
        struct dict * elem;
    
        struct dict dicts[] = {
            { "zero"    , "" },
            { "one"        , "" },
            { "two"        , "" },
            { "three"    , "" },
            { "four"    , "" },
            { "five"    , "" },
            { "six"        , "" },
            { "seven"    , "" },
            { "eight"    , "" },
            { "night"    , "" },
            { "night"    , "" },
        };
    
        /* Step 1 : 测试堆上array对象 */
        int i = -1, len = LEN(dicts);
        ARRAY_NEW(a, len, sizeof(struct dict));
    
        // 插入数据
        while (++i < len) {
            elem = array_push(a);
            *elem = dicts[i];
        }
    
        // 打印数据测试
        puts("----------- start data look at the following:");
        array_each(a, (each_f)_dict_echo, NULL);
    
        // 排序一下
        array_sort(a, _dict_cmp);
    
        // 打印数据测试
        puts("----------- sort data look at the following:");
        for(i=0; i<len; ++i) {
            elem = array_get(a, i);
            _dict_echo(elem, NULL);
        }
    
        ARRAY_DELETE(a);
        
        exit(EXIT_SUCCESS);
    }
    View Code

    最终效果 , 先看编译结果图

    在展示部分栈上测试结果图

    一切正常. 以上就是具体设计思路. 在具体工程中会一些细节不同, 例如对于内存申请和销毁走统一接口. 但是总的关于array设计是一样的.

    有兴趣可以将上面4个文件看看, 自己coding test . 很实用!

    后记  - 每天都是重新开始

      错误是难免, 欢迎更正. 哼哈 O(∩_∩)O~~

    耶利亚女郎   http://music.163.com/#/song?id=119018

     

  • 相关阅读:
    tp5.1 多级控制器
    JS中三个点(...)是什么鬼?
    vue reqwest与fetch的使用
    new Vue({ render: h => h(App), }).$mount('#app')到底什么意思
    ant design vue 表格和国际化的使用
    JAVA日报
    JAVA日报
    JAVA日报
    JAVA日报
    JAVA日报
  • 原文地址:https://www.cnblogs.com/life2refuel/p/5768987.html
Copyright © 2011-2022 走看看