C语言 实现一个可以保存任意类型数据的双向链表 无类型 双向链表 任意类型存储 宏语句处理

C语言 实现一个可以保存任意类型数据的双向链表 无类型 双向链表 任意类型存储 宏语句处理

看起来很高大上是吧?
我们来看看核心结构体:

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;

怎么样?是否已经知道怎么实现了?
对,你的想法是对的就是

  • 借助void*类型
    • 可以指向任意一个地址
    • 也可以转换为任意确切的地址类型

可能你还有点懵,我们来看下下面的语句练练手:

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_

总结一下

  • 使用void*类型的链表
  • 好处是:
    • 任意数据类型皆可存储,下到基本数据类型,上到结构,类,甚至是容器,但是如果类型只会用一种,便得到简化
  • 坏处是:
    • 不同的数据类型存储,需要依赖于约定的数据类型表示进行识别,这样就增加了代码量

你可能感兴趣的:(C语言,私房菜,双向链表,任意数据类型,C语言,宏,void指针)