- 一、需求分析
- 二、设计思路
- 三、数据
- 1、内存数据结构
- 2、文件数据
- 四、主要功能接口
- 五、问题以及解决方案
- 1.从txt文件中读取数据到内存时
- 2.用scanf函数从stdin中读取输入数据,因错误输入的数据可能导致scanf函数死循环的问题
- 3.itoa函数不是C语言标准库函数,需要实现该函数
- 六、完整代码
- 七、效果截图
一、需求分析
设计一个通讯录管理系统,记录联系人的手机号码,姓名,联系地址,职业,公司,邮箱。可以增加联系人信息,删除联系人信息,修改联系人信息,查找联系人,排序,打印通讯录信息,一键清空通信录等
二、设计思路
1.根据需求分析,需要对数据进行增加,删除,修改,查找,排序等,我选择了双向链表作为内存数据结构,考虑到每个人的手机号码是唯一的,选择了手机号码作为key,同时考虑到数据量很大的情况下,可能会影响排序性能,所以考虑用手机号码的前四位进行分段存储,每一段对应一个链表,所以代码中管理了10000条双向链表,分别对应手机号码前四位0-9999的手机号码段。
2.数据持久化,我选择将通讯录内存数据保存到txt文件中,从内存数据到txt文件,用C语言库函数提供的fopen函数和sprintf函数即可实现,从txt文件数据到内存数据,用C语言库函数fopen函数和strtok函数即可实现。
三、数据 1、内存数据结构
联系人信息
struct People
{
char tel[20]; //手机号码
char name[30]; //姓名
char address[100]; //联系地址
char occupation[20]; //职业
char company[40]; //公司
char email[30]; //邮箱
};
链表中的一个节点
struct People_Node
{
struct People people; //联系人信息
struct People_Node *next; //上一个节点
struct People_Node *pre; //下一个节点
};
一条链表
struct People_List
{
int len; //链表节点总个数
struct People_Node *head; //头节点
struct People_Node *tail; //尾节点
};
2、文件数据
默认 contact.txt 文件(注意最后一行是空白行)
手机号码 姓名 联系地址 职业 公司 邮箱
contact.txt 文件 保存示例(用测试命令生产的数据)
手机号码 姓名 联系地址 职业 公司 邮箱 10121882476 宋八 天津 快递员 云上科技 555@163.com 10698756589 宋八 武汉 老师 云上科技 333@qq.com 12121198969 唐九 武汉 个体 小云直播 666@163.com 14779567330 王五 广西 个体 小葱蔬菜 111@163.com 18817493813 李四 北京 快递员 滕芬网络 111@163.com
四、主要功能接口
增加联系人相关
int add_con(); //增加联系人 int add_people(struct People people); //增加联系人到链表
删除联系人相关
int del_con(); //删除联系人 int del_people(struct People people); //从链表中删除联系人
修改联系人相关
int mod_con(); //修改联系人信息 int mod_people(struct People people); //从链表中修改联系人信息
查找联系人相关
int find_con(); //查找联系人 struct People_Node *find_people(struct People people); //从链表中查找联系人
排序通讯录相关
void sort_con(); //排序通讯录 void sort_people_list(); //按照手机号码从小到大排序通讯录并输出到屏幕
打印通讯录相关
void print_con(); //打印通讯录 void print_people_list(); //打印通讯录列表 void print_people(struct People *p); //打印联系人信息
清空通讯录
int clean_con(); //清空通讯录
退出通讯录
void exit_con(); //退出通讯录
五、问题以及解决方案 1.从txt文件中读取数据到内存时
fprintf(fp, "%s %s %s %s %s %sn", people.tel, people.name, people.address, people.occupation, people.company, people.email);
问题1:如上代码所示,因为用fprintf函数写入到txt文件时,最后一个数据是加了n的,所以从txt文件中读取数据到内存数据时,应该将 替换掉n,再保存在内存中,如下代码所示。
//字符串拷贝剔除掉换行符n
void strcpy_not_n(char *des, char *src)
{
while (*src != ' ')
{
if (*src != 'n')
*des = *src;
else
*des = ' ';
src++;
des++;
}
}
从txt文件中加载数据到内存代码片段
switch (column)
{
case 0:
strcpy(people.tel, str);
break;
case 1:
strcpy(people.name, str);
break;
case 2:
strcpy(people.address, str);
break;
case 3:
strcpy(people.occupation, str);
break;
case 4:
strcpy(people.company, str);
break;
default:
strcpy_not_n(people.email, str); //去掉n
break;
}
str = strtok(NULL, LINE);
column++;
问题2:因为我们存储数据的txt文件,默认最后一行是空白行,所以从txt文件中加载数据到内存的时候,应该过滤掉空白行,不然我们从txt文件中加载数据会多了一个6个数据都为空的记录,具体处理如下所示。
从txt文件中加载数据到内存代码片段
char buf[BUFF_SIZE];
int len = 0;
int row = 0;
while(fgets(buf, BUFF_SIZE, fp))
{
if ((row++) == 0) //第一行是注释 手机号码 姓名 联系地址 职业 公司 邮箱
continue;
if(buf[0] == 'n') //过滤掉读到的是空白行
continue;
2.用scanf函数从stdin中读取输入数据,因错误输入的数据可能导致scanf函数死循环的问题
因为scanf函数,我们从命令行输入数据,并按下enter换行键后,scanf函数会从stdin缓冲区中按照我们格式化的格式读取输入的数据,并且不符合读取格式的数据会跳过,并还留在缓冲区中,直到按照我们的格式读取到完整数据为止,并且输入过多的未读取的数据也还在缓冲区中,所以为了防止残留的stdin中的垃圾数据阻碍我们下一次从stdin缓冲区中读取到不准确的数据,我们应该在scanf函数读取到完整数据后,对stdin缓冲区进行清空,处理如下:
//清空无效的输入
void clean_stdin()
{
char s;
do
{
scanf("%c", &s);
}while(s != 'n');
}
部分代码段
scanf("%s %s %s %s %s", people.name, people.address, people.occupation, people.company, people.email);
clean_stdin();
3.itoa函数不是C语言标准库函数,需要实现该函数
//数字转字符串函数
char *itoa_my(int value, char *str, int radix) {
static char dig_my[] =
"0123456789"
"abcdefghijklmnopqrstuvwxyz";
int n = 0, neg = 0;
unsigned int v;
char* p, *q;
char c;
if (radix == 10 && value < 0) {
v = -value;
neg = 1;
}else
{
v = value;
}
do {
str[n++] = dig_my[v%radix];
v /= radix;
} while (v);
if (neg)
str[n++] = '-';
str[n] = ' ';
for (p = str, q = p + (n-1); p < q; ++p, --q)
c = *p, *p = *q, *q = c;
return str;
}
六、完整代码
contact.h 文件
#ifndef CONTACT_H
#define CONTACT_H
#define CON_TXT_PATH "./contact.txt"
#define BUFF_SIZE 256
#define LINE " " //分割符号
#define COUNT_TEL_MAX 9999
#define RETURN_MAIN ".."
#define RETURN_MAIN_FLAG 100
#define PEOPLE_INFO "手机号码t姓名t联系地址t职业t公司t邮箱n"
enum OPTION
{
TEST = 0,
ADD = 1,
DEL,
MOD,
FIND,
SORT,
PRINT,
CLEAN,
EXIT,
END
};
//联系人信息
struct People
{
char tel[20]; //手机号码
char name[30]; //姓名
char address[100]; //联系地址
char occupation[20]; //职业
char company[40]; //公司
char email[30]; //邮箱
};
//链表中的一个节点
struct People_Node
{
struct People people; //联系人信息
struct People_Node *next; //上一个节点
struct People_Node *pre; //下一个节点
};
//一个链表
struct People_List
{
int len; //链表节点总个数
struct People_Node *head; //头节点
struct People_Node *tail; //尾节点
};
//手机号码前四位对应的链表集合
struct People_List *g_tel[COUNT_TEL_MAX];
void display_menu(); //列出功能菜单
int init_con(); //初始化通讯录
int load_con(); //从txt文件中加载通讯录
int add_con(); //增加联系人
int add_people(struct People people); //增加联系人到链表
int del_con(); //删除联系人
int del_people(struct People people); //从链表中删除联系人
int mod_con(); //修改联系人信息
int mod_people(struct People people); //从链表中修改联系人信息
int find_con(); //查找联系人
struct People_Node *find_people(struct People people); //从链表中查找联系人
void sort_con(); //排序通讯录
void sort_people_list(); //按照手机号码从小到大排序通讯录并输出到屏幕
void print_con(); //打印通讯录
void print_people_list(); //打印通讯录列表
void print_people(struct People *p); //打印联系人信息
int clean_con(); //清空通讯录
void exit_con(); //退出通讯录
int test_cmd(); //测试命令
//辅助函数
void clean_stdin(); //清空无效的输入
int check_return_main(const char *str); //是否返回主菜单
int get_key(const char *tel); //返回手机号码对应的数组数组下标
int is_digit_string(const char *str); //检测str是否为全数字
int check_people(struct People people); //检测输入的格式比如手机号码格式是否正确等
void strcpy_not_n(char *des, char *src); //字符串拷贝剔除掉换行符n
void save_people_list(); //保持数据到txt文件
char *itoa_my(int value, char *str, int radix); //数字转字符串函数
#endif
contact.c 文件
#include#include #include #include #include #include #include "contact.h" extern int errno; int main() { srand((unsigned)time(NULL)); init_con(); enum OPTION op = 0; int r = 0; while(1) { display_menu(); r = scanf("%d", &op); if (r == 0 || op < TEST || op >= END) { clean_stdin(); printf("输入错误,请重新选择功能n"); continue; } switch(op) { case TEST: { while(1) { r = test_cmd(); if (r == RETURN_MAIN_FLAG) break; } break; } case ADD: { while(1) { r = add_con(); if (r == RETURN_MAIN_FLAG) break; } break; } case DEL: { while(1) { r = del_con(); if (r == RETURN_MAIN_FLAG) break; } break; } case MOD: { while(1) { r = mod_con(); if (r == RETURN_MAIN_FLAG) break; } break; } case FIND: { while(1) { r = find_con(); if (r == RETURN_MAIN_FLAG) break; } break; } case SORT: sort_con(); break; case PRINT: print_con(); break; case CLEAN: { while(1) { r = clean_con(); if (r != 0) break; } break; } case EXIT: exit_con(); break; default: break; } } return 0; } //列出功能菜单 void display_menu() { printf("*****************************************************************************************n"); printf("** 通讯录 **n"); printf("** **n"); printf("** 请选择功能 **n"); printf("** 0:测试命令 **n"); printf("** 1:增加联系人 **n"); printf("** 2:删除联系人 **n"); printf("** 3:修改联系人 **n"); printf("** 4:查找联系人 **n"); printf("** 5:排序联系人 **n"); printf("** 6:打印通讯录 **n"); printf("** 7:清空通讯录 **n"); printf("** 8:退出 **n"); printf("** **n"); printf("** 在子功能下输入:.. 返回到主目录 **n"); printf("** **n"); printf("*****************************************************************************************n"); } //初始化通讯录 int init_con() { int i = 0; for (i = 0; i < COUNT_TEL_MAX; i++) { struct People_List *list = (struct People_List *)malloc(sizeof(struct People_List)); list->len = 0; list->head = NULL; list->tail = NULL; g_tel[i] = list; } return load_con(); } //从contact.txt文件中加载通讯录 int load_con() { FILE *fp = fopen(CON_TXT_PATH, "r"); if (!fp) { fprintf(stderr, "打开%s文件错误:%sn", CON_TXT_PATH, strerror(errno)); return 0; } char buf[BUFF_SIZE]; int len = 0; int row = 0; while(fgets(buf, BUFF_SIZE, fp)) { if ((row++) == 0) //第一行是注释 手机号码 姓名 联系地址 职业 公司 邮箱 continue; if(buf[0] == 'n') //过滤掉读到的是空白行 continue; int column = 0; struct People people; char *str = strtok(buf, LINE); while(str) { switch (column) { case 0: strcpy(people.tel, str); break; case 1: strcpy(people.name, str); break; case 2: strcpy(people.address, str); break; case 3: strcpy(people.occupation, str); break; case 4: strcpy(people.company, str); break; default: strcpy_not_n(people.email, str); //去掉n break; } str = strtok(NULL, LINE); column++; } add_people(people); row++; } fclose(fp); //print_people_list(); return 1; } //增加联系人 int add_con() { printf("请输入手机号码 姓名 联系地址 职业 公司 邮箱n"); struct People people; scanf("%s", people.tel); if(check_return_main(people.tel)) { clean_stdin(); return RETURN_MAIN_FLAG; } scanf("%s %s %s %s %s", people.name, people.address, people.occupation, people.company, people.email); clean_stdin(); int r = check_people(people); if (r == -1) return 0; if (find_people(people)) { printf("手机号码:%s 的联系人已经存在!n", people.tel); return 0; } r = add_people(people); if (!r) return 0; //print_people_list(); FILE *fp = fopen(CON_TXT_PATH, "a+"); if (!fp) { fprintf(stderr, "打开%s文件错误:%sn", CON_TXT_PATH, strerror(errno)); return 0; } fprintf(fp, "%s %s %s %s %s %sn", people.tel, people.name, people.address, people.occupation, people.company, people.email); fclose(fp); printf("添加联系人成功!n"); return 1; } //增加联系人到链表 int add_people(struct People people) { struct People_List *plist = NULL; //加入手机号码对应的链表库 struct People_Node *pnode = (struct People_Node *)malloc(sizeof(struct People_Node)); if (!pnode) { fprintf(stderr, "申请内存失败:%sn", strerror(errno)); return 0; } pnode->people = people; pnode->pre = NULL; pnode->next = NULL; int key = get_key(people.tel); plist = g_tel[key]; if ((plist->len++) == 0) //链表为空时 { plist->head = pnode; plist->tail = pnode; } else { pnode->pre = plist->tail; plist->tail->next = pnode; plist->tail = pnode; } return 1; } //删除联系人 int del_con() { printf("请输入要删除的联系人的手机号码n"); struct People people; int r = scanf("%s", &people.tel); clean_stdin(); if(check_return_main(people.tel)) return RETURN_MAIN_FLAG; if (r == 0) { printf("输入手机号码格式不正确--------r = %dn", r); return 0; } r = check_people(people); if (r == -1) return 0; return del_people(people); } //从链表中删除联系人 int del_people(struct People people) { int key = get_key(people.tel); struct People_Node *pnode = g_tel[key]->head; int find = 0; while(pnode) { if (strcmp(people.tel, pnode->people.tel) == 0) { find = 1; break; } pnode = pnode->next; } if (!find) { printf("没有找到要删除的联系人n"); return 0; } g_tel[key]->len = g_tel[key]->len - 1; if (pnode == g_tel[key]->head) { g_tel[key]->head = pnode->next; if (g_tel[key]->len == 0) g_tel[key]->tail = NULL; else pnode->next->pre = NULL; } else if (pnode == g_tel[key]->tail) { g_tel[key]->tail = pnode->pre; pnode->pre->next = NULL; } else { pnode->pre->next = pnode->next; pnode->next->pre = pnode->pre; } free(pnode); //print_people_list(); save_people_list(); printf("删除联系人成功!n"); return 1; } //修改联系人信息 int mod_con() { printf("请输入需要修改联系人的 手机号码 姓名 联系地址 职业 公司 邮箱n"); struct People people; scanf("%s", people.tel); if(check_return_main(people.tel)) { clean_stdin(); return RETURN_MAIN_FLAG; } scanf("%s %s %s %s %s", people.name, people.address, people.occupation, people.company, people.email); clean_stdin(); if(check_return_main(people.tel)) return RETURN_MAIN_FLAG; int r = check_people(people); if (r == -1) return 0; return mod_people(people); } //从链表中修改联系人信息 int mod_people(struct People people) { int key = get_key(people.tel); struct People_Node *pnode = g_tel[key]->head; int find = 0; while(pnode) { if (strcmp(people.tel, pnode->people.tel) == 0) { find = 1; break; } pnode = pnode->next; } if (!find) { printf("没有找到要修改的联系人n"); return 0; } pnode->people = people; //print_people_list(); save_people_list(); printf("修改联系人成功!n"); return 1; } //查找联系人 int find_con() { printf("请输入要查找的联系人的手机号码n"); struct People people; int r = scanf("%s", &people.tel); clean_stdin(); if(check_return_main(people.tel)) return RETURN_MAIN_FLAG; if (r == 0) { printf("输入手机号码格式不正确n"); return 0; } r = check_people(people); if (r == -1) return 0; struct People_Node *pnode = find_people(people); if (!pnode) { printf("没有查找到手机号码对应的联系人!n"); return 0; } printf(PEOPLE_INFO); print_people(&pnode->people); return 1; } //从链表中查找联系人 struct People_Node *find_people(struct People people) { int key = get_key(people.tel); struct People_Node *pnode = g_tel[key]->head; while(pnode) { if (strcmp(people.tel, pnode->people.tel) == 0) { return pnode; } pnode = pnode->next; } return NULL; } //排序通讯录 void sort_con() { sort_people_list(); } //按照手机号码从小到大排序通讯录并输出到屏幕 void sort_people_list() { // printf("---------------before sort---------------n"); // print_people_list(); struct People_List *plist = NULL; struct People_Node *pnode = NULL; struct People_Node *pnext = NULL; struct People_Node *pmin = NULL; struct People ptemp; int i = 0; for (i = 0; i < COUNT_TEL_MAX; i++) { plist = g_tel[i]; pnode = plist->head; while (pnode) { pmin = pnode; //ptemp = pnode; pnext = pnode->next; while(pnext) { if (atof(pmin->people.tel) > atof(pnext->people.tel)) pmin = pnext; pnext = pnext->next; } if (pmin != pnode) //当前节点不是最小节点 { ptemp = pnode->people; pnode->people = pmin->people; pmin->people = ptemp; } pnode = pnode->next; } } // printf("---------------after sort---------------n"); // print_people_list(); printf("排序成功!n"); } //打印通讯录 void print_con() { printf("----------开始打印通讯录----------n"); print_people_list(); printf("----------打印通讯录完毕----------n"); } //打印通讯录列表 void print_people_list() { int i = 0; struct People_Node *pnode; printf(PEOPLE_INFO); for (i = 0; i < COUNT_TEL_MAX; i++) { pnode = g_tel[i]->head; while (pnode) { //printf("key = %d ", i); print_people(&(pnode->people)); pnode = pnode->next; } } } //打印联系人信息 void print_people(struct People *p) { printf("%st%st%st%st%st%sn", p->tel, p->name, p->address, p->occupation, p->company, p->email); } //清空通讯录 int clean_con() { printf("是否清空通讯录:y or nn"); char op[100]; int r = scanf("%s", &op); clean_stdin(); if (check_return_main(op) || strcmp(op, "n") == 0) return RETURN_MAIN_FLAG; if (strcmp(op, "y") != 0) { printf("输入:y or n 错误!n"); return 0; } printf("----------开始清空通讯录,开始释放内存数据----------n"); int i = 0; struct People_List *plist; struct People_Node *pcur = NULL; struct People_Node *pnext = NULL; for (i = 0; i < COUNT_TEL_MAX; i++) { plist = g_tel[i]; pcur = plist->head; while (pcur) { pnext = pcur->next; //printf("free node tel = %sn", pcur->people.tel); free(pcur); pcur = pnext; } plist->len = 0; plist->head = NULL; plist->tail = NULL; } save_people_list(); printf("----------释放内存数据成功,清空通讯录成功----------n"); return 1; } //退出通讯录 void exit_con() { printf("----------开始保存通讯录----------n"); save_people_list(); printf("----------开始退出通讯录,开始释放内存数据----------n"); int i = 0; struct People_List *plist; struct People_Node *pcur = NULL; struct People_Node *pnext = NULL; for (i = 0; i < COUNT_TEL_MAX; i++) { plist = g_tel[i]; pcur = plist->head; while (pcur) { pnext = pcur->next; //printf("free node tel = %sn", pcur->people.tel); free(pcur); pcur = pnext; } free(plist); } printf("----------释放内存数据成功,退出通讯录成功----------n"); exit(1); } //清空无效的输入 void clean_stdin() { char s; do { scanf("%c", &s); }while(s != 'n'); } //是否返回主菜单 int check_return_main(const char *str) { if (strcmp(str, RETURN_MAIN) == 0) return 1; return 0; } //返回手机号码对应的数组数组下标 int get_key(const char *tel) { char str[5]; memcpy(str, tel, 4); int key = atoi(str); if (key > COUNT_TEL_MAX || key < 0) return -1; return key; } //检测str是否为全数字 int is_digit_string(const char *str) { if (*str == ' ') return 0; while (*str != ' ') { if(!isdigit(*str++)) return 0; } return 1; } // 检测输入的格式比如手机号码格式是否正确等 int check_people(struct People people) { if (!is_digit_string(people.tel)) //是否是数字 { printf("手机号码格式不全是数字n"); return -1; } if (strlen(people.tel) < 4) //手机号码是否过短 { printf("手机号码不能低于4位n"); return -1; } return 0; } //字符串拷贝剔除掉换行符n void strcpy_not_n(char *des, char *src) { while (*src != ' ') { if (*src != 'n') *des = *src; else *des = ' '; src++; des++; } } //保持数据到txt文件 void save_people_list() { FILE *fp = fopen(CON_TXT_PATH, "w"); if (!fp) { fprintf(stderr, "打开%s文件错误:%sn", CON_TXT_PATH, strerror(errno)); return ; } int i = 0; fprintf(fp, "手机号码 姓名 联系地址 职业 公司 邮箱n"); int interval = 3; char dest[1024] = ""; char str[256] = ""; int record = 0; struct People_Node *pnode = NULL; for (i = 0; i < COUNT_TEL_MAX; i++) { pnode = g_tel[i]->head; while (pnode) { record = record + 1; sprintf(str, "%s %s %s %s %s %sn", pnode->people.tel, pnode->people.name, pnode->people.address, pnode->people.occupation, pnode->people.company, pnode->people.email); strcat(dest, str); strcpy(str, ""); if (record%interval == 0) { fprintf(fp, dest); record = 0; strcpy(dest, ""); } pnode = pnode->next; } } if (strcmp(dest, "") != 0) fprintf(fp, dest); fclose(fp); } //测试命令 int test_cmd() { printf("1:随机增加n个联系人,例如:1 1000 就是随机增加1000个联系人n"); char cmd1[20]; int cmd2 = 0; scanf("%s", &cmd1); if(check_return_main(cmd1)) { clean_stdin(); return RETURN_MAIN_FLAG; } scanf("%d", &cmd2); clean_stdin(); if (atoi(cmd1) != 1 || cmd2 <= 0) { printf("输入的测试命令格式不对n"); return 0; } char *family_names[10] = { "李一一","欧阳二","张三","李四","王五", "易六","高七","宋八","唐九","杨十" }; char *address[10] = { "广州","上海","天津","北京","武汉", "拉萨","新疆","河北","河南","广西" }; char *occupation[10] = { "老师","快递员","工程师","个体","医生", "护士","培训师","务农","健身教练","CEO" }; char *company[10] = { "滕芬网络","云上科技","佳佳超市","水煮鱼餐饮","来一杯奶茶", "好多好吃","小葱蔬菜","水果批发商","酸菜鱼特产","小云直播" }; char *email[10] = { "111@163.com","222@qq.com","333@qq.com","444@163.com","555@163.com", "666@163.com","777@163.com","888@qq.com","999@qq.com","123@163.com" }; struct People people; int i = 0, j = 0; int temp; char tel[12] = "1"; char str[2]; for (i = 0; i < cmd2; i ++) { for (j = 0; j < 10; j++) { temp = (int)rand()%10; //printf("%dn", temp); itoa_my(temp, str, 10); strcat(tel, str); strcpy(str, ""); } strcpy(people.tel, tel); strcpy(people.name, family_names[(int)rand()%10]); strcpy(people.address, address[(int)rand()%10]); strcpy(people.occupation, occupation[(int)rand()%10]); strcpy(people.company, company[(int)rand()%10]); strcpy(people.email, email[(int)rand()%10]); //printf("%sn", tel); add_people(people); strcpy(tel, "1"); } save_people_list(); printf("新增联系人成功n"); return 1; } //数字转字符串函数 char *itoa_my(int value, char *str, int radix) { static char dig_my[] = "0123456789" "abcdefghijklmnopqrstuvwxyz"; int n = 0, neg = 0; unsigned int v; char* p, *q; char c; if (radix == 10 && value < 0) { v = -value; neg = 1; }else { v = value; } do { str[n++] = dig_my[v%radix]; v /= radix; } while (v); if (neg) str[n++] = '-'; str[n] = ' '; for (p = str, q = p + (n-1); p < q; ++p, --q) c = *p, *p = *q, *q = c; return str; }
七、效果截图
首页
测试命令增加10个联系人,并打印通讯录
退出通讯录



