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

(转载至:http://www.ibm.com/developerworks/cn/linux/l-tip-prompt/tip02/,感谢T. W. Burger先生)

一个简化的问题示例

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


两个结构类似的链表

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;}elsenewlist->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;}elsenewlist->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;}elsenewlist->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++ 类,并且这个类可以处理任何数据对象或类(在一个项目中,它处理大约二十个不同的对象)。您永远不必重新编写这段代码。

转载于:https://www.cnblogs.com/yin-jingyu/archive/2012/03/06/2381315.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/422553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

php csv转excel 双引号,PHP高效导出Excel(CSV)

CSV,是Comma Separated Value(逗号分隔值)的英文缩写,通常都是纯文本文件。如果你导出的Excel没有什么高级用法的话,只是做导出数据用那么建议使用本方法,要比PHPexcel要高效的多。二十万数据导出大概需要2到3秒。/*** 导出excel(csv)* data …

linux php 上级目录,Linux目录架构详解_php

linux和Windows操作系统的显著区别之一就是目录架构的不同。Linux操作系统的目录架构遵循文件系统层级结构标准。不知你是否使用ls命令浏览过Linux的根目录“/”,亲爱的读者,您都了解这些目录的含义吗?ls -l / 遍历文件系统(点击看大图)本文将引领您浏览…

t-sql中的事务控制及错误处理

------------------------------------------------事务控制----------------------------------------------------- Sql Server 2005/2008中提供了begin tran,commit tran和rollback tran来使用事务。begin tran表示开始事务, commit tran表示提交事务…

ADT

ADT:https://dl-ssl.google.com/android/eclipse/ SDK:http://developer.android.com/sdk/index.html转载于:https://www.cnblogs.com/Robbery/archive/2012/03/08/2385892.html

《那些年啊,那些事——一个程序员的奋斗史》——79

旧的4.3'主板是废弃了,新的带CMMB的板子开始了。对于新的板子来说,其实只是在旧的板子上做改动,比如删掉蓝牙模块啊,去掉视频输入啊等;最大的不同,只是根据Telechips的原理图增加CMMB部分而已。…

Win配置Apache+mod_wsgi+django环境+域名

转自:http://liluo.org/2010/05/win-apache-mod_wsgi-django-domain/ Python是落落的最爱,Django是Python web framework中的佼佼者,所以一直超爱。当然,Python web server也有N多选择,这里落落使用Apachemod_wsgi来配…

screen执行php,Linux中screen命令及使用方法

Screen简介Screen是一款由GNU计划开发的用于命令行终端切换的自由软件。用户可以通过该软件同时连接多个本地或远程的命令行会话,并在其间自由切换。GNU Screen可以看作是窗口管理器的命令行界面版本。它提供了统一的管理多个会话的界面和相应的功能。GUN Screen&am…

CSS的历史与工作原理

1. 浏览器的发展与CSS网页浏览器主要通过HTTP协议连接网页服务器而取得网页,HTTP容许网页浏览器送交资料到网页服务器并且获取网页。目前最常用的 HTTP 是 HTTP/1.1,这个协议在RFC2616中被完整定义。HTTP/1.1 有其一套Internet Explorer并不完全支援的标…

java类可选,java – 是否有类可选,但非可选的类?

它是方便的声明函数映射值和消费它们,如果它们存在。在你有几个强制对象和几个可选的情况下,我发现自己包装在Optional.of(mandatoryObject)中的其他人,所以我可以使用相同的表达式,而不必向后写。Food vegetables Food.someVege…

数据库字符串处理函数

在数据库操作中,经常要进行字符串的拼接和替换等操作。下面总结归纳如下: 示例数据表people如下: 1 sql server数据库中的sql语句可以直接用 号进行字符串的拼接。举例如下: select Name ‘ ’ gentle as message from people …

[zz]Apache Thrift学习小记

参考: http://incubator.apache.org/thrift/ http://wiki.apache.org/thrift/FrontPage http://jnb.ociweb.com/jnb/jnbJun2009.html非常好的入门教程 http://developers.facebook.com/thrift/thrift-20070401.pdfthrift开发者写的论文 http://wiki.apache.org/thri…

php引用python出现502,【已解决】python执行出错:HTTPError: HTTP Error 502: Bad Gateway

【已解决】python执行出错:HTTPError: HTTP Error 502: Bad Gateway【背景】某此脚本执行,出现错误:LINE 1687 : ERROR Unknown Error !Traceback (most recent call last):File “E:WebServerWordPressto_wphi-baidu-mover_v2hi-baidu-mo…

can't load apple.laf.AquaLookAndFeel (Ant in Eclipse can't find it

转载自:http://lists.apple.com/archives/java-dev/2004/Oct/msg00529.html Re: "cant load apple.laf.AquaLookAndFeel" (Ant in Eclipse cant find it) Subject: Re: "cant load apple.laf.AquaLookAndFeel" (Ant in Eclipse cant find it)Fr…

php中的会话控制器,php – codeigniter检查每个控制器中的用户会话

另一个选项是创建基本控制器。将函数放在基本控制器中,然后继承。要在CodeIgniter中实现这一点,请在应用程序的libraries文件夹中创建一个名为MY_Controller.php的文件。class MY_Controller extends Controller{public function __construct(){parent::…

EXT.NET高效开发(一)——概述

之前就有想法说说这方面,直到看到我上一篇博客《EXT.NET复杂布局(一)——工作台》的回复: 小龙3:ext.net 比使用傳統的webform控件开发时间多多少? 我就决定提前写这一系列了。小龙3应该感到荣幸。嘿嘿。 相…

php 类的注释标准,php标准注释

文件头部模板/***这是一个什么文件**此文件程序用来做什么的(详细说明,可选。)。* author richard* version $Id$* since 1.0*/函数头部注释/*** some_func* 函数的含义说明** access public* param mixed $arg1 参数一的说明* param mixed $arg2 参数二的说明* par…

前端学习(417):京东制作页面25中间部分的准备工作

引入index.css作为中部样式 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Comp…

前端学习(418):京东制作页面26中间模块划分

index.css /* 中间模块 */ .grid{height: 480px;} .grid-coll1{width: 190px;height: 100%;background-color: pink;} .grid-coll2{width: 790px;height: 100%;background-color: skyblue;margin-left:10px;} .grid-coll3{width: 190px;height: 100%;background-color:purple;…

Linux下,安装配置Weblogic

Linux下&#xff0c;安装配置Weblogic2009-03-20 15:03一、安装配置JDK //如果应用不需要高版本的JDK的话&#xff0c;可以不单独安装JDK 1. 安装JDK # chmod ax jdk-1_5_0_15-linux-i586.bin # ./jdk-1_5_0_15-linux-i586.bin # mv jdk1.5.0_15 /usr/local/jdk //JDK安…

php_self nginx,nginx中的PATH_INFO为什么会影响$_SERVIER['PHP_SELF']

发现问题&#xff1a;使用ThinkPHP3.2.2在nginx部署网站&#xff0c;设置URL_MODEL2&#xff0c;使用U方法在本地生成的链接形如:/public/index但是在部署在nginx上却出现了问题 其中的URL生成了./public/index通过追寻ThinkPHP源码在ThinkPHP.php文件中发现了如下代码:if(IS_C…