C語言圖書管理借閱系統——ncurses庫的使用

一、前言

作爲一隻大四狗,最近還跟着大二同學修了一門課(當然不是之前沒通過啦),課程是高級語言課程設計,高級語言指的是C語言 :),內容是做一個XX管理系統,我選擇了圖書管理系統,先介紹下我做的系統:

  • 主要功能:
    • 讀者信息管理:添加、修改、刪除、查詢讀者信息。
    • 圖書信息管理:添加圖書、修改圖書、刪除圖書、查詢圖書
    • 圖書借閱歸還:圖書借閱和歸還,以及列出借閱情況。
    • 信息統計彙總:圖書總量統計、圖書借閱統計等。
  • 日誌功能:記錄用戶、圖書、借閱相關信息的日誌。
  • 參與對象:管理員和用戶,管理員主要指圖書館相關工作負責人員,用戶指老師或者教工,可以從圖書館借書。
  • 數據存儲格式:文件。
  • 數據組織方式:鏈表。
  • 界面:ncurses庫。
  • 其它:CMake組織項目、GitHub版本控制:代碼地址 、Linux操作系統運行。

爲了體現我大學四年也不是白唸的,當然得體現出逼格,那就從界面下手,於是我選擇了ncurses這個終端字符庫,最後的界面是這樣的:

這裏寫圖片描述

整個界面分爲三部分,上面顯示系統名稱和時間,用戶登錄之後還會顯示用戶名;左下是整個程序的功能菜單部分;右下是系統日誌,負責動態顯示添加用戶、添加圖書、借閱書籍等操作;三個部分由三個線程負責,但是由於ncurses庫本身不是線程安全的(多個線程同時操作輸出會出現問題),因此需要用互斥鎖來互斥控制輸出流程,後邊會詳細說。

二、ncueses庫的使用

(1)介紹
簡而言之就是一個終端下使用的,可以讓你改變字符輸出位置輸出顏色並且創建窗口的圖形庫,更多介紹參見這裏
(2)安裝

$ sudo yum install ncurses-devel   //RedHat
$ sudo apt-get install libncurses5-dev libncursesw5-dev  //Ubuntu

(3)使用
有關它的使用,參考下面兩篇文章和作者 非常詳細 的代碼示例,基本上就可以上手了:

文章一:NCURSES 函數簡要參考:講解ncurses的初始化、輸入輸出、顏色、窗口等等。
文章二:NCURSES Programming HOWTO中文版:作者有許多小例子。
參考三:代碼示例

三、遇到的問題

(1)ncurses圖形庫不是線程安全的,之前設計的界面如下所示:

這裏寫圖片描述

但是發現三個線程同時輸出之後亂碼,最後這個問題在stackoverflow上得到了明確: https://stackoverflow.com/questions/47878870/when-use-ncurses-in-multi-threaded-the-terminal-garbled,確實是由於線程安全問題引起的,最後加互斥鎖解決,簡易代碼如下:

#include <stdio.h>
#include <ncurses.h>
#include <pthread.h>
#include <time.h>
#include <unistd.h>

pthread_mutex_t MUTEX;  /* mutex for sync display */

#define LOCK pthread_mutex_lock(&MUTEX)
#define UNLOCK pthread_mutex_unlock(&MUTEX)

typedef struct _WIN_struct {
    int startx, starty;
    int height, width;
} WIN;

WIN winTitle;      /* title win */
WIN winMenu;   /* Main menu win */
WIN winNews;       /* win news */

WINDOW *create_newwin(int height, int width, int starty, int startx) {
    WINDOW *local_win;
    local_win = newwin(height, width, starty, startx);
    box(local_win, 0, 0);
    wrefresh(local_win);
    return local_win;
}

char *getTimeNow() {
    time_t rawtime;
    struct tm *timeinfo;
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    return asctime(timeinfo);
}

void *threadfunc_title(void *p) {
    WINDOW *windowTitle;
    LOCK;
    windowTitle = create_newwin(winTitle.height, winTitle.width, winTitle.starty, winTitle.startx);
    UNLOCK;

    /* show title and time */
    for (;;) {
        sleep(1);
    }

}

void *threadfunc_menu(void *p) {
    WINDOW *windowMenu;
    LOCK;
    windowMenu = create_newwin(winMenu.height, winMenu.width, winMenu.starty, winMenu.startx);
    UNLOCK;

    for (;;) {
        /* now do nothing */
        sleep(1);
    }

}

void *threadfunc_news(void *p) {
    WINDOW *windowNews;
    LOCK;
    windowNews = create_newwin(winNews.height, winNews.width, winNews.starty, winNews.startx);
    UNLOCK;

    for (;;) {
        sleep(1);
    }
}

void initWin(WIN *p_win, int height, int width, int starty, int startx) {
    p_win->height = height;
    p_win->width = width;
    p_win->starty = starty;
    p_win->startx = startx;
}


int main(int argc, char *argv[])
{
    pthread_t pidTitle;
    pthread_t pidMenu;
    pthread_t pidNews;

    initscr();
    start_color();
    cbreak();
    keypad(stdscr, TRUE);
    noecho();

    /* init location */
    initWin(&winTitle, LINES*0.25, COLS, 0 , 0);
    initWin(&winMenu, LINES*0.75, COLS*0.60, LINES*0.25, 0);
    initWin(&winNews, LINES*0.75, COLS*0.40, LINES*0.25, COLS*0.60);

    pthread_create(&pidTitle, NULL, threadfunc_title, NULL);
    pthread_create(&pidMenu, NULL, threadfunc_menu, NULL);
    pthread_create(&pidNews, NULL, threadfunc_news, NULL);

    pthread_join(pidTitle, NULL);
    pthread_join(pidMenu, NULL);
    pthread_join(pidNews, NULL);

    endwin();
    return 0;
}

(2)while (!feof(fp)) 來判斷文件結尾是不可靠的,feof(fp)判斷文件結尾指的是:文件最後一個字符的下一個字符,具體參考 https://baike.baidu.com/item/feof/10942186?fr=aladdin ,使用 fread的弊端是它的返回值不能區分是到了文件結尾還是遇到了錯誤:

這裏寫圖片描述

這是之前的代碼:

int readUser() {
    FILE *fp;
    user *p = (user *) malloc(sizeof(user));
    user *q = (user *) malloc(sizeof(user));
    USER_HEAD = p;
    USER_MAXID = -1;

    fp = fopen(USER_PATH, "r+");
    if (fp == NULL) {
        fp = fopen(USER_PATH, "w+");
        free(q);
        return 0;
    }

    while (!feof(fp)) {
        fscanf(fp, "%d %s %s %s %s %d\n", &(q->user_id), q->user_stid,
               q->user_name, q->user_address, q->user_mail, &(q->user_status));

        /* update USER_MAXID*/
        if (q->user_id > USER_MAXID) {
            USER_MAXID = q->user_id;
        }

        p->next = q;
        p = q;
        q = (user *) malloc(sizeof(user));
    }

    p->next = NULL;
    free(q);
    fclose(fp);
    return 0;
}

最後使用read讀取,加O_CREATE也可以防止文件不存在:

int readUser() {
    int fd;
    int size;
    user *p = (user *) malloc(sizeof(user));
    user *q = (user *) malloc(sizeof(user));
    USER_HEAD = p;
    USER_MAXID = 0;

    fd = open(USER_PATH, O_RDONLY | O_CREAT, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        exit(1);
    }

    while ((size = read(fd, q, sizeof(user))) != 0) {
        if (q->user_id > USER_MAXID) {
            USER_MAXID = q->user_id;
        }
        p->next = q;
        p = q;
        q = (user *) malloc(sizeof(user));
    }

    p->next = NULL;
    free(q);
    close(fd);
    return 0;
}

(3)c語言如何獲取系統時間:使用localtime()和asctime()返回的字符串中會包含\n,不太友好,這可能是歷史原因,具體參考:http://www.developerq.com/article/1494738998,自己封裝一個去掉\n字符,代碼如下:

char *getTimeNow() {
    char *timestr;
    time_t rawtime;
    struct tm *timeinfo;
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    timestr = asctime(timeinfo);
    /* del \n */
    timestr[strlen(timestr) - 1] = '\0';
    return timestr;
}

[完]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章