问答文章1 问答文章501 问答文章1001 问答文章1501 问答文章2001 问答文章2501 问答文章3001 问答文章3501 问答文章4001 问答文章4501 问答文章5001 问答文章5501 问答文章6001 问答文章6501 问答文章7001 问答文章7501 问答文章8001 问答文章8501 问答文章9001 问答文章9501

在C/C++中如何构造通用的对象链表

发布网友 发布时间:2022-04-27 03:26

我来回答

2个回答

热心网友 时间:2022-06-25 11:21

cpp用模板呗。c的话,就只能用void*了

热心网友 时间:2022-06-25 11:21

链表的难点在于必须复制链表处理函数来处理不同的对象,即便逻辑是完全相同的。例如:

两个结构类似的链表

struct Struct_Object_A
{
int a;
int b;
Struct_Object_A *next;
} OBJECT_A;
typedef struct Struct_Object_B
{
int a;
int b;
int c;
Struct_Object_B *next;
} OBJECT_B;

上面定义的两个结构只有很小的一点差别。OBJECT_B 和 OBJECT_A 之间只差一个整型变量。但是,在编译器看来,它们仍然是非常不同的。必须为存储在链表中的每个对象复制用来添加、删除和搜索链表的函数。为了解决这个问题,可以使用具有全部三个变量的一个联合或结构,其中整数 c 并不是在所有的情况下都要使用。这可能变得非常复杂,并会形成不良的编程风格。

C 代码解决方案:虚拟链表
此问题更好的解决方案之一是虚拟链表。虚拟链表是只包含链表指针的链表。对象存储在链表结构背后。这一点是这样实现的,首先为链表节点分配内存,接着为对象分配内存,然后将这块内存分配给链表节点指针,如下所示:

虚拟链表结构的一种实现

typedef struct liststruct
{
liststruct *next;
} LIST, *pLIST;
pLIST Head = NULL;
pLIST AddToList( pLIST Head, void * data, size_t datasize )
{
pLIST newlist=NULL;
void *p;
// 分配节点内存和数据内存
newlist = (pLIST) malloc( datasize + sizeof( LIST ) );
// 为这块数据缓冲区指定一个指针
p = (void *)( newlist + 1 );
// 复制数据
memcpy( p, data, datasize );
// 将这个节点指定给链表的表头
if( Head )
{
newlist->next = Head;
}
else
newlist->next = NULL;
Head = newlist;
return Head;
}

链表节点现在建立在数据值副本的基本之上。这个版本能很好地处理标量值,但不能处理带有用 malloc 或 new 分配的元素的对象。要处理这些对象,LIST 结构需要包含一个一般的解除函数指针,这个指针可用来在将节点从链表中删除并解除它之前释放内存(或者关闭文件,或者调用关闭方法)。

一个带有解除函数的链表

typedef void (*ListNodeDestructor)( void * );
typedef struct liststruct
{
ListNodeDestructor DestructFunc;
liststruct *next;
} LIST, *pLIST;
pLIST AddToList( pLIST Head, void * data, size_t datasize,
ListNodeDestructor Destructor )
{
pLIST newlist=NULL;
void *p;
// 分配节点内存和数据内存
newlist = (pLIST) malloc( datasize + sizeof( LIST ) );
// 为这块数据缓冲区指定一个指针
p = (void *)( newlist + 1 );
// 复制数据
memcpy( p, data, datasize );
newlist->DestructFunc = Destructor;

// 将这个节点指定给链表的表头
if( Head )
{
newlist->next = Head;
}
else
newlist->next = NULL;
Head = newlist;
return Head;
}
void DeleteList( pLIST Head )
{
pLIST Next;
while( Head )
{
Next = Head->next;
Head->DestructFunc( (void *) Head );
free( Head );
Head = Next;
}
}
typedef struct ListDataStruct
{
LPSTR p;
} LIST_DATA, *pLIST_DATA;
void ListDataDestructor( void *p )
{
// 对节点指针进行类型转换
pLIST pl = (pLIST)p;
// 对数据指针进行类型转换
pLIST_DATA pLD = (pLIST_DATA) ( pl + 1 );
delete pLD->p;
}
pLIST Head = NULL;
void TestList()
{
pLIST_DATA d = new LIST_DATA;
d->p = new char[24];
strcpy( d->p, "Hello" );
Head = AddToList( Head, (void *) d, sizeof( pLIST_DATA ),
ListDataDestructor );
// 该对象已被复制,现在删除原来的对象
delete d;
d = new LIST_DATA;
d->p = new char[24];
strcpy( d->p, "World" );
Head = AddToList( Head, (void *) d, sizeof( pLIST_DATA ),
ListDataDestructor );
delete d;
// 释放链表
DeleteList( Head );
}

在每个链表节点中包含同一个解除函数的同一个指针似乎是浪费内存空间。确实如此,但只有链表始终包含相同的对象才属于这种情况。按这种方式编写链表允许您 将任何对象放在链表中的任何位置。大多数链表函数要求对象总是相同的类型或类。虚拟链表则无此要求。它所需要的只是将对象彼此区分开的一种方法。要实现这 一点,您既可以检测解除函数指针的值,也可以在链表中所用的全部结构前添加一个类型值并对它进行检测。当然,如果要将链表编写为一个 C++ 类,则对指向解除函数的指针的设置和存储只能进行一次。

C++ 解决方案:类链表
本解决方案将 CList 类定义为从 LIST 结构导出的一个类,它通过存储解除函数的单个值来处理单个存储类型。请注意添加的 GetCurrentData() 函数,该函数完成从链表节点指针到数据偏移指针的数学转换。

一个虚拟链表对象

// 定*除函数指针
typedef void (*ListNodeDestructor)( void * );
// 未添加解除函数指针的链表
typedef struct ndliststruct
{
ndliststruct *next;
} ND_LIST, *pND_LIST;
// 定义处理一种数据类型的链表类
class CList : public ND_LIST
{
public:
CList(ListNodeDestructor);
~CList();
pND_LIST AddToList( void * data, size_t datasize );
void *GetCurrentData();
void DeleteList( pND_LIST Head );
private:
pND_LIST m_HeadOfList;
pND_LIST m_CurrentNode;
ListNodeDestructor m_DestructFunc;
};
// 用正确的起始值构造这个链表对象
CList::CList(ListNodeDestructor Destructor)
: m_HeadOfList(NULL), m_CurrentNode(NULL)
{
m_DestructFunc = Destructor;
}
// 在解除对象以后删除链表
CList::~CList()
{
DeleteList(m_HeadOfList);
}
// 向链表中添加一个新节点
pND_LIST CList::AddToList( void * data, size_t datasize )
{
pND_LIST newlist=NULL;
void *p;
// 分配节点内存和数据内存
newlist = (pND_LIST) malloc( datasize + sizeof( ND_LIST ) );
// 为这块数据缓冲区指定一个指针
p = (void *)( newlist + 1 );
// 复制数据
memcpy( p, data, datasize );
// 将这个节点指定给链表的表头
if( m_HeadOfList )
{
newlist->next = m_HeadOfList;
}
else
newlist->next = NULL;
m_HeadOfList = newlist;
return m_HeadOfList;
}
// 将当前的节点数据作为 void 类型返回,以便调用函数能够将它转换为任何类型
void * CList::GetCurrentData()
{
return (void *)(m_CurrentNode+1);
}
// 删除已分配的链表
void CList::DeleteList( pND_LIST Head )
{
pND_LIST Next;
while( Head )
{
Next = Head->next;
m_DestructFunc( (void *) Head );
free( Head );
Head = Next;
}
}
// 创建一个要在链表中创建和存储的结构
typedef struct ListDataStruct
{
LPSTR p;
} LIST_DATA, *pND_LIST_DATA;
// 定义标准解除函数
void ClassListDataDestructor( void *p )
{
// 对节点指针进行类型转换
pND_LIST pl = (pND_LIST)p;
// 对数据指针进行类型转换
pND_LIST_DATA pLD = (pND_LIST_DATA) ( pl + 1 );
delete pLD->p;
}
// 测试上面的代码
void MyCListClassTest()
{
// 创建链表类
CList* pA_List_of_Data = new CList(ClassListDataDestructor);
// 创建数据对象

pND_LIST_DATA d = new LIST_DATA;
d->p = new char[24];
strcpy( d->p, "Hello" );
// 创建指向链表顶部的局部指针
pND_LIST Head = NULL;
//向链表中添加一些数据
Head = pA_List_of_Data->AddToList( (void *) d,
sizeof( pND_LIST_DATA ) );
// 该对象已被复制,现在删除原来的对象
delete d;
// 确认它已被存储
char * p = ((pND_LIST_DATA) pA_List_of_Data->GetCurrentData())->p;
d = new LIST_DATA;
d->p = new char[24];
strcpy( d->p, "World" );
Head = pA_List_of_Data->AddToList( (void *) d, sizeof( pND_LIST_DATA ) );
// 该对象已被复制,现在删除原来的对象
delete d;
// 确认它已被存储
p = ((pND_LIST_DATA) pA_List_of_Data->GetCurrentData())->p;
// 删除链表类,析构函数将删除链表
delete pA_List_of_Data;
}

小结
从前面的讨论来看,似乎仅编写一个简单的链表就要做大量的工作,但这只须进行一次。很容易将这段代码扩充为一个处理排序、搜索以及各种其他任务的 C++ 类,并且这个类可以处理任何数据对象或类(在一个项目中,它处理大约二十个不同的对象)。您永远不必重新编写这段代码。
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com
光线为什么不存在 炒菜时要等到油冒烟吗 高考数学选修没涂有分吗? 高考选修不涂有分数吗 《宫》中插曲是什么乐器演奏的? 电视剧《宫》的主题曲和片尾曲分别是什么? 爱我的人和我爱的人原唱是裘海正还是游鸿明 刑法对追诉时效的规定 刑事案件追诉期限的法律规定是什么? 爱奇艺海外版怎么下载 M.Y.COMIC是什么意思 电脑主机不定时重启 有时重启平凡 浙江省交通运输厅的机构职能 平湖耀江房地产开发有限公司整体资产评估项目资产评估报告书 浙江省文学艺术界联合会的机构章程 浙江省财政厅的主要职责 浙江省公*,市国有资产管理委员会和财政支付中心,都是什么单位?好不好? 浙江省人民政府国有资产监督管理委员会的人员编制 主要涉及国有产权管理的法律都有哪些? 浙江省人民政府国有资产监督管理委员会的介绍 浙江省国有资产管理委员会办公地点在什么地方 萌宠拼拼小程序里的是假货吗 没有与拼多多平台敢拼单吗? 2019全民拼拼拼是骗子吗 爱拼拼箱科技有限公司怎么样? 成语拼拼拼能挣钱吗成语拚拼拼真的吗? 拼拼货司机好做吗? 松鼠拼拼团长招募靠谱吗?佣金真的能到账吗? 淘宝买洪恩拼音靠谱吗? 淘淘拼拼单靠谱吗 大学学费是一年还是一学期? 大学收费问题? 请问大学报名学费学年是是一年缴费还是分两个学期?上面二本学校5000元/学年 一年交多少 大学学费是怎么收的 一学年还是还是一学期 问一下,大学的学费是一学期一交还是一年一交啊 本科大学学费是一学期一交还是一年一交 大学学费的学年制是一个学期的还是两个学期的? 唐高祖(李渊)和隋炀帝(杨广)是什么关系? 唐高祖和隋炀帝是什么关系? 隋炀帝杨广与唐高祖李渊是什么关系? 隋炀帝杨广与唐高祖李渊是什么关系呢? 唐高祖李渊和隋炀帝杨广是什么关系呢? 隋炀帝杨光与唐高祖李渊是什么关系? 李渊与杨广是什么关系 唐朝的李世民和隋炀帝,两者之间有何联系? 隋文帝杨坚与唐高祖李渊是什么关系??? 我们知道;唐朝取代的是隋朝;可是,你知道吗?唐高祖李渊居然和隋炀帝是亲戚关系;怎么回事 唐高祖李渊和隋炀帝的关系 唐太宗与隋炀帝有何亲属关系? 隋文帝杨坚和唐高祖李渊有什么亲戚关系?