C项目实践--图书管理系统(2)

前面在<<C项目实践—图书管理系统(1)>>中把系统中的三大功能模块中可能涉及到的常量,结构体及相关函数进行了声明定义,下来就来实现它们。 执行系统首先从登录到系统开始,所以首先我们先来实现登录模块,打开management.c 文件。

登录模块完成的主要功能是用户登录和退出系统。用户登录系统时,会要求输入登录名和密码,并根据不同用户权限调用不同的菜单显示;退出系统时,不同类型的用户退出时调用的函数也不相同。

1.预处理

预处理包括加载头文件,定义常量等。注意我们zai management.h 不定义常量而在实现文件中来定义这些常量是因为有可能其它实现文件需要访问management模块的相应函数,那么它就需要包含managment实现模块的头文件,那么如果我们把这些产量定义在这个头文件中的话,其它函数就可以透过extern使用到这个常量,如果我们定义到实现文件中,那么它就用不到了。

首先加载要包含的头文件,具体如下:

//Header Info
#include <stdlib.h>
#include <stdio.h>
#include "book.h"
#include "user.h"
#include "management.h"

接下来定义一些用户管理模块中需要用到的全局常量,具体如下:

//Global constant definition 
#define MENU_ADMIN_COUNT 9          //管理员操作主菜单的选项个数
#define MENU_SEARCH_BOOK_COUNT 6    //管理员查询菜单的选项个数
#define MENU_USER_COUNT 7           //普通用户操作主菜单的选项个数
 
//字符数组menu_title中存储的是系统的标题字符
char menu_title[] =
    "=============================================
"
    "|              图书管理系统             |
"
    "+-------------------------------------------+
";
//字符数组menu_admin中存储的是管理员操作主菜单时显示的字符
char menu_admin[] =
    "|                                       |
"
    "|  图书管理                             |
"
    "|      <1>新增图书                      |
"
    "|      <2>浏览图书                      |
"
    "|      <3>查找图书                      |
"
    "|      <4>删除图书                      |
"
    "|      <5>保存图书                      |
"
    "|                                       |
"
    "|  用户管理                             |
"
    "|      <6>新增用户                      |
"
    "|      <7>查找用户                      |
"
    "|      <8>保存用户                      |
"
    "|                                       |
"
    "|      <9>退出系统                      |
"
    "+-------------------------------------------+
";
//函数指针数组admin_func()存储的是管理员权限操作主菜单所对应的函数
//Function defintion
void (* admin_func[])() =
{
    add_book,
    view_book,
    show_search_book_menu,
    delete_book,
    save_books,
    add_user,
    search_user,
    save_users,
    admin_exit
};
//字符数组menu_admin_search_book存储的是管理员登录系统,查询子菜单显示的字符
char menu_admin_search_book[] = 
    "|                                       |
"
    "|  查找图书                             |
"
    "|      <1>按书名查                      |
"
    "|      <2>按作者查找                    |
"
    "|      <3>按出版社查找                  |
"
    "|      <4>按出版日期查找                |
"
    "|      <5>按国际标准书号(ISBN)查找      |
"
    "|      <6>返回主菜单                    |
"
    "+-------------------------------------------+
";
//函数指针数组admin_search_book_func 中存储的是管理员权限登录后,查询图书的函数
void (* admin_search_book_func[])() = 
{
    search_book_by_name,
    search_book_by_author,
    search_book_by_publisher,
    search_book_by_pubdate,
    search_book_by_isbn
};
//字符数组menu_user是普通用户登录系统后显示的提示字符
char menu_user[] = 
    "|                                       |
"
    "|      <1>浏览图书                      |
"
    "|      <2>按书名查找图书                |
"
    "|      <3>按作者查找图书                |
"
    "|      <4>按出版社查找图书              |
"
    "|      <5>按出版日期查找图书            |
"
    "|      <6>按国际标准书号(ISBN)查找图书  |
"
    "|      <7>退出系统                      |
"
    "+-------------------------------------------+
";
 
//函数指针数组user_func存储的是普通用户权限登录主菜单所对应的函数
void (* user_func[])() =
{
    view_book,
    search_book_by_name,
    search_book_by_author,
    search_book_by_publisher,
    search_book_by_pubdate,
    search_book_by_isbn,
    user_exit
};

接下来根据功能需求来实现主要的处理函数

1.显示管理员操作的主菜单界面

函数名称:show_admin_menu

函数功能:显示管理权限登录系统后操作的主菜单,等待用户输入,根据用户输入信息调用相应的函数。

处理过程:管理员权限登录系统后,首先调用本函数来显示系统菜单,等待用户输入1-9之间的数字,如果输入正确,则根据用户的输入调用相应的函数来处理,否则提示输入有误,按任意键重新显示主菜单界面。

例如输入1,将会用admin_func[0]()方式来调用add_book()函数进行新增图书信息操作。具体实现如下:

//Main-Processing Function
//显示管理员操作的主菜单界面
void show_admin_menu()
{
    int selected = 0;
    //这里的判断条件selected <1 || selected > MENU_ADMIN_COUNT
    //可直接改成1 这样就成了 while(1) ,此时while循环最后的selected赋值
    //也可以去掉了。 
    while(selected <1 || selected > MENU_ADMIN_COUNT)
    {
        system("cls");
        printf(menu_title);
        printf(menu_admin);
 
        printf(">请选择要进行的操作:");
        fflush(stdin);
        scanf("%d",&selected);
        if(selected < 1 || selected > MENU_ADMIN_COUNT)
        {
            printf(">输入有误! 请选择[%d-%d]之间的数字. 按任意键继续...",1,MENU_ADMIN_COUNT);
            fflush(stdin);
            getchar();
        }else{
            admin_func[selected - 1]();
        }
        selected = 0;
    }
}

2.显示管理员查询图书的菜单

函数名称:show_search_book_menu

函数功能:管理员权限登录之后选择操作3调用此函数,用来对图书信息进行查询。

处理过程:1打印管理员查询子菜单,2等待输入1-6中间的数值,如果输入1-6之外的数字,则给出相应的提示,提示用户按任意键,返回查询子菜单,3.输入正确,则根据用户的输入调用相应的函数来处理,例如输入1,则用admin_search_book_func[0]()方式调用search_book_by_name()函数按书名来查找图书信息。具体实现如下:

void show_search_book_menu()
{
    int selected = 0;
    while(1)
    {
        system("cls");
        printf(menu_title);
        printf(menu_admin_search_book);
 
        printf(">请选择要进行的操作:");
        fflush(stdin);
        scanf("%d",&selected);
        //如果选择了选项6,则返回主菜单界面
        if(selected == MENU_SEARCH_BOOK_COUNT)
        {
            break;
        }
        //注意如果输入的不是数字而是字符之类的,那么selected的值将是0, 所以
        //此时不能设置成selected<0 而是需要设置成selected <1 
        //如果设置成 selected <0 ,那么当从键盘中输入字符例如C
        //此时因为scanf("%d",selected)用的%d来接收数据,那么输入字符自然被判断为不是
        //数值,所以不会把输入的不是数值的值赋值给selected 所以selected的值保持不变
        //不管输入的什么字符只要不是数值型的,都不会把它给selected 
        if(selected < 1 || selected > MENU_SEARCH_BOOK_COUNT)
        {
            printf(">输入有误! 请选择[%d-%d]之间的数字. 按任意键继续...",1,MENU_ADMIN_COUNT);
            fflush(stdin);
            getchar();
        }else{
            admin_search_book_func[selected -1]();
        }
    }//end while-loop
}

3显示普通用户操作的菜单

函数名称:show_user_menu

函数功能:显示普通用户权限登录系统后的操作菜单,等待用户输入,根据输入信息做相应的处理。

处理流程:1.普通用户登录系统,首先调用本函数显示操作菜单,等待用户输入1-7之间的数值,2.如果输入有误,给出提示信息,并按任意键重新显示操作菜单3.如果输入正确就根据输入信息调用相应的函数来处理。例如输入1,则用user_func[0]()方式来调用view_book()函数来浏览图书信息。具体实现如下:

void show_user_menu()
{
    int selected = 0;
    while(1)
    {
        system("cls");
        printf(menu_title);
        printf(menu_user);
 
        printf("请选择要进行的操作:");
        fflush(stdin);
        scanf("%d",&selected);
 
        if(selected < 1 || selected > MENU_USER_COUNT)
        {
            printf(">输入有误! 请选择[%d-%d]之间的数字. 按任意键继续...",1,MENU_ADMIN_COUNT);
            fflush(stdin);
            getchar();
        }else{
            user_func[selected -1]();
        }
    }
}

4.管理员退出系统

函数名称:admin_exit

函数功能:管理员退出系统,保存用户和图书信息,并释放链表内容,防止内存泄露

处理过程:先调用保存文件信息函数将用户链表中的信息和图书链表中的信息分别保存到相应的文件中,然后清空这些链表内容,最后退出系统。具体实现如下:

void admin_exit()
{
    char sure = 'N';
    printf(">确定要退出吗?(y or n)");
    fflush(stdin);
    sure = getchar();
    if(sure == 'y' || sure == 'Y')
    {
        save_users_to_file(); //将用户链表中的信息保存到用户文件中
        clear_users();        //清空用户链表内容 
        save_books_to_file(); //将图书链表中的信息保存到图书文件中
        clear_books();        //清空图书链表内容
        exit(0);
    }
}

5.普通用户退出

函数名称:user_exit

函数功能:释放链表内容,然后退出系统。

处理流程:先将用户链表和图书链表中的内容清空,然后退出系统。具体实现如下:

void user_exit()
{
    char sure = 'N';
    printf(">确定要退出吗?(y or n)");
    fflush(stdin);
    sure = getchar();
    if(sure == 'y' || sure == 'Y')
    {
        clear_users();
        clear_books();
        exit(0);
    }
}
6系统入口函数main函数,用户登录入口
//Entry Functions
int main()
{
    //初始化用户信息模块,即如果
    //不存在用户信息文件则创建用户信息文件
    //并初始化一个管理员帐号,
    //如果存在则不做任何处理
    init_user();  
    //加载用户信息
    //从用户文件中将用户信息加载到用户单链表中
    load_users();
    //初始化图书信息文件,
    //如果不存在存储图书信息的文件, 则创建一个
    //如果存在这个文件,则什么都不做
    init_book();
    //将图书信息文件中的图书信息加载到图书链表中来
    load_books();
 
    printf("图书管理系统登录...
");
    //根据用户登录类型来显示不同权限下的操作菜单
    if(login() == ADMIN)
    {
        show_admin_menu();
    }else{
        show_user_menu();
    }
}

到目前为止,登录模块management.c中的功能就全部实现了,下面进入图书信息管理模块,图书信息管理模块主要功能包括图书的增加,浏览,查询,删除及保存等操作。其中查询模块中又分为按书名,作者,出版社,出版日期及ISBN这5种方式查询,其中按ISBN可以精确查找图书信息而其它4种查询方式支持模糊查找。首先打开book.c, 首先包含要用到的头文件:

//Header Info
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h> //断言,在内存分配失败时使用
#include "book.h"
#include "management.h"
#include "user.h"
接下来需要定义一个常量,用来表示存储图书信息的文件
#define BOOK_FILE "book.dat"
定义一个图书链表的头节点,并初始化为NULL
book* fist_book = NULL; // book 结构体链表的头节点
下面按主菜单的显示顺序来实现其相应的图书操作功能
1.新增图书信息
函数名称:add_book
函数功能:新增图书信息,在管理员操作菜单中,选择1调用此函数。
处理过程:1.创建一个图书节点new_book 2.初始化new_book 3.调用函数input_book(),提示用户输入图书信息,为new_book赋值 4调用函数get_last_book(),取得链表中最后一个节点,给p赋值5.如果链表为空,将new_book赋值给头节点,否则,将p连到最后一个节点之后。 具体实现如下:
 
//新增图书信息
void add_book()
{
    char try_again = 'Y'; //是否继续进行新增图书的标识符
    book* p = NULL; //用来查找当前图书链表中的最后一个节点的临时指针变量
    book* new_book = NULL;//定义一个新的book结构节点,指向存储输入的图书信息地址的指针
    while(try_again == 'Y' || try_again == 'y') //如果标识符为y 或Y 则继续调用add_book()
    {    
        new_book = (book*)malloc(sizeof(book));
    //    if(NULL == new_book) throw printf("内存分配失败!
");
        assert(new_book != NULL); //断言,如果new_book 为NULL 则抛出异常
        memset(new_book, 0, sizeof(book));//内存赋值为0
        new_book->next = NULL; 
        printf(">新增图书...
");
        input_book(&(new_book->bi));
        p = get_last_book();
 
        //将新增节点new_book连接到图书链表的最后一个节点的后面
        if(NULL == p)
        {
            first_book = new_book;
        }else{
            p->next = new_book;
        }
        printf(">继续添加图书吗?(y or n): ");
        fflush(stdin);
        try_again = getchar();
    }//end while-loop
}

2.浏览图书信息

函数名称:view_book

函数功能:浏览图书链表中的所有图书信息,如果记录超过5条则提示翻页显示。具体实现如下:

void view_book()
{
    book* p = NULL;
    char input = 'Y';
    int count = 0;
    while(input == 'y' || input == 'Y')
    {
        count = 0;
        p = first_book;
        printf("+-------------------------------------------+
");
        printf("|书  名                  |作  者            |
");
        printf("+-------------------------------------------+
");
 
        while( NULL != p)
        { 
            //C中printf()的输入默认是右对齐,采用 %-24s表示-24位右对齐,
            //从而实现左对齐
            printf("|%-24s|%-18s|
",p->bi.book_name,p->bi.author);
            printf("+-------------------------------------------+
");
            count++;
            if(count == 5)
            {
                count = 0;
                printf(">显示下一页?(y or n):");
                fflush(stdin);
                input = getchar();
                if(input != 'y' && input != 'Y')
                {
                    break;
                }
            }
            p = p->next;
        }//end inner while-loop
 
        printf(">再次浏览图书吗?(y or n):");
        fflush(stdin);
        input = getchar();
    }// end while-loop
}

3.按书名查找图书信息

函数名称:search_book_by_name

函数功能:在管理员操作查询子菜单中,选择1调用此函数,按书名查询图书信息,调用findstr()函数实现模糊查找,实现模糊查找。

处理流程:1.提示用户输入要查找的书名 2调用函数findstr()函数进行模糊查找 3调用函数show_book显示查询到的图书信息,否则提示没有找到相应的图书信息。具体实现如下:

//按书名查找图书
void search_book_by_name()
{
    book* p = NULL;
    //定义一个字符串数组用来接收输入的书名,初始化为NULL
    char s[MAX_BOOK_NAME] = {0};
    char input = 'Y'; //是否进行查找查找的标识符,'Y'表示继续查找操作
    //统计查找到的图书的记录条数,初始化为0,表示没有找到相关信息
    int count = 0; 
    printf(">查找图书...
");
    while(input == 'Y' || input == 'y')
    {
        count = 0;
        p = first_book; //p指向图书链表头节点
        memset(s,0,MAX_BOOK_NAME); // 清空s
        printf(">请输入书名(最大长度为%d):",MAX_BOOK_NAME);
        fflush(stdin);
        scanf("%[^
]",s); //接收带空格的字符串
 
        while(NULL != p)
        { //p不为空时,调用findstr查找书名中是否包含输入的字符串s 
            //findstr支持模糊查找
            if(findstr(p->bi.book_name, s) != -1)
            {
                show_book(&(p->bi));//显示查找到的图书
                count++;
            }
            p = p->next;
        }//end while-loo
        if(count == 0)
        {
            printf(">没有找到图书 %s. 继续查找吗?(y or n):",s);
            fflush(stdin);
            input = getchar();
            continue;
        }
        
        printf(">共找到 %d 本图书...
",count);
        printf(">继续查找吗?(y or n):");
        fflush(stdin);
        input = getchar();
        
    }//end while-loop
}

4.按作者查询图书信息

函数名称:search_book_by_author

函数功能:在管理员操作查询子菜单中,选择2调用此函数,按作者查找图书信息。具体实现如下:

//按作者查询图书信息
void search_book_by_author()
{
    book* p = NULL;
    char s[MAX_AUTHOR] = {0}; //存储输入书名的变量
    char input = 'Y';
    int count = 0;
    int i = 0;
    printf(">查找图书...
");
    while(input == 'y' || input == 'Y')
    {
        count = 0;
        p  = first_book;
        memset(s, 0, MAX_AUTHOR);
        printf(">请输入作者(最大长度为%d):",MAX_AUTHOR);
        fflush(stdin);
        scanf("%s",s);
 
        while(NULL != p)
        {
            if(findstr(p->bi.author,s) != -1)
            {
                show_book(&(p->bi));
                count++;
            }
            p = p->next;
        }
 
        if(count == 0)
        {
            printf(">没有找到作者为 %s 的图书. 继续查找吗?(y or n):",s);
            fflush(stdin);
            input = getchar();
            continue;
        }
 
        printf(">共找到 %d 本图书...
",count);
        printf(">继续查找吗?(y or n):");
        fflush(stdin);
        input = getchar();
    }//end while-loop
}

5.按出版社查询图书信息

函数名称:search_book_by_publisher

函数功能:在管理员操作查询子菜单中,选择3调用此函数,按出版社查找图书信息。具体实现如下:

//按出版社查找图书信息
void search_book_by_publisher()
{
    book* p = NULL;
    char s[MAX_PUBLISHER] = {0};//存储输入出版社的变量
    char input = 'Y';
    int count = 0;
    int  i = 0; 
 
    printf(">查找图书...
");
    while(input == 'y' || input == 'Y')
    {
        count = 0;
        p = first_book;
        memset(s,0, MAX_AUTHOR);
        printf(">请输入出版社(最大长度为%d):",MAX_PUBLISHER);
        fflush(stdin);
        scanf("%s",s);
        while(NULL !=p)
        {
            if(findstr(p->bi.publisher,s) != -1)
            {
                show_book(&(p->bi));
                count++;
            }
            p = p->next;
        }
 
        if(count == 0)
        {
            printf(">没有找到出版社为 %s 的图书. 继续查找吗?(y or n):",s);
            fflush(stdin);
            input = getchar();
            continue;
        }
        printf(">共找到 %d 本图书...
",count);
        printf(">继续查找吗?(y or n):");
        fflush(stdin);
        input = getchar();
    }
}

6.按出版日期查询图书信息

函数名称:search_book_by_pubdate

函数功能:在管理员操作查询子菜单中,选择4调用此函数,按出版日期查找图书信息。具体实现如下:

void search_book_by_pubdate()
{
    book* p = NULL;
    char s[MAX_DATE] =  {0};
    char input = 'Y';
    int count = 0;
    int i = 0; 
    printf(">查找图书...
");
    while (input == 'y'|| input == 'Y')
    {
        count = 0;
        p = first_book;
        memset(s,0,MAX_DATE);
        printf(">请输入出版社日期(最大长度为 %d):",MAX_DATE);
        fflush(stdin);
        scanf("%s", s);
        while(NULL != p)
        {
            if(findstr(p->bi.pub_date , s) != -1)
            {
                show_book(&(p->bi));
                count++;
            }
            p = p->next;
        }
        if(count == 0)
        {
            printf(">没有找到出版日期为 %s 的图书. 继续查找吗?(y or n):",s);
            fflush(stdin);
            input = getchar();
            continue;
        }
 
        printf(">共找到 %d 本图书...
",count);
        printf(">继续查找吗?(y or n):");
        fflush(stdin);
        input = getchar();
    }
}

7.按ISBN查询图书信息

函数名称:search_book_by_isbn

函数功能:在管理员操作查询子菜单中,选择5调用此函数,按ISBN精确查找图书信息。具体实现如下:

void search_book_by_isbn()
{
    char input = 'Y';
    char isbn[MAX_ISBN] = {0};
    book* p = NULL;
    book* result = NULL;
 
    while(input == 'Y' || input == 'y')
    {
        printf(">查找图书...
");
        printf(">请输入ISBN (最大长度为 %d):",MAX_ISBN);
        fflush(stdin);

scanf("%s",isbn); p = first_book; // p 指向头节点

        result = NULL;
        while(NULL != p)
        {
            if(strcmp(p->bi.ISBN, isbn) == 0)
            {
                result = p;
                break;
            }
            p = p->next;
        }
 
        if(result != NULL)
        {
            printf(">查找到图书...
");
            show_book(&(result->bi));
        }else{
            printf(">没有找到ISBN为 %s 的图书. 
",isbn);
        }
        printf(">继续查找吗?(y or n)");
        fflush(stdin);
        input = getchar();
    }
}

8.删除图书信息

函数名称:delete_book

函数功能:实现图书信息的删除操作。在管理员主菜单中选择4,调用此函数。函数要求管理员输入图书的ISBN,根据输入,在图书链表中查找该图书是否存在,如果存在显示该图书信息,并提示用户是否确认删除,如果管理员输入"Y"或"y", 则从图书链表中删除该图书信息,删除完之后提示是否继续进行删除操作。如果查找不成功,则给出提示信息。

处理流程:1.首先提示用户输入ISBN,2查询图书链表中是否存在该ISBN的图书信息,如果不存在则给出提示信息,否则先调用show_book函数显示该图书信息,并提示用户是否确定要删除该图书,如果用户输入"y"或"Y", 则删除该图书,并提示是否继续删除操作。具体实现如下:

//删除图书操作
void delete_book()
{
    char input = 'Y';
    char isbn[MAX_ISBN] = {0}; //保存输入要查找的ISBN的变量
    book* p = NULL;
    book* result = NULL;
    while(input == 'Y' || input == 'y')
    {
        printf(">删除图书...
");
        printf(">请输入ISBN(最大长度为 %d):",MAX_ISBN);
        fflush(stdin);
        scanf("%s",isbn);
        p = first_book;
        result = NULL;
        while (NULL != p )
        {
            if(strcmp(p->bi.ISBN , isbn) == 0)
            {
                result = p;
                break;
            }
            p = p->next;
        }
        if(result != NULL)
        {
            show_book(&(result->bi));
            printf(">确认删除吗?(y or n)");
            fflush(stdin);
            input = getchar();
            if(input == 'y' || input == 'Y')
            {
                //在get_previous_book()中考虑p是否为头节点的情况
                get_previous_book(p)->next = p->next;
                free(p);
            }
        }else{
            printf(">没有找到ISBN为 %s 的图书.
",isbn);
        }
 
        printf(">继续删除其它图书吗?(y or n)");
        fflush(stdin);
        input = getchar();
    }
}

9.保存图书

1.函数名称:save_books

函数功能:管理员在操作主菜单中选择5调用本函数,用来保存图书的信息。函数中通过调用函数save_books_to_file()将图书信息保存到文件中,并给出提示新。具体实现如下:

void save_books()
{
    save_books_to_file();
    printf(">保存成功! 按任意键返回...");
    fflush(stdin);
    getchar();
}

2.函数名称:save_books_to_file

函数功能:将图书信息保存到文件中, 具体实现如下:

void save_books_to_file()
{
    //wb 表示以只写方式打开一个二进制文件
    //这里以只写方式打开二进制文件BOOK_FILE
    //因为fwrite和fread的写与读都是针对二进制而言的
    FILE* fp = fopen(BOOK_FILE,"wb");
    book *p = first_book;
    while(p != NULL)
    {
        fwrite(&(p->bi),sizeof(book_info),1,fp);
        //文件指针偏移量,每次都置为文件尾部
        fseek(fp,0,SEEK_END);
        p =p->next;
    }
    fclose(fp);
}

为了完成图书管理功能,还需要一些辅助函数,它们的实现如下:

1.图书模块初始化

函数名称:init_book

函数功能:图书模块的初始化,如果图书文件不存在,创建一个图书文件,如果创建文件失败,给出提示信息。具体实现如下:

//图书模块初始化
void init_book()
{
    FILE* fp = NULL;
    fp = fopen(BOOK_FILE ,"r");
    if(fp == NULL)
    {
        fp = fopen(BOOK_FILE,"w");
        if(fp == NULL)
        {
            printf("不能创建文件,按任意键退出...");
            fflush(stdin);
            exit(0);
        }
    }
    fclose(fp);
}

2.加载图书信息

函数名称:load_books

函数功能:从图书文件中将图书信息加载到图书单链表中。

处理流程:1.定义图书节点b 和last, 2初始化b,3打开图书文件,4.从文件中逐个读取图书信息,读出的图书信息放在节点b中。如果是第一本书,直接将b赋值给头节点(first_book),否则找到链表的最后一个节点,将b连到最后一个节点后面,5.释放节点b,6.关闭文件。具体实现如下:

//加载图书信息
void load_books()
{
    book* b = NULL;
    book* last = NULL;
    FILE* fp = NULL;
    int count = 0;
    b = (book*)malloc(sizeof(book));
    //if(NULL == b) throw printf("节点创建失败...
");
    assert(b != NULL); //断言b 不为空,否则抛出异常
    memset(b,0,sizeof(book));
    b->next = NULL;
    fp = fopen(BOOK_FILE,"rb");
 
    //用fread通过文件指针fp 把图书文件中的信息读取到b结构体中内
    //每次读取一个book_info大小的数据,每调用一次fread,只读取
    //一次数据
    while(fread(&(b->bi),sizeof(book_info),1,fp) == 1)
    {
        //读取完的数据将链接到当前图书链表的最后一个节点的后面
        if(first_book == NULL)
        {
            first_book = b;
        }else{
            last = get_last_book();
            last->next = b;
        }
        count++;
        //将文件指针移动到下一本书的位置
        fseek(fp,count*sizeof(book_info),SEEK_SET);
        b = (book*)malloc(sizeof(book));
    //    if(NULL == b) throw printf("节点创建失败...
");
        assert(b != NULL);
        b->next = NULL;
    }
    free(b);
    b = NULL;
    fclose(fp);
}

3.清除图书链表

函数名称:clear_books

函数功能:从内存中将图书链表清空。具体实现如下:

//清除图书链表
void clear_books()
{
    book* p = NULL;
    //从链表头节点开始清空
    //如果链表中有2本以上的图书 则进入while循环体
    while(first_book != NULL)
    {
        if(first_book->next != NULL)
        {
            p = first_book;
            //头节点向后移动一位
            first_book = first_book->next;
            free(p);//释放原来的头节点
            p = NULL;
        }else{//清除链表中最后一本书
            free(first_book);
            first_book = NULL;
        }
    }
}

4.查找字符串

函数名称:findstr

函数功能:查找字符串str是否在字符串source中,如果在,返回str在source中的位置,否则返回-1,该函数能够实现图书的模糊查找。实现原理:

首先用变量source存储从单链表中获取到的要对比的该字段的值,用str存储用户输入的待查的字符串,然后通过strlen()函数分别得到这个两个字符串的长度。对比分下面几种情况:

1.如果待查的字符串str的长度大于从单链表中获取的该字段值的长度,则肯定不符合查找的结果!

2.如果待查找的字符串str长度为0(说明没有输入待查找字符),或者是从单链表中获取到的该字段值的长度为0(可能单链表目前为空链表中)那么显然也是找不到符合条件的结果。

3.如果待查找字符str的长度和从单链表中获得到的该字段值的长度相等,那么这时就要通过strcmp()函数来对比,此时的对比属于精确比对。如果strcmp()返回值为0,那么就找到了符合条件的结果。

4.如果待查字符串str的长度小于从单链表中获取到该字段的字符串source的长度,那么此时就需要进行模糊查找了。模糊查找就是从source字符串的第一个字符开始,以str的长度为单位分成若干段,然后逐段与str进行比对,每一段比对是通过单个字符比对来完成的,总共进行m-n+1次对比。如果发现有某一段比对完全匹配,则算找到了一个符合条件的结果,至此一次模糊查找即结束。然后遍历整个单链表,把所有符合条件的结果都找出来显示。

模糊查找举例说明:

比如待查书名的字符串str = ngs , 从单链表中抽取的该字段值即取得某个节点的书名的值source = Strings

很明显 strlen(source) > strlen(str) 属于上面分析的第4种需要模糊查找的情况,模糊查找通过两个for循环来控制,第一个for用来分割source字符串,第二个for用来控制每次对比的长度,实现代码分析如下:

source = Strings;      

str = ngs;

m = strlen(source);

n = strlen(str);

if(m > n)

for(i = 0; i <(m-n+1);i++) //第一个for循环

for(j = o ; j < n ; j++) // 第二个for循环

   if(source[i+j] != str[j]) break;  

显然上面的 m = 7 , n = 3 ,  那么 m – n = 4

i  = 0 ; i < (m – n +1) , 那么i的取值是 0,1,2,3,4

j = 0 , j < n ,那么j 的取值是 0, 1,2

source 字符串的内存布局 ( source = Strings )

source字符内容

S

t

r

i

n

g

s

字符数组下标

0

1

2

3

4

5

6

str字符串的内存布局 ( str  = ngs )

str字符内容

n

g

s

字符数组下标

0

1

2

第一轮循环:

i = 0 , 此时j = 0 j <n  对比分别是

source [i+j]  = source [0] = 'S' 对比 str[j] = str[0] = 'n' 明显不相等 如果在实际对比中此轮对比就结束了

source[i+j] = source [1] = 't' 对比 str[j] = str[1] = 'g'

source [i+j] = source [2] = 'r' 对比 str [j] = str[2] = 's'

每次对比三个字符长度即一个str字符长度。

然后进行i = 1, 2, 直到i = 4 对比完成,如果此时都没有找到匹配的,则当前取出的这个字段值不符合条件,继续提取单链表中该字段中的下一个值进行查找,遍历整个单链表。

在这个对比过程中,如果发现某一轮对比完全符合,例如上面的当i  = 4 这一轮对比时 双方取出的三个字符都是ngs 那么此时就完全符合, 那么此时就算找到一个符合条件的结果了!

具体实现如下:

//模糊查找字符串
int findstr(char* source, char* str)
{
    int pos = -1;
    int i = 0;
    int j = 0;
    int m = strlen(source);//计算单链表中相应字段的长度
    int n = strlen(str);//计算待查找字符串的长度
    if(m == 0 || n == 0 || m <n) 
    {   //m == 0 : 如果原单链表中没有这个字段值,即目前单链表是空的
        //如果没有输入字符,或输入空字符,此时待查字符长度为0
        //或者原始字段值的长度小于待查字符长度
        //都直接返回 值为-1的pos变量,表示没有符合待查字符的字段值
        return pos;
    }
    //如果得到单链表中相应字段值与待查字段值长度相等
    if(m == n)
    {    //则直接用strcmp对比
        //strcmp(str1,str2);
        //如果str1字符串的长度小于str2 则strcmp返回一个小于0的值
        //如果str1字符串的长度大于str2,则strcmp返回一个大于0的值
        //如果str1字符串的内容和str2的内容一模一样,则返回值为0
        if(strcmp(source, str) == 0)
        {
            return 0;
        }else{
            return -1;
        }
    }
    //模糊查找实现
    //如果单链表中相应字段值的长度大于待查找字符串的长度
    //那么就启用模糊查找函数,实现原理即将source字符串和
    //待查字符串对比(m-n-1)每次对比长度为待查字符串的长度
    for(i = 0; i <=(m-n);i++)
    {
        pos =i;
        for(j = 0; j<n;j++)
        {
            if(source[i+j] != str[j])
            {
                pos = -1; 
                break;
            }
        }
        if(pos != -1)
        {
            break;
        }
    }
    return pos;
}

5.取得链表中最后一个节点

函数名称:get_last_book

函数功能:取得图书链表中的最后一个节点。具体实现如下:

//获取链表中的最后一个节点,并返回该节点指针
book* get_last_book()
{
    book* p = first_book;
    //p == NULL 表明为空链表
    if(p == NULL)
    {
        return p;
    }
    while((NULL != p)&&(p->next != NULL))
    {
        p  = p->next ;
    }
    return p;
}

6.输入图书信息

函数名称:input_book

函数功能:给出提示信息,提示用户输入图书信息。具体实现如下:

void input_book(book_info* info)
{
    printf(">请输入书名(最大长度为 %d):",MAX_BOOK_NAME);
    fflush(stdin);
    /*scanf("%s",info->book_name);*/  //不能接收带空格的字符串
    scanf("%[^
]",info->book_name); //可以接收带空格的字符串
    printf(">请输入作者(最大长度为 %d):",MAX_AUTHOR);
    fflush(stdin);
    scanf("%s",info->author);
    printf(">请输入出版社(最大长度为 %d):",MAX_PUBLISHER);
    fflush(stdin);
    scanf("%s",info->publisher);
    printf(">请输入出版日期(最大长度为 %d):",MAX_DATE);
    fflush(stdin);
    scanf("%[^
]",info->pub_date);
    printf(">请输入ISBN(最大长度为 %d):",MAX_ISBN);
    fflush(stdin);
    scanf("%s",info->ISBN);
    printf(">请输入页数:");
    scanf("%d",&(info->pages));
}

7.显示图书信息

函数名称:show_book

函数功能:显示图书信息。 具体实现如下:

void show_book(book_info* info)
{   
    printf("--------------------------------------------
");
    printf("    书名: %s
",info->book_name);
    printf("    作者: %s
",info->author);
    printf("  出版社: %s
",info->publisher);
    printf("出版日期: %s
",info->pub_date);
    printf("…………………………………………………………
");
    printf("    ISBN: %s
",info->ISBN);
    printf("    页数: %d
",info->pages);
    printf("
");
}

8.取得图书节点p的前驱节点

函数名称:get_previous_book

函数功能:取得图书节点p的前驱节点。具体实现如下:

 
book* get_previous_book(book* p)
{
    book* previous = first_book;
    while(previous != NULL)
    {
        if(previous->next == p)
        {
            break;
        }
        previous = previous->next;
    }
    return previous;
}
至此,图书管理模块的功能基本实现完毕,接下来将要实现用户管理模块的功能,具体实现请转入:<< C项目实践--图书管理系统(3)>>
原文地址:https://www.cnblogs.com/AI-Algorithms/p/3395075.html