1. TODO
- 用戶登陸的文件加密、輸入密碼加密
- 中英文菜單切換
- 界面美化
- 通訊錄中重名的情況處理
2. BUG
2018 12 03
- 菜單邏輯bug
- 文件保存再次讀取後會有丟失
- 只有一個用戶時,刪除用戶出現bug
- 通訊錄擴容後會segment fluat 11
3. CODE
main.c
#include "contact.h"
int main(int argc, const char * argv[])
{
LoginCtrl();
return 0;
}
contact.h
#ifndef contact_h
#define contact_h
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
typedef enum USIZE
{
NAME_SIZE = 20,
SEX_SIZE = 5,
AGE_SIZE = 3,
TEL_SIZE = 15,
ADDR_SIZE = 100,
PASSWORD_SIZE = 18,
INIT_CONTACT_SIZE = 2,
}usize;
typedef struct User
{
char user_name[NAME_SIZE];
char user_password[PASSWORD_SIZE];
}user, *p_user, **pp_user;
typedef struct People
{
char peo_name[NAME_SIZE];
char peo_sex[SEX_SIZE];
char peo_age[AGE_SIZE];
char peo_tel[TEL_SIZE];
char peo_addr[ADDR_SIZE];
}people, *p_people, **pp_people;
typedef struct Contact
{
int con_total;
int con_now;
people peoples[0];
}contact, *p_contact, **pp_contact;
// 日誌
// leval:INFO,WARNING,ERROR
void LogInfo(const char * leval, const char * msg);
// 未登錄的菜單
// 登陸,註冊
void MenuUnlogin(void);
// 登陸
// 登陸成功則進入功能菜單,否則返回0
int UserLogin(void);
// 註冊
// 註冊成功返回1,否則返回0
int UserRegist(void);
// 登陸控制
void LoginCtrl(void);
// 功能主控函數
// 參數:當前登陸的用戶
void MainCtrl(p_user pu);
// 登陸成功後的功能菜單
void MainMenu(p_user pu);
// 初始化通訊錄
// 參數:當前登陸的用戶
// 返回值:初始化好的通訊錄指針。如果是首次登陸(不存在目前用戶的通訊錄文件)則創建新文件,否則加載已有文件
p_contact ContactInit(p_user pu);
// 根據用戶名獲取當前的通訊錄文件名
char * GetFileName(p_user pu);
// 判斷通訊錄是否爲滿
// 滿了返回1,否則返回0
int ContactIsFull(p_contact pct);
// 判斷通訊錄是否爲空
// 是返回1,否則返回0
int ContactIsEmpty(p_contact pct);
// 增加一個聯繫人
// 參數:當前打開的通訊錄
void AddPeople(p_contact pct);
// 輸入聯繫人的信息
void GetPeopleInfo(p_people pp);
// 按姓名查找聯繫人
void SearchPeople(p_contact pct);
// 輸出聯繫人信息
void DispPeopleInfo(p_people pp);
// 根據聯繫人姓名排序
void SortPeople(p_contact pct);
// 比較函數(回調函數)
int NameCmpFun(const void * n1, const void * n2);
// 顯示所有聯繫人信息
void DispAllPeople(p_contact pct);
// 刪除單個聯繫人
void DeletePeople(p_contact pct);
// 清空所有聯繫人
// 參數:傳入當前打開的通訊錄文件,當前用戶(驗證密碼用)
void ClearAllPeople(p_contact pct, p_user pu);
// 擴容函數
// 擴容成功返回1 否則返回0
int IncContact(pp_contact ppct);
// 在程序退出的時候保存信息到文件
void SaveToFile(p_contact pct, p_user pu);
#endif /* contact_h */
contact.c
#include "contact.h"
void LogInfo(const char * leval, const char * msg)
{
assert(leval);
assert(msg);
printf("#%s:%s\n", leval, msg);
}
int ContactIsFull(p_contact pct)
{
assert(pct);
return pct->con_now == pct->con_total ? 1 : 0;
}
int ContactIsEmpty(p_contact pct)
{
assert(pct);
return pct->con_now == 0 ? 1 : 0;
}
void MenuUnlogin(void)
{
LogInfo("INFO", "通訊錄 V2.0");
printf("########################\n");
printf("#### 0. 退出 ####\n");
printf("#### 1. 登陸 ####\n");
printf("#### 2. 註冊 ####\n");
printf("########################\n");
printf(">");
}
void MainMenu(p_user pu)
{
assert(pu);
char msg[30] = "當前用戶";strcat(msg, pu->user_name);
LogInfo("INFO", msg);
printf("##############################\n");
printf("#### 0. 退出 ####\n");
printf("#### 1. 添加聯繫人 ####\n");
printf("#### 2. 查找聯繫人 ####\n");
printf("#### 3. 排序聯繫人 ####\n");
printf("#### 4. 刪除聯繫人 ####\n");
printf("#### 5. 列出所有聯繫人 ####\n");
printf("#### 6. 清空所有聯繫人 ####\n");
printf("##############################\n");
printf(">");
}
char * GetFileName(p_user pu)
{
assert(pu);
char * contact_file_name = malloc(30);
strcpy(contact_file_name, pu->user_name);
strcat(contact_file_name, ".txt");
return contact_file_name;
}
int UserLogin(void)
{
user u;
FILE * fp = NULL;
printf("用戶名>");
scanf("%s", u.user_name);
fp = fopen(u.user_name, "r");
if(!fp)
{
char msg[30] = "用戶";strcat(msg, u.user_name);strcat(msg, "不存在");
LogInfo("ERROR", msg);
fclose(fp);
}
else
{
char pwd[PASSWORD_SIZE];
fscanf(fp, "%s", u.user_password);
printf("密碼>");
scanf("%s", pwd);
if(0 == strcmp(pwd, u.user_password))
{
char msg[30] = "歡迎";strcat(msg, u.user_name);
LogInfo("INFO", msg);
// GOTO: MainCtrl
MainCtrl(&u); /* 登錄成功,跳到主要功能控制函數,並傳入登陸的用戶信息 */
exit(0);
}
}
return 0;
}
int UserRegist(void)
{
user u;
FILE * fp = NULL;
int flag = 1;
while(flag)
{
printf("用戶名>");
scanf("%s", u.user_name);
fp = fopen(u.user_name, "r");
if(fp) // 如果打開文件成功,說明存在此用戶
{
fclose(fp);
char msg[30] = "用戶名";strcat(msg, u.user_name);strcat(msg, "已被註冊");
LogInfo("ERROR", msg);
flag = 1;
continue;
}
flag = 0;
}
printf("密碼>");
scanf("%s", u.user_password);
fp = fopen(u.user_name, "a+");
if(!fp)
{
LogInfo("ERROR", "創建用戶文件失敗,請檢查當前目錄權限");
return 0;
}
else
{
fprintf(fp, "%s", u.user_password);
fclose(fp);
LogInfo("INFO", "註冊成功");
}
return 0;
}
void LoginCtrl(void)
{
int select = 1;
while(select)
{
MenuUnlogin();
scanf("%d", &select);
switch(select)
{
case 0: // exit
LogInfo("INFO", "再見");
exit(0);
case 1: // login
UserLogin();
break;
case 2: // registe
UserRegist();
break;
default:
LogInfo("INFO", "輸入有誤");
break;
}
}
}
// 初始化通訊錄
// 參數:當前登陸的用戶
// 返回值:初始化好的通訊錄指針。如果是首次登陸(不存在目前用戶的通訊錄文件)則創建新文件,否則加載已有文件
p_contact ContactInit(p_user pu)
{
assert(pu);
p_contact pct = malloc(INIT_CONTACT_SIZE*sizeof(people) + sizeof(contact));
if(!pct)
{
LogInfo("ERROR", "初始化通訊錄失敗,分配通訊錄空間失敗");
return NULL;
}
char contact_file_name[30];
strcpy(contact_file_name, GetFileName(pu));
FILE * fp = fopen(contact_file_name, "r");
if(!fp) // 如果沒有文件
{
LogInfo("INFO", "正在創建當前用戶的通訊錄文件");
fp = fopen(contact_file_name, "wb+");
if(!fp) // 創建文件失敗
{
LogInfo("ERROR", "創建文件失敗");
return NULL;
}
else // 創建文件成功
{
fclose(fp);
LogInfo("INFO", "創建當前用戶的通訊錄文件成功");
pct->con_total = INIT_CONTACT_SIZE;
pct->con_now = 0;
}
}
else // 存在文件
{
fp = fopen(contact_file_name, "wb+");
fread(pct, sizeof(*pct), 1, fp);
pct = malloc(sizeof(people) * pct->con_total + sizeof(contact));
if(!pct)
{
LogInfo("ERROR", "分配空間失敗");
return NULL;
}
else
{
fread(pct->peoples, sizeof(people)*pct->con_now, 1, fp);
fclose(fp);
return pct;
}
}
return pct;
}
int IncContact(pp_contact ppct)
{
assert(ppct);
int new_total = (*ppct)->con_total * 2;
int new_size = new_total * sizeof(people) * 2 + sizeof(contact);
p_contact pct = (p_contact)realloc(*ppct, new_size);
if(!pct)
{
LogInfo("ERROR", "擴容失敗");
return 0;
}
pct->con_total = new_total;
*ppct = pct;
LogInfo("INFO", "擴容成功");
return 1;;
}
void AddPeople(p_contact pct)
{
assert(pct);
if(ContactIsFull(pct))
{
LogInfo("INFO", "通訊錄已滿,正在擴容");
IncContact(&pct);
}
GetPeopleInfo(&pct->peoples[pct->con_now]);
char msg[30] = "已添加聯繫人";
strcat(msg, pct->peoples[pct->con_now].peo_name);
LogInfo("INFO", msg);
pct->con_now ++;
}
void GetPeopleInfo(p_people pp)
{
assert(pp);
printf("姓名>");
scanf("%s", pp->peo_name);
printf("性別>");
scanf("%s", pp->peo_sex);
printf("年齡>");
scanf("%s", pp->peo_age);
printf("電話>");
scanf("%s", pp->peo_tel);
printf("住址>");
scanf("%s", pp->peo_addr);
}
void SearchPeople(p_contact pct)
{
assert(pct);
char input_name[NAME_SIZE];
printf("要查找的聯繫人姓名>");
scanf("%s", input_name);
for(int i=0; i<pct->con_now; i++)
{
if(0 == strcmp(input_name, pct->peoples[i].peo_name))
{
DispPeopleInfo(&pct->peoples[i]);
}
}
}
void DispPeopleInfo(p_people pp)
{
assert(pp);
// printf("|姓名\t\t|");
// printf("性別\t\t|");
// printf("年齡\t\t|");
// printf("電話\t\t|");
// printf("住址\t\t|\n");
printf("|%s\t\t", pp->peo_name);
printf("|%s\t\t", pp->peo_sex);
printf("|%s\t\t", pp->peo_age);
printf("|%s\t\t", pp->peo_tel);
printf("|%s\t\t|\n", pp->peo_addr);
}
int NameCmpFun(const void * n1, const void * n2)
{
assert(n1);
assert(n2);
p_people p1 = (p_people)n1;
p_people p2 = (p_people)n2;
return strcmp(p1->peo_name, p2->peo_name);
}
void SortPeople(p_contact pct)
{
assert(pct);
LogInfo("INFO", "正在按姓名排序所有聯繫人");
qsort(&pct->peoples, pct->con_now, sizeof(people), NameCmpFun);
LogInfo("INFO", "排序完成");
}
void DispAllPeople(p_contact pct)
{
assert(pct);
printf("|姓名\t\t|");
printf("性別\t\t|");
printf("年齡\t\t|");
printf("電話\t\t|");
printf("住址\t\t|\n");
for(int i=0; i<pct->con_now; i++)
{
DispPeopleInfo(&pct->peoples[i]);
}
}
void DeletePeople(p_contact pct)
{
assert(pct);
if(ContactIsEmpty(pct))
{
LogInfo("INFO", "通訊錄爲空");
return;
}
/* TODO:優化!刪除首先是要查找,可以利用上面的查找函數!(修改下查找函數的返回值) */
char input_name[NAME_SIZE];
printf("要查找的聯繫人姓名>");
scanf("%s", input_name);
for(int i=0; i<pct->con_now; i++)
{
if(0 == strcmp(input_name, pct->peoples[i].peo_name))
{
memcpy(&pct->peoples[i], &pct->peoples[pct->con_now - 1], sizeof(people));
pct->con_now--;
LogInfo("INFO", "刪除成功");
}
}
}
char * StrToLower(char * str)
{
assert(str);
char * ret = str;
while(*str)
{
*str = tolower(*str);
str++;
}
return ret;
}
void ClearAllPeople(p_contact pct, p_user pu)
{
assert(pct);
assert(pu);
LogInfo("WARNING", "確定?");
// fflush(stdin); // 調試中,輸入6回車後到這裏就會接收到回車
// int YorN = 0;
// scanf("%c*", &YorN);
// YorN = getchar();
printf("(yes/no)>");
char yesORno[5] = {0};
scanf("%s", yesORno);
StrToLower(yesORno);
// puts(yesORno);
if(0 == strcmp(yesORno, "yes"))
{
char pwd[PASSWORD_SIZE];
printf("密碼>");
scanf("%s", pwd);
if(0 == strcmp(pwd, pu->user_password)) // 密碼校驗成功,清空所有聯繫人
{
pct->con_now = 0;
LogInfo("INFO", "清空成功");
}
else
{
LogInfo("ERROR", "密碼錯誤");
return;
}
}
else
{
return;
}
}
void SaveToFile(p_contact pct, p_user pu)
{
assert(pct);
assert(pu);
char * filename = GetFileName(pu);
FILE * fp = fopen(filename, "rb");
if(!fp)
{
LogInfo("ERROR", "保存文件過程中,打開文件失敗");
return;
}
fwrite(pct, sizeof(contact) + pct->con_now * sizeof(people), 1, fp);
LogInfo("INFO", "文件保存成功");
fclose(fp);
}
void MainCtrl(p_user pu)
{
assert(pu);
int select = 1;
p_contact pct = NULL;
// Init contact
pct = ContactInit(pu);
while(select)
{
MainMenu(pu);
scanf("%d", &select);
switch(select)
{
case 0:
SaveToFile(pct, pu);
LogInfo("INFO", "再見");
return; // 在登陸函數中退出
break;
case 1:// add
AddPeople(pct);
break;
case 2:// search
SearchPeople(pct);
break;
case 3:// sort
SortPeople(pct);
break;
case 4:// delete
DeletePeople(pct);
break;
case 5:// show all
DispAllPeople(pct);
break;
case 6:// clear all
ClearAllPeople(pct, pu);
break;
default:
LogInfo("WARNING", "輸入有誤");
break;
}/* switch */
}/* while */
}