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 = , //结果正确的返回宏
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~~
C基础 万能动态数组的更多相关文章
- VBS基础篇 - 动态数组
VBS中的动态数组需要使用System.Collections.ArrayList '建立动态数组 Dim Arrl : Set Arrl = CreateObject("System.Co ...
- C语言基础 - 实现动态数组并增加内存管理
用C语言实现一个动态数组,并对外暴露出对数组的增.删.改.查函数 (可以存储任意类型的元素并实现内存管理) 这里我的编译器就是xcode 分析: 模拟存放 一个 People类 有2个属性 字符串类型 ...
- C语言- 基础数据结构和算法 - 动态数组
听黑马程序员教程<基础数据结构和算法 (C版本)>,照着老师所讲抄的, 视频地址https://www.bilibili.com/video/BV1vE411f7Jh?p=1 喜欢的朋友可 ...
- vc++基础班[28]---动态数组及动态链表的讲解
C++中也有相应的动态数组.动态链表.映射表的模板类,就是STL中的:vector.list.map 他们属于C++标准中的一部分,对于程序的移植性来说也是不错的,但是在MFC编程中使用 CArray ...
- 万能动态库调用工具IDMA(InvokeDllMethodsAdvance)
万能动态库调用工具IDMA 开发者:马兆瑞 QQ/微信:624762543 百度云下载链接:https://pan.baidu.com/s/1skW5W4H CSDN下载链接:http://d ...
- 常用数据结构-线性表及Java 动态数组 深究
[Java心得总结六]Java容器中——Collection在前面自己总结的一篇博文中对Collection的框架结构做了整理,这里深究一下Java中list的实现方式 1.动态数组 In compu ...
- c语言 动态数组
C语言中,在声明数组时,必须明确告诉编译器数组的大小,之后编译器就会在内存中为该数组开辟固定大小的内存.有些时候,用户并不确定需要多大的内存,使用多大的数组,为了保险起见,有的用户采用定义一个大数组的 ...
- 【C++基础】sizeof 数组 指针 空NULL
笔试遇到很多sizeof的小题,博主基础堪忧,怒总结如下,还是要巩固基础啊啊啊! sizeof操作符 对象所占 栈内存空间的大小,单位是字节 关键词:char 数组 指针 结构体 class [注意 ...
- go基础系列:数组
了解Python.Perl.JavaScript的人想必都知道它们的数组是动态的,可以随需求自动增大数组长度.但Go中的数组是固定长度的,数组一经声明,就无法扩大.缩减数组的长度.但Go中也有类似的动 ...
随机推荐
- 深入理解Delete(JavaScript)
深入理解Delete(JavaScript) Delete 众所周知是删除对象中的属性. 但如果不深入了解delete的真正使用在项目中会出现非常严重的问题 (: Following 是翻译 ka ...
- java.util.Stack类简介(栈)
Stack是一个后进先出(last in first out,LIFO)的堆栈,在Vector类的基础上扩展5个方法而来 Deque(双端队列)比起stack具有更好的完整性和一致性,应该被优先使用 ...
- BZOJ4514:[SDOI2016]数字配对——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=4514 有 n 种数字,第 i 种数字是 ai.有 bi 个,权值是 ci. 若两个数字 ai.aj ...
- BZOJ1016:[JSOI2008]最小生成树计数——题解
https://www.lydsy.com/JudgeOnline/problem.php?id=1016 现在给出了一个简单无向加权图.你不满足于求出这个图的最小生成树,而希望知道这个图中有多少个不 ...
- BZOJ1009:[HNOI2008]GT考试——题解
http://www.lydsy.com/JudgeOnline/problem.php?id=1009 Description 阿申准备报名参加GT考试,准考证号为N位数X1X2....Xn(0&l ...
- SPOJ1825/FTOUR2:Free tour II——包看得懂/看不懂题解
http://www.spoj.com/problems/FTOUR2/en/ 题目大意:给一棵黑白染色的树,求边权和最大且经过黑点不超过K的路径. ———————————————————— 前排膜拜 ...
- HDOJ.1029 Ignatius and the Princess IV(map)
Ignatius and the Princess IV 点我跳转到题面 点我一起学习STL-MAP 题意分析 给出一个奇数n,下面有n个数,找出下面数字中出现次数大于(n+1)/2的数字,并输出. ...
- 笔记-树形dp
this is not a 正经的note you may not understand Problem 1:二叉树,有权,要选它父亲才能选它,$n\leq200,m\leq500$ I: $dp_{ ...
- 自动清理N天前的二进制日志
这里以自动清理5天前的二进制日志为例(做了同步或依赖于二进制日志备份的请慎用): 以root身份登录数据库,执行以下命令: ; 首次设置expire_logs_days参数后需要执行flush log ...
- 我们自己写的solr查询的代码作为search项目中的dao
我们自己写的solr查询的代码作为search项目中的dao,但是启动时会报错: 其实就是说 searchServiceImpl 中我们 Autowired 的 SearchDao 类 spring ...