图书管理系统(纯C语言)
- 项目简介
- 实现的功能
- 后续
- 开发环境和工具
- 主要知识
- 结构体
- 链表
- 创建链表
- 链表的基本操作
- 增(链表的插入)
- 删(制定结点删除)
- 查
- 改
- 冒泡排序
- 文件操作
- gdb调试
- 调试过程
- 源代码
项目简介
实现的功能
基本功能为增删查改,目前已实现的功能为:
0.退出系统
1.登记书籍
2.浏览书籍
3.借阅书籍
4.归还书籍
5.书籍排序
6.删除书籍
7.查找书籍
后续
用户管理,代码测试。
开发环境和工具
Ubuntu 20.04.4 LTS
VS Code
主要知识
结构体
在本项目中,创建了两个结构体,用于存放书籍信息和链表结点信息。
书籍信息
struct bookInfo
{
int ISBN;
char name[20];
float price;
int num;
};
链表结点信息
struct Node
{
struct bookInfo data; // 数据域
struct Node* Next; //指针域,指向直接后继元素
};
链表
创建链表
链表基本知识 创建链表表头
struct Node* createHead()
{
// 动态内存申请
struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
// 初始化表头
// headNode->data = 1; //头结点不存数据
headNode->Next = NULL;
return headNode;
}
创建结点,存储用户数据。
// 创建节点,为插入作准备
// 把用户的数据变为结构体变量
struct Node* createNode(struct bookInfo data)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->Next = NULL;
return newNode;
}
链表的基本操作
增(链表的插入)
选择一种插入方式即可。
表头法插入基本流程示意图。
void insertNodeByHead(struct Node* headNode, struct bookInfo data)
{
struct Node *newNode = createNode(data);
newNode->Next = headNode->Next;
headNode->Next = newNode;
}
表尾法插入基本流程示意图。
// 表尾插入法,找表尾(pMove->Next = NULL时)
void insertNodeByTail(struct Node* headNode, struct bookInfo data)
{
struct Node* pMove = headNode;
while(pMove->Next != NULL)
{
pMove = pMove->Next;
}
struct Node* newNode = createNode(data);
pMove->Next = newNode;
}
删(制定结点删除)
按照书籍名进行删除。
// 找要删除的结点和前一个结点;posNode -->指的是要删除的目标节点
// posLeftNode->Next = posNode->Next;posNode->Next-->指的是删除结点的下一个结点
// free(posNode);
void deleteNodeByName(struct Node* headNode, char *bookName)
{
struct Node* posLeftNode = headNode; //前一个结点
struct Node* posNode = headNode->Next; //删除的结点
//如果在当前结点没找到,两个指针都要往下走,此时的左节点即为当前结点,此时的当前结点就变为左结点的Next
while(posNode != NULL && strcmp(posNode->data.name,bookName))
{
posLeftNode = posNode; //此时的左节点即为当前结点
posNode = posLeftNode->Next; //此时的当前结点就变为左结点的Next
}
// 讨论查找的结果
if(posNode == NULL)
{
// printf("未找到");
return;
}
else
{
posLeftNode->Next = posNode->Next; //把删除结点的前一个结点和后一个结点连接起来。
free(posNode);
posNode = NULL;
printf("删除成功!\n");
}
}
查
按书籍名进行查找。
struct Node* searchByName(struct Node* headNode,char* bookName)
{
struct Node* posNode = headNode->Next; //查找的结点
while(posNode != NULL && strcmp(posNode->data.name,bookName))
{
posNode =posNode->Next;
}
return posNode;
}
用户交互处进行查找的判断。
case 7:
printf("【 查找 】\n");
printf("请输入需要查找的书名:");
scanf("%s",bookTemp.name);
result = searchByName(list,bookTemp.name);
if(result == NULL)
{
printf("未找到相关信息!\n");
}
else
{
printf("ISBN\tname\tprice\tnum\n");
printf("%d\t%s\t%.1f\t%d\n",result->data.ISBN,result->data.name,result->data.price,result->data.num);
result = NULL;
printf("查找成功!\n");
}
break;
改
这部分的代码,主要体现在借阅和归还处。创建一个临时的结构体指针result用于存放查找的结果(struct Node* result = NULL;)。
case 3:
printf("【 借阅 】\n"); // 书籍存在且数量不为0,可以借阅,数量-1;书籍不存在,借阅失败。
printf("请输入借阅书籍的名字:\n");
scanf("%s",bookTemp.name);
result = searchByName(list,bookTemp.name);
if(result == NULL)
{
printf("没有书籍相关信息,无法借阅\n");
}
else
{
if(result->data.num == 0)
{
printf("当前书籍数量为0,无法借阅\n");
}
else
{
printf("可以借阅\n");
result->data.num--;
}
}
saveInfoToFile("bookinfo.txt",list);
break;
case 4:
printf("【 归还 】\n"); // 书籍数量+1。
printf("请输入归还书籍的名字:\n");
scanf("%s",bookTemp.name);
result = searchByName(list,bookTemp.name);
if(result == NULL)
{
printf("该书籍来源非法\n");
}
else
{
printf("归还成功\n");
result->data.num++;
}
saveInfoToFile("bookinfo.txt",list);
break;
冒泡排序
// 冒泡排序
void bubbleSortList(struct Node* headNode)
{
for(struct Node* p = headNode->Next; p != NULL; p = p->Next) //外层循环判断是否只有一个结点,只有一个结点则不进行排序
{
for(struct Node* q = headNode->Next; q->Next != NULL; q = q->Next) //内层循环对相邻结点进行比较
{
if(q->data.price < q->Next->data.price) //按价格,从大到小排序。
{
struct bookInfo tempData;
tempData = q->data;
q->data = q->Next->data;
q->Next->data = tempData;
}
}
}
}
文件操作
// 文件操作
// 写操作
void saveInfoToFile(const char* fileName,struct Node* headNode)
{
FILE* fp = fopen(fileName,"w");
struct Node* pMove = headNode->Next;
while(pMove != NULL)
{
fprintf(fp,"%d\t%s\t%f\t%d\n",pMove->data.ISBN,pMove->data.name,pMove->data.price,pMove->data.num);
pMove =pMove->Next;
}
fclose(fp);
}
// 读操作
void readInfoFromFile(const char* fileName,struct Node* headNode)
{
FILE* fp = fopen(fileName,"r");
// 文件第一次打开不存在,就创建
if(fp == NULL)
{
fp = fopen(fileName,"w+");
}
// 创建一个临时变量储存从文件中读的信息,再插入到链表中
struct bookInfo tempData;
while(fscanf(fp,"%d\t%s\t%f\t%d\n",&tempData.ISBN,tempData.name,&tempData.price,&tempData.num) != EOF)
{
insertNodeByHead(list,tempData);
}
fclose(fp);
}
gdb调试
在程序运行过程在出现里段错误(核心已转储)。
最后发现是在动态内存申请时多打了一个sizeof
导致的。
正确形式:
struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
错误形式:
struct Node* headNode = (struct Node*)malloc(sizeof(sizeof(struct Node)));
调试过程
错误信息。
解决方法:输入以下命令查看程序core文件的大小。可以看到core文件的大小为0,这表明错误信息无法存入core文件。
ulimit -a
输入下列命令,修改core文件的大小为无限制。
ulimit -c unlimited
再次查看core文件大小,发现文件大小修改为unlimited。
调用gdb进行调试。(我的core文件名就是core)
输入q退出gdb。
gcc -g ./图书管理系统.c -o ./图书管理系统
gdb 图书管理系统
core-file core
bt
源代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// 3.写数据
// 3.1 程序怎样处理数据 --->链表
// 3.2 数据的结构 --->图书信息
struct bookInfo
{
int ISBN;
char name[20];
float price;
int num;
};
struct Node
{
struct bookInfo data; // 数据域
struct Node* Next; //指针域,指向直接后继元素
};
struct Node* list = NULL; //全局链表
// 创建表头,-->结构体变量
struct Node* createHead()
{
// 动态内存申请
struct Node* headNode = (struct Node*)malloc(sizeof(struct Node));
// 初始化表头
// headNode->data = 1; //头结点不存数据
headNode->Next = NULL;
return headNode;
}
// 创建节点,为插入作准备
// 把用户的数据变为结构体变量
struct Node* createNode(struct bookInfo data)
{
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->Next = NULL;
return newNode;
}
// 插入,只需要一种方式,这里是表头法插入,必须先连接后断开
void insertNodeByHead(struct Node* headNode, struct bookInfo data)
{
struct Node *newNode = createNode(data);
newNode->Next = headNode->Next;
headNode->Next = newNode;
}
// 表尾插入法,找表尾(pMove->Next = NULL时)
// void insertNodeByTail(struct Node* headNode, struct bookInfo data)
// {
// struct Node* pMove = headNode;
// while(pMove->Next != NULL)
// {
// pMove = pMove->Next;
// }
// struct Node* newNode = createNode(data);
// pMove->Next = newNode;
// }
// 指定位置删除
// 找要删除的结点和前一个结点;posNode -->指的是要删除的目标节点
// posLeftNode->Next = posNode->Next;posNode->Next-->指的是删除结点的下一个结点
// free(posNode);
void deleteNodeByName(struct Node* headNode, char *bookName)
{
struct Node* posLeftNode = headNode; //前一个结点
struct Node* posNode = headNode->Next; //删除的结点
//如果在当前结点没找到,两个指针都要往下走,此时的左节点即为当前结点,此时的当前结点就变为左结点的Next
while(posNode != NULL && strcmp(posNode->data.name,bookName))
{
posLeftNode = posNode; //此时的左节点即为当前结点
posNode = posLeftNode->Next; //此时的当前结点就变为左结点的Next
}
// 讨论查找的结果
if(posNode == NULL)
{
// printf("未找到");
return;
}
else
{
posLeftNode->Next = posNode->Next; //把删除结点的前一个结点和后一个结点连接起来。
free(posNode);
posNode = NULL;
printf("删除成功!\n");
}
}
// 查找
struct Node* searchByName(struct Node* headNode,char* bookName)
{
struct Node* posNode = headNode->Next; //查找的结点
while(posNode != NULL && strcmp(posNode->data.name,bookName))
{
posNode =posNode->Next;
}
return posNode;
}
// 打印
void printList(struct Node* headNode)
{
struct Node* pMove = headNode->Next; //头结点不存数据
printf("ISBN\tname\tprice\tnum\n");
while(pMove != NULL) //链表地址不为空时打印数据,为空时,表示链表打印结束
{
printf("%d\t%s\t%.1f\t%d\n", pMove->data.ISBN,pMove->data.name,pMove->data.price,pMove->data.num);
pMove = pMove->Next;
}
}
// 1.界面
void Menu()
{
printf("--------------------\n");
printf("\t图书管理系统\n");
printf("\t0.退出系统\n");
printf("\t1.登记书籍\n");
printf("\t2.浏览书籍\n");
printf("\t3.借阅书籍\n");
printf("\t4.归还书籍\n");
printf("\t5.书籍排序\n");
printf("\t6.删除书籍\n");
printf("\t7.查找书籍\n");
printf("--------------------\n");
printf("请输入(0~7)\n");
}
// 文件操作
// 写操作
void saveInfoToFile(const char* fileName,struct Node* headNode)
{
FILE* fp = fopen(fileName,"w");
struct Node* pMove = headNode->Next;
while(pMove != NULL)
{
fprintf(fp,"%d\t%s\t%f\t%d\n",pMove->data.ISBN,pMove->data.name,pMove->data.price,pMove->data.num);
pMove =pMove->Next;
}
fclose(fp);
}
// 读操作
void readInfoFromFile(const char* fileName,struct Node* headNode)
{
FILE* fp = fopen(fileName,"r");
// 文件第一次打开不存在,就创建
if(fp == NULL)
{
fp = fopen(fileName,"w+");
}
// 创建一个临时变量储存从文件中读的信息,再插入到链表中
struct bookInfo tempData;
while(fscanf(fp,"%d\t%s\t%f\t%d\n",&tempData.ISBN,tempData.name,&tempData.price,&tempData.num) != EOF)
{
insertNodeByHead(list,tempData);
}
fclose(fp);
}
// 冒泡排序
void bubbleSortList(struct Node* headNode)
{
for(struct Node* p = headNode->Next; p != NULL; p = p->Next) //外层循环判断是否只有一个结点,只有一个结点则不进行排序
{
for(struct Node* q = headNode->Next; q->Next != NULL; q = q->Next) //内层循环对相邻结点进行比较
{
if(q->data.price < q->Next->data.price) //按价格,从大到小排序。
{
struct bookInfo tempData;
tempData = q->data;
q->data = q->Next->data;
q->Next->data = tempData;
}
}
}
}
// 2.交互
void Key()
{
int userKey =0;
struct bookInfo bookTemp; //创建一个临时变量存储书籍信息。
struct Node* result = NULL;
scanf("%d", &userKey);
switch (userKey)
{
case 0:
printf("【 退出 】\n");
printf("退出成功\n");
// getchar();
exit(0); //关闭程序
break;
case 1:
printf("【 登记 】\n");
printf("输入书籍的信息(ISBN,name,price,num):");
scanf("%d\t%s\t%f\t%d\n",&bookTemp.ISBN,bookTemp.name,&bookTemp.price,&bookTemp.num);
insertNodeByHead(list,bookTemp);
saveInfoToFile("bookinfo.txt",list); //每次登记完,将链表里的数据写到文件中
break;
case 2:
printf("【 浏览 】\n"); //打印链表
printList(list);
break;
case 3:
printf("【 借阅 】\n"); // 书籍存在且数量不为0,可以借阅,数量-1;书籍不存在,借阅失败。
printf("请输入借阅书籍的名字:\n");
scanf("%s",bookTemp.name);
result = searchByName(list,bookTemp.name);
if(result == NULL)
{
printf("没有书籍相关信息,无法借阅\n");
}
else
{
if(result->data.num == 0)
{
printf("当前书籍数量为0,无法借阅\n");
}
else
{
printf("可以借阅\n");
result->data.num--;
}
}
saveInfoToFile("bookinfo.txt",list);
break;
case 4:
printf("【 归还 】\n"); // 书籍数量+1。
printf("请输入归还书籍的名字:\n");
scanf("%s",bookTemp.name);
result = searchByName(list,bookTemp.name);
if(result == NULL)
{
printf("该书籍来源非法\n");
}
else
{
printf("归还成功\n");
result->data.num++;
}
saveInfoToFile("bookinfo.txt",list);
break;
case 5:
printf("【 排序 】\n");
bubbleSortList(list);
printList(list);
break;
case 6:
printf("【 删除 】\n");
printf("请输入删除书籍的名字:");
scanf("%s",bookTemp.name);
deleteNodeByName(list,bookTemp.name);
// 删除完成后,需要同步到文件(写操作)
saveInfoToFile("bookinfo.txt",list);
break;
case 7:
printf("【 查找 】\n");
printf("请输入需要查找的书名:");
scanf("%s",bookTemp.name);
result = searchByName(list,bookTemp.name);
if(result == NULL)
{
printf("未找到相关信息!\n");
}
else
{
printf("ISBN\tname\tprice\tnum\n");
printf("%d\t%s\t%.1f\t%d\n",result->data.ISBN,result->data.name,result->data.price,result->data.num);
result = NULL;
printf("查找成功!\n");
}
break;
default:
printf("【 ERROR 】\n");
break;
}
}
int main()
{
list =createHead();
readInfoFromFile("bookinfo.txt", list); // 当系统运行时,将文件中的数据读到链表当中
while (1)
{
Menu();
Key();
//getchar();
system("read");
}
getchar();
return 0;
}