C语言数据结构-线性结构——双向链表

文章详细描述了一个C语言项目,包括Makefile的结构、源文件的组织(如Queue目录下的bin、include、obj、src子目录和相关Makefile)、以及编译过程中的伪目标和编译链接规则,涉及双向链表的函数实现和头文件管理。
摘要由CSDN通过智能技术生成

一、思想

二、代码

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只包含了编译目标文件的规则,并没有包含链接这些目标文件生成最终可执行文件的规则。如果需要链接生成可执行文件,通常还需要添加相应的链接规则。

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值