C語言多用戶通訊錄

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 */
}

4. 留下建議或意見吧~

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