目录
[1. 数据结构](#1. 数据结构)
[2. 核心功能](#2. 核心功能)
一、基本概念
顺序表实现的通讯录 是一种基于数组结构的联系人信息管理系统。它将数据结构的理论知识(顺序表)应用到实际场景中(管理联系人信息)。
二、通俗理解
就像一本纸质通讯录
-
顺序表就像一本有固定页数的本子(数组)
-
联系人就像本子上记录的一条条信息
-
添加联系人就像在本子上写新内容
-
删除联系人就像撕掉或划掉某页内容
-
查找联系人就像翻看本子找人名
技术角度理解
内存中的样子:
位置0\] → 张三 \| 13800138000 \| 北京 \[位置1\] → 李四 \| 13900139000 \| 上海 \[位置2\] → 王五 \| 13700137000 \| 广州 \[位置3\] → 空 \[位置4\] → 空
三、这个程序是什么
它是一个命令行下的简易通讯录软件,具有以下特点:
1. 数据结构
-
顺序表:底层是一个数组,连续存储联系人
-
联系人:每个联系人是包含姓名、电话、地址的结构体
2. 核心功能
可以进行的操作:
├── 添加联系人(在末尾追加)
├── 删除联系人(按姓名查找后删除)
├── 修改联系人(更新信息)
├── 查找联系人(按姓名搜索)
├── 显示所有(列出全部联系人)
└── 自动扩容(空间不够时自动扩大)
四、通讯录代码的实现
1.多个文件的创建
如图所示

这是第一步,接下来我会一一介绍里面的全部内容和代码的意思
我们将从通讯录的定义结构体、初始化、销毁、增添数据、删除数据、修改、查找、展示数据这几个方面来进行全部的解析通讯录的实现
Contact.h文件
完整代码如下:
cpp
#define NAME_MAX 20
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100
//定义联系人数据结构
//姓名 性别 年龄 电话 地址
typedef struct personInfo // personInfo 是联系人的意思
{
char name[NAME_MAX];
char gender[GENDER_MAX];
int age;
char tel[TEL_MAX];
char addr[ADDR_MAX];
}peoInfo;
//通讯录实现的方法:对顺序表进行操作就行了
//给顺序表改一个名字,叫通讯录
typedef struct Seqlist Contact;
//通讯录的初始化
void ContactInit(Contact* con); //这里的 con 就是顺序表
//通讯录的销毁
void ContactDesTroy(Contact* con);
//通讯录增添数据
void ContactAdd(Contact* con);
//通讯录删除数据
void ContactDel(Contact* con);
//通讯录的修改
void ContactModify(Contact* con);
//通讯录的查找
void ContactFind(Contact* con);
//展示通讯录数据
void ContactShow(Contact* con);
Seqlist.h文件
完整代码如下:
cpp
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include "Contact.h"
//<Seqlist.h>该头文件一般进行方法的声明,等等,相当于书本的目录
//定义顺序表的结构
//第1种:静态顺序表(不推荐使用)
// #define N 100 //宏的定义
//struct Seqlist
//{
// int arr[N];
// int size;
//};
//第2种:动态顺序表(推荐使用)
//typedef int SLDataType; //重新命名*arr的类型,这里可以是 int *arr,也可以是其他的类型 ,此外SLDataType的中文意思是'顺序表的数据类型'
//为了实现通讯录项目,此时将上面的代码名称改为下面这个
typedef peoInfo SLDataType;
typedef struct Seqlist // Seqlist是结构体的名称,即'顺序表'它是两个单词的简写
{
SLDataType* arr; //SLDataType中文是'顺序表数据类型的意思'(这里可以是 int *arr) 因为我们存入的数据类型可能是字符类型,通过指针地址可以更加应对动态空间的变化,所以这里是*arr数组
int size; //有效的数据个数,即我们目前要真正存入的数据
int capacity; //空间大小,即当我们申请新的空间之后的数组空间大小
}SL; //SL是结构体,将Seqlist替换为SL结构体,不是命名
//上面这行代码 == typedef struct Seqlist SL ; (二选一就行了)
//顺序表初始化
void SLInit(SL *ps); //传入一个参数然后进行初始化 //SLInit中文的意思是'顺序表的初始化'
//顺序表的销毁
void SLDestroy(SL* ps);
//头部和尾部的插入
void SLPushBack(SL* ps, SLDataType x); //SLPushBack中文意思是'尾部插入'
void SLPushFront(SL* ps, SLDataType x); // '头部插入'
//头部和尾部的删除
void SLPopBack(SL* ps); //SLPopBack中文意思是'尾部删除'
void SLPopFront(SL* ps); // '头部删除'
//指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType);
//指定位置之前删除数据
void SLErase(SL* ps, int pos);
int SLFind(SL* ps, SLDataType);
Contact.c文件
完整代码如下:
cpp
#include "Contact.h"
#include "Seqlist.h"
//通讯录的初始化
void ContactInit(Contact* con)
{
//实际上要进行的是顺序表的初始化
//但是,顺序表的初始化已经实现好了
//于是我们直接调用函就行了
SLInit(con);
}
//通讯录的销毁
void ContactDesTroy(Contact* con)
{
SLDestroy(con); //这里的销毁和上面的初始化一样,直接调用函数就行了
}
//通讯录的增添
void ContactAdd(Contact* con)
{
//获取用户输入的内容 :姓名 性别 年龄 电话 地址
peoInfo info; //直接引入之前已经声明好的内容
printf("请输入要添加的联系人的姓名");
scanf("%s", info.name);
printf("请输入要添加的联系人的性别");
scanf("%s", info.gender);
printf("请输入要添加的联系人的年龄");
scanf("%d", &info.age); //唯一一个不是数组名的变量,因为使用scanf必须要取地址
printf("请输入要添加的联系人的电话");
scanf("%s", info.tel);
printf("请输入要添加的联系人的地址");
scanf("%s", info.addr);
//此时往通讯录中添加联系人的数据
//头插尾插......都可以
//这里我们选择尾插
SLPushBack(con, info); //con是通讯录 info是我们要插入的数据
}
//查找数据
int FindByName(Contact* con ,char name[])
{
for (int i = 0; i < con->size; i++)
{
if (0 == strcmp(con->arr[i].name, name)) //进行比较,如果相等则返回为0
{
//找到了
return 1;
}
}
//此时没有找到
return -1; //返回假值,退出程序
}
//通讯录删除数据
void ContactDel(Contact* con)
{
//只有当数数据存在的时候,才能执行删除操作
//所以我们删除数据之前,先必须查找是否存在数据
char name[NAME_MAX];
printf("请输入要删除的联系人姓名:\n");
scanf("%s", name);
int find = FindByName(con, name);
if(find<0)
{
printf("输入的联系人数据不存在 \n");
return;
}
//此时要删除的联系人的数据存在
SLErase(con, find);
printf("删除成功 \n");
}
//展示通讯录数据
void ContactShow(Contact* con)
{
//先将表头打印出来
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
//遍历通讯录,按照格式打印每一个联系人数据
for (int i = 0; i < con->size; i++)
{
printf("%s %s %d %s %s\n",con->arr[i].name,con->arr[i].gender,con->arr[i].age,con->arr[i].tel,con->arr[i].addr);
}
}
//通讯录的修改
void ContactModify(Contact* con)
{
//要修改的联系人数据必须存在
char name[NAME_MAX];
printf("请输入要修改的用户姓名: \n");
scanf("%s", name);
int find = FindByName(con, name); //判断联系人是否存在
if (find < 0)
{
printf("要修改的联系人数据不存在\n");
return;
}
//此时联系人存在
//可以进行修改
printf("请输入新的姓名 :\n");
scanf("%s", con->arr[find].name);
printf("请输入新的性别 :\n");
scanf("%s", con->arr[find].gender);
printf("请输入新的年龄 :\n");
scanf("%d", con->arr[find].age);
printf("请输入新的电话 :\n");
scanf("%s", con->arr[find].tel);
printf("请输入新的地址 :\n");
scanf("%s", con->arr[find].addr);
printf("修改成功\n");
}
//通讯录的查找
void ContactFind(Contact* con)
{
char name[NAME_MAX];
printf("请输入要查找的联系人姓名\n");
scanf("%s", name);
int find = FindByName(con, name); //判断联系人是否存在
if (find < 0)
{
printf("要查找的联系人数据不存在\n");
return;
}
//此时联系人数据存在
//先将表头打印出来
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
printf("%s %s %d %s %s\n", con->arr[find].name, con->arr[find].gender, con->arr[find].age, con->arr[find].tel, con->arr[find].addr);
}
Seqlist.c文件
完整代码如下:
cpp
#include "Seqlist.h" //由于多个文件的创建,必须引入对应的头文件才能使用里面的内容
//顺序表的初始化 (类似于结构体的初始化)
void SLInit(SL* ps) //这里我们给结构体的参数命名为 ps ,由于我们传过来的是sl的地址,所以我们必须用指针来接收
{
//结构体非指针的初始化,即使用点 . 来进行解引用
//ps.arr = NULL;
//ps.size = 0;
//ps.capacity = 0;
//但是由于我们接收的是指针地址,所以我们必须用指针的解引用,即使用 -> 进行解引用,而不是点 .
ps->arr = NULL;
ps->size = 0;
ps->capacity = 0;
}
//顺序表的销毁 (向系统申请空间的时候,必须的释放空间,否则会导致空间泄漏)
void SLDestroy(SL* ps)
{
if (ps->arr) //等价于 if(ps->arr != NULL)
{
free(ps->arr); //释放空间
}
ps->arr = NULL;
ps->size;
ps->capacity = 0;
}
//检查申请空间之后的容量情况
void SLcheckCapacity(SL* ps) //申请空间成功之后才会进行下面的步骤
{
if (ps->capacity == ps->size) //如果当前的容量等于我们目前已有的数据的时候,是处于空间不够的情况,此时我们不能插入数据,于是要申请空间
{
//申请空间
//malloc calloc realloc 三个可以申请空间的函数,这里我们选择 realloc是最合适的,因为它具有增容的作用
//三目表达式,即两个数据进行判断是否为真假,真选左边,假选右边
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity; //将判断好的数据进行存入newCapacity
SLDataType* tmp = (SLDataType)realloc(ps->arr, newCapacity * sizeof(SLDataType)); //一般申请原空间的2倍是最合适的,然后数据的空间是字节大小,所以我们还要乘以数据类型
//判断是否能够申请空间成功
if (tmp == NULL)
{
perror("realloc fail !!!"); //申请空间失败
exit(1); //申请失败,直接退出程序,不再进行
}
SLcheckCapacity(ps);
//此时申请空间成功
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
//尾部插入
void SLPushBack(SL* ps, SLDataType x) //SLDataType x == int x == char x 这里我们只是将数据类型变成了动态的
{
assert(ps); // 检查指针是否为空
// 必须检查容量,不够就扩容
SLcheckCapacity(ps);
//此时进行插入数据
//这里是将 x 往顺序表ps里面进行插入
//下面两行代码等价于 ps->arr[ps->size++] = x;
ps->arr[ps->size] = x;
++ps->size;
}
//头部插入
void SLPushFront(SL* ps, SLDataType x)
{
//申请空间和上面的一模一样,这里直接复制了
SLcheckCapacity(ps);
assert(ps); //判断ps的参数是否为空
//此时进行插入数据
//插入的方法:先让顺序表中已有的数据整体向后移动一位,空出前面的一个位置之后再进行头部插入
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1]; //要一直到arr[1] = arr[0]为止才真正整体移动完成 ,即前面的一个数往后移动变成了后面一个数,
}
//此时数据移动完成,已经空出了一个位置
ps->arr[0] = x; //在空出的位置直接插入 x 这个数据
ps->size++;
}
//尾部删除
void SLPopBack(SL* ps)
{
assert(ps); //断言,判断ps的参数是否是错误的,是错误则直接报错误
assert(ps->size); // 判断size的下标是否正确,即顺序表是否为空
//此时的顺序表不为空
--ps->size;
}
//头部删除
void SLPopFront(SL* ps)
{
assert(ps);
assert(ps->size);
//头部删除方法:让数据整体往前移动一位
for (int i = 0; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1]; //等价于 arr[size-2] = arr[size-1]
}
ps->size--;
}
//在指定位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x) //pos对应顺序表的下标
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);//下标不能为负
//插入数据之前看看空间够不够,不够直接申请空间
SLcheckCapacity(ps);
//插入方法:先让pos及以后的数据整体往后移动一位
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1]; //一直移到 arr[pos+1] = arr[pos] 为止
}
ps->arr[pos] = x; //此时将 x 插入对应的位置
ps->size++;
}
//指定位置之前删除数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(pos >= 0 && pos < ps->size);
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1]; // 一直到 arr[size-2] = arr[size-1]
}
ps->size--;
}
text.c文件
完整代码如下:
cpp
#include "Seqlist.h" //由于多个文件的创建,必须引入对应的头文件才能使用里面的内容
#include "Seqlist.c"
#include "Contact.h"
void menu()
{
printf("************通讯录**************\n");
printf("***1.增加联系人 2.删除联系人*************\n");
printf("***3.修改联系人 4.查找联系人****************\n");
printf("***5.展示联系人 0.退出通讯录***************\n");
printf("*********************************************\n");
}
int main()
{
int op = -1;
Contact con;
ContactInit(&con); //初始化
do
{
menu();
printf("请选择你的操作\n");
scanf("%d", &op);
} while (op != 0);
//此时根据我们所选择的操作写上对应的代码去实现
switch (op)
{
case 1:
ContactAdd(&con); //增加联系人
break;
case 2:
ContactDel(&con); //删除联系人
break;
case 3:
ContactModify(&con); //修改联系人
break;
case 4:
ContactFind(&con); //查找联系人
break;
case 5:
ContactShow(&con); //展示联系人
break;
case 0:
printf("退出通讯录\n"); //退出通讯录
break;
default:
break;
}
//数据执行完之后,进行数据销毁
ContactDesTroy(&con);
return 0;
}
后面的具体具体解释我们放到下一章里面给大家去讲解,这里我就先公布所有的代码!!!!