一、前言
作爲一隻大四狗,最近還跟着大二同學修了一門課(當然不是之前沒通過啦),課程是高級語言課程設計,高級語言指的是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;
}
[完]