一、前言
作为一只大四狗,最近还跟着大二同学修了一门课(当然不是之前没通过啦),课程是高级语言课程设计,高级语言指的是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;
}
[完]