进阶版通讯录(动态版)

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

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


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


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;
}


三、总结


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

相关文章
|
7天前
|
弹性计算 关系型数据库 微服务
基于 Docker 与 Kubernetes(K3s)的微服务:阿里云生产环境扩容实践
在微服务架构中,如何实现“稳定扩容”与“成本可控”是企业面临的核心挑战。本文结合 Python FastAPI 微服务实战,详解如何基于阿里云基础设施,利用 Docker 封装服务、K3s 实现容器编排,构建生产级微服务架构。内容涵盖容器构建、集群部署、自动扩缩容、可观测性等关键环节,适配阿里云资源特性与服务生态,助力企业打造低成本、高可靠、易扩展的微服务解决方案。
1175 3
|
6天前
|
机器学习/深度学习 人工智能 前端开发
通义DeepResearch全面开源!同步分享可落地的高阶Agent构建方法论
通义研究团队开源发布通义 DeepResearch —— 首个在性能上可与 OpenAI DeepResearch 相媲美、并在多项权威基准测试中取得领先表现的全开源 Web Agent。
861 12
|
5天前
|
机器学习/深度学习 物联网
Wan2.2再次开源数字人:Animate-14B!一键实现电影角色替换和动作驱动
今天,通义万相的视频生成模型又又又开源了!Wan2.2系列模型家族新增数字人成员Wan2.2-Animate-14B。
453 10
|
16天前
|
人工智能 运维 安全
|
7天前
|
弹性计算 Kubernetes jenkins
如何在 ECS/EKS 集群中有效使用 Jenkins
本文探讨了如何将 Jenkins 与 AWS ECS 和 EKS 集群集成,以构建高效、灵活且具备自动扩缩容能力的 CI/CD 流水线,提升软件交付效率并优化资源成本。
331 0
|
7天前
|
消息中间件 Java Apache
SpringBoot集成RocketMq
RocketMQ 是一款开源的分布式消息中间件,采用纯 Java 编写,支持事务消息、顺序消息、批量消息、定时消息及消息回溯等功能。其优势包括去除对 ZooKeeper 的依赖、支持异步和同步刷盘、高吞吐量及消息过滤等特性。RocketMQ 具备高可用性和高可靠性,适用于大规模分布式系统,能有效保障消息传输的一致性和顺序性。
400 2
|
14天前
|
人工智能 异构计算
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
敬请锁定《C位面对面》,洞察通用计算如何在AI时代持续赋能企业创新,助力业务发展!
|
7天前
|
云栖大会
阿里云云栖大会2025年9月24日开启,免费申请大会门票,速度领取~
2025云栖大会将于9月24-26日举行,官网免费预约畅享票,审核后短信通知,持证件入场
1183 12