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;
}
相关文章
|
25天前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
39 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
25天前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
55 6
|
29天前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
47 6
|
1月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
126 13
|
29天前
|
大数据 C语言
C 语言动态内存分配 —— 灵活掌控内存资源
C语言动态内存分配使程序在运行时灵活管理内存资源,通过malloc、calloc、realloc和free等函数实现内存的申请与释放,提高内存使用效率,适应不同应用场景需求。
|
1月前
|
存储 编译器 数据处理
C 语言结构体与位域:高效数据组织与内存优化
C语言中的结构体与位域是实现高效数据组织和内存优化的重要工具。结构体允许将不同类型的数据组合成一个整体,而位域则进一步允许对结构体成员的位进行精细控制,以节省内存空间。两者结合使用,可在嵌入式系统等资源受限环境中发挥巨大作用。
62 11
|
29天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
1月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
58 11
|
27天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
61 1
|
1月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。