博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C基础 万能动态数组
阅读量:5030 次
发布时间:2019-06-12

本文共 14521 字,大约阅读时间需要 48 分钟。

引言 - 动态数组切入

  开发中动态类型无外乎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的内联函数. 内联函数是应用场景很多, 宏一般的性能, 并且还可以当函数调试!

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

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

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

     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 
#define sm_free freetypedef 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]\n", 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 
#include
#include
// 导入需要的辅助函数static void * sm_malloc(size_t sz) { void * ptr = malloc(sz); if(NULL == ptr) { fprintf(stderr, "sm_malloc malloc sz = %ld is error!\n", 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!\n", 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 voidarray_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 voidarray_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 = gccDEBUG = -ggdb3 -WallRUN = $(CC) $(DEBUG) -o $@ $^RUNO = $(CC) -c -o $@ $

具体的测试文件 test_array.c

#include "array.h"#include 
#include
#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]\n", 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
View Code

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

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

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

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

 

后记  - 每天都是重新开始

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

  http://music.163.com/#/song?id=119018

 

转载于:https://www.cnblogs.com/life2refuel/p/5768987.html

你可能感兴趣的文章
tensorRT使用python进行网络定义
查看>>
Luogu p1478 陶陶摘苹果(升级版)
查看>>
《第一本docker书》- 第一章笔记
查看>>
bzoj2818 Gcd
查看>>
Go语言中结构体的使用-第2部分OOP
查看>>
GET和POST有什么区别?及为什么网上的多数答案都是错的。
查看>>
MAC OS X下的Linux环境
查看>>
《那些年啊,那些事——一个程序员的奋斗史》连载再开感言
查看>>
分享45个设计师应该见到的新鲜的Web移动设备用户界面PSD套件
查看>>
常用模块之time模块
查看>>
synchronized锁住的是代码还是对象
查看>>
Codeforces 461B Appleman and Tree:Tree dp
查看>>
第十八章 5strncat函数的使用
查看>>
hibernate 复合主键映射
查看>>
【转】c++继承中的内存布局
查看>>
常见错误 无法将this 从const type 转换为 非const type
查看>>
Python2与Python3用法区别
查看>>
TCP的三次握手和四次握手
查看>>
每天一点点之 taro 框架 - 生命周期 & state
查看>>
js的日期操作:String转date日期格式、求日期差
查看>>