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

在这里插入图片描述

通讯录的实现及动态内存管理

  • 1.通讯录实现要求
  • 2.静态版通讯录实现
    • 2.1 contact.h文件实现
    • 2.2 contact.c文件实现
    • 2.3 main.c文件实现
    • 2.4 静态版通讯录全部文件代码
  • 3.动态内存管理
    • 3.1 为什么存在动态内存分配
    • 3.2 动态内存函数的介绍
    • 3.3 常见的动态内存错误
    • 3.4 C/C++程序的内存开辟
    • 3.5 柔性数组
  • 4.动态版通讯录实现
    • 4.1 contact.h文件改动
    • 4.2 contact.c文件改动
    • 4.3 main.c文件改动
    • 4.4 动态版通讯录全部文件代码
  • 5.结语

1.通讯录实现要求

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

提供方法

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

在这里插入图片描述

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++。
在这里插入图片描述
显示联系人函数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指针,第二就是打印题头,然后对结构体数组进行遍历打印,注意对齐,根据不同情况可自行调整。
在这里插入图片描述

查找联系人函数(内含)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–。
在这里插入图片描述

修改联系人函数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,则说明没有这个联系人,提示后返回退出,如果有,则同增加联系人依次修改,当然你也可以再进行优化,修改某一项。
在这里插入图片描述

查找联系人函数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函数打印,只不过这里只打印一个。
在这里插入图片描述

删除所有联系人函数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选项用的是前面枚举定义的各个名称方便辨别。
在这里插入图片描述

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

3.动态内存管理

3.1 为什么存在动态内存分配

我们已经掌握的内存开辟方式有:

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

但是上述的开辟空间的方式有两个特点:

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
    但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了。

3.2 动态内存函数的介绍

malloc和free

C语言提供了一个动态内存开辟的函数:
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

  1. 如果开辟成功,则返回一个指向开辟好空间的指针。
  2. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
  3. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  4. 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:

void free (void* ptr);

free函数用来释放动态开辟的内存。

  1. 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
  2. 如果参数 ptr 是NULL指针,则函数什么事都不做。

malloc和free都声明在 stdlib.h 头文件中。

calloc

C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:

void* calloc (size_t num, size_t size);
  1. 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  2. 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

举个例子:

#include <stdio.h>
#include <stdlib.h>
int main()
{int* p = (int*)calloc(10, sizeof(int));if (NULL != p){}free(p);p = NULL;return 0;
}

在这里插入图片描述
所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

realloc

  1. realloc函数的出现让动态内存管理更加灵活。
  2. 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。

函数原型如下:

void* realloc (void* ptr, size_t size);
  1. ptr 是要调整的内存地址
  2. size 调整之后新大小
  3. 返回值为调整之后的内存起始位置。
  4. 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
  5. realloc在调整内存空间的是存在两种情况:

情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
在这里插入图片描述

情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

情况2
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

由于上述的两种情况,realloc函数的使用就要注意一些。

3.3 常见的动态内存错误

对NULL指针的解引用操作

void test()
{int* p = (int*)malloc(INT_MAX / 4);*p = 20;//如果p的值是NULL,就会有问题free(p);
}

对动态开辟空间的越界访问

void test()
{int i = 0;int* p = (int*)malloc(10 * sizeof(int));if (NULL == p){exit(EXIT_FAILURE);}for (i = 0; i <= 10; i++){*(p + i) = i;//当i是10的时候越界访问}free(p);
}

对非动态开辟内存使用free释放

void test()
{int a = 10;int *p = &a;free(p);//ok?
}

使用free释放一块动态开辟内存的一部分

void test()
{int* p = (int*)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}

对同一块动态内存多次释放

void test()
{int* p = (int*)malloc(100);free(p);free(p);//重复释放
}

动态开辟内存忘记释放(内存泄漏)

void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();while (1);
}

忘记释放不再使用的动态开辟的空间会造成内存泄漏

切记
动态开辟的空间一定要释放,并且正确释放。

3.4 C/C++程序的内存开辟

C/C++程序内存分配的几个区域

  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分配方式类似于链表。
  3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。

3.5 柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

例如:

typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;

或有些编译器会报错无法编译可以改成:

typedef struct st_type
{int i;int a[];//柔性数组成员
}type_a;

柔性数组的特点

  1. 结构中的柔性数组成员前面必须至少一个其他成员。
  2. sizeof 返回的这种结构大小不包括柔性数组的内存。
  3. 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
    例如:
typedef struct st_type
{int i;int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4

柔性数组的使用

int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{p->a[i] = i;
}
free(p);

这个柔性数组成员a,相当于获得了100个整型元素的连续空间。

柔性数组的优势

上述的 type_a 结构也可以设计为:

typedef struct st_type
{int i;int* p_a;
}type_a;
type_a* p = (type_a*)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int*)malloc(p->i * sizeof(int));
//业务处理
for (i = 0; i < 100; i++)
{p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;

上述 代码1代码2 可以完成同样的功能,但是 代码1 的实现有两个好处:

第一个好处是:方便内存释放

如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

第二个好处是:这样有利于访问速度

连续的内存有益于提高访问速度,也有益于减少内存碎片。

4.动态版通讯录实现

我们就在原有的静态表上改动即可。

4.1 contact.h文件改动

#define N 3
#define INC 2typedef struct Contact
{peo* data;int capacity;int sz;
}Con;void DistoryCon(Con* cont);

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

4.2 contact.c文件改动

通讯录初始化函数InitCon修改

void InitCon(Con* cont)
{assert(cont);cont->data = (peo*)malloc(N * sizeof(peo));if (cont->data == NULL){perror("malloc");return;}cont->sz = 0;cont->capacity = N;
}

首先断言,之后我们给结构体数组分配初始值3个结构体大小空间,然后判断是否开辟成功,sz初始值为0,capacity初始值为我们开始设定的N也就是3。

增加检查容量函数

int CheckCapacity(Con* cont)
{if (cont->sz == cont->capacity){peo* tmp = (peo*)realloc(cont->data, (cont->capacity + INC) * sizeof(peo));if (tmp == NULL){perror("relloc");return 0;}else{cont->data = tmp;cont->capacity += INC;printf("增容成功");return 1;}}return 1;
}

这里我们用于判断容量是否已满,如果已满,我们则使用realloc函数进行扩容,这时我们不能直接扩容,而是要先用临时指针,如果空间申请成功,再将地址赋给通讯录,如果未申请成功,则为NULL指针,提示后返回0,增容成功,tmp地址传给data,容量+2,并提示增容成功,返回1,无需增容也返回1.

增加联系人函数add修改

void add(Con* cont)
{assert(cont);if (0 == CheckCapacity(cont))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");
}

把之前的条件判断改为容量函数的复用,若为0,直接返回退出。
在这里插入图片描述

增加函数销毁动态申请的空间DistoryCon

void DistoryCon(Con* cont)
{free(cont->data);cont->data = NULL;cont->capacity = 0;cont->sz = 0;
}

这个函数是为了将已申请的空间还给内存,防止内存泄漏。

4.3 main.c文件改动

这个文件只有一处改动,就是在switch语句中的case 0处增加了销毁动态申请的空间函数DistoryCon调用
在这里插入图片描述

4.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 3
#define INC 2typedef 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;int capacity;int sz;
}Con;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);void DistoryCon(Con* cont);

//contact.c

#include "contact.h"void InitCon(Con* cont)
{assert(cont);cont->data = (peo*)malloc(N * sizeof(peo));if (cont->data == NULL){perror("malloc");return;}cont->sz = 0;cont->capacity = N;
}int CheckCapacity(Con* cont)
{if (cont->sz == cont->capacity){peo* tmp = (peo*)realloc(cont->data, (cont->capacity + INC) * sizeof(peo));if (tmp == NULL){perror("relloc");return 0;}else{cont->data = tmp;cont->capacity += INC;printf("增容成功");return 1;}}return 1;
}void add(Con* cont)
{assert(cont);if (0 == CheckCapacity(cont))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);
}void DistoryCon(Con* cont)
{free(cont->data);cont->data = NULL;cont->capacity = 0;cont->sz = 0;
}

//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:DistoryCon(&cont);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;
}

5.结语

有兴趣的小伙伴可以关注作者,如果觉得内容不错,请给个一键三连吧,蟹蟹你哟!!!
制作不易,如有不正之处敬请指出
感谢大家的来访,UU们的观看是我坚持下去的动力
在时间的催化剂下,让我们彼此都成为更优秀的人吧!!!
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/23063.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微服务系列文章之 nginx负载均衡

nginx负载均衡 负载均衡建立在现有网络结构之上&#xff0c;提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽&#xff0c;增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。 随着网站的发展&#xff0c;服务器压力越来越大&#xff0c;我们可能首先会将数…

Wi-Fi 相关概念

Wi-Fi 相关概念 802.11 Wi-Fi 标准及其含义频宽 和 带宽 的概念20MHz与40MHz的区别2.4G 频段2.4G的频道编号和中心频率 5G WiFi 频段中国开放的的5G WiFi频段&#xff1a;5G的频道编号和中心频率&#xff1a; 802.11历史进程一、802.11重要发展二、802.11协议族 参考 802.11 Wi…

2023下半年软考高级系统架构设计师怎么报名?

软考高级系统架构设计师报名时间&#xff1a; 广西2023下半年软考高级系统架构设计师报名时间&#xff1a;8月15日8:00至8月24日17:00 广东2023下半年软考高级系统架构设计师报名时间&#xff1a;8月16日9:00-8月24日17:00 甘肃2023下半年软考高级系统架构设计师报名时间&am…

AcWing 107. 超快速排序—逆序对

问题链接: AcWing 107. 超快速排序 问题描述 分析 这道题考查的算法不难&#xff0c;就只是利用归并排序来求逆序对的数量&#xff0c;但是主要是如何分析问题&#xff0c;如何能从问题中看出来和逆序对数量有关&#xff0c;现在的题目基本上很少是那种模板算法题了&#xff…

漏洞复现 || TerraMaster TOS exportUser.php 远程命令执行

阅读须知 技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的直接或间接后果和损失,均由使用者本人负责。…

大坝安全监测中需要做好检查监测

大坝安全监测是人们了解大坝运行状态和安全状况的有效手段和方法。它的目的主要是了解大坝安全状况及其发展态势&#xff0c;是一个包括由获取各种环境、水文、结构、安全信息到经过识别、计算、判断等步骤&#xff0c;最终给出一个大坝安全 程度的全过程。 此过程包括&#xf…

SpringCloud Alibaba——Nacos服务领域模型

目录 一、Nacos服务领域模型二、Nacos服务领域模型图解 一、Nacos服务领域模型 模型名称解释Namespace实现环境隔离&#xff0c;默认值publicGroup不同的service可以组成一个Group&#xff0c;默认值Default-GroupService服务名称Cluster对指定的微服务虚拟划分&#xff0c;默…

Linux自主学习 - 多线程的创建(#include<pthread.h>)

备注&#xff1a;vscode通过ssh连接虚拟机中的ubuntu&#xff0c;ubuntu-20.04.3-desktop-amd64.iso 函数pthread_create() // pthread.h中的函数pthread_create()extern int pthread_create (pthread_t *__restrict __newthread, // 线程标识符const pthread_attr_t *…

python 面向对象编程

文章目录 前言如何理解面向对象编程在 python 中如何使用面向对象编程定义类创建对象self添加和获取对象属性添加属性类外添加属性类中添加属性 访问属性类外访问属性类中访问属性 魔法方法__ init __() 方法__ str __()方法__ del __() 方法 前言 大家好&#xff0c;前面我们…

Rust操作MySQL

查询 本部分是对 「Rust入门系列」Rust 中使用 MySQL[1]的学习与记录 经常使用的时间处理库&#xff1a; chrono 流式查询使用&#xff1a; query_iter 输出到Vec使用&#xff1a; query 映射到结构体使用&#xff1a; query_map 获取单条数据使用&#xff1a; query_first 命名…

网络通信原理系统的认知(NEBASE第十四课)

1 物理层 第一层 物理层&#xff1a;建立、维护、断开物理连接&#xff0c;定义了接口及介质&#xff0c;实现了比特流的传输。 1.1传输层介质分类 有线介质&#xff1a;网线 &#xff08;双绞线&#xff09;光纤 无线: 无线电 1.2 双绞线 五类线 cat5 &#xff1a;适用 10…

HOT62-N皇后

leetcode原题链接&#xff1a;N皇后 题目描述 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返…