【C语言-通讯录的分析与实现】

简介: 我们以手机里面的通讯录为例,说明通讯录主要有以下什么功能,我们先用一张简单的图来说明一下

需求分析

我们以手机里面的通讯录为例,说明通讯录主要有以下什么功能,我们先用一张简单的图来说明一下

50b8bddbc4e7962da632cbfc9a025540_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWJjZDU1MjE5MTg2OA==,size_19,color_FFFFFF,t_70,g_se,x_16.png

1.添加人员

2.按字母排序显示人员

3.删除人员

4.查找人员

5.保存到文件

6.从文件中加载人员

功能2如果按照字母排序显示人员**,选择哈希表或者红黑树的结构来实现人员的保存和查询会有比较高的效率**,不过本篇博客也是给刚刚学C语言或者数据结构的人员看的,可能对这2种数据结构不是很熟悉,所以为了简单,选择了链表的方式来实现,链表的优点就是插入和删除效率高,但是查找的效率低,因此2这个功能也简单化了,就仅仅是显示人员,而不按字母排序。


系统设计

在程序开发之前,一般对需求分析完之后,需要设计系统的实现,在设计的过程中,主要有数据结构的设计和架构设计,不管是一个多么简单的系统,希望大家都要有一个分层的思想。下面就简单的介绍一下。


分层设计

80eac3d4b184359514ac27ad6d0ed09b_watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAYWJjZDU1MjE5MTg2OA==,size_20,color_FFFFFF,t_70,g_se,x_16.png


一般系统开发的过程中也是分为这3层,最上层为业务逻辑层,中间层为接口层,底层为数据结构层

下面以通讯录添加人员的功能来说明这几层的作用。

(1)业务逻辑层:主要是调用接口层实现业务逻辑的,比如添加人员,我们需要填入人员的基本信息,比如人的姓名,电话等,然后将调用接口层,最终实现人员的保存。

(2)接口层:接口层的作用主要是为业务逻辑层服务的,比如要实现一个人员的保存,接口层不包括具体人员的信息记录,而是调用支持层来完成人员的保存

(3)支持层:真正实现保存人员的功能,这里就是链表的插入操作。

分层思想的好处:主要是解耦的作用,易于系统的扩展。比如以后想实现按字母排序人员的功能,想更换为红黑树或者哈希表的结构来保存人员信息,那么只需要修改支持层代码即可。


数据结构设计

使用结构体来保存一个人员的基本信息,主要包括姓名,电话(如果还希望扩展其他属性,可以自行添加)。使用链表来记录所有人员的基本信息。保存到文件时,可以选择以xml,json的格式来存储,这里选择一种基本的格式来保存一个人的信息到文件,一个人员保存为一行,格式如下:name:xxx,phone:xxx。


知识点

(1)链表的增加删除操作

关于链表的操作,这里就不说明了,如果对链表的操作不熟悉的同学,请自行百度或者书籍阅读相关知识。

(2)文件接口的使用

主要有以下接口,主要使用了fopen,fprintf,fflush,fclose,feof,fgets操作文件的接口,如果不熟悉这些接口的使用,可以参考C语言文件操作API接口说明。


代码实现

存储文件格式说明

格式文件: name: “name”,phone: “123456”

name 和phone之间的",“没有空格,”:" 后面有空格,如果不按照这样的格式存储,那么读取的时候可能就读不到正确的值,因为parse_token函数解析是按照这个格式进行解析的。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define NAME_LENGTH   16
#define PHONE_LENGTH  32
#define BUFFER_LENGTH  128
#define MIN_TOKEN_LENGTH  5
#define INFO    printf
//(list)加括号是因为list传递的时候带*,*是优先级比->低
#define LIST_INSERT(item, list) do {  \
  item->prev = NULL;      \
  item->next = list;      \
  if ((list) != NULL) (list)->prev = item; \
  (list) = item;      \
} while(0)
#define LIST_REMOVE(item, list) do {  \
  if (item->prev != NULL) item->prev->next = item->next; \
  if (item->next != NULL) item->next->prev = item->prev; \
  if ((list) == item) (list) = item->next;      \
  item->prev = item->next = NULL;       \
} while(0)
struct person {
  char name[NAME_LENGTH];
  char phone[PHONE_LENGTH];
  struct person* next;
  struct person* prev;
};
struct contacts {
  struct person* people;
  int count;//记录当前通讯录的用户数
};
enum {
  OPER_INSERT = 1,
  OPER_PRINT,
  OPER_DELETE,
  OPER_SEARCH,
  OPER_SAVE,
  OPER_LOAD
};
// define interface 
int person_insert(struct person** ppeople, struct person* ps) {
  if (ps == NULL) return -1;
  LIST_INSERT(ps, *ppeople);
  return 0;
}
int person_delete(struct person** ppeople, struct person* ps) {
  if (ps == NULL) return -1;
  if (ppeople == NULL) return -2;
  LIST_REMOVE(ps, *ppeople);
  return 0;
}
struct person* person_search(struct person* people, const char* name) {
  struct person* item = NULL;
  for (item = people; item != NULL; item = item->next) {
  if (!strcmp(name, item->name))//找到了
    break;
  }
  return item;
}
int person_traversal(struct person* people) {
  struct person* item = NULL;
  for (item = people; item != NULL; item = item->next) {
  //每次导入到文件的时候每一个数据都插入了\n来区分每一行
  //如果是导入显示的,不能\n结尾,否则显示有一行空行
  if (strstr(item->phone, "\n") != NULL){
    INFO("name: %s,phone: %s", item->name, item->phone);
  }
  else {
    INFO("name: %s,phone: %s\n", item->name, item->phone);
  }
  }
  return 0;
}
int save_file(struct person* people, const char* filename) {
  FILE* fp = fopen(filename, "w");
  if (fp == NULL) return -1;
  struct person* item = NULL;
  for (item = people; item != NULL; item = item->next) {
  fprintf(fp, "name: %s,phone: %s\n", item->name, item->phone);
  fflush(fp);//强制刷新到文件
  }
  fclose(fp);
}
int parser_token(char* buffer, int length, char* name, char* phone) {
  if (buffer == NULL) return -1;
  //保证读取到的buffer有内容,有可能插入的时候最后一行插入了一行空行
  if (length < MIN_TOKEN_LENGTH)
  return -2;
  int i = 0, j = 0, status = 0;//status代表名字和电话的开始
  // name: qiuxiang,telephone: 98765678123
  for (i = 0; buffer[i] != ','; i++) { //解析出name ,一直到‘,’处停止
  if (buffer[i] == ' ') { //name后有一个空格,注意存储的时候格式要正确
    status = 1;
  }
  else if (status == 1) {
    name[j++] = buffer[i];
  }
  }
  status = 0;
  j = 0;
  for (; i < length; i++) {
  if (buffer[i] == ' ') {
    status = 1;
  }
  else if (status == 1) {
    phone[j++] = buffer[i];
  }
  }
  INFO("file token : %s --> %s\n", name, phone);
  return 0;
}
int load_file(struct person** ppeople, int* count, const char* filename) {
  FILE* fp = fopen(filename, "r");
  if (fp == NULL)
  {
  INFO("read file error,please check the file is exist!\n");
  return -1;
  }
  int read_count_from_count = 0; //记录成功读取记录的条数
  while (!feof(fp)) {
  char buffer[BUFFER_LENGTH] = { 0 };
  fgets(buffer, BUFFER_LENGTH, fp);
  int length = strlen(buffer);
  puts(buffer);
  // name: qiuxiang,telephone: 98765678123
  char name[NAME_LENGTH] = { 0 };
  char phone[PHONE_LENGTH] = { 0 };
  if (0 != parser_token(buffer, length, name, phone)) {
    continue;
  }
  struct person* p = (struct person*)malloc(sizeof(struct person));
  if (p == NULL) return -2;
  memcpy(p->name, name, NAME_LENGTH);
  memcpy(p->phone, phone, PHONE_LENGTH);
  person_insert(ppeople, p);
  ++read_count_from_count;
  (*count)++;
  }
  fclose(fp);
  if (read_count_from_count == 0) {// 读取失败
  INFO("read 0 count record, please check the file format is correct!\n");
  }
  else {
  INFO("success read %d record!,please print the record!\n", read_count_from_count);
  }
  return 0;
}
// end
int insert_entry(struct contacts* cts) {
  if (cts == NULL) return -1;
  struct person* p = (struct person*)malloc(sizeof(struct person));
  if (p == NULL) return -2;
  memset(p, 0, sizeof(struct person));
  // name
  INFO("Please Input Name: \n");
  scanf("%s", p->name); //
  // phone
  INFO("Please Input Phone: \n");
  scanf("%s", p->phone);
  // add people
  if (0 != person_insert(&cts->people, p)) {
  free(p);
  return -3;
  }
  cts->count++;
  INFO("Insert Success\n");
  return 0;
}
int print_entry(struct contacts* cts) {
  if (cts == NULL) return -1;
  // cts->people
  person_traversal(cts->people);
}
int delete_entry(struct contacts* cts) {
  if (cts == NULL) return -1;
  INFO("Please Input Name : \n");
  char name[NAME_LENGTH] = { 0 };
  scanf("%s", name);
  // person
  struct person* ps = person_search(cts->people, name);
  if (ps == NULL) {
  INFO("Person don't Exit\n");
  return -2;
  }
  INFO("name: %s, phone: %s\n", ps->name, ps->phone);
  // delete
  person_delete(&cts->people, ps);
  free(ps);
  return 0;
}
int search_entry(struct contacts* cts) {
  if (cts == NULL) return -1;
  INFO("Please Input Name : \n");
  char name[NAME_LENGTH] = { 0 };
  scanf("%s", name);
  // person
  struct person* ps = person_search(cts->people, name);
  if (ps == NULL) {
  INFO("Person don't Exit\n");
  return -2;
  }
  INFO("name: %s,phone: %s\n", ps->name, ps->phone);
  return 0;
}
int save_entry(struct contacts* cts) {
  if (cts == NULL) return -1;
  INFO("Please Input Save Filename :\n");
  char filename[NAME_LENGTH] = { 0 };
  scanf("%s", filename);
  save_file(cts->people, filename);
}
int load_entry(struct contacts* cts) {
  if (cts == NULL) return -1;
  INFO("Please Input Load Filename :\n");
  char filename[NAME_LENGTH] = { 0 };
  scanf("%s", filename);
  load_file(&cts->people, &cts->count, filename);
}
void menu_info(void) {
  INFO("\n\n********************************************************\n");
  INFO("***** 1. Add Person\t\t2. Print People ********\n");
  INFO("***** 3. Del Person\t\t4. Search Person *******\n");
  INFO("***** 5. Save People\t\t6. Load People *********\n");
  INFO("***** Other Key for Exiting Program ********************\n");
  INFO("********************************************************\n\n");
}
int main() {
  struct contacts* cts = (struct contacts*)malloc(sizeof(struct contacts));
  if (cts == NULL) return -1;
  memset(cts, 0, sizeof(struct contacts));
  while (1) {
  menu_info();
  INFO("please input your choice!\n");
  int select = 0;
  scanf("%d", &select);
  switch (select) {
  case OPER_INSERT:
    insert_entry(cts);
    break;
  case OPER_PRINT:
    print_entry(cts);
    break;
  case OPER_DELETE:
    delete_entry(cts);
    break;
  case OPER_SEARCH:
    search_entry(cts);
    break;
  case OPER_SAVE:
    save_entry(cts);
    break;
  case OPER_LOAD:
    load_entry(cts);
    break;
  default:
    goto exit;
  }
  }
exit:
  INFO("Bye Bye!\n");
  free(cts);
  return 0;
}

代码来源

腾讯课堂-零声学院king老师


相关文章
|
2月前
|
C语言
用c语言实现一个通讯录
用c语言实现一个通讯录
10 0
|
2月前
|
存储 C语言
C语言详解【通讯录的实现】
C语言详解【通讯录的实现】
|
5月前
|
程序员 C语言 C++
C语言操作符if语句好习惯 详解分析操作符(详解4)
C语言操作符if语句好习惯 详解分析操作符(详解4)
|
4月前
|
C语言
【C语言】动态内存管理基础知识——动态通讯录,如何实现通讯录容量的动态化
动态内存管理的函数有:malloc,calloc,ralloc,free,本文讲解动态内存函数和使用,如何进行动态内存管理,实现通讯录联系人容量的动态化,对常见动态内存错误进行总结。
35 0
|
6天前
|
存储 算法 C语言
C语言进阶:顺序表(数据结构基础) (以通讯录项目为代码练习)
C语言进阶:顺序表(数据结构基础) (以通讯录项目为代码练习)
|
15天前
通讯录(C语言版)
通讯录(C语言版)
|
2月前
|
程序员 C语言
【C语言实战项目】通讯录(动态增容版)
【C语言实战项目】通讯录(动态增容版)
21 0
|
2月前
|
存储 编译器 C语言
【C语言实战项目】通讯录
【C语言实战项目】通讯录
28 0
|
2月前
|
小程序 C语言
【C语言】输入一个十进制正整数,将它对应的二进制数的各位逆序,形成新的十进制数输出。题目分析及拓展应用。
【C语言】输入一个十进制正整数,将它对应的二进制数的各位逆序,形成新的十进制数输出。题目分析及拓展应用。
27 0
|
2月前
|
C语言
【C语言】通讯录实现(下)
【C语言】通讯录实现(下)