图书管理系统(纯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;
}

链表的基本操作

增(链表的插入)

选择一种插入方式即可。

表头法插入基本流程示意图。

图书管理系统JavaScript 图书管理系统c语言_图书管理系统JavaScript

void insertNodeByHead(struct Node* headNode, struct bookInfo data)
{
    struct Node *newNode = createNode(data);
    newNode->Next = headNode->Next;
    headNode->Next = newNode;
}

表尾法插入基本流程示意图。

图书管理系统JavaScript 图书管理系统c语言_链表_02

// 表尾插入法,找表尾(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)));

调试过程

错误信息。

图书管理系统JavaScript 图书管理系统c语言_链表_03


解决方法:输入以下命令查看程序core文件的大小。可以看到core文件的大小为0,这表明错误信息无法存入core文件。

ulimit -a

图书管理系统JavaScript 图书管理系统c语言_结点_04

输入下列命令,修改core文件的大小为无限制。

ulimit -c unlimited

再次查看core文件大小,发现文件大小修改为unlimited

图书管理系统JavaScript 图书管理系统c语言_c语言_05

调用gdb进行调试。(我的core文件名就是core)

输入q退出gdb。

gcc -g ./图书管理系统.c -o ./图书管理系统
gdb 图书管理系统
core-file core
bt

图书管理系统JavaScript 图书管理系统c语言_链表_06

源代码

#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;
    
}