• 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

     

  • 相关阅读:
    UVa 1151 Buy or Build【最小生成树】
    UVa 216 Getting in Line【枚举排列】
    UVa 729 The Hamming Distance Problem【枚举排列】
    HDU 5214 Movie【贪心】
    HDU 5223 GCD
    POJ 1144 Network【割顶】
    UVa 11025 The broken pedometer【枚举子集】
    HDU 2515 Yanghee 的算术【找规律】
    Java基本语法
    Java环境变量,jdk和jre的区别,面向对象语言编程
  • 原文地址:https://www.cnblogs.com/life2refuel/p/5768987.html
Copyright © 2020-2023  润新知