一、思想
二、代码
Queue目录下
bin-子目录、include-子目录、obj-子目录、src-子目录、Makefile文件
Makefile
# 定义了一个名为All的目标
All:
# 在./src/目录下执行make命令,构建该目录下的目标
make -C ./src/
# 在./obj/目录下执行make命令,构建该目录下的目标
make -C ./obj/
# 声明一个伪目标,伪目标是一个始终会被执行的目标,即使存在一个同名的文件
.PHONY: CLEAN
# 定义了一个名为CLEAN的目标
CLEAN:
# 删除obj目录下的所有.o文件(通常是编译产生的目标文件)
rm obj/*.o
# 删除bin目录下的所有文件(通常是链接产生的可执行文件)
rm bin/*
解释:
make -C ./src/:这条命令表示在./src/目录下执行make命令。-C选项用于指定make命令应该在哪个目录下执行。
make -C ./obj/:与上面的命令类似,这条命令表示在./obj/目录下执行make命令。
.PHONY: CLEAN:这行代码声明了一个伪目标CLEAN。伪目标是一个不会被文件名冲突的目标。这意味着,即使存在一个名为CLEAN的文件,执行make CLEAN时,make工具仍然会执行CLEAN目标下的命令,而不会误认为CLEAN是一个文件名。
rm obj/*.o:这条命令用于删除obj目录下的所有.o文件。
rm bin/*:这条命令用于删除bin目录下的所有文件。
需要注意的是,这个Makefile依赖于./src/和./obj/目录中存在相应的Makefile或Makefile.in文件,因为这里使用了make -C命令来在子目录中执行make。如果这些子目录中不存在Makefile,那么这个Makefile将不会按预期工作。
bin-子目录
可执行文件App
include-子目录
myhead.h-头文件
// 检查是否已经包含了此头文件,防止重复包含
#ifndef _MYHEAD_H
// 如果没有包含,则定义宏_MYHEAD_H
#define _MYHEAD_H
// 引入相关的头文件
#include<stdio.h> // 标准输入输出库
#include<stdlib.h> // 标准库,包含内存分配、程序终止等函数
#include<string.h> // 字符串操作库
// 定义数据类型为整形
typedef int data_type;
// 定义双向链表节点的结构体
typedef struct doublelinknode
{
struct doublelinknode *Pre; // 指向前一个节点的指针
data_type Data; // 节点存储的数据
struct doublelinknode *Next; // 指向后一个节点的指针
} DBNode;
// 定义返回值的枚举类型
typedef enum ret{
DBNode_NULL = -4, // 节点为空
POS_ERR, // 位置错误
DBNode_FULL, // 节点已满
DBNode_EMPTY, // 节点为空(可能与其他值重复,建议重命名)
OK, // 操作成功
} ret_e;
// 函数声明
// 打印菜单
// 函数无参数,返回选择的选项(具体类型未给出,但根据上下文推测可能是int)
int Menu(void);
// 创建一个新的双向链表节点
// 函数无参数,返回新节点的指针,如果失败则返回NULL
DBNode *CreatNode(void);
// 插入元素到双向链表中
// 参数:链表头节点,插入位置,插入的值
// 返回值:成功返回OK,失败返回错误信息(ret_e类型)
int InsertItem(DBNode *pHead, int pos, data_type item);
// 显示双向链表中的所有元素
// 参数:链表头节点
// 返回值:成功返回OK,失败返回失败原因(ret_e类型)
int ShowLink(DBNode *pHead);
// 删除双向链表中的节点
// 参数:链表头节点,删除的位置,存储删除值的指针(用于返回删除的值)
// 返回值:成功返回OK,失败返回失败原因(ret_e类型)
int Delete(DBNode *pHead, int pos, data_type *item);
#endif
注释和解释:
#ifndef _MYHEAD_H 和 #define _MYHEAD_H 是预处理指令,用于防止头文件被重复包含。
typedef 用于定义新的数据类型或别名。
struct doublelinknode 定义了一个双向链表节点的结构体,其中包含指向前一个节点和后一个节点的指针,以及存储的数据。
enum ret 定义了一个枚举类型,用于表示函数操作的返回值,如成功、失败等。
函数声明部分声明了将要实现的函数,包括函数名、参数和返回类型。
在 Delete 函数中,data_type *item 是一个指向 data_type 的指针,用于返回被删除节点的值。
obj-子目录
Makefile-文件
# 定义了一个名为ALL的目标
ALL:
# 使用gcc编译器将当前目录下的所有.o文件链接成一个可执行文件APP
# 并将其放置在上级目录的bin文件夹中
gcc *.o -o ../bin/APP
解释:
ALL::这是一个Makefile的目标标签。当你在命令行中输入make ALL时,Make工具会查找名为ALL的目标并执行其下的命令。
gcc *.o -o ../bin/APP:这是ALL目标下的命令。
gcc:这是GNU编译器集合(GNU Compiler Collectio
src-子目录
main.c
#include"../include/myhead.h" // 引入自定义的头文件,包含了双向链表的结构和相关函数的声明
int main(int argc, const char *argv[]) // 主函数入口
{
// 创建一个新的双向链表节点,初始化为NULL
DBNode *pNew = NULL;
// 定义选项变量,用于存储菜单选项
int opt = 0;
// 定义插入位置变量
int pos = 0;
// 定义插入元素变量
data_type item = 0;
// 定义变量用于接收函数返回值
int ret = 0;
// 循环打印菜单并处理用户输入
while(1)
{
// 调用Menu函数打印菜单并获取用户选项
opt = Menu();
// 如果用户选择退出(-1),则跳出循环
if(-1 == opt) break;
// 根据用户选项执行不同的操作
switch(opt)
{
case 1: // 插入元素
printf("请输入要插入元素的位置和值:");
scanf("%d %d",&pos,&item); // 读取用户输入的位置和值
ret = InsertItem(pNew,pos,item); // 调用插入函数
break;
case 2: // 删除元素
printf("请输入要删除的位置:");
scanf("%d",&pos); // 读取用户输入的删除位置
ret = Delete(pNew,pos,&item); // 调用删除函数,并获取删除的元素值
printf("删除的是%d\n",item); // 输出删除的元素值
break;
case 3: // 搜索元素(未实现)
break;
case 4: // 修改元素(未实现)
break;
case 5: // 显示链表
ret = ShowLink(pNew); // 调用显示链表函数
break;
case 6: // 保存链表(未实现)
break;
case 7: // 加载链表(未实现)
break;
case 8: // 销毁链表(未实现)
break;
case 9: // 创建一个新的双向链表节点
pNew = CreatNode(); // 调用创建节点函数
printf("pNew = %p\n",pNew); // 输出新节点的地址
break;
default: // 默认情况,用户输入了无效选项
puts("请输入有效选项!");
}
// 根据函数返回值打印错误信息
switch(ret)
{
case POS_ERR: // 位置错误
puts("POS_ERR");
break;
case DBNode_FULL: // 节点已满
puts("DBNode_FULL");
// 这里应该调用扩容的函数,但当前未实现(注释中提到)
break;
case DBNode_NULL: // 节点为空
puts("DBNode_NULL");
// 这里应该重新分配空间,但当前未实现(注释中提到)
break;
case DBNode_EMPTY: // 链表为空
puts("DBNode_EMPTY");
break;
// 其他返回值情况未列出,可以添加更多case处理
}
}
return 0; // 主函数返回0,表示程序正常退出
}
menu.c
#include"../include/myhead.h" // 引入自定义的头文件,包含了双向链表相关的结构定义和函数声明
// 打印菜单
// 函数返回值:获取的选项
int Menu(void)
{
int opt = 0; // 定义整型变量opt,用于存储用户选择的菜单项
// 打印菜单的各个选项
puts("\t\t 1-----插入元素"); // 打印插入元素选项
puts("\t\t 2-----删除元素"); // 打印删除元素选项
puts("\t\t 3-----查询元素"); // 打印查询元素选项
puts("\t\t 4-----修改元素"); // 打印修改元素选项
puts("\t\t 5-----显示元素"); // 打印显示元素选项
puts("\t\t 6-----保存"); // 打印保存选项
puts("\t\t 7-----导入"); // 打印导入选项
puts("\t\t 8-----销毁"); // 打印销毁选项
puts("\t\t 9-----创建双向链表"); // 打印创建双向链表选项
puts("\t\t-1-----退出"); // 打印退出选项
printf("请选择功能:"); // 提示用户选择功能
scanf("%d",&opt); // 读取用户输入的功能选项并存储到opt变量中
return opt; // 返回用户选择的功能选项
}
create.c
#include"../include/myhead.h" // 引入自定义的头文件,可能包含双向链表节点DBNode的定义
// 函数功能:创建一个新的双向链表节点
// 函数参数:无
// 函数返回值:成功返回新节点的指针,失败返回NULL
DBNode *CreatNode(void)
{
DBNode *pNew = NULL; // 定义一个指向DBNode类型的指针变量pNew,并初始化为NULL
// 分配内存空间给新节点
pNew = (DBNode *)malloc(sizeof(DBNode)); // 使用malloc函数为DBNode类型分配内存空间,并将返回的指针赋值给pNew
// 如果内存分配失败,返回NULL
if(!pNew) // 检查是否成功分配内存,如果pNew为NULL,则分配失败
return NULL; // 返回NULL,表示创建节点失败
// 将新节点的内存区域初始化为0
memset(pNew, 0, sizeof(DBNode)); // 使用memset函数将新分配的内存区域全部设置为0,这样可以确保新节点的所有字段都被初始化为默认值
// 返回新节点的指针
return pNew; // 返回指向新节点的指针,创建节点成功
}
insert.c
#include"../include/myhead.h" // 引入自定义的头文件,可能包含双向链表节点DBNode的定义和相关的宏定义
// 函数功能:在双向链表的指定位置插入元素
// 函数参数:pHead - 指向双向链表头节点的指针,pos - 插入位置,item - 要插入的值
// 函数返回值:成功返回OK,失败返回错误信息
int InsertItem(DBNode *pHead, int pos, data_type item)
{
DBNode *pNew = NULL; // 新节点的指针
DBNode *pTmp = pHead; // 临时指针,用于遍历链表
int i = 0; // 计数器,用于记录当前遍历到的位置
// 入参判断
// 如果链表为空(头节点为NULL),返回错误DBNode_NULL
if(!pHead) return DBNode_NULL;
// 如果插入位置小于0,返回错误POS_ERR
if(pos < 0) return POS_ERR;
// 创建新节点
pNew = CreatNode(); // 调用CreatNode函数创建新节点,注意这里应该是CreateNode而非CreatNode,除非CreatNode在其他地方有定义
// 如果创建新节点失败,返回错误信息(此处代码未体现,需要添加错误处理)
if (!pNew) return NODE_CREATE_ERR; // 假设定义了NODE_CREATE_ERR作为节点创建失败的错误码
// 为新节点赋值
pNew->Data = item; // 将传入的item值赋给新节点的Data字段
// 寻找插入位置
while(i < pos && pTmp != NULL) {
// 移动到下一个节点
pTmp = pTmp->Next; // 移动临时指针到下一个节点
i++; // 计数器递增
}
// 如果pTmp为空,说明pos超出了链表长度,返回错误POS_ERR
if(!pTmp) return POS_ERR;
// 插入新节点
// 修改新节点的前后指针
pNew->Next = pTmp->Next; // 新节点的Next指向原pTmp节点的下一个节点
pNew->Pre = pTmp; // 新节点的Pre指向原pTmp节点
// 如果原pTmp节点的下一个节点不为空,修改其Pre指针指向新节点
if(pTmp->Next != NULL) {
pTmp->Next->Pre = pNew; // 原pTmp节点的下一个节点的Pre指向新节点
}
// 修改原pTmp节点的Next指针指向新节点
pTmp->Next = pNew; // 完成插入操作
return OK; // 插入成功,返回OK
}
delete.c
#include"../include/myhead.h" // 引入自定义的头文件,可能包含双向链表节点DBNode的定义和相关的宏定义
// 函数功能:删除指定位置的节点
// 函数参数:pHead - 指向双向链表头节点的指针,pos - 删除的位置,item - 指向将要存储删除节点数据的变量的指针
// 函数返回值:成功返回OK(0),失败返回失败原因(非0值)
int Delete(DBNode *pHead, int pos, data_type *item) {
int i = 0; // 计数器,用于遍历链表直到找到要删除的节点位置
DBNode *pDel = NULL; // 指向要删除的节点的指针
DBNode *pDel_Pre = pHead; // 指向要删除节点的前一个节点的指针
// 如果链表为空(头节点为NULL),返回错误DBNode_NULL
if (!pHead) return DBNode_NULL;
// 如果链表只有一个头节点(没有实际的数据节点),返回错误DBNode_EMPTY
if (!pHead->Next) return DBNode_EMPTY;
// 如果删除位置小于0,返回错误POS_ERR
if (pos < 0) return POS_ERR;
// 初始化pDel为头节点的下一个节点,即链表的第一个数据节点
pDel = pHead->Next;
// 寻找要删除的节点位置
while (i < pos && pDel != NULL) {
// 移动到下一个节点
pDel_Pre = pDel_Pre->Next; // 前驱指针向后移动
pDel = pDel->Next; // 删除指针向后移动
i++; // 计数器递增
}
// 如果没有找到要删除的节点(比如位置超出了链表长度),返回错误POS_ERR
if (!pDel) return POS_ERR;
// 保存删除节点的数据到item指向的变量中
*item = pDel->Data;
// 删除节点
if (pDel->Next != NULL) {
// 如果要删除的节点不是链表的最后一个节点,则修改其后继节点的Pre指针
pDel->Next->Pre = pDel_Pre;
}
// 修改前驱节点的Next指针,跳过要删除的节点
pDel_Pre->Next = pDel->Next;
// 释放要删除节点的内存
free(pDel);
// 删除成功,返回0(假设0代表OK)
return 0;
}
show.c
#include"../include/myhead.h"
//函数功能:显示双向链表中的所有元素
//函数参数:被显示元素的双向链表
//函数返回值:成功返回OK,失败返回失败原因
int ShowLink(DBNode *pHead){
DBNode *pTmp = NULL;
//如果链表为空,返回错误
if(!pHead) return DBNode_NULL;
//如果链表只有一个头节点,返回错误
if(pHead->Next == NULL) return DBNode_EMPTY;
//从头节点的下一个节点开始遍历链表
pTmp = pHead->Next;
while(pTmp){
printf("%d ",pTmp->Data);
pTmp = pTmp->Next;
}
puts(" ");
return OK;
}
Makefile-文件
# ALL是一个伪目标,用于指定需要编译生成的所有目标文件列表
ALL: ../obj/main.o ../obj/menu.o ../obj/creat.o ../obj/insert.o ../obj/show.o ../obj/delete.o
# 编译main.c源文件生成../obj/main.o目标文件
../obj/main.o: main.c
gcc -c $< -o $@
# 编译menu.c源文件生成../obj/menu.o目标文件
../obj/menu.o: menu.c
gcc -c $< -o $@
# 编译creat.c源文件生成../obj/creat.o目标文件
../obj/creat.o: creat.c
gcc -c $< -o $@
# 编译insert.c源文件生成../obj/insert.o目标文件
../obj/insert.o: insert.c
gcc -c $< -o $@
# 编译show.c源文件生成../obj/show.o目标文件
../obj/show.o: show.c
gcc -c $< -o $@
# 编译delete.c源文件生成../obj/delete.o目标文件
../obj/delete.o: delete.c
gcc -c $< -o $@
注释说明:
ALL: 是一个伪目标,它本身并不对应一个真实的文件,而是用于指定一系列依赖关系,方便在需要时一次性构建所有目标文件。这里列出了需要编译生成的所有.o目标文件。
每一个.o文件目标都依赖于一个.c源文件。例如,../obj/main.o依赖于main.c,当make命令执行时,它会查找依赖的源文件,并使用gcc -c命令编译这些源文件。
gcc -c $< -o $@ 是编译命令,其中gcc是GNU编译器集合中的C语言编译器,-c选项表示只编译不链接,$<是一个自动变量,代表依赖项列表中的第一个项目(在这里是.c源文件),$@也是一个自动变量,代表当前规则中的目标(在这里是.o目标文件)。这条命令的作用是将源文件编译成目标文件。
需要注意的是,这个Makefile只包含了编译目标文件的规则,并没有包含链接这些目标文件生成最终可执行文件的规则。如果需要链接生成可执行文件,通常还需要添加相应的链接规则。