链表的使用里,双向循环链表的使用是最广的。双向链表的每个数据结点中都有两个指针,分别指向直接后继和直接前驱,所以从双向链表的任意一个结点开始,都可以很方便的访问它的前驱结点和后继结点。
这是我练习的一个双向链表的代码,用VS2017环境编译,因为本人偏爱日志调试的方法,所以代码里日志打印内容占了一定的行数。
在写之前参考了一些资料,尤其是这篇博客(http://blog.csdn.net/fisherwan/article/details/25796625),写得通俗易懂。感谢作者fisherwan。
double way circular List.c 代码
#include "stdafx.h"
#define logpath "E:\\VS2017test\\log.txt"//将日志打印的目录地址用宏表示
int main()
{
int len = 0;
char logbody[MAX_LOG];
//调试开始的初始日志打印,“__LINE__”可以用来获取行号
snprintf(logbody, MAX_LOG, "line:[%d], begin.", __LINE__);
write_log_file((char*)logpath, logbody, strlen(logbody));
//创建链表,并给链表赋初值
PNODE pHead= Create_List();
if (pHead)
{
snprintf(logbody, MAX_LOG, "line:[%d], creat list succ.", __LINE__);
write_log_file((char*)logpath, logbody, strlen(logbody));
}
//获取刚刚创建的链表长度以供打印
len = Length_List( pHead);
snprintf(logbody, MAX_LOG, "line:[%d], lenth of list:%d.", __LINE__,len);
write_log_file((char*)logpath, logbody, strlen(logbody));
//准备在链表的第2位插入数值为12的内容
int insert_pos = 2;
int insert_val = 12;
//将内容插入到链表的指定位置
if (1 == Insert_List(pHead, insert_pos, insert_val))
{
snprintf(logbody, MAX_LOG, "line:[%d], insert pos:%d,insert value:%d", __LINE__, insert_pos, insert_val);
write_log_file((char*)logpath, logbody, strlen(logbody));
}
//获取插入内容后的的链表长度以供打印
len = Length_List(pHead);
snprintf(logbody, MAX_LOG, "line:[%d], lenth of list:%d.", __LINE__,len);
write_log_file((char*)logpath, logbody, strlen(logbody));
//准备把链表的第3位删除。delete_val赋任意初值,在删除过程中赋真实值以供打印。
int delete_pos = 3;
int delete_val = 0;
if (1 == Delete_List(pHead, delete_pos, delete_val))
{
snprintf(logbody, MAX_LOG, "line:[%d],delete list succ. delete pos:%d", __LINE__, delete_pos);
write_log_file((char*)logpath, logbody, strlen(logbody));
}
//获取删除内容后的的链表长度以供打印
len = Length_List(pHead);
snprintf(logbody, MAX_LOG, "line:[%d], lenth of list:%d.", __LINE__,len);
write_log_file((char*)logpath, logbody, strlen(logbody));
//对链表进行排序操作
if (1 == Sort_List(pHead))
{
snprintf(logbody, MAX_LOG, "line:[%d],sort list succ.", __LINE__);
write_log_file((char*)logpath, logbody, strlen(logbody));
}
//获取排序后的的链表长度以供打印
len = Length_List(pHead);
snprintf(logbody, MAX_LOG, "line:[%d], lenth of list:%d.", __LINE__,len);
write_log_file((char*)logpath, logbody, strlen(logbody));
//调试结束的初始日志打印
snprintf(logbody, MAX_LOG, "line:[%d], end.", __LINE__);
write_log_file((char*)logpath, logbody, strlen(logbody));
return 0;
}
//获取当前时刻,用于日志打印
void get_local_time(char* buffer)
{
struct tm rawtime;
time_t time_seconds = time(0);
localtime_s(&rawtime, &time_seconds);
snprintf(buffer, 100, "%04d-%02d-%02d %02d:%02d:%02d",
(rawtime.tm_year + 1900), rawtime.tm_mon, rawtime.tm_mday,
rawtime.tm_hour, rawtime.tm_min, rawtime.tm_sec);
}
//调用的日志打印函数,日志格式为“时间 行号 内容”。
int write_log_file(char *filename, char* buffer, size_t buf_size)
{
FILE *fp_log;
errno_t err;
char logcontent[MAX_LOG];
char now[MAX_UNIT];
memset(now, 0, sizeof(now));
get_local_time(now);//获取当前时刻
//传进来的日志内容buffer里已经有了行号和内容,这里在前面添加上时刻
snprintf(logcontent, MAX_LOG, "time:%s %s\n", now, buffer);
//调用文件句柄打开日志文件
err = fopen_s(&fp_log, (char*)filename, "at+");
if (0 != err)
{
printf("Open log file failed ,errno is : [%d]", err);
return -1;
}
if (fp_log != NULL)
{
//将日志内容写入文件,关闭句柄,将句柄置NULL
err = fwrite(logcontent, strlen(logcontent) + 1, 1, fp_log);
fclose(fp_log);
fp_log = NULL;
}
else
return -1;//这是错误情况不打印错误日志,因为这本身就是日志函数
return 0;
}
PNODE Create_List(void)
{
int len=3; //即将创建的链表的长度
int i;
int val[3] = {30,40,50};//创建链表的三个结点的值
int valtmp=0;
char logbody[MAX_LOG];
PNODE pHead = (PNODE)malloc(sizeof(NODE));//分配一个头节点
if (NULL == pHead)
{
snprintf(logbody, MAX_LOG, "line:[%d],Memory allocation failure.", __LINE__);
write_log_file((char*)logpath, logbody, strlen(logbody));
exit(-1);
}
else
{
//一开始建立链表的时候,表头向前和向后都指向自己
PNODE pTail = pHead;
pHead->pNext = pHead;
pHead->pPre = pHead;
pHead->data = 0;
//依次插入三个初始的链表内容值
for (i = 0; i<len; i++)
{
PNODE p = (PNODE)malloc(sizeof(NODE));//分配节点内存
if (NULL == p)
{
snprintf(logbody, MAX_LOG, "line:[%d],Memory allocation failure.", __LINE__);
write_log_file((char*)logpath, logbody, strlen(logbody));
exit(-1);
}
else
{
valtmp=val[i];
p->data = valtmp;
//打印此刻插入链表的位数序号和内容
snprintf(logbody, MAX_LOG, "line:[%d], insert list seq %d, date value is %d.", __LINE__,i, p->data);
write_log_file((char*)logpath, logbody, strlen(logbody));
//插入时注意维持双向链表的前向和后向的连通
p->pPre = pTail;
p->pNext = pHead;
pTail->pNext = p;
pHead->pPre = p;
pTail = p;
}
}
}
return pHead;
}
//链表的第pos有效元素前面插入元素val
int Insert_List(PNODE pHead, int pos, int val)
{
int i = 0;
PNODE p = pHead;
char logbody[MAX_LOG];
while ((NULL != p) && (i<pos - 1)) // 从头节点开始往后寻找,一直找到第pos个元素前面一个元素的位置
{
p = p->pNext;
i++;
}
if (p == NULL || i > pos - 1) //异常保护,如果链表为空或者上一步搜索出来的i值不合理则报错
{
snprintf(logbody, MAX_LOG, "line:[%d], insert[pos:%d,val:%d] error.", __LINE__,pos,val);
write_log_file((char*)logpath, logbody, strlen(logbody));
return 0;
}
//p指针指向链表第pos个有效节点的前驱,即指向第pos-1节点。新节点建立要申请NODE结构体大小的内存
PNODE q = (PNODE)malloc(sizeof(NODE));
//插入时注意维持双向链表的前向和后向的连通
q->data = val;
q->pNext = p->pNext;
q->pPre = p;
p->pNext = q;
q->pNext->pPre = q;
return 1;
}
//把第pos位置的链表内容删除
int Delete_List(PNODE pHead, int pos, int val)
{
int i = 0;
PNODE p = pHead;
char logbody[MAX_LOG];
while ((NULL != p) && (i<pos - 1))// 从头节点开始往后寻找,一直找到第pos个元素前面一个元素的位置
{
p = p->pNext;
i++;
}
if (p == NULL || i > pos - 1) //异常保护,如果链表为空或者上一步搜索出来的i值不合理则报错
{
snprintf(logbody, MAX_LOG, "line:[%d], delete[pos:%d] error.", __LINE__, pos);
write_log_file((char*)logpath, logbody, strlen(logbody));
return 0;
}
//删除节点
PNODE q = p->pNext; //q指向待删除的节点;
val = q->data;
p->pNext = q->pNext; //修改链表指针指向;
q->pNext->pPre = p;
free(q); //释放q所指向节点的内存;
q = NULL;//重要,否则会出现野指针;
return 1;
}
//链表有效元素的个数
int Length_List(PNODE pHead)
{
char logbody[MAX_LOG];
int len = 0; //定义变量要记得初始化;
PNODE p = pHead->pNext;
while (pHead != p)
{
len++;
//打印此时链表的各个位置分别是什么内容
snprintf(logbody, MAX_LOG, "line:[%d], the value of listnumber %d is %d.", __LINE__, len-1,p->data);
write_log_file((char*)logpath, logbody, strlen(logbody));
p = p->pNext;
}
//打印此时的链表长度
snprintf(logbody, MAX_LOG, "line:[%d],current list length is %d.", __LINE__, len);
write_log_file((char*)logpath, logbody, strlen(logbody));
return len;
}
//对链表中的元素进行排序
int Sort_List(PNODE pHead)
{
int i, j;
int temp;
int len = Length_List(pHead);
PNODE p, q;//指向链表第一个有效元素
for (i = 0, p = pHead->pNext; i<len - 1; i++, p = p->pNext)
{
for (j = i + 1, q = p->pNext; j<len; j++, q = q->pNext)
{
//交换数据,冒泡
if (p->data>q->data)
{
temp = p->data;
p->data = q->data;
q->data = temp;
}
}
}
return 1;
}
双向链表的头文件stdafx.h
#pragma once
#include "targetver.h"
#include <stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<time.h>
#define MAX_LOG 256
#define MAX_NAME 128
#define MAX_UNIT 256
typedef struct Node
{
int data;//数据域,用来存放数据域;
struct Node *pNext;//定义一个结构体指针,指向下一次个与当前节点数据类型相同的节点
struct Node *pPre;//定义一个结构体指针,指向上一次个与当前节点数据类型相同的节点
}NODE, *PNODE; //NODE等价于 struct Node; PNODE等价于struct Node *; 此处用大写是为了与变量区分,可以让人容易看出是个数据类型
PNODE Create_List(void);
int Insert_List(PNODE pHead, int pos, int val);
int Delete_List(PNODE pHead, int pos, int val);
int Length_List(PNODE pHead);
int Sort_List(PNODE pHead);
void get_local_time(char* buffer);
int write_log_file(char*, char*, size_t);
双向链表的运行日志:
time:2018-01-02 22:16:32 line:[13], begin.
time:2018-01-02 22:16:32 line:[156], insert list seq 0, date value is 30.
time:2018-01-02 22:16:32 line:[156], insert list seq 1, date value is 40.
time:2018-01-02 22:16:32 line:[156], insert list seq 2, date value is 50.
time:2018-01-02 22:16:32 line:[20], creat list succ.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 0 is 30.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 1 is 40.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 2 is 50.
time:2018-01-02 22:16:32 line:[245],current list length is 3.
time:2018-01-02 22:16:32 line:[26], lenth of list:3.
time:2018-01-02 22:16:32 line:[36], insert pos:2,insert value:12
time:2018-01-02 22:16:32 line:[240], the value of listnumber 0 is 30.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 1 is 12.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 2 is 40.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 3 is 50.
time:2018-01-02 22:16:32 line:[245],current list length is 4.
time:2018-01-02 22:16:32 line:[42], lenth of list:4.
time:2018-01-02 22:16:32 line:[50],delete list succ. delete pos:3
time:2018-01-02 22:16:32 line:[240], the value of listnumber 0 is 30.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 1 is 12.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 2 is 50.
time:2018-01-02 22:16:32 line:[245],current list length is 3.
time:2018-01-02 22:16:32 line:[56], lenth of list:3.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 0 is 30.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 1 is 12.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 2 is 50.
time:2018-01-02 22:16:32 line:[245],current list length is 3.
time:2018-01-02 22:16:32 line:[62],sort list succ.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 0 is 12.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 1 is 30.
time:2018-01-02 22:16:32 line:[240], the value of listnumber 2 is 50.
time:2018-01-02 22:16:32 line:[245],current list length is 3.
time:2018-01-02 22:16:32 line:[68], lenth of list:3.
time:2018-01-02 22:16:32 line:[72], end.
VS单步跟踪时观察链表如图: