说明:
本文章旨在总结备份、方便以后查询,由于是个人总结,如有不对,欢迎指正;另外,内容大部分来自网络、书籍、和各类手册,如若侵权请告知,马上删帖致歉。
QQ 群 号:513683159 【相互学习】
内容来源:
《系统程序员成长计划》
上一篇:读书笔记 ——《系统程序员成长计划》篇2:封装
下一篇:读书笔记 ——《系统程序员成长计划》篇4:拥抱变化
目录:
一、双链表分类
1、专用双链表
双链表的实现和调用耦合在一起,只能被一个调用者使用,不能单独在其他地方被重用。
优点:
①性能更高。(可直接访问数据成员,省去包装函数带来的性能开销,故可提高时间性能,无需事先完整接口,生成代码更小,因此可提高空间性能)
②依赖更少。(自己实现不依赖别人,这样移植会更加简单)
③实现简单。(无需考虑复杂情况,符合当前应用即可,无需提供完整接口)
2、通用双链表
具有通用性,可多出被重复使用。
优点:(从全局看)
可靠性更高。(重用性高,随着每次重用而不断改进)
②开发效率高。(因为可重用,故开发成本随每次重用而降低)
二、如何编写一个通用双链表
1.结点数据存什么?
①存值:存入时复制一份数据,保存数据的指针和长度。
(复制数据会带来性能开销,C语言中该形式链表少见)
typedef struct _DListNode
{
struct _DListNode* prev;
struct _DListNode* next;
void* data;
size_t length;
}DListNode;
②存指针:只保存指向对象的指针,存取效率高,存放整数时,可把void* 强制转换成整数使用,避免内存分配。
typedef struct _DListNode
{
struct _DListNode* prev;
struct _DListNode* next;
void* data;
}DListNode;
2.让C++可用:
由于C++允许同名函数,故编译器会对函数名重新编码,导致函数名与原函数名不同造成找不到函数的情况,故为让C语言实现的函数可在C++中使用,需在头文件中添加以下内容:
#ifdef __cplusplus
extern "C"{
#endif
...
#ifdef__cplusplus
#endif
3.完整接口:
接口完整能满足各种情况需求的需要。
三、代码实现:
单文件(未封装)版
#include<stdio.h>
#include<stdlib.h> /malloc需调用该函数
typedef struct DNode /定义双链表节点结构体
{
struct DNode* priv; /前驱指针
struct DNode* next; /后继指针
int data; /结点数据
}DNode;
int main(int argc,char* argv[]){
DNode* head = (DNode*)malloc(sizeof(DNode)); /分配DNode数据类型大小的空间,并返回该空间首地址(malloc函数返回值为空指针类型,故需加上强制转换类型)
head->priv = NULL; /头结点的前驱指针永远指向空(因为它前面没有任何结点)
head->next = NULL; /头结点的后继指针暂时为空(后面会指向下一个结点首地址)
head->data = 1; /头结点数据赋值1
DNode* dlinklist = head; /定义指针dlinklist,并指向头结点首地址(主要表示该双链表,后面会不断的移动指向下一个结点)
for(int i = 2;i <= 5; i++){
DNode* node = (DNode*)malloc(sizeof(DNode)); /创建新结点
node->data = i; /新结点赋值
node->next = NULL; /新结点指向为空
node->priv = NULL; /新结点指向为空
dlinklist->next = node; /将新结点地址存入前结点的后继指针中
node->priv = dlinklist; /将前结点的地址存入新结点的前驱指针中
dlinklist = dlinklist->next; /移动链表指针,指向下一个结点
}
printf("%d\n",head->next->next->next->priv->data); /输出对应结点的数值(第三个结点的值[此处])
return 0;
}
实验结果:3
单文件(封装)版
#include<stdio.h>
#include<stdlib.h> /malloc需调用该函数
typedef struct DNode{ /定义双链表节点结构体
struct DNode* priv; /前驱指针
struct DNode* next; /后继指针
int data; /结点数据
}DNode,*Dlinklist;
DNode* node_creat(int data){ /结点创建函数
DNode* node = (DNode*)malloc(sizeof(DNode));
node->priv= NULL;
node->next=NULL;
node->data = data;
return node;
}
int main(int argc,char *argv[]){
DNode* head = node_creat(1); /头结点
Dlinklist dlinklist = head; /表示链表
int data[]={1,2,3,4,5,6,7,8,9,0};
for(int i = 1;i < sizeof(data)/sizeof(data[0]);i++){
DNode* DNode = node_creat(data[i]);
dlinklist->next = DNode; /将新结点地址存入前结点的后继指针中
DNode->priv = dlinklist; /将前结点的地址存入新结点的前驱指针中
dlinklist=dlinklist->next; /移动链表指针,指向下一个结点
}
printf("head = %d\n",head->next->next->next->priv->data);
return 0;
}
单文件(封装)(书中示例)版
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
/* 双链表结点 */
typedef struct _DListNode
{
struct _DListNode* prev; //前驱指针
struct _DListNode* next; //后继指针
void* data; //数据(空指针类型,灵活度高)
}DListNode;
/* 双链表头结点 */
typedef struct _DList
{
DListNode* first; //头结点仅有一个指针指向下一个结点,无数据
}DList;
/* 返回值状态(枚举类型) */
typedef enum _DListRet
{
DLIST_RET_OK, //正常
DLIST_RET_OOM, //内存溢出
DLIST_RET_STOP, //停止
DLIST_RET_PARAMS, //
DLIST_RET_FAIL //失败
}DListRet;
typedef DListRet (*DListDataPrintFunc)(void* data); //函数指针
/* 双链表结点创建 参数:data 为结点数据*/
static DListNode* dlist_node_create(void* data)
{
DListNode* node = (DListNode*)malloc(sizeof(DListNode)); //分配结点空间
if(node != NULL) //增加健壮性,若内存分配失败则不执行。
{
node->prev = NULL; //前驱指针指向为空
node->next = NULL; //后继指针指向为空
node->data = data; //将函数行参赋值
}
return node; //返回结点空间的起始地址
}
/* 计算双链表长度 参数:thiz 为双链表表头指针*/
size_t dlist_length(DList* thiz)
{
size_t length = 0; //定义length变量并初始值为0
DListNode* iter = thiz->first; //将头指针地址传给iter指针(双链表结点指针类型)
while(iter != NULL) //若指向不为空(下面还有结点),则
{
length++; //length自增
iter = iter->next; //继续指向下一个结点
}
return length; //返回双链表长度
}
/* 销毁双链表结点 参数:node 为双链表的结点*/
static void dlist_node_destroy(DListNode* node)
{
if(node != NULL) //若双链表结点不为空,则
{
node->next = NULL; //将前驱指针指向空(注意,一定要赋值为空,否则指针还是有指向,虽然下面会将对应空间释放)
node->prev = NULL; //将后继指针指向空(注意,一定要赋值为空,否则指针还是有指向,虽然下面会将对应空间释放)
free(node); //释放该结点空间
}
return;
}
/* 双链表创建(带头指针) */
DList* dlist_create(void)
{
DList* thiz = (DList*)malloc(sizeof(DList)); //创建头结点大小空间
if(thiz != NULL) //增加健壮性,若头结点空间分配失败
{
thiz->first = NULL; //给头结点后继结点指针赋值为NULL
}
return thiz; //返回头结点地址(也是双链表的表头)
}
/* 获取对应结点的地址 参数:thiz 为双链表表头指针,index 为想要读取的双链表下标位置 ,fail_return_last 为?*/
static DListNode* dlist_get_node(DList* thiz, size_t index, int fail_return_last)
{
DListNode* iter = thiz->first; //将头结点指针地址给iter指针
while(iter != NULL && iter->next != NULL && index > 0) //若当前结点不为空(存在),下一个结点也不为空(存在),链表下标(大于0)
{
iter = iter->next; //指向下一个结点
index--; //下标自减
}
if(!fail_return_last) //若为fail_return_last为0则执行
{
iter = index > 0 ? NULL : iter; //若下标大于0则iter指针指向NULL,小于0则iter指针指向没有变化
}
return iter; //返回对应结点的地址
}
/* 双链表插入 参数:thiz 为双链表表头指针 ,index 为想要插入双链表的下标位置, data 为想要插入的数据*/
DListRet dlist_insert(DList* thiz, size_t index, void* data)
{
DListNode* node = NULL; //双链表结点指针指向NULL
DListNode* cursor = NULL; //双链表节点指针指向NULL(cursor表示游标)
if((node = dlist_node_create(data)) == NULL) //若双链表结点创建失败
{
return DLIST_RET_OOM; //返回内存不足溢出错误
}
if(thiz->first == NULL) //若头结点后继指针为NULL(双链表表头为空,说明后面没有结点则直接将结点给表头的后继结点即可)
{
thiz->first = node; //将node结点地址赋值给头结点后继指针(这边的node结点地址为上一个条件判断的中创建的结点地址[若分两句应该会清楚很多])
return DLIST_RET_OK; //返回双链表创建成功
}
cursor = dlist_get_node(thiz, index, 1); //获取想要插入结点的地址,此时游标地址就为想要插入的地址
if(index < dlist_length(thiz)) //若想要插入的结点的位置在双链表的长度里面,则
{
if(thiz->first == cursor) //判断双链表表头指针与游标指针相等不相等(头指针需要的特殊处理)
{
thiz->first = node; //相等,则将node结点地址赋值给头结点的后继指针
}
else //不相等
{
cursor->prev->next = node; //此时游标即为要插入的结点位置,故需要先找到前面结点,再把创建的结点连接在前面结点的后继指针上
node->prev = cursor->prev; //再给新插入的结点的前驱指针赋值,指向前面这个结点
} //以上完成了结点插入的操作,但是新插入的结点后继指针还未指向。(还有后面结点的连接)
node->next = cursor; //这边就是将新插入的结点的后继指针指向指向原先这个结点(现在便为后面结点)的位置
cursor->prev = node; //将后面结点的前继指针指向新结点
}
else //若想要插入结点位置超出双链表的长度了
{
cursor->next = node; //因为能够获取得到对应的结点,故实际上是新结点插在双链表的最后一个结点位置
node->prev = cursor; //即进行最后结点的前驱和后继指针的插入。
}
return DLIST_RET_OK; //返回成功
}
/* 双链表的追加 */
DListRet dlist_prepend(DList* thiz, void* data)
{
return dlist_insert(thiz, 0, data);
}
/* 双链表的附加 */
DListRet dlist_append(DList* thiz, void* data)
{
return dlist_insert(thiz, -1, data);
}
/* 双链表的删除 参数:为双链表表头指针 ,index 为想要删除的双链表下标位置 */
DListRet dlist_delete(DList* thiz, size_t index)
{
DListNode* cursor = dlist_get_node(thiz, index, 0); //获取对应下标位置并赋值给cursor(游标即为要删除的结点)
if(cursor != NULL) //若游标不为空,即想要删除的结点存在
{
if(cursor == thiz->first) //若游标与表头地址相同
{
thiz->first = cursor->next; //则表头指向要删除结点的后继结点
}
if(cursor->next != NULL) //若游标的后继指针不为空
{
cursor->next->prev = cursor->prev; //
}
if(cursor->prev != NULL)
{
cursor->prev->next = cursor->next;
}
dlist_node_destroy(cursor);
}
return DLIST_RET_OK;
}
DListRet dlist_get_by_index(DList* thiz, size_t index, void** data)
{
DListNode* cursor = dlist_get_node(thiz, index, 0);
if(cursor != NULL)
{
*data = cursor->data;
}
return cursor != NULL ? DLIST_RET_OK : DLIST_RET_FAIL;
}
DListRet dlist_set_by_index(DList* thiz, size_t index, void* data)
{
DListNode* cursor = dlist_get_node(thiz, index, 0);
if(cursor != NULL)
{
cursor->data = data;
}
return cursor != NULL ? DLIST_RET_OK : DLIST_RET_FAIL;
}
void dlist_destroy(DList* thiz)
{
DListNode* iter = thiz->first;
DListNode* next = NULL;
while(iter != NULL)
{
next = iter->next;
dlist_node_destroy(iter);
iter = next;
}
thiz->first = NULL;
free(thiz);
return;
}
static DListRet print_int(void* data)
{
printf("%d ", (int)data);
return DLIST_RET_OK;
}
DListRet dlist_print(DList* thiz, DListDataPrintFunc print)
{
DListRet ret = DLIST_RET_OK;
DListNode* iter = thiz->first;
while(iter != NULL)
{
print(iter->data);
iter = iter->next;
}
return ret;
}
int main(int argc, char* argv[])
{
int i = 0;
int n = 100;
DList* dlist = dlist_create();
for(i = 0; i < n; i++)
{
assert(dlist_append(dlist, (void*)i) == DLIST_RET_OK);
}
for(i = 0; i < n; i++)
{
assert(dlist_prepend(dlist, (void*)i) == DLIST_RET_OK);
}
dlist_print(dlist, print_int);
dlist_destroy(dlist);
return 0;
}
多文件(封装)(书中示例)版
dlist.c
/*
* File: dlist.c
* Author: Li XianJing <xianjimli@hotmail.com>
* Brief: double list implementation.
*
* Copyright (c) Li XianJing
*
* Licensed under the Academic Free License version 2.1
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* History:
* ================================================================
* 2008-11-09 Li XianJing <xianjimli@hotmail.com> created
*
*/
#include <stdlib.h>
#include "dlist.h"
typedef struct _DListNode
{
struct _DListNode* prev;
struct _DListNode* next;
void* data;
}DListNode;
struct _DList
{
DListNode* first;
};
/*创建双链表结点*/
static DListNode* dlist_node_create(void* data)
{
DListNode* node = malloc(sizeof(DListNode));
if(node != NULL)
{
node->prev = NULL;
node->next = NULL;
node->data = data;
}
return node;
}
static void dlist_node_destroy(DListNode* node)
{
if(node != NULL)
{
node->next = NULL;
node->prev = NULL;
free(node);
}
return;
}
DList* dlist_create(void)
{
DList* thiz = malloc(sizeof(DList));
if(thiz != NULL)
{
thiz->first = NULL;
}
return thiz;
}
//dlist_get_node(dlist, -1, 1)
static DListNode* dlist_get_node(DList* thiz, size_t index, int fail_return_last)
{
DListNode* iter = thiz->first; //将结点指向的位置给iter()
while(iter != NULL && iter->next != NULL && index > 0) //若结点指向非空 且结点还存在下一个结点 且下标是大于0的话,不停的向下移动
{
iter = iter->next; //
index--;
}
if(!fail_return_last)
{
iter = index > 0 ? NULL : iter;
}
return iter;
}
//DListRet dlist_insert(dlist, -1, (void*)i);
DListRet dlist_insert(DList* thiz, size_t index, void* data)
{
DListNode* node = NULL; //定义双链表结点结构类型的指针,并指向NULL
DListNode* cursor = NULL; //定义双链表结点结构类型的指针,并指向NULL
if((node = dlist_node_create(data)) == NULL) //创建双链表结点空间大小并将首地址给node,若指向空则返回内存溢出错误。(内存不足)
{
return DLIST_RET_OOM;
}
if(thiz->first == NULL) //若头结点指向空(下面没有结点),则头结点指向node(上一条if判断语句已经指向双链表结点大小的空间[分成两句看起来就会清晰])
{
thiz->first = node;
return DLIST_RET_OK;
}
cursor = dlist_get_node(thiz, index, 1); //cursor作为游标,先获取当前链表下标的值。这边应该就是首结点的位置
if(index < dlist_length(thiz)) //length获取双链表的长度(从头指针开始遍历)
{
if(thiz->first == cursor) //若游标为头结点的话,则把头结点指向node(新结点)
{
thiz->first = node;
}
else //不是头结点的话,则
{
cursor->prev->next = node; //头插法???
node->prev = cursor->prev;
}
node->next = cursor;
cursor->prev = node;
}
else
{
cursor->next = node;
node->prev = cursor;
}
return DLIST_RET_OK;
}
DListRet dlist_prepend(DList* thiz, void* data)
{
return dlist_insert(thiz, 0, data);
}
DListRet dlist_append(DList* thiz, void* data)
{
return dlist_insert(thiz, -1, data);
}
DListRet dlist_delete(DList* thiz, size_t index)
{
DListNode* cursor = dlist_get_node(thiz, index, 0);
if(cursor != NULL)
{
if(cursor == thiz->first)
{
thiz->first = cursor->next;
}
if(cursor->next != NULL)
{
cursor->next->prev = cursor->prev;
}
if(cursor->prev != NULL)
{
cursor->prev->next = cursor->next;
}
dlist_node_destroy(cursor);
}
return DLIST_RET_OK;
}
DListRet dlist_get_by_index(DList* thiz, size_t index, void** data)
{
DListNode* cursor = dlist_get_node(thiz, index, 0);
if(cursor != NULL)
{
*data = cursor->data;
}
return cursor != NULL ? DLIST_RET_OK : DLIST_RET_FAIL;
}
DListRet dlist_set_by_index(DList* thiz, size_t index, void* data)
{
DListNode* cursor = dlist_get_node(thiz, index, 0);
if(cursor != NULL)
{
cursor->data = data;
}
return cursor != NULL ? DLIST_RET_OK : DLIST_RET_FAIL;
}
size_t dlist_length(DList* thiz) //获取双链表的长度,从头结点不停向下遍历
{
size_t length = 0;
DListNode* iter = thiz->first;
while(iter != NULL)
{
length++;
iter = iter->next;
}
return length;
}
DListRet dlist_print(DList* thiz, DListDataPrintFunc print)
{
DListRet ret = DLIST_RET_OK;
DListNode* iter = thiz->first;
while(iter != NULL)
{
print(iter->data);
iter = iter->next;
}
return ret;
}
void dlist_destroy(DList* thiz)
{
DListNode* iter = thiz->first;
DListNode* next = NULL;
while(iter != NULL)
{
next = iter->next;
dlist_node_destroy(iter);
iter = next;
}
thiz->first = NULL;
free(thiz);
return;
}
dlist.h
/*
* File: dlist.h
* Author: Li XianJing <xianjimli@hotmail.com>
* Brief: double list header file.
*
* Copyright (c) Li XianJing
*
* Licensed under the Academic Free License version 2.1
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* History:
* ================================================================
* 2008-11-09 Li XianJing <xianjimli@hotmail.com> created
*
*/
#ifndef DLIST_H
#define DLIST_H
/*
* C++中允许同名函数存在,故编译器会对函数名重新编码。
* 当C++代码包含C语言头文件时,重新编码的函数没与C语言原函数名不一致,会造成找不到函数的情况。
* 故:为了C语言实现的函数可在C++中调用,需要在头文件中加上:
#ifdef __cplusplus
extern "C" {
#endif
...(具体内容)
#ifdef __cplusplus
}
#endif
*/
#ifdef __cplusplus
extern "C" {
#endif/*__cplusplus*/
/*
* enum 定义枚举类型(在实际编程中,数据取值有限,故最好给每个值取个名字方便使用[以下用来表示双链表创建的不同的状态])
* 默认从0开始,往后递增。也可单独赋值,若后值又不赋值,则后值都是前值递增得来
* 需注意:
* 1.这些标识符作用范围是全局(严格来说main函数内部),不能再定义同名变量
2.这些标识符都是常量,不可对它们再赋值,只可赋值给其他变量
3.与宏定义(预处理阶段替换)类似,枚举在编译阶段替换。故不占用数据区的内存,而是直接编译到命令中,放到代码区(无法用&取得地址)。
*/
typedef enum _DListRet //typedef取别名,故出现DListRet 相当于 enum _DListRet
{
DLIST_RET_OK, //成功
DLIST_RET_OOM, //内存溢出(OutOfMemory)
DLIST_RET_STOP, //停止
DLIST_RET_PARAMS, //
DLIST_RET_FAIL //失败
}DListRet;
struct _DList; //声明结构体_DList
typedef struct _DList DList; //取别名:DList 相当于 struct _DList
typedef DListRet (*DListDataPrintFunc)(void* data); //取别名(函数指针): DListDataPrintFunc 相当于(*DListRet)(void* data)
DList* dlist_create(void);
DListRet dlist_insert(DList* thiz, size_t index, void* data);
DListRet dlist_prepend(DList* thiz, void* data);
DListRet dlist_append(DList* thiz, void* data);
DListRet dlist_delete(DList* thiz, size_t index);
DListRet dlist_get_by_index(DList* thiz, size_t index, void** data);
DListRet dlist_set_by_index(DList* thiz, size_t index, void* data);
size_t dlist_length(DList* thiz);
DListRet dlist_print(DList* thiz, DListDataPrintFunc print);
void dlist_destroy(DList* thiz);
#ifdef __cplusplus
}
#endif/*__cplusplus*/
#endif/*DLIST*/
main.c
/*
* File: main.c
* Author: Li XianJing <xianjimli@hotmail.com>
* Brief: demo how to print dlist.
*
* Copyright (c) Li XianJing
*
* Licensed under the Academic Free License version 2.1
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* History:
* ================================================================
* 2008-11-09 Li XianJing <xianjimli@hotmail.com> created
*
*/
#include <stdio.h>
#include <assert.h> //assert宏所需头文件
#include "dlist.h"
static DListRet print_int(void* data)
{
printf("%d ", (int)data);
return DLIST_RET_OK;
}
int main(int argc, char* argv[])
{
int i = 0;
int n = 100;
DList* dlist = dlist_create(); //创建头结点(只有一个指针大小空间,无数据)
for(i = 0; i < n; i++)
{
assert(dlist_append(dlist, (void*)i) == DLIST_RET_OK); //assert宏原型:void assert(int expression); 作用:若条件返回错误,则终止程序
//append追加函数:调用insert(thiz, -1, data); 参数-1为下标,这边表示头结点的下表,之后根据插入函数插入不断赋值
}
for(i = 0; i < n; i++)
{
assert(dlist_prepend(dlist, (void*)i) == DLIST_RET_OK);
}
dlist_print(dlist, print_int);
dlist_destroy(dlist);
return 0;
}