双向链表与两个指针
双向链表的每个节点包含两个指针:一个指向前驱节点(prev
),另一个指向后继节点(next
)。这种结构支持双向遍历,但需注意指针维护,避免内存泄漏或断裂。
Makefile 时间戳机制
Makefile 通过比较目标文件和依赖文件的时间戳决定是否重新编译。若 obj.c
的时间戳晚于对应的 .o
文件,则触发重新编译;否则跳过。
关键规则示例
main: main.o utils.o
gcc -o main main.o utils.o
main.o: main.c utils.h
gcc -c main.c
utils.o: utils.c utils.h
gcc -c utils.c
Makefile 语法规范
文件名与对齐:第一行通常声明目标,第二行用 Tab
缩进规则。
目标与依赖:冒号前为目标文件,冒号后为依赖文件。
变量定义:支持 =
(赋值)和 +=
(追加)。
清理操作:clean
目标用于删除临时文件。
变量与路径示例
CC = gcc
INC = ./include
SRC = main.c utils.c
OBJ = app
$(SRC): $(OBJ)
$(CC) $^ -o $@ -I$(INC)
clean:
rm $(OBJ)
-I
指定头文件目录,-L
指定库文件目录。$@
表示当前目标文件名,$^表示依赖文件。
声明链表的数据,结点,表头。
typedef struct studet
{
int id;
char name[32];
int score;
}DataType;
typedef struct node
{
DataType data;
struct node *ppre;
struct node *pnext;
}Node;
typedef struct doulink
{
Node *phead;
int clen;
}PdLink;
typedef enum {FORWARD, BACKWARD} DIR;
头插
核心逻辑:链表为空,则直接在表头后边插入;链表不为空,需要将待插入结点的next指向表头指向的结点,表头指向结点的prv指向待插入结点,表头指向待插入结点。
void InsertHeadDoulink(PdLink *pdlink,DataType data)
{
Node *pnode = malloc(sizeof(Node));
if(NULL == pnode)
{
printf("malloc pnode error\n");
}
pnode->data = data;
pnode->pnext = NULL;
pnode->ppre = NULL;
if(Isempty(pdlink))
{
pdlink->phead = pnode;
}
else
{
pnode->pnext = pdlink->phead;
pdlink->phead->ppre = pnode;
pdlink->phead = pnode;
}
pdlink->clen++;
双向链表遍历
既可以向后遍历,又可以向前遍历。
void ShowDouLink(PdLink *pdlink,DIR dir)
{
if(Isempty(pdlink))
{
printf("empty\n");
return ;
}
Node *temp = pdlink->phead;
if(dir == FORWARD)
{
while(temp)
{
printf("%d %s %d\n",temp->data.id, temp->data.name, temp->data.score);
temp = temp->pnext;
}
}
else if(dir == BACKWARD)
{
while(temp->pnext)
{
temp = temp->pnext;
}
while(temp)
{
printf("%d %s %d\n",temp->data.id, temp->data.name, temp->data.score);
temp = temp->ppre;
}
}
双向链表尾插
核心逻辑:链表为空,则直接在表头后边插入;链表不为空,需要先遍历,找到最后一个,待插结点的prv指向最后一个结点,最后一个结点的next指向待插结点。
void InsertTailDouLink(PdLink * pdlink,DataType data)
{
Node *pnode = malloc(sizeof(Node));
if(NULL == pnode)
{
printf("malloc Tail error\n");
return ;
}
pnode->data = data;
pnode->pnext = NULL;
pnode->ppre = NULL;
if(Isempty(pdlink))
{
pdlink->phead = pnode;
}
else
{
Node *temp = pdlink->phead;
while(temp->pnext)
{
temp = temp->pnext;
}
temp->pnext = pnode;
pnode->ppre = temp;
}
pdlink->clen++;
}
头删
核心逻辑:链表中的结点为1,直接释放空间,并且将phead置空,链表当前长度 pdlink->clen
减 1;结点为多个,将临时指针 temp
指向链表的当前头节点 pdlink->phead,
将链表的头节点指针 pdlink->phead
更新为原头节点的下一个节点 temp->pnext,
释放原头节点 temp
占用的内存空间,将新头节点的前驱指针 ppre
设为 NULL
,表示它现在是链表的第一个节点,链表当前长度 pdlink->clen
减 1。
void DeleteHeadDouLink(PdLink *pdlink)
{
if(Isempty(pdlink))
{
printf("empty\n");
return ;
}
if(1 == pdlink->clen)
{
free(pdlink->phead);
pdlink->phead = NULL;
pdlink->clen--;
}
else
{
Node *temp = pdlink->phead;
pdlink->phead = temp->pnext;
free(temp);
pdlink->phead->ppre = NULL;
pdlink->clen--;
}
}
尾删
核心逻辑:链表中的结点为1,直接释放空间,并且将phead置空,链表当前长度 pdlink->clen
减 1;结点为多个,使用while
循环遍历链表,直到temp->pnext
为NULL
,此时temp
指向链表最后一个节点。将倒数第二个节点的pnext
指针设为NULL
,断开与尾节点的连接。释放尾节点temp
占用的内存。将链表长度计数器pdlink->clen
减1。
void DeleteTailDouLink(PdLink *pdlink)
{
if(Isempty(pdlink))
{
printf("empty\n");
return ;
}
Node *temp = pdlink->phead;
if(1 == pdlink->clen)
{
free(temp);
pdlink->phead = NULL;
pdlink->clen--;
}
else
{
while(temp->pnext)
{
temp = temp->pnext;
}
temp->ppre->pnext = NULL;
free(temp);
pdlink->clen--;
}
}
查找
核心逻辑:优先检查链表状态,避免无效操作。同时验证节点存在性ptemp
和名称匹配性strcmp
,确保安全遍历。直接利用指针状态作为查找结果的标志,简化逻辑判断。
Node *FindNmaeofDouLink(PdLink *pdlink,char *name)
{
if(Isempty(pdlink))
{
printf("empty\n");
return NULL;
}
Node *ptemp = pdlink->phead;
while(ptemp && strcmp(ptemp->data.name,name))
{
ptemp = ptemp->pnext;
}
if(ptemp)
{
return ptemp;
}
else
{
return NULL;
}
}
修改
核心逻辑:检查传入的双向链表 pdlink
是否为空。如果链表为空,打印 "empty" 并直接返回。如果链表不为空,调用 FindNmaeofDouLink
函数在链表中查找与 name
匹配的节点。该函数返回指向找到的节点的指针 temp
。如果 temp
不为空(即找到匹配节点),将节点的 data.score
字段更新为 newsorce
值。
void ChangeSorce(PdLink *pdlink,char *name,int newsorce)
{
if(Isempty(pdlink))
{
printf("empty\n");
return ;
}
Node * temp = FindNmaeofDouLink(pdlink,name);
if(temp)
{
temp->data.score = newsorce;
}
else
{
printf("not found\n");
}
}
删除链表
核心逻辑:检查双向链表的头节点 phead
是否为空。如果头节点为空,说明链表没有存储任何数据节点,直接释放链表结构 pdlink
的内存。如果头节点不为空,说明链表中存在数据节点。通过循环检查当前链表长度 clen
,只要长度不为零,就调用 DeleteTailDouLink
函数从链表尾部删除节点。这个过程会持续直到所有数据节点都被删除。最后,释放链表结构 pdlink
的内存。
void DestroyDouLink(PdLink *pdlink)
{
if(pdlink->phead == NULL)
{
free(pdlink);
}
else
{
while(pdlink->clen)
{
DeleteTailDouLink(pdlink);
}
free(pdlink);
}
}