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. 留下建议或意见吧~

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值