进阶版通讯录(动态版)

简介: 进阶版通讯录(动态版)

一、通讯录各个函数的实现


下面各函数的代码主要是以修改上一个初阶的通讯录的代码为主,初阶通讯录解释过的代码在这里就不再解释了,主要是针对新增加的内容进行解释。


Contact结构体的定义需要稍作改变:


typedef struct Contact
{
  peo_info* data;//data从静态版本的数组变成现在动态版本的指针
  int sz;
  int capacity;
}Contact;


1.1 初始化通讯录函数的实现


void InitContact(Contact* con)
{
  assert(con);
  peo_info* p = (peo_info*)calloc(2, sizeof(peo_info));
  if (p == NULL)
  {
    perror("InitContact");
    return;
  }
  con->data = p;
  con->sz = 0;
  con->capacity = 2;
}


1.2 添加联系人函数的实现


void check_capacity(Contact* con)
{
  assert(con);
  //如果capacity和sz相等,那证明con已经满了,需要增容
  if (con->capacity == con->sz)
  {
      //用realloc函数开辟capacity+2个空间,第一个参数是
      //原来空间的地址,第二个参数是新的空间的大小,单位是
      //字节
    peo_info* p = (peo_info*)realloc(con->data, sizeof(peo_info) * (con->capacity + 2));
    if (p == NULL)
    {
      perror("realloc");
      return;
    }
    else
    {
        //开辟空间也把新的空间的地址交给con->data维护,
        //保持统一性
      con->data = p;
      con->capacity += 2;//容量+2
      printf("增容成功!\n");
    }
  }
}
void Add(Contact* con)
{
  assert(con);
  check_capacity(con);//判断是否需要增容
  printf("请输入添加的联系人姓名:>");
  scanf("%s", con->data[con->sz].name);
  printf("请输入添加的联系人性别:>");
  scanf("%s", con->data[con->sz].sex);  
  printf("请输入添加的联系人年龄:>");
  scanf("%d", &con->data[con->sz].age);
  printf("请输入添加的联系人电话:>");
  scanf("%s", con->data[con->sz].tele);
  printf("请输入添加的联系人地址:>");
  scanf("%s", con->data[con->sz].addr);
  con->sz++;
  printf("添加成功\n");
}


1.3 删除联系人函数的实现


//option是根据菜单选择的数字通过枚举变量传递过来的,具体看
//调用函数传递的枚举变量是什么
int FindPeoInfo(Contact* con, int option)
{
  assert(con);
  switch (option)
  {
  case DEL:
  {
    printf("请输入需要删除的联系人名字:>");
    break;
  }
  case SEARCH:
  {
    printf("请输入需要查找的联系人名字:>");
    break;
  }
  case MOD:
  {
    printf("请输入需要修改的联系人名字:>");
    break;
  }
  }
  char name[20] = { 0 };
  scanf("%s", name);
  int i = 0;
  for (i = 0; i < con->sz; i++)
  {
    if (strcmp(con->data[i].name, name) == 0)
    {
      return i;//找到联系人返回下标
    }
  }
  return -1;//找不到返回-1
}
void AdjustCapacity(Contact* con)
{
  assert(con);
  //注意,第二个参数是sizeof(peo_info) * (con->capacity - 2)
  //是con->capacity - 2,回收空间,容量变少。
  peo_info* p = (peo_info*)realloc(con->data, sizeof(peo_info) * (con->capacity - 2));
  if (p == NULL)
  {
    perror("AdjustCapacity");
    return;
  }
  //依然需要保持统一性,用con->data维护空间
  con->data = p;
  con->capacity -= 2;//记得要con->capacity-=2
  printf("多余空间回收成功!\n");
}
void Del(Contact* con,int DEL)
{
  assert(con);
  //查找联系人,DEL是一个枚举变量,详细的枚举定义请看后面汇总
  //代码的contact.h
  int pos = FindPeoInfo(con, DEL);
  if (-1 == pos)
  {
    printf("要删除的联系人不存在!\n");
    return;
  }
  int i = 0;
  for (i = pos; i < con->sz - 1; i++)
  {
    con->data[i] = con->data[i + 1];//从后往前覆盖删除
  }
  con->sz--;
  printf("删除成功!\n");
  if (con->sz == con->capacity - 2 && con->sz!=2)
  {
      //因为每次增加2个容量,则当删除掉两个联系人的时候就回收
      //两个联系人结构体的内存。
    AdjustCapacity(con);
  }
}


1.4 查找联系人函数的实现


//option是根据菜单选择的数字通过枚举变量传递过来的,具体看
//调用函数传递的枚举变量是什么
int FindPeoInfo(Contact* con, int option)
{
  assert(con);
  switch (option)
  {
  case DEL:
  {
    printf("请输入需要删除的联系人名字:>");
    break;
  }
  case SEARCH:
  {
    printf("请输入需要查找的联系人名字:>");
    break;
  }
  case MOD:
  {
    printf("请输入需要修改的联系人名字:>");
    break;
  }
  }
  char name[20] = { 0 };
  scanf("%s", name);
  int i = 0;
  for (i = 0; i < con->sz; i++)
  {
    if (strcmp(con->data[i].name, name) == 0)
    {
      return i;//找到联系人返回下标
    }
  }
  return -1;//找不到返回-1
}
void Search(Contact* con,int SEARCH)
{
  assert(con);
  int pos = FindPeoInfo(con,SEARCH);
  if (pos == -1)
  {
    printf("找不到此联系人!\n");
    return;
  }
  printf("%-20s\t%-10s\t%-5s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "电话", "地址");
  printf("%-20s\t%-10s\t%-5d\t%-12s\t%-20s\n", con->data[pos].name,
    con->data[pos].sex,
    con->data[pos].age,
    con->data[pos].tele,
    con->data[pos].addr);
}


1.5 改变联系人信息函数的实现


//option是根据菜单选择的数字通过枚举变量传递过来的,具体看
//调用函数传递的枚举变量是什么
int FindPeoInfo(Contact* con, int option)
{
  assert(con);
  switch (option)
  {
  case DEL:
  {
    printf("请输入需要删除的联系人名字:>");
    break;
  }
  case SEARCH:
  {
    printf("请输入需要查找的联系人名字:>");
    break;
  }
  case MOD:
  {
    printf("请输入需要修改的联系人名字:>");
    break;
  }
  }
  char name[20] = { 0 };
  scanf("%s", name);
  int i = 0;
  for (i = 0; i < con->sz; i++)
  {
    if (strcmp(con->data[i].name, name) == 0)
    {
      return i;//找到联系人返回下标
    }
  }
  return -1;//找不到返回-1
}
void Mod(Contact* con,int MOD)
{
  assert(con);
  int pos = FindPeoInfo(con, MOD);
  if (pos == -1)
  {
    printf("该联系人不存在!\n");
    return;
  }
  printf("请输入新的联系人姓名:>");
  scanf("%s", con->data[pos].name);
  printf("请输入添加的联系人性别:>");
  scanf("%s", con->data[pos].sex);
  printf("请输入添加的联系人年龄:>");
  scanf("%d", &con->data[pos].age);
  printf("请输入添加的联系人电话:>");
  scanf("%s", con->data[pos].tele);
  printf("请输入添加的联系人地址:>");
  scanf("%s", con->data[pos].addr);
  printf("修改成功!\n");
}


1.6 展示联系人信息函数的实现


void Show(Contact* con)
{
  assert(con);
  int i = 0;
  printf("%-20s\t%-10s\t%-5s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "电话", "地址");
  for (i = 0; i < con->sz; i++)
  {
    printf("%-20s\t%-10s\t%-5d\t%-12s\t%-20s\n", con->data[i].name,
                           con->data[i].sex, 
                           con->data[i].age, 
                           con->data[i].tele, 
                           con->data[i].addr);
  }
}


1.7 通讯录排序函数的实现


void swap(char* buf1, char* buf2, size_t width)
{
  size_t i = 0;
  //数组中的每一个数据占width个字节,我们只需要循环width次就能把两个元素的每一个字节都交换,
  //最终两个元素的内容也就交换成功了
  for (i = 0; i < width; i++)
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
{
  //这里是典型的冒泡排序的方法
  size_t i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    size_t j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      //这里的cmp函数就是就是比较相邻的两个元素的大小,如果返回值大于0,则证明前一个元素
      //比后一个元素大,则需要交换这两个元素。由于这里的base指针的类型是void*,所以我们
      //首先需要将它强制类型转换成char*类型的指针,那为什么是转换成char*而不是int*, 
      //double*呢?其实很简单,你试想一下,我们比较完了两个相邻的元素之后是不是需要
      //拿后一个和这两个元素中大的元素进行比较大小,但是大家别忘了,指针类型的大小可是决 
      //定了你指针加1跳过几个字节的啊,整形指针加1跳过一个整形,字符指针加1跳过一个字节
      //但是我们并不知道将来这个函数会被用来排序什么类型的数据的啊,但是无论是数目类型的 
      //数据,它的大小都至少为1个字节吧,所以转换成(char*)类型是最合理的。而参数中的宽 
      //度又正好能让我们找到下一个元素,只需要再起始地址加上宽度*j就能找到下一个元素了
      //所以if语句里面的判断条件应该这样写
      if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
      {
        swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
      }
    }
  }
}
int cmp_by_name(const void* e1, const void* e2)
{
  //通过姓名对结构体进行排序,需要用到strcmp函数,依然是返回1,0,-1
  return strcmp(((peo_info*)e1)->name, ((peo_info*)e2)->name);
}
void Sort(Contact* con)
{
  assert(con);
  //qsort(con->data, con->sz, sizeof(con->data[0]), cmp_by_name);
  bubble_sort(con->data, con->sz, sizeof(con->data[0]), cmp_by_name);
  printf("排序成功\n");
  Show(con);
}


1.8 通讯录销毁并退出函数的实现


void Destroy(Contact* con)
{
  assert(con);
  free(con->data);//释放开辟的空间
  con->data = NULL;//建议最后置为空指针NULL,防止被非法使用
  con->sz = 0;//数据置为0
  con->capacity = 0;//容量置为0
  printf("通讯录销毁成功!\n");
}


二、通讯录代码汇总


contact.h


#pragma once
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
enum option
{
  EXIT,//本质是数字0
  ADD,//本质是数字1
  DEL,//本质是数字2
  SEARCH,//本质是数字3
  MOD,//本质是数字4
  SHOW,//本质是数字5
  SORT//本质是数字6
};
typedef struct peo_info
{
  char name[20];
  char sex[10];
  int age;
  char addr[20];
  char tele[12];
}peo_info;
typedef struct Contact
{
  peo_info* data;//data从静态版本的数组变成现在动态版本的指针
  int sz;
  int capacity;
}Contact;
//函数声明
extern void InitContact(Contact* con);
extern void Add(Contact* con);
extern void Show(Contact* con);
extern void Del(Contact* con,int DEL);
extern void Search(Contact* con,int SEARCH);
extern void Mod(Contact* con,int MOD);
extern void Sort(Contact* con);
extern void Destroy(Contact* con);


contact.c


#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"
void InitContact(Contact* con)
{
  assert(con);
  peo_info* p = (peo_info*)calloc(2, sizeof(peo_info));
  if (p == NULL)
  {
    perror("InitContact");
    return;
  }
  con->data = p;
  con->sz = 0;
  con->capacity = 2;
}
void check_capacity(Contact* con)
{
  assert(con);
  if (con->capacity == con->sz)
  {
    peo_info* p = (peo_info*)realloc(con->data, sizeof(peo_info) * (con->capacity + 2));
    if (p == NULL)
    {
      perror("realloc");
      return;
    }
    else
    {
      con->data = p;
      con->capacity += 2;
      printf("增容成功!\n");
    }
  }
}
void Add(Contact* con)
{
  assert(con);
  check_capacity(con);
  printf("请输入添加的联系人姓名:>");
  scanf("%s", con->data[con->sz].name);
  printf("请输入添加的联系人性别:>");
  scanf("%s", con->data[con->sz].sex);  
  printf("请输入添加的联系人年龄:>");
  scanf("%d", &con->data[con->sz].age);
  printf("请输入添加的联系人电话:>");
  scanf("%s", con->data[con->sz].tele);
  printf("请输入添加的联系人地址:>");
  scanf("%s", con->data[con->sz].addr);
  con->sz++;
  printf("添加成功\n");
}
void Show(Contact* con)
{
  assert(con);
  int i = 0;
  printf("%-20s\t%-10s\t%-5s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "电话", "地址");
  for (i = 0; i < con->sz; i++)
  {
    printf("%-20s\t%-10s\t%-5d\t%-12s\t%-20s\n", con->data[i].name,
                           con->data[i].sex, 
                           con->data[i].age, 
                           con->data[i].tele, 
                           con->data[i].addr);
  }
}
int FindPeoInfo(Contact* con, int option)
{
  assert(con);
  switch (option)
  {
  case DEL:
  {
    printf("请输入需要删除的联系人名字:>");
    break;
  }
  case SEARCH:
  {
    printf("请输入需要查找的联系人名字:>");
    break;
  }
  case MOD:
  {
    printf("请输入需要修改的联系人名字:>");
    break;
  }
  }
  char name[20] = { 0 };
  scanf("%s", name);
  int i = 0;
  for (i = 0; i < con->sz; i++)
  {
    if (strcmp(con->data[i].name, name) == 0)
    {
      return i;
    }
  }
  return -1;
}
void AdjustCapacity(Contact* con)
{
  assert(con);
  peo_info* p = (peo_info*)realloc(con->data, sizeof(peo_info) * (con->capacity - 2));
  if (p == NULL)
  {
    perror("AdjustCapacity");
    return;
  }
  con->data = p;
  con->capacity -= 2;
  printf("多余空间回收成功!\n");
}
void Del(Contact* con,int DEL)
{
  assert(con);
  int pos = FindPeoInfo(con, DEL);
  if (-1 == pos)
  {
    printf("要删除的联系人不存在!\n");
    return;
  }
  int i = 0;
  for (i = pos; i < con->sz - 1; i++)
  {
    con->data[i] = con->data[i + 1];
  }
  con->sz--;
  printf("删除成功!\n");
  if (con->sz == con->capacity - 2 && con->sz!=2)
  {
    AdjustCapacity(con);
  }
}
void Search(Contact* con,int SEARCH)
{
  assert(con);
  int pos = FindPeoInfo(con,SEARCH);
  if (pos == -1)
  {
    printf("找不到此联系人!\n");
    return;
  }
  printf("%-20s\t%-10s\t%-5s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "电话", "地址");
  printf("%-20s\t%-10s\t%-5d\t%-12s\t%-20s\n", con->data[pos].name,
    con->data[pos].sex,
    con->data[pos].age,
    con->data[pos].tele,
    con->data[pos].addr);
}
void Mod(Contact* con,int MOD)
{
  assert(con);
  int pos = FindPeoInfo(con, MOD);
  if (pos == -1)
  {
    printf("该联系人不存在!\n");
    return;
  }
  printf("请输入新的联系人姓名:>");
  scanf("%s", con->data[pos].name);
  printf("请输入添加的联系人性别:>");
  scanf("%s", con->data[pos].sex);
  printf("请输入添加的联系人年龄:>");
  scanf("%d", &con->data[pos].age);
  printf("请输入添加的联系人电话:>");
  scanf("%s", con->data[pos].tele);
  printf("请输入添加的联系人地址:>");
  scanf("%s", con->data[pos].addr);
  printf("修改成功!\n");
}
void swap(char* buf1, char* buf2, size_t width)
{
  size_t i = 0;
  //数组中的每一个数据占width个字节,我们只需要循环width次就能把两个元素的每一个字节都交换,
  //最终两个元素的内容也就交换成功了
  for (i = 0; i < width; i++)
  {
    char tmp = *buf1;
    *buf1 = *buf2;
    *buf2 = tmp;
    buf1++;
    buf2++;
  }
}
void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
{
  //这里是典型的冒泡排序的方法
  size_t i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    size_t j = 0;
    for (j = 0; j < sz - 1 - i; j++)
    {
      //这里的cmp函数就是就是比较相邻的两个元素的大小,如果返回值大于0,则证明前一个元素
      //比后一个元素大,则需要交换这两个元素。由于这里的base指针的类型是void*,所以我们
      //首先需要将它强制类型转换成char*类型的指针,那为什么是转换成char*而不是int*, 
      //double*呢?其实很简单,你试想一下,我们比较完了两个相邻的元素之后是不是需要
      //拿后一个和这两个元素中大的元素进行比较大小,但是大家别忘了,指针类型的大小可是决 
      //定了你指针加1跳过几个字节的啊,整形指针加1跳过一个整形,字符指针加1跳过一个字节
      //但是我们并不知道将来这个函数会被用来排序什么类型的数据的啊,但是无论是数目类型的 
      //数据,它的大小都至少为1个字节吧,所以转换成(char*)类型是最合理的。而参数中的宽 
      //度又正好能让我们找到下一个元素,只需要再起始地址加上宽度*j就能找到下一个元素了
      //所以if语句里面的判断条件应该这样写
      if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
      {
        swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
      }
    }
  }
}
int cmp_by_name(const void* e1, const void* e2)
{
  //通过姓名对结构体进行排序,需要用到strcmp函数,依然是返回1,0,-1
  return strcmp(((peo_info*)e1)->name, ((peo_info*)e2)->name);
}
void Sort(Contact* con)
{
  assert(con);
  //qsort(con->data, con->sz, sizeof(con->data[0]), cmp_by_name);
  bubble_sort(con->data, con->sz, sizeof(con->data[0]), cmp_by_name);
  printf("排序成功\n");
  Show(con);
}
void Destroy(Contact* con)
{
  assert(con);
  free(con->data);
  con->data = NULL;
  con->sz = 0;
  con->capacity = 0;
  printf("通讯录销毁成功!\n");
}


test.c


#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"
void menu(void)
{
  printf("*******************************\n");
  printf("****   1.add     2.del     ****\n");
  printf("****   3.search  4.mod     ****\n");
  printf("****   5.show    6.sort    ****\n");
  printf("*******     0.exit      *******\n");
}
int main()
{
  Contact con;
  InitContact(&con);
  int input = 0;
  do
  {
    menu();
    printf("请输入:>");
    scanf("%d", &input);
    switch (input)
    {
    case ADD:
    {
      Add(&con);
      break;
    }
    case DEL:
    {
      Del(&con,DEL);
      break;
    }
    case SEARCH:
    {
      Search(&con,SEARCH);
      break;
    }
    case MOD:
    {
      Mod(&con,MOD);
      break;
    }
    case SHOW:
    {
      Show(&con);
      break;
    }
    case SORT:
    {
      Sort(&con);
      break;
    }
    case EXIT:
    {
      Destroy(&con);
      printf("退出通讯录!!!\n");
      break;
    }
    default:
    {
      printf("选择错误,请重新选择!!\n");
      break;
    }
    }
  } while (input);
  return 0;
}


三、总结


以上就是通讯录的动态版本,其实就是让这个通讯录的容量变得更加灵活了,空间不足了可以动态地增长,多了可以减少,这样做能有效地避免内存的浪费,其实动态版本真正需要改变的也只有增加联系人和减少联系人和最后的销毁通讯录需要释放空间,防止内存泄漏,其他的基本上不用怎么去修改

相关文章
|
8月前
|
存储
通讯录(动态实现与文件优化版)
通讯录(动态实现与文件优化版)
61 1
【C进阶】通讯录的实现(静态+动态)(上)
【C进阶】通讯录的实现(静态+动态)(上)
【动态通讯录】
【动态通讯录】
48 0
【文件版&动态版通讯录】
【文件版&动态版通讯录】
40 0
C进阶:通讯录(动态版本 + 文件操作)附源码(下)
C进阶:通讯录(动态版本 + 文件操作)附源码(下)
76 0
|
存储 搜索推荐
C进阶:通讯录(动态版本 + 文件操作)附源码(上)
C进阶:通讯录(动态版本 + 文件操作)附源码
49 0
【C进阶】通讯录的实现(静态+动态)(下)
【C进阶】通讯录的实现(静态+动态)(下)
【C进阶】通讯录的实现(静态+动态)(中)
【C进阶】通讯录的实现(静态+动态)(中)
手把手教你写通讯录(含动态版) 1
手把手教你写通讯录(含动态版)