C语言进阶之通讯录的实现(静态版和动态版)以及动态内存管理(上)

简介: 开头我们先断言,防止传NULL指针,第二步初始化长度为0,最后将结构体数组中的元素均初始化为0即可。

4ecc681fd22c45dfaeaab3b83d7610c1.png


1.通讯录实现要求


通讯录可以用来存储1000个人的信息,每个人的信息包括:姓名、性别、年龄、电话、住址


提供方法:


  1. 添加联系人信息
  2. 删除指定联系人信息
  3. 查找指定联系人信息
  4. 修改指定联系人信息
  5. 显示所有联系人信息
  6. 清空所有联系人
  7. 以名字排序所有联系人


fd9bf82b40e043eba428897fe3c742f4.png


2.静态版通讯录实现


整个程序的实现需要建立三个文件


contact.h 通讯录的结构体定义及函数的声明


contact.c 函数的实现


main.c 主函数调用


2.1 contact.h文件实现


头文件引用和宏定义


#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#define NAME_MAX 10 //名字的长度(单位:字节)
#define SEX_MAX 6   //性别长度
#define ADDR_MAX 20 //住址长度
#define TELE_MAX 12 //电话长度
#define N 100       //通讯录的长度(单位为个人结构体大小)


结构体定义


typedef struct people //个人结构体定义
{
  char name[NAME_MAX]; //姓名
  char sex[SEX_MAX];   //性别
  int age;             //年龄
  char tele[TELE_MAX]; //电话
  char addr[ADDR_MAX]; //住址
}peo;
typedef struct Contact //通讯录结构体定义
{
  peo data[N]; //个人结构体数组
  int sz;      //结构体长度
}Con;


枚举定义,方便主函数switch语句调用,易分辨。


enum
{
  EXIT,//退出
  ADD,//增加
  DEL,//删除
  MOD,//修改
  SEARCH,//查找
  SHOW,//打印
  DISTORY,//删除全部
  QSORT//以名称排序
};


函数声明


void InitCon(Con* cont);//初始化函数
void add(Con* cont);//增加联系人
void show(const Con* cont);//打印联系人
int FindName(const Con* cont, const char* name);//查找联系人(内含)
void del(Con* cont);//删除联系人
void mod(Con* cont);//修改联系人
void sel(const Con* cont);//查找联系人打印
void Distory(Con* cont);//删除所有联系人
int cmp_stu_by_name(const void* p1, const void* p2);//排序调用的回调函数


下面我们来看最关键的部分,即为函数定义


2.2 contact.c文件实现


首先我们要包含头文件#include "contact.h"


初始化函数InitCon


void InitCon(Con* cont)
{
  assert(cont);
  cont->sz = 0;
  memset(cont->data, 0, sizeof(cont->data));
}


开头我们先断言,防止传NULL指针,第二步初始化长度为0,最后将结构体数组中的元素均初始化为0即可。


添加联系人函数add


void add(Con* cont)
{
  assert(cont);
  if (cont->sz == N)
  {
    printf("添加失败,通讯录已满");
    return;
  }
  printf("请输入要添加联系人的信息:\n");
  printf("请输入姓名:");
  scanf("%s", cont->data[cont->sz].name);
  printf("请输入性别:");
  scanf("%s", cont->data[cont->sz].sex);
  printf("请输入年龄:");
  scanf("%d", &cont->data[cont->sz].age);
  printf("请输入电话:");
  scanf("%s", cont->data[cont->sz].tele);
  printf("请输入地址:");
  scanf("%s", cont->data[cont->sz].addr);
  cont->sz++;
  printf("添加成功\n");
}


开始一样,我们先断言防止传NULL指针,第二就是判断空间有没有满,如果满了,直接返回退出并提示,如果没满,则继续添加联系人信息,添加完之后提示添加成功,再将sz++。


ab71dcf2083143faa802b1de9f9e4c90.png


显示联系人函数show


void show(const Con* cont)
{
  assert(cont);
  int i = 0;
  printf("        通讯录\n");
  printf("  +---------------------------------------------------------------+\n");
  printf("  |姓名   性别  年龄  电话    地址    |\n");
  for (i = 0; i < cont->sz; i++)
  {
    printf("  |%-10s  %-5s  %-5d  %-11s %-15s |\n", cont->data[i].name, cont->data[i].sex, cont->data[i].age, cont->data[i].tele, cont->data[i].addr);
  }
  printf("  +---------------------------------------------------------------+\n");
}


首先一样,我们先断言防止传NULL指针,第二就是打印题头,然后对结构体数组进行遍历打印,注意对齐,根据不同情况可自行调整。


770f05b8f4c34f52978ed94669bfd860.png


查找联系人函数(内含)FindName


int FindName(const Con* cont, const char* name)//查找人函数
{
  assert(cont && name);//防止传入空指针
  int i = 0;
  for (i = 0; i < cont->sz; i++)
  {
    if (!strcmp(cont->data[i].name, name))//查找通讯录是否有这个名字
    {
      return i;//有就返回下标
    }
  }
  return -1;//没有返回一个负数.
}


实现这个函数主要是为了后面的删除、修改和查找并打印函数,方便复用,减少代码冗余。

首先同样,我们先断言防止传NULL指针,然后遍历使用strcmp函数进行字符匹配,查看是否有这个名字,有就返回下标,没有就返回负数。


删除联系人函数del


void del(Con* cont)//删除联系人函数
{
  assert(cont);
  int i = 0;
  char name[NAME_MAX];
  printf("请输入需要删除的联系人的姓名:\n");
  scanf("%s", name);
  i = FindName(cont, name);
  if (i == -1)
  {
    printf("通讯录中没有该联系人,删除失败\n");
    return;
  }
  for (; i < cont->sz - 1; i++)
  {
    cont->data[i] = cont->data[i + 1];
  }
  cont->sz--;
  printf("删除成功,姓名为%s的联系人已删除\n", name);
}


首先同样,我们先断言防止传NULL指针,然后创建一个字符数组,方便输入姓名,再复用上面的查找函数,如果返回-1,则说明没有这个联系人,提示后返回退出,如果有,则按照其返回的下标向后遍历依次向前覆盖。最后sz–。


8c9a0ef682a649e2bbf63a62a6b1030f.png


修改联系人函数mod


void mod(Con* cont)//修改联系人函数
{
  assert(cont);
  int ret = 0;
  char name[NAME_MAX];
  printf("请输入要修改的联系人的姓名:\n");
  scanf("%s", name);
  ret = FindName(cont, name);
  if (ret == -1)
  {
    printf("通讯录中没有该联系人,修改失败\n");
    return;
  }
  printf("请输入修改后联系人的信息:\n");
  printf("请输入姓名:");
  scanf("%s", cont->data[ret].name);
  printf("请输入性别:");
  scanf("%s", cont->data[ret].sex);
  printf("请输入年龄:");
  scanf("%d", &cont->data[ret].age);
  printf("请输入电话:");
  scanf("%s", cont->data[ret].tele);
  printf("请输入地址:");
  scanf("%s", cont->data[ret].addr);
  printf("修改成功\n");
}


首先同样,我们先断言防止传NULL指针,然后创建一个字符数组,方便输入姓名,再复用上面的查找函数,如果返回-1,则说明没有这个联系人,提示后返回退出,如果有,则同增加联系人依次修改,当然你也可以再进行优化,修改某一项。


f415cf8c0559416aab10ae792ab5051d.png


查找联系人函数sel


void sel(const Con* cont)//查询联系人函数
{
  char name[NAME_MAX];
  printf("请输入要查询的联系人的名字:\n");
  scanf("%s", name);
  int ret = FindName(cont, name);
  if (ret == -1)
  {
    printf("通讯录中没有该联系人,查询失败.\n");
    return;
  }
  printf("查询成功,该联系信息如下:\n");
  printf("  +---------------------------------------------------------------+\n");
  printf("  |姓名   性别  年龄  电话    地址    |\n");
  printf("  |%-10s  %-5s  %-5d  %-11s %-15s |\n", cont->data[ret].name, cont->data[ret].sex, cont->data[ret].age, cont->data[ret].tele, cont->data[ret].addr);
  printf("  +---------------------------------------------------------------+\n");
}


首先我们创建一个字符数组,方便输入姓名,再复用上面的查找函数,如果返回-1,则说明没有这个联系人,提示后返回退出,如果有,则同show函数打印,只不过这里只打印一个。


83b3c05993f64ccb8e6e1e97f8a53404.png


删除所有联系人函数Distory


void Distory(Con* cont)
{
  assert(cont);
  cont->sz = 0;
  printf("全部联系人删除成功!\n");
}


删除全部联系人,只需要将sz归0即可,下一次添加就会直接覆盖,而且也不能再访问之前的联系人数据了。


排序联系人函数的回调函数cmp_stu_by_name


int cmp_stu_by_name(const void* p1, const void* p2)
{
  return strcmp(((peo*)p1)->name, ((peo*)p2)->name);
}


这是调用库函数qsort给通讯录以姓名排序的一个回调函数,不理解的可以看我之前指针进阶那篇文章,其中就包含了qsort函数的使用及其回调函数的定义。


2.3 main.c文件实现


#include"contact.h"
void menu()
{
  printf("\n        欢迎使用通讯录:\n");
  printf("  +-------------------------------------------------------------+\n");
  printf("  | 1.添加联系人     2.删除联系人       |\n");
  printf("  | 3.修改联系人     4.查询联系人       |\n");
  printf("  | 5.展示通讯录     6.删除全部联系人      |\n");
  printf("  | 7.排序联系人           0.退出通讯录       |\n");
  printf("  +-------------------------------------------------------------+\n");
  printf("请选择:");
}
int main()
{
  int input = 0;
  Con cont;
  InitCon(&cont);
  while (1) {
    do
    {
      menu();
      scanf("%d", &input);
      switch (input)
      {
      case EXIT:
        printf("退出通讯录");
        break;
      case ADD:
        add(&cont);
        break;
      case DEL:
        del(&cont);
        break;
      case MOD:
        mod(&cont);
        break;
      case SEARCH:
        sel(&cont);
        break;
      case SHOW:
        show(&cont);
        break;
      case DISTORY:
        Distory(&cont);
        break;
      case QSORT:
        qsort(cont.data, cont.sz, sizeof(peo), cmp_stu_by_name);
        printf("排序成功\n");
        break;
      default:
        printf("输入错误");
        break;
      }
    } while (input);
    break;
  }
  return 0;
}


这里首先就是一个菜单排版,然后就是switch语句对各函数调用,这里的每个case选项用的是前面枚举定义的各个名称方便辨别。


8f23bc3dbd27446c837f664368101d4e.png


2.4 静态版通讯录全部文件代码


//contact.h


#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#define NAME_MAX 10
#define SEX_MAX 6
#define ADDR_MAX 20
#define TELE_MAX 12
#define N 100
typedef struct people
{
  char name[NAME_MAX];
  char sex[SEX_MAX];
  int age;
  char tele[TELE_MAX];
  char addr[ADDR_MAX];
}peo;
typedef struct Contact
{
  peo data[N];
  int sz;
}Con;
enum
{
  EXIT,
  ADD,
  DEL,
  MOD,
  SEARCH,
  SHOW,
  DISTORY,
  QSORT
};
InitCon(Con* cont);
void add(Con* cont);
void show(const Con* cont);
int FindName(const Con* cont, const char* name);
void del(Con* cont);
void mod(Con* cont);
void sel(const Con* cont);
void Distory(Con* cont);
int cmp_stu_by_name(const void* p1, const void* p2);


//contact.c


#include "contact.h"
InitCon(Con* cont)
{
  assert(cont);
  cont->sz = 0;
  memset(cont->data, 0, sizeof(cont->data));
}
void add(Con* cont)
{
  assert(cont);
  if (cont->sz == N)
  {
    printf("添加失败,通讯录已满");
    return;
  }
  printf("请输入要添加联系人的信息:\n");
  printf("请输入姓名:");
  scanf("%s", cont->data[cont->sz].name);
  printf("请输入性别:");
  scanf("%s", cont->data[cont->sz].sex);
  printf("请输入年龄:");
  scanf("%d", &cont->data[cont->sz].age);
  printf("请输入电话:");
  scanf("%s", cont->data[cont->sz].tele);
  printf("请输入地址:");
  scanf("%s", cont->data[cont->sz].addr);
  cont->sz++;
  printf("添加成功\n");
}
void show(const Con* cont)
{
  assert(cont);
  int i = 0;
  printf("        通讯录\n");
  printf("  +---------------------------------------------------------------+\n");
  printf("  |姓名   性别  年龄  电话    地址    |\n");
  for (i = 0; i < cont->sz; i++)
  {
    printf("  |%-10s  %-5s  %-5d  %-11s %-15s |\n", cont->data[i].name, cont->data[i].sex, cont->data[i].age, cont->data[i].tele, cont->data[i].addr);
  }
  printf("  +---------------------------------------------------------------+\n");
}
int FindName(const Con* cont, const char* name)//查找人函数
{
  assert(cont && name);//防止传入空指针
  int i = 0;
  for (i = 0; i < cont->sz; i++)
  {
    if (!strcmp(cont->data[i].name, name))//查找通讯录是否有这个名字
    {
      return i;//有就返回下标
    }
  }
  return -1;//没有返回一个负数.
}
void del(Con* cont)//删除联系人函数
{
  assert(cont);//防止传入空指针
  int i = 0;
  char name[NAME_MAX];
  printf("请输入需要删除的联系人的姓名:\n");
  scanf("%s", name);
  i = FindName(cont, name);
  if (i == -1)
  {
    printf("通讯录中没有该联系人,删除失败\n");
    return;
  }
  for (; i < cont->sz - 1; i++)//注意这里sz要-1,因为下面用到了i+1下标
  {
    cont->data[i] = cont->data[i + 1];
  }
  cont->sz--;
  printf("删除成功,姓名为%s的联系人已删除\n", name);
}
void mod(Con* cont)//修改联系人函数
{
  assert(cont);//防止传入空指针
  int ret = 0;
  char name[NAME_MAX];
  printf("请输入要修改的联系人的姓名:\n");
  scanf("%s", name);
  ret = FindName(cont, name);
  if (ret == -1)
  {
    printf("通讯录中没有该联系人,修改失败\n");
    return;
  }
  printf("请输入修改后联系人的信息:\n");
  printf("请输入姓名:");
  scanf("%s", cont->data[ret].name);
  printf("请输入性别:");
  scanf("%s", cont->data[ret].sex);
  printf("请输入年龄:");
  scanf("%d", &cont->data[ret].age);
  printf("请输入电话:");
  scanf("%s", cont->data[ret].tele);
  printf("请输入地址:");
  scanf("%s", cont->data[ret].addr);
  printf("修改成功.\n");
}
void sel(const Con* cont)//查询联系人函数
{
  char name[NAME_MAX];
  printf("请输入要查询的联系人的名字:\n");
  scanf("%s", name);
  int ret = FindName(cont, name);
  if (ret == -1)
  {
    printf("通讯录中没有该联系人,查询失败.\n");
    return;
  }
  printf("查询成功,该联系信息如下:\n");
  printf("  +---------------------------------------------------------------+\n");
  printf("  |姓名   性别  年龄  电话    地址    |\n");
  printf("  |%-10s  %-5s  %-5d  %-11s %-15s |\n", cont->data[ret].name, cont->data[ret].sex, cont->data[ret].age, cont->data[ret].tele, cont->data[ret].addr);
  printf("  +---------------------------------------------------------------+\n");
}
void Distory(Con* cont)
{
  assert(cont);
  cont->sz = 0;
  printf("全部联系人删除成功!\n");
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
  return strcmp(((peo*)p1)->name, ((peo*)p2)->name);
}


//main.c


#include"contact.h"
void menu()
{
  printf("\n        欢迎使用通讯录:\n");
  printf("  +-------------------------------------------------------------+\n");
  printf("  | 1.添加联系人     2.删除联系人       |\n");
  printf("  | 3.修改联系人     4.查询联系人       |\n");
  printf("  | 5.展示通讯录     6.删除全部联系人      |\n");
  printf("  | 7.排序联系人           0.退出通讯录       |\n");
  printf("  +-------------------------------------------------------------+\n");
  printf("请选择:");
}
int main()
{
  int input = 0;
  Con cont;
  InitCon(&cont);
  while (1) {
    do
    {
      menu();
      scanf("%d", &input);
      switch (input)
      {
      case EXIT:
        printf("退出通讯录");
        break;
      case ADD:
        add(&cont);
        break;
      case DEL:
        del(&cont);
        break;
      case MOD:
        mod(&cont);
        break;
      case SEARCH:
        sel(&cont);
        break;
      case SHOW:
        show(&cont);
        break;
      case DISTORY:
        Distory(&cont);
        break;
      case QSORT:
        qsort(cont.data, cont.sz, sizeof(peo), cmp_stu_by_name);
        printf("排序成功\n");
        break;
      default:
        printf("输入错误");
        break;
      }
    } while (input);
    break;
  }
  return 0;
}
相关文章
|
2天前
|
C语言
C语言—内存函数的实现和模拟实现(内存函数的丝绸之路)
C语言—内存函数的实现和模拟实现(内存函数的丝绸之路)
17 0
|
3天前
|
程序员 编译器 C语言
C语言----动态内存分配(malloc calloc relloc free)超全知识点
C语言----动态内存分配(malloc calloc relloc free)超全知识点
14 6
|
3天前
|
存储 程序员 编译器
C语言:动态内存管理
C语言:动态内存管理
11 1
|
3天前
|
存储 编译器 程序员
C语言:数据在内存中的存储
C语言:数据在内存中的存储
15 2
|
3天前
|
存储 编译器 C语言
C语言:字符函数 & 字符串函数 & 内存函数
C语言:字符函数 & 字符串函数 & 内存函数
15 2
|
3天前
|
存储 C语言 开发者
【C言专栏】C 语言实现动态内存分配
【4月更文挑战第30天】C语言中的动态内存分配允许程序运行时按需分配内存,提供处理未知数据量的灵活性。这涉及`malloc()`, `calloc()`, `realloc()`, 和 `free()`四个标准库函数。`malloc()`分配指定大小的内存,`calloc()`同时初始化为零,`realloc()`调整内存大小,而`free()`释放内存。开发者需谨慎处理内存泄漏和指针使用,确保程序的稳定性和性能。动态内存分配是C语言中的重要技能,但也需要良好的内存管理实践。
|
3天前
|
存储 C语言
C语言进阶---------作业复习
C语言进阶---------作业复习
|
3天前
|
存储 Linux C语言
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-2
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
3天前
|
自然语言处理 Linux 编译器
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)-1
C语言进阶第十一节 --------程序环境和预处理(包含宏的解释)
|
3天前
|
存储 编译器 C语言
C语言进阶第十课 --------文件的操作-1
C语言进阶第十课 --------文件的操作