c语言通过宏实现类似c++的泛型编程效果——泛型链表

本文介绍了如何使用C语言通过宏定义实现泛型链表,包括节点结构、链表结构以及查找、添加、删除和更改操作。虽然提高了代码复用性,但调试难度增加,可通过宏展开辅助调试。

序言:在学了c++之后,突然间想能不能用c来模拟实现一个泛型链表。

直接进入正题。

众所周知,c++在定义了一个模板后,如果在使用了一个类型的模板函数或类,编译器在编译时会自动创建该类型的函数或类。

而我们如果想使用c实现泛型,可以用宏来定义一个模板(幸好c的代码缩进没有严格要求),如果我们要用不同类型的函数,我们可以手动去定义。

上代码"list.h"

#pragma once

#include <stdint.h>
#include <stdbool.h>
#include <malloc.h>


#define LLNODE(type) struct type##_llnode{struct type##_llnode* next;type e;};
#define LLIST(type) struct type##_llist{struct type##_llnode* list;int num;};
#define LLFIND(type) bool type##_llfind(struct type##_llist* L,int pos,type * e);
#define LLADD(type) bool type##_lladd(struct type##_llist* L,int pos,type e);
#define LLDELETE(type) bool type##_lldelete(struct type##_llist* L,int pos);
#define LLCHANGE(type) bool type##_llchange(struct type##_llist* L,int pos,type e);
#define INCLUDE_LL(type) LLNODE(type) LLIST(type) LLFIND(type) LLADD(type) LLDELETE(type) LLCHANGE(type)

#define POS_LEGAL_LORO(pos,b,e) ((pos>=b)&&(pos<(int)(e)))
#define POS_LEGAL_LORC(pos,b,e) ((pos>=b)&&(pos<=(int)(e)))

#define LLFIND_FUN(type) bool type##_llfind(struct type##_llist* L,int pos,type * e)\
{\
	if(!POS_LEGAL_LORO(pos,0,L->num))return false;\
	if(e == NULL)return false;\
	struct type##_llnode* node = L->list;\
	for(int i = 0;i<pos;i++)node = node->next;\
	*e = node->e;\
	return true;\
}

#define LLADD_FUN(type) bool type##_lladd(struct type##_llist* L,int pos,type e)\
{\
	if(!POS_LEGAL_LORC(pos,0,L->num))return false;\
	struct type##_llnode* n = (struct type##_llnode *)malloc(sizeof(struct type##_llnode));\
	if(n == NULL)return false;\
	n->next = NULL;\
	n->e = e;\
    if (pos == 0) \
	{\
		n->next = L->list;\
		L->list = n;\
		L->num++;\
		return true;\
	}\
	struct type##_llnode* node = L->list;\
	for(int i = 0;i<pos-1;i++)node = node->next;\
	n->next = node->next;\
	node->next = n;\
	L->num++;\
	return true;\
}

#define LLDELETE_FUN(type) bool type##_lldelete(struct type##_llist* L,int pos)\
{\
	if(!POS_LEGAL_LORO(pos,0,L->num))return false;\
	struct type##_llnode* n = L->list;\
    if (pos == 0) \
	{\
		L->list = n->next;\
		free(n);\
		L->num--;\
		return true;\
	}\
	struct type##_llnode* node = L->list;\
	for(int i = 0;i<pos-1;i++)node = node->next;\
	n = node->next;\
	node->next = n->next;\
	free(n);\
	L->num--;\
	return true;\
}

#define LLCHANGE_FUN(type) bool type##_llchange(struct type##_llist* L,int pos,type e)\
{\
	if(!POS_LEGAL_LORO(pos,0,L->num))return false;\
	struct type##_llnode* node = L->list;\
	for(int i = 0;i<pos;i++)node = node->next;\
	node->e = e;\
	return true;\
}

#define SOURCE_LL(type) LLFIND_FUN(type) LLADD_FUN(type) LLDELETE_FUN(type) LLCHANGE_FUN(type)

#define LLcreate(type,name) struct type##_llist name = {NULL,0}
#define LLlen(L) ((L)->num)
#define LLfind(type) type##_llfind
#define LLadd(type) type##_lladd
#define LLdelete(type) type##_lldelete
#define LLchange(type) type##_llchange
#define LLpush_back(type,l,e) type##_lladd(l,(l)->num,e)
#define LLpop_up(type,l) type##_lldelete(l,((l)->num - 1))

INCLUDE_LL(int)
INCLUDE_LL(float)

 

#define LLNODE(type) struct type##_llnode{struct type##_llnode* next;type e;};
#define LLIST(type) struct type##_llist{struct type##_llnode* list;int num;};
#define LLFIND(type) bool type##_llfind(struct type##_llist* L,int pos,type * e);
#define LLADD(type) bool type##_lladd(struct type##_llist* L,int pos,type e);
#define LLDELETE(type) bool type##_lldelete(struct type##_llist* L,int pos);
#define LLCHANGE(type) bool type##_llchange(struct type##_llist* L,int pos,type e);
#define INCLUDE_LL(type) LLNODE(type) LLIST(type) LLFIND(type) LLADD(type) LLDELETE(type) LLCHANGE(type)

↑这是链表类以及它的方法的模板,INCLUDE_LL(type)是声明,好让我们能够去使用这些操作,不过我在下面定义了更便于去使用的方法

#define POS_LEGAL_LORO(pos,b,e) ((pos>=b)&&(pos<(int)(e)))
#define POS_LEGAL_LORC(pos,b,e) ((pos>=b)&&(pos<=(int)(e)))

↑这是确定pos是否合法的宏,POS_LEGAL_LORO左开右开区间  POS_LEGAL_LORC左开右闭区间 

#define LLFIND_FUN(type) bool type##_llfind(struct type##_llist* L,int pos,type * e)\
{\
	if(!POS_LEGAL_LORO(pos,0,L->num))return false;\
	if(e == NULL)return false;\
	struct type##_llnode* node = L->list;\
	for(int i = 0;i<pos;i++)node = node->next;\
	*e = node->e;\
	return true;\
}

#define LLADD_FUN(type) bool type##_lladd(struct type##_llist* L,int pos,type e)\
{\
	if(!POS_LEGAL_LORC(pos,0,L->num))return false;\
	struct type##_llnode* n = (struct type##_llnode *)malloc(sizeof(struct type##_llnode));\
	if(n == NULL)return false;\
	n->next = NULL;\
	n->e = e;\
    if (pos == 0) \
	{\
		n->next = L->list;\
		L->list = n;\
		L->num++;\
		return true;\
	}\
	struct type##_llnode* node = L->list;\
	for(int i = 0;i<pos-1;i++)node = node->next;\
	n->next = node->next;\
	node->next = n;\
	L->num++;\
	return true;\
}

#define LLDELETE_FUN(type) bool type##_lldelete(struct type##_llist* L,int pos)\
{\
	if(!POS_LEGAL_LORO(pos,0,L->num))return false;\
	struct type##_llnode* n = L->list;\
    if (pos == 0) \
	{\
		L->list = n->next;\
		free(n);\
		L->num--;\
		return true;\
	}\
	struct type##_llnode* node = L->list;\
	for(int i = 0;i<pos-1;i++)node = node->next;\
	n = node->next;\
	node->next = n->next;\
	free(n);\
	L->num--;\
	return true;\
}

#define LLCHANGE_FUN(type) bool type##_llchange(struct type##_llist* L,int pos,type e)\
{\
	if(!POS_LEGAL_LORO(pos,0,L->num))return false;\
	struct type##_llnode* node = L->list;\
	for(int i = 0;i<pos;i++)node = node->next;\
	node->e = e;\
	return true;\
}

#define SOURCE_LL(type) LLFIND_FUN(type) LLADD_FUN(type) LLDELETE_FUN(type) LLCHANGE_FUN(type)

↑方法实例模板,SOURCE_LL(type)用于创建方法实例

#define LLcreate(type,name) struct type##_llist name = {NULL,0}
#define LLlen(L) ((L)->num)
#define LLfind(type) type##_llfind
#define LLadd(type) type##_lladd
#define LLdelete(type) type##_lldelete
#define LLchange(type) type##_llchange
#define LLpush_back(type,l,e) type##_lladd(l,(l)->num,e)
#define LLpop_up(type,l) type##_lldelete(l,((l)->num - 1))

↑便于我们使用的一些方法,包括创建表头,获取链表长度,增删查改,以及尾删和尾插。使用起来类似于函数重载,函数名固定,但是,参数类型不同,不过我们要显性的指明类型,如:我要往一个float元素的链表插入一个元素,我们必须有这样写,

LLadd(float)(&list,0,1.32);

这样写LLadd有点像返回函数指针的函数😁

使用方法:

        在一个头文件加入INCLUDE_LL(type),实例化声明,type是你要使用的类型,如:

在“list.h"声明

INCLUDE_LL(int)
INCLUDE_LL(float)

        同时在一个源文件加入SOURCE_LL(type),实例化方法,如:

#include "llist.h"

SOURCE_LL(int)

SOURCE_LL(float)

这样就可以使用了

#include "llist.h"
#include <stdio.h>

int main()
{
	LLcreate(float, L);
	LLpush_back(float, &L, 3.32);
	LLpush_back(float, &L, 2.34);
	LLadd(float)(&L, 0, 1.32);
	LLadd(float)(&L, 0, 324.456);
	for (float i = 0,ip = 0; i < LLlen(&L); i++)
	{
		LLfind(float)(&L, i,&ip);
		printf("%f\n", ip);
	}
	putchar('\n');
	LLpop_up(float, &L);
	for (float i = 0, ip = 0; i < LLlen(&L); i++)
	{
		LLfind(float)(&L, i, &ip);
		printf("%f\n", ip);
	}

	return 0;
}

窗口打印:

 

定义链表元素类型type时可以是任何类型,前提是用宏模板手动去定义了这套类和方法

这样写的好处是,提高了代码的复用率,用起来更加舒服,但是带来的后果就是调试困难(不过幸好vs有宏展开功能,在使用时无法大脑模拟调试是可以用宏展开来调试)

不过这只是一种c泛型编程的写法,还可以用void*来实现,不过缺点还是,需要在使用时时刻记住你使用的是什么类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值