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

[完]

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杨博东的博客

请我喝瓶可乐鼓励下~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值