看起来很高大上是吧?
我们来看看核心结构体:
typedef struct __algo_any_list_node
{
void * data; //指向数据的指针
long data_size; //数据的大小,有时可能会用到
int data_type; //数据的类型
struct __algo_any_list_node * previous; //向前指针
struct __algo_any_list_node * next; //向后指针
}AlgoAnyListNode,*PAlgoAnyListNode;
typedef AlgoAnyListNode AlgoAnyList;
typedef PAlgoAnyListNode PAlgoAnyList;
怎么样?是否已经知道怎么实现了?
对,你的想法是对的就是
可能你还有点懵,我们来看下下面的语句练练手:
int a=0x01020304;
char * cp=(char *)(void *)&a;
for(int i=0;i<sizeof(a);i++)
{
printf("%02x",(unsigned char)cp[i]);
}
怎么样,这样,我们就能够以char * 方式去访问一个int变量,这里只是简单的演示一下,void的用法
但是,另一个问题来了,void 虽然可以随便指向,但是,如果我们要取数据,只有一个void*显然毫无用处,因此我们添加一个属性:
int data_type; //数据的类型
这样,我们就可以根据我们约定的值,按照约定去取对应类型的值了
假如:
//定义一下类型
#define TYPE_INT 0x01
//先来赋值一下
int a=12;
AlgoAnyListNode node;
node.data=(void *)&a;
node.data_type=TYPE_INT;
//下面按照约定取数据
if(node.data_type==TYPE_INT)
{
printf("%d\n",*((int *)node.data));
}
好了,上面的代码很简单,其中主要就是两句话
node.data=(void *)&a;
*((int *)node.data)
我们来解决这两句话,
第一句:
node.data本来就是void类型,任何地址都可以隐式转换为void,因此可以写为:
node.data=&a;
第二句:
我们知道,node.data是void类型,加上int就转换为int*类型:(int )node.data,再对这个指针取值((int *)node.data)就取到了此地址的值
最后一个问题,为什么还要有一个data_size属性?
long data_size; //数据的大小,有时可能会用到
对于基本数据类型来说,大小是固定的,这个属性基本是不用了,因为没有什么意义,但是如果在结构体或者其他类型中的时候,这个属性将可能会有用处,比如:现在将一个文件拆分为多个部分放入链表中,对于文件来说,他可能是二进制文件(二进制数据集),这样的话,就算知道了指针data和类型data_type,那也不知道指向的数据的大小,毕竟二进制数据,不像字符串一样可以使用’\0’来标识字符串的结束,因此只能使用长度的方式来处理(这是其中一个比较简单的方式)
有了这些基础,我也废话不多说,直接上我的实现代码:
我们先来看看调用代码
#include"AlgoAnyList.hpp"
void testAlgoAnyList()
{
//直接添加一个int类型的进入链表节点
AlgoAnyList * list = ALGO_ANY_LIST_BASE_MAKE_NODE(12, int, ALGO_ANY_LIST_DATA_TYPE_INT);
//添加10个int类型数据进入链表
for (int i = 0; i < 10; i++)
{
AlgoAnyListNode * node = ALGO_ANY_LIST_BASE_MAKE_NODE(i,int, ALGO_ANY_LIST_DATA_TYPE_INT);
list = AlgoAnyList_appendList(list, node);
}
//添加10个double类型数据进入链表
for (int i = 0; i < 10; i++)
{
AlgoAnyListNode * node = ALGO_ANY_LIST_BASE_MAKE_NODE(i*0.5, double, ALGO_ANY_LIST_DATA_TYPE_DOUBLE);
list = AlgoAnyList_appendList(list, node);
}
//添加10个字符串类型数据进入链表
for (int i = 0; i < 10; i++)
{
AlgoAnyListNode * node = NULL;
ALGO_ANY_LIST_MEMORY_MAKE_NODE("hello,哈哈哈", node,15,ALGO_ANY_LIST_DATA_TYPE_LPCHAR);
list = AlgoAnyList_appendList(list, node);
}
//遍历链表,根据不同的类型做一个输出
AlgoAnyList * cur = list;
while (cur)
{
if (cur->data_type == ALGO_ANY_LIST_DATA_TYPE_INT)
{
printf("%d ", ALGO_ANY_LIST_GET_DATA(cur->data, int));
ALGO_ANY_LIST_FREE_DATA(cur->data, int);
}
else if (cur->data_type == ALGO_ANY_LIST_DATA_TYPE_DOUBLE)
{
printf("%lf ", ALGO_ANY_LIST_GET_DATA(cur->data, double));
ALGO_ANY_LIST_FREE_DATA(cur->data, double);
}
else if (cur->data_type == ALGO_ANY_LIST_DATA_TYPE_LPCHAR)
{
printf("%s ", ALGO_ANY_LIST_GET_DATA_MEMORY(cur->data,char *));
ALGO_ANY_LIST_FREE_DATA_MEMORY(cur->data, char*);
}
cur = cur->next;
}
cur = list;
}
int main(int argc, char * argv[])
{
testAlgoAnyList();
system("pause");
return 0;
}
输出
12 0 1 2 3 4 5 6 7 8 9 0.000000 0.500000 1.000000 1.500000 2.000000 2.500000 3.000000 3.500000 4.000000 4.500000 hello, 哈哈哈 hello,哈哈哈 hello,哈哈哈 hello,哈哈哈 hello,哈哈哈 hello,哈哈哈 hello,哈哈哈 hello,哈哈哈 hello,哈哈哈 hello,哈 哈哈 请按任意键继续. . .
上面有不少的宏,为了帮助我们更简便的书写代码,为什么呢?
我们知道,我们需要的是void*类型,也就是无论什么指针,你得给我一个指针
那么如果我直接给一个立即数:12,这玩意儿是一个常量,地址?
另外:
int a=12;
void * p=&a;
这样的方式取地址不就行了吗?
但是,是否考虑过,你这样一个临时变量,在作用域范围类,链表数据自然没有任何问题,但是,如果我把链表传递给其他函数呢?搞不好过了作用域,这个临时变量就失效了,那么这个指针就变成了一个不正确的指针,野指针,这显然会出问题
解决方法:
int * pa=new int;
*pa=12;
void * p=pa;
解决办法是有了,但是每次都这样写代码,显然要骂街的
因此我们创造一个宏,帮助我们完成这种无聊的事情
//基本数据类型的宏构造,参数:数据,数据类型,约定类型,例如:AlgoAnyListNode* node=ALGO_ANY_LIST_BASE_MAKE_NODE(12,int,ALGO_ANY_LIST_DATA_TYPE_INT)
//或者其他类型支持:赋值,malloc(sizeof(类型))的均可
//因此对数组并不适用
#define ALGO_ANY_LIST_BASE_MAKE_NODE(data,type,data_type) \
AlgoAnyList_makeNode(\
&((* (type*)malloc(sizeof(type))) = data),\
data_type,\
sizeof(type)\
)
好了,我们一步一步看:
type * p=(type*)malloc(sizeof(type));
char * p=(char *)malloc(sizeof(char));
怎么样,这一步没问题了吧,也就是申请一个type类型的空间嘛
type * p=(type*)malloc(sizeof(type));
(*p=data)
接着上面的p,这不就是赋值吗?没毛病
AlgoAnyList_makeNode(*p,data_type,sizeof(type));
没问题了吧,就是一个函数调用,对吧
所以整体来说,我们就是申请了一个type类型的空间来保存data,并且约定类型是data_type
下面又来了一个宏,我们来看一下
//字符串构造宏,适用于char * 的字符串,参数:字符串地址,保存节点,例如:AlgoAnyListNode * node = NULL;ALGO_ANY_LIST_LPCHAR_MAKE_NODE("hello", node);
#define ALGO_ANY_LIST_LPCHAR_MAKE_NODE(data,psave) \
do\
{\
int size=sizeof(char)*(strlen(data)+1);\
char * buf=(char *)malloc(size);\
strcpy(buf,data);\
psave = AlgoAnyList_makeNode(buf, ALGO_ANY_LIST_DATA_TYPE_LPCHAR, size);\
} while (0)
看了上一个宏,这一个宏自然也看得懂,可能有点问题的就是
do
{
//...
}while(0)
这样一个只执行一次的循环有什么用?
这个问题很简单,C语言对于宏的处理,就是直接做了一个替换操作
既然是替换,如果没有一个这样的包裹,就可能导致和外部变量混用,产生不可预知的编译结果,然而宏是不报错的,知道运行失败才能知道失败了
那么,这样一个do-while(0),首先保证了运行一次,并且由于{}括号的原因,就限定了一个变量的作用域,这样就不会和外部变量交叉发生混乱
因此,这也是一个宏应用的技巧,另外也可以用
if(1)
{
//...
}
代替do-while(0)
也就是如此,这些宏–有的在宏内发生了临时变量的定义,在早期的纯C编译环境下是不支持的,由于传统C语言的变量需要在一开始定义,不能就地定义,所以这些宏将会出现错误
看到这里,我相信已经被劝退了不少观众了,上实现源码
对宏敏感的同学,就不要看宏,只需要知道怎么使用即可
#ifndef _ALGO_ANY_LIST_HPP_
#define _ALGO_ANY_LIST_HPP_
/*
author:ugex
-----------------------------------
使用C语言的方式,构造一个通用链表
命名规则:
宏部分:
ALGO_ANY_LIST_开头
其他部分:
AlgoAnyList
链表元素需要准守规则,data指向地址一定要是在堆上的
临时变量取地址是不行的,因为这会有危险(导致野指针)
另外,一个节点也必须是堆上的,否则如果你跨函数操作的时候,
节点可能会发生危险,被错误的释放或者野指针
*/
#include
//基本数据类型定义,你也可以使用自己的定义
#define ALGO_ANY_LIST_DATA_TYPE_NULL 0x00
#define ALGO_ANY_LIST_DATA_TYPE_CHAR 0x01
#define ALGO_ANY_LIST_DATA_TYPE_SHORT 0x02
#define ALGO_ANY_LIST_DATA_TYPE_INT 0x03
#define ALGO_ANY_LIST_DATA_TYPE_LONG 0x04
#define ALGO_ANY_LIST_DATA_TYPE_FLOAT 0x05
#define ALGO_ANY_LIST_DATA_TYPE_DOUBLE 0x06
#define ALGO_ANY_LIST_DATA_TYPE_LPCHAR 0x07
//用户数据类型值,开始值
#define ALGO_ANY_LIST_DATA_TYPE_USER 0x100
//基本数据类型的宏构造,参数:数据,数据类型,约定类型,例如:AlgoAnyListNode* node=ALGO_ANY_LIST_BASE_MAKE_NODE(12,int,ALGO_ANY_LIST_DATA_TYPE_INT)
//或者其他类型支持:赋值,malloc(sizeof(类型))的均可
//因此对数组并不适用
#define ALGO_ANY_LIST_BASE_MAKE_NODE(data,type,data_type) AlgoAnyList_makeNode(&((* (type*)malloc(sizeof(type))) = data),data_type,sizeof(type))
//字符串构造宏,适用于char * 的字符串,参数:字符串地址,保存节点,例如:AlgoAnyListNode * node = NULL;ALGO_ANY_LIST_LPCHAR_MAKE_NODE("hello", node);
#define ALGO_ANY_LIST_LPCHAR_MAKE_NODE(data,psave) do{int size=sizeof(char)*(strlen(data)+1);char * buf=(char *)malloc(size);strcpy(buf,data);psave = AlgoAnyList_makeNode(buf, ALGO_ANY_LIST_DATA_TYPE_LPCHAR, size);} while (0)
//内存空间构造,适用于一块内存,因此也适用于字符串构造,参数:数据源地址,数据大小,约定类型
#define ALGO_ANY_LIST_MEMORY_MAKE_NODE(pdata,psave,data_size,data_type) do{void * buf=(void *)malloc(data_size);memcpy(buf,pdata,data_size);psave = AlgoAnyList_makeNode(buf, data_type, data_size);}while(0)
//释放用于构造宏所得到的空间,适用于基本数据类型,不适用于字符串,其实就是malloc配对的free调用,参数:节点的data,数据类型,例如:ALGO_ANY_LIST_FREE_DATA(node->data,int)
//如果要应用于字符串,第二个参数应为char,而不是char*
#define ALGO_ANY_LIST_FREE_DATA(pdata,type) do{if(pdata){free((type *)pdata);pdata=NULL;}}while(0)
//释放一块内存,参数:地址,指针类型,例如:ALGO_ANY_LIST_FREE_DATA_MEMORY(node->data,char *)
#define ALGO_ANY_LIST_FREE_DATA_MEMORY(pdata,pointertype) do{if(pdata){free((pointertype)pdata);pdata=NULL;}}while(0)
//宏取值,适用于基本数据类型,不适用于字符串,例如:ALGO_ANY_LIST_GET_DATA(cur->data, int)
#define ALGO_ANY_LIST_GET_DATA(pdata,type) (*(type*)pdata)
//宏取值,取内存数据,转换指针类型
#define ALGO_ANY_LIST_GET_DATA_MEMORY(pdata,pointertype) ((pointertype)pdata)
typedef struct __algo_any_list_node
{
void * data; //指向数据的指针
long data_size; //数据的大小,有时可能会用到
int data_type; //数据的类型
struct __algo_any_list_node * previous; //向前指针
struct __algo_any_list_node * next; //向后指针
}AlgoAnyListNode,*PAlgoAnyListNode;
typedef AlgoAnyListNode AlgoAnyList;
typedef PAlgoAnyListNode PAlgoAnyList;
//创建一个链表节点
AlgoAnyListNode* AlgoAnyList_makeNode()
{
AlgoAnyListNode* ret = (AlgoAnyListNode*)malloc(sizeof(AlgoAnyListNode));
ret->data = NULL;
ret->data_type = ALGO_ANY_LIST_DATA_TYPE_NULL;
ret->data_size = 0;
ret->previous = NULL;
ret->next = NULL;
return ret;
}
//带数据创建一个链表节点
AlgoAnyListNode* AlgoAnyList_makeNode(void * data, int data_type, long data_size/*=0*/)
{
AlgoAnyListNode* ret = AlgoAnyList_makeNode();
ret->data = data;
ret->data_type = data_type;
ret->data_size = data_size;
return ret;
}
//删除一个节点,返回永为NULL,这样方便你进行这样的调用:
//node=node.free(node);
//而不是:node.free(node);node=NULL;
AlgoAnyListNode* AlgoAnyList_freeNode(AlgoAnyListNode * node)
{
if (node)
free(node);
return NULL;
}
//重置节点,因此,你需要提前处理data段,是否需要释放内存
AlgoAnyListNode* AlgoAnyList_resetNode(AlgoAnyListNode * ret)
{
ret->data = NULL;
ret->data_type = ALGO_ANY_LIST_DATA_TYPE_NULL;
ret->data_size = 0;
ret->previous = NULL;
ret->next = NULL;
return ret;
}
//重置一个节点的前后指针
AlgoAnyListNode* AlgoAnyList_resetPointers(AlgoAnyListNode * node)
{
if (node)
{
node->previous = NULL;
node->next = NULL;
}
return node;
}
//将两个完整链表连接:返回值list1=list1+list2
//如果list2只有一个节点,那么就相当于添加一个节点
AlgoAnyListNode* AlgoAnyList_appendList(AlgoAnyListNode * list1, AlgoAnyListNode * list2)
{
AlgoAnyListNode * list1_last = list1;
while (list1_last->next != NULL)
{
list1_last = list1_last->next;
}
AlgoAnyListNode * list2_last = list2;
while (list2_last->next != NULL)
{
list2_last = list2_last->next;
}
list1_last->next = list2;
list2->previous = list1_last;
return list1;
}
//将完整列表list2插入到节点lstnode之后,lstnode节点原来之后的元素,被放置到list2之后,返回值lstnode0=lstnode0+list2+lstnode1+lstnode2+...
//如果list2只有一个节点,那么就相当于插入一个节点
AlgoAnyListNode* AlgoAnyList_insertList(AlgoAnyListNode * lstnode, AlgoAnyListNode * list2)
{
AlgoAnyListNode * lstnode_next = lstnode->next;
AlgoAnyListNode * list2_last = list2;
while (list2_last->next != NULL)
{
list2_last = list2_last->next;
}
lstnode->next = list2;
if (lstnode_next != NULL)
lstnode_next->previous = list2_last;
list2->previous = lstnode;
list2_last->next = lstnode_next;
return lstnode;
}
//删除一个节点,至于删除的节点,你需要自己处理(因为不知道传入的是否是new出来的,还是直接临时对象)
//返回值:lstnode->previous
//删除节点的父节点(如果没有父节点,那么就返回它的子节点,父子节点都没有,返回NULL)
AlgoAnyListNode* AlgoAnyList_deleteNode(AlgoAnyListNode * lstnode)
{
AlgoAnyListNode * parent = lstnode->previous;
AlgoAnyListNode * child = lstnode->next;
lstnode->previous = NULL;
lstnode->next = NULL;
if (parent == NULL && child == NULL)
{
return NULL;
}
if (parent == NULL)
{
child->previous = parent;
return child;
}
if (child == NULL)
{
parent->next = child;
return parent;
}
child->previous = parent;
parent->next = child;
return parent;
}
//根据链表其中一个节点,找到链表最开头的节点,如果你弄成循环链表,那就不要使用此方法
AlgoAnyListNode * AlgoAnyList_getFirstNode(AlgoAnyListNode * lstnode)
{
AlgoAnyListNode * ret = lstnode;
while (ret->previous != NULL)
{
ret = ret->previous;
}
return ret;
}
//根据链表其中一个节点,找到链表最末尾的节点,如果你弄成循环链表,那就不要使用此方法
AlgoAnyListNode * AlgoAnyList_getLastNode(AlgoAnyListNode * lstnode)
{
AlgoAnyListNode * ret = lstnode;
while (ret->next != NULL)
{
ret = ret->next;
}
return ret;
}
//检查,向后指针是否构成了循环链表
bool AlgoAnyList_isLoopNextList(AlgoAnyListNode * lstnode)
{
AlgoAnyListNode * ret = lstnode;
while (ret->next != NULL)
{
ret = ret->next;
if (ret == lstnode)
return true;
}
return false;
}
//检查,向前指针是否构成了循环链表
bool AlgoAnyList_isLoopPreviousList(AlgoAnyListNode * lstnode)
{
AlgoAnyListNode * ret = lstnode;
while (ret->previous != NULL)
{
ret = ret->previous;
if (ret == lstnode)
return true;
}
return false;
}
#endif // _ALGO_ANY_LIST_HPP_
总结一下