引言 - 动态数组切入

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

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

    twemproxy  https://github.com/twitter/twemproxy/tree/master/src 

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

关于动态数组设计的总思路, 主要体现在下面的数组结构 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 = , //结果正确的返回宏
RT_ErrorBase = -, //错误基类型, 所有错误都可用它, 在不清楚的情况下
RT_ErrorParam = -, //调用的参数错误
RT_ErrorMalloc = -, //内存分配错误
RT_ErrorFopen = -, //文件打开失败
RT_ErrorClose = -, //文件描述符读取关闭, 读取完毕也会返回这个
} 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, , , 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

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

// 定义可变数组类型 对象
typedef struct array * array_t; /*
* 在栈上创建对象 ##var
* var : 创建对象名称
* size : 创建对象总长度
* alloc : 每个元素分配空间大小
*/
#define ARRAY_NEW(var, size, alloc) \
struct array $__##var = { NULL, , , 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 = -, 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!\n", sz);
exit(EXIT_FAILURE);
}
memset(ptr, , 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, , 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 > )
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 == ); 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 <<= ;
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 && != 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 && != a->len);
return (unsigned char *)a->as + a->alloc * (a->len - );
} /*
* 两个数组进行交换
* 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 && != 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;
}

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

/*
* 返回创建数组对象
* size : 创建数组的总大小个数
* alloc : 数组中每个元素的字节数
* : 返回创建的数组对象
*/
array_t
array_new(unsigned size, size_t alloc) {
struct array * a = sm_malloc(sizeof(struct array));
if (size * alloc > )
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 <<= ;
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]\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 = -, 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 = -, 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=; i<len; ++i) {
elem = array_get(a, i);
_dict_echo(elem, NULL);
} ARRAY_DELETE(a); exit(EXIT_SUCCESS);
}

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

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

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

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

后记  - 每天都是重新开始

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

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

C基础 万能动态数组的更多相关文章

  1. VBS基础篇 - 动态数组

    VBS中的动态数组需要使用System.Collections.ArrayList '建立动态数组 Dim Arrl : Set Arrl = CreateObject("System.Co ...

  2. C语言基础 - 实现动态数组并增加内存管理

    用C语言实现一个动态数组,并对外暴露出对数组的增.删.改.查函数 (可以存储任意类型的元素并实现内存管理) 这里我的编译器就是xcode 分析: 模拟存放 一个 People类 有2个属性 字符串类型 ...

  3. C语言- 基础数据结构和算法 - 动态数组

    听黑马程序员教程<基础数据结构和算法 (C版本)>,照着老师所讲抄的, 视频地址https://www.bilibili.com/video/BV1vE411f7Jh?p=1 喜欢的朋友可 ...

  4. vc++基础班[28]---动态数组及动态链表的讲解

    C++中也有相应的动态数组.动态链表.映射表的模板类,就是STL中的:vector.list.map 他们属于C++标准中的一部分,对于程序的移植性来说也是不错的,但是在MFC编程中使用 CArray ...

  5. 万能动态库调用工具IDMA(InvokeDllMethodsAdvance)

    万能动态库调用工具IDMA 开发者:马兆瑞     QQ/微信:624762543 百度云下载链接:https://pan.baidu.com/s/1skW5W4H CSDN下载链接:http://d ...

  6. 常用数据结构-线性表及Java 动态数组 深究

    [Java心得总结六]Java容器中——Collection在前面自己总结的一篇博文中对Collection的框架结构做了整理,这里深究一下Java中list的实现方式 1.动态数组 In compu ...

  7. c语言 动态数组

    C语言中,在声明数组时,必须明确告诉编译器数组的大小,之后编译器就会在内存中为该数组开辟固定大小的内存.有些时候,用户并不确定需要多大的内存,使用多大的数组,为了保险起见,有的用户采用定义一个大数组的 ...

  8. 【C++基础】sizeof 数组 指针 空NULL

    笔试遇到很多sizeof的小题,博主基础堪忧,怒总结如下,还是要巩固基础啊啊啊! sizeof操作符 对象所占 栈内存空间的大小,单位是字节 关键词:char  数组 指针 结构体 class [注意 ...

  9. go基础系列:数组

    了解Python.Perl.JavaScript的人想必都知道它们的数组是动态的,可以随需求自动增大数组长度.但Go中的数组是固定长度的,数组一经声明,就无法扩大.缩减数组的长度.但Go中也有类似的动 ...

随机推荐

  1. CentOS 文件隐藏属性

    1.chattr用于配置文件的隐藏属性 语法: chattr [-RVf] [-+=aAcCdDeijsStTu] [-v version] files... 选项与参数: +:增加某个特殊参数,其他 ...

  2. 获取接口参数名带有“abc”的参数的值

    public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext) va ...

  3. BZOJ3930:[CQOI2015]选数——题解

    http://www.lydsy.com/JudgeOnline/problem.php?id=3930 https://www.luogu.org/problemnew/show/P3172#sub ...

  4. cf 443 D. Teams Formation](细节模拟题)

    cf 443 D. Teams Formation(细节模拟题) 题意: 给出一个长为\(n\)的序列,重复\(m\)次形成一个新的序列,动态消除所有k个连续相同的数字,问最后会剩下多少个数(题目保证 ...

  5. Tree and Permutation dfs hdu 6446

    Problem Description There are N vertices connected by N−1 edges, each edge has its own length.The se ...

  6. POJ 2763 Housewife Wind 纯粹LCA写法(简单无脑)

    Description After their royal wedding, Jiajia and Wind hid away in XX Village, to enjoy their ordina ...

  7. POJ 2976 二分

    Dropping tests Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 12515   Accepted: 4387 D ...

  8. kubernetes创建资源对象yaml文件例子--pod详解

    apiVersion: v1 #指定api版本,此值必须在kubectl apiversion中 kind: Pod #指定创建资源的角色/类型 metadata: #资源的元数据/属性 name: ...

  9. 替换Jar包中的一个文件 Replace a file in a JAR

    例如: jar uf myJarFile.jar com\vsoft\servlet\myServlet.class This will replace the class myServlet.cla ...

  10. nginx 状态监控

    通过查看Nginx的并发连接,我们可以更清除的知道网站的负载情况.Nginx并发查看有两种方法(之所以这么说,是因为笔者只知道两种),一种是通过web界面,一种是通过命令,web查看要比命令查看显示的 ...