通讯录(基于顺序表)
分析
我们知道通讯录存放着许多人的通讯信息
其实我们可以将通讯录理解成一个顺序表,每一个空间都存放这着一个人的通讯信息
如图所示:
顺序表本身也是个结构体
而我们采用结构体去存放联系人信息
也就是顺序表的每个单元存放着一个结构体 里面存放着联系人信息
这里我们可以知道
通讯录的实现是基于顺序表的
代码:
我们实现这个通讯录项目需要五个文件
分别是两个头文件 和 三个源文件
因为该通讯录的底层就是顺序表
因此我们需要顺序表的代码:
SeqList.h 头文件
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include<string.h>
# include"Contact.h" //引入通讯录头文件
// 动态顺序表的定义
//typedef int SLDataType;
typedef peoInfo SLDataType;// 将我们的联系人数据 结构 作为 我们顺序表的存储元素
typedef struct SeqList
{
SLDataType* arr;
int size; // 记录有效数据个数
int capacity; // 记录空间
}SL;
// 动态顺序表的功能函数声明
// 顺序表的初始化
void SLInit(SL* ps);
// 顺序表的销毁
void SLDestory(SL* ps);
// 顺序表的打印
void SLPrint(SL s);
//头部插入删除 / 尾部插入删除
void SLPushBack(SL* ps, SLDataType x);
void SLPopBack(SL* ps);
void SLPushFront(SL* ps, SLDataType x);
void SLPopFront(SL* ps);
// 顺序表指定位置的插入
void SLInsert(SL* ps, int pos, SLDataType x);
// 顺序表的指定位置的删除
void SLErase(SL* ps, int pos);
// 顺序表的查找
int SLFind(SL* ps);
注意: 这里引入了Contact.h头文件,那么在Contact.h头文件中不能再引入 SeqList.h头文件,不然会造成交叉引用 。程序会报错。
Contact.h头文件:
#pragma once
// 联系人数据
// 姓名 性别 年龄 电话 地址
#define NAME_MAX 20
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100
typedef struct personInfo
{
char name[NAME_MAX];
char gender[GENDER_MAX]; // 性别
int age;
char tel[TEL_MAX]; // 电话
char addr[ADDR_MAX]; // 地址
}peoInfo;
typedef struct SeqList Contact; // 将顺序表改个名字 改成通讯录相关的名字
//这里的 struct SeqList 是一个 外置声明
// 定义完动态顺序表 作为我们的通讯录
// 那我们要对通讯录有着实际操作的能力 其实对通讯录操作就是对顺序表的操作
// 通讯录的初始化
void ContactInit(Contact* 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);
// 通讯录的存储文件的读取
void LoadContact(Contact* con);
// 将通讯录写入文件
void SaveContact(Contact* con);
SeqList.c源文件
这里要注意了 顺序表的功能有一些在通讯录是用不上的
因为通讯录存储的数据形式是结构体类型
用来存储 联系人数据的
比如SLFind 等等就用不到需要重新编写
# include"SeqList.h"
// 这里存放着所有顺序表功能所需函数的实现
// 顺序表的初始化
void SLInit(SL* ps)
{
ps->arr = NULL;
ps->size = 0;
ps->capacity = 0;
}
// 顺序表的销毁
void SLDestory(SL* ps)
{
// 判断顺序表内是否还有内容
if (ps->arr)
{
free(ps->arr);
}
ps->arr = NULL;
ps->size = 0;
ps->capacity = 0;
}
顺序表的打印
//void SLPrint(SL s)
//{
// for (int i = 0; i < s.size; i++)
// {
// printf("%d ", s.arr[i]); // 这里s.arr[i] 等价于 *(s.arr + i)
// }
// printf("\n");
//}
// 判断空间是否足够的函数
void SLCheckCapacity(SL* ps)
{
if (ps->size == ps->capacity)
{
// 扩容
// 首先判断顺序表的空间是否为0 不是 就扩容2倍 是就给4个初始空间
int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));// 注意realloc函数的增容的单位是字节
// 判断是否扩容成功
if (tmp == NULL)
{
perror("realloc");
exit(1);
}
// 扩容成功更新
ps->arr = tmp;
ps->capacity = newCapacity;
}
}
// 顺序表的尾插
void SLPushBack(SL* ps, SLDataType x)
{
assert(ps);
// 首先判断空间是否足够我们去插入
SLCheckCapacity(ps);
// 空间足够 进行尾插
ps->arr[ps->size] = x;
ps->size++; // 每次插入一个数据 该顺序表元素数量+1
}
// 顺序表的头插
void SLPushFront(SL* ps, SLDataType x)
{
assert(ps);
// 判断空间是否足够进行插入
SLCheckCapacity(ps);
// 头插
// 让每个数据往后移动一个空间
for (int i = ps->size; i > 0; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
// 插入
ps->arr[0] = x;
ps->size++;
}
// 顺序表的尾删
void SLPopBack(SL* ps)
{
assert(ps);
// 尾删之前需要判断 顺序表内部是否还存在元素
assert(ps->size);
// 尾删
//ps->arr[ps->size - 1] = -1; 这句话不需要 也能实现
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];
}
ps->size--;
}
// 顺序表的指定位置插入
void SLInsert(SL* ps, int pos, SLDataType x)
{
assert(ps);
// 对pos下标进行判断
assert(pos >= 0 && pos <= ps->size);
// 对顺序表的空间进行判断
SLCheckCapacity(ps);
// 进行插入
// 让pos位置后的全部数据往后移动一个空间
for (int i = ps->size; i > pos; i--)
{
ps->arr[i] = ps->arr[i - 1];
}
// 插入
ps->arr[pos] = x;
ps->size++;
}
// 顺序表的指定位置删除
void SLErase(SL* ps, int pos)
{
assert(ps);
// 对pos进行判断
assert(pos >= 0 && pos < ps->size);
// 判断顺序表是否为空
assert(ps->size > 0);
// 进行删除
for (int i = pos; i < ps->size - 1; i++)
{
ps->arr[i] = ps->arr[i + 1];
}
// // 只剩下一个元素的特殊情况
//if (ps->size == 1)
//{
// ps->arr[0] = 0;
//}
ps->size--;
}
顺序表的查找
//int SLFind(SL* ps, SLDataType x)
//{
// assert(ps);
// if (ps->size == 0)
// {
// printf("该顺序表为空,无法查找数据\n");
// }
//
// // 查找
// for (int i = 0; i < ps->size; i++)
// {
// if (ps->arr[i] == x)
// {
// return i;
// }
// }
//
// return -1;
//}
Contact.c源文件
# include "Contact.h"
# include "SeqList.h"
// 通讯录的初始化
void ContactInit(Contact* con)
{
// 我们知道其实就是对顺序表的初始化
// 而我们已经在SeqList.c文件当中写好了顺序表的初始化函数
SLInit(con);
}
// 通讯录的销毁
void ContactDestroy(Contact* con)
{
SLDestory(con);
}
// 通讯录的展示
void PeoInfoPrint(peoInfo s) // 对传进来的联络人信息的展示
{
printf("%2s %2s %2d %2s %2s\n", s.name, s.gender, s.age, s.tel, s.addr);
// 这里可以手动调整格式
}
void ContactShow(Contact* con)
{
printf("该通讯录的联系人信息如下:\n");
// 先把表头打印出来
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
// 把通讯录存储的每一个联系人的信息都给打印出来
for (int i = 0; i < con->size; i++)
{
PeoInfoPrint(con->arr[i]);// con->arr[i]是peoInfo结构体
}
}
// 通讯录的添加数据
void ContactAdd(Contact* con)
{
// 获取用户输入的内容 姓名+性别+年龄+电话+地址
//
// 我们之前在"Contact.h"头文件 中定义了 存储联系人数据的 结构体
// 将用户输入的数据 全部放到 penInfo结构体中
// penInfo结构体 其实就是我们通讯录中存储的数据类型
peoInfo info;
//SLDataType info; 等价于上面的peoInfo info;
printf("请输入要添加的联系人姓名:\n");
scanf("%s", info.name);
printf("请输入要添加的联系人性别:\n");
scanf("%s", info.gender);
printf("请输入要添加的联系人年龄:\n");
scanf("%d", &(info.age)); // 加了取地址 是因为age是整型不是数组
printf("请输入要添加的联系人电话:\n");
scanf("%s", info.tel);
printf("请输入要添加的联系人地址:\n");
scanf("%s", info.addr);
// 往通讯录中添加联系人数据
SLPushBack(con, info); // 我们选择尾插来添加数据 这里可以自由选择
printf("添加成功\n");
}
// 通过联系人的某一个特征去删除联系人的特征
// 这里用的是名字 也可以用地址 和 电话 , 性别和年龄特征相同的太多了 不好去删除
int FindByName(Contact* con, char name[])
{
// 首先我需要遍历通讯录
for (int i = 0; i < con->size; i++)
{
if (strcmp(con->arr[i].name, name) == 0)// con->arr[i]代表penInfo结构体
{
// 走到这里说明找到了
return i;
}
}
// 走到这里说明没有找到
return -1;
}
// 通讯录的删除数据
void ContactDel(Contact* con)
{
// 输入要删除的数据
char name[NAME_MAX];
printf("请输入要删除的联系人的姓名:\n");
scanf("%s", name);
// 首先我需要去找到这个数据 我才能删除
// 查找
int ret = FindByName(con, name);
if (ret >= 0) // 说明找到了需要删除的数据
{
SLErase(con, ret); // 将该联系人删除 这里删除是将一整个联系人的所有数据删除
printf("你所要删除的联系人:%s,已经成功删除\n", name);
}
else
{
printf("你所要删除的联系人,不存在于通讯录中!!!\n");
return;
}
}
// 通讯录的修改
void ContactModify(Contact* con)
{
// 判断你所修改的联系人是否存储在通讯录
char name[NAME_MAX];
printf("请输入你需要修改的用户姓名:\n");
scanf("%s", name);
// 通过我们自己定义的FindByName来判断是否存在
int ret = FindByName(con, name);
if (ret >= 0)// 找到了
{
// 展示联系人信息
printf("该联系人的信息如下:\n");
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
PeoInfoPrint(con->arr[ret]);
// 修改联系人信息
// 请选择你需要修改的数据
int n = 0;
do {
printf("请选择你需要修改的数据\n");
printf("*****************************************\n");
printf("****1.姓名*******2.性别******3.年龄*******\n");
printf("****4.电话*******5.地址******0.退出*******\n");
printf("*****************************************\n");
scanf("%d", &n);
switch (n)
{
case 1:
printf("请输入新的姓名\n");
scanf("%s", con->arr[ret].name);
break;
case 2:
printf("请输入新的性别\n");
scanf("%s", con->arr[ret].gender);
break;
case 3:
printf("请输入新的年龄\n");
scanf("%d", &(con->arr[ret].age));
break;
case 4:
printf("请输入新的电话\n");
scanf("%s", con->arr[ret].tel);
break;
case 5:
printf("请输入新的地址\n");
scanf("%s", con->arr[ret].addr);
break;
case 0:
printf("已退出修改\n");
break;
default:
printf("输入不合法,请重新输入\n");
break;
}
} while (n);
printf("修改成功\n");
}
else // 该联系人不存在
{
printf("你所要修改的联系人:%s 不在通讯录中!!!\n", name);
return;
}
}
// 通讯录查找
void ContactFind(Contact* con)
{
char name[NAME_MAX];
printf("请输入要查找的联系人姓名:\n");
scanf("%s", name);
// 判断通讯里是否有我们要查找的这个联系人
int ret = FindByName(con, name);
if (ret >= 0) // 找到了
{
printf("你所查找的联系人%s信息如下:\n", name);
// 找到了我们就把该联系人的信息打印出来
PeoInfoPrint(con->arr[ret]);
}
else
{
printf("你所查找的联系人不存在!!!\n");
return;
}
}
// 通讯录的文件读取
void LoadContact(Contact* con)
{
// 打开文件
FILE* pf = fopen("contact.txt", "rb");
// 判断文件是读取成功
if (pf == NULL)
{
perror("fopen");
return;
}
// 将联络人数据从文件中读取出来 放到通讯录中
int i = 0;
peoInfo info;
while (fread(&info, sizeof(peoInfo), 1, pf))
{
SLPushBack(con, info); // 将联络人数据存入通讯录中
}
printf("历史数据导入成功\n");
// 关闭文件
fclose(pf);
pf = NULL;
}
// 通讯录中的数据存入文件当中
void SaveContact(Contact* con)
{
// 打开文件
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return;
}
// 将通讯录中的联络人数据 写入文件中
for (int i = 0; i < con->size; i++)
{
fwrite(con->arr + i, sizeof(SLDataType), 1, pf);
}
printf("已经成功保存联络人数据\n");
// 关闭文件
fclose(pf);
pf = NULL;
}
test.c源文件:
# include"SeqList.h"
// 对通讯录的函数功能测试
/*
// 对通讯录的函数功能测试
void Test01()
{
Contact con; // 这里创建的是顺序表的变量
// 等价于 SL s1
// 通讯录初始化
ContactInit(&con);
// 通讯录的添加
ContactAdd(&con);
ContactAdd(&con);
// 通讯录的展示
ContactShow(&con);
// 通讯录的删除
ContactDel(&con);
ContactShow(&con);
// 通讯录的修改
ContactModify(&con);
ContactShow(&con);
// 通讯录的查找
ContactFind(&con);
// 通讯录的销毁
ContactDestroy(&con);
}
int main()
{
Test01();
return 0;
}
*/
void menu()
{
printf("******************通讯录**********************\n");
printf("********1.增加联系人 2.删除联系人************\n");
printf("********3.修改联系人 4.查找联系人************\n");
printf("********5.展示联系人 0.退出******************\n");
}
int main()
{
Contact con;
ContactInit(&con);
while (1)
{
int m = 0;
printf("是否要导入历史数据?\n");
printf("1.yes,2.no\n");
scanf("%d", &m);
if (m == 1)
{
LoadContact(&con);// 从文件中读取联络人数据
break;
}
else if (m == 2)
{
printf("已退出......\n");
break;
}
else
printf("输入不合法,请重新输入\n");
}
int n = 0;
do {
menu();
printf("请选择您的操作\n");
scanf("%d", &n);
switch (n)
{
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:
printf("操作不合法,请重新输入\n");
break;
}
} while (n);
while (1)
{
int m = 0;
printf("是否要保存联系人数据?\n");
printf("1.yes,2.no\n");
scanf("%d", &m);
if (m == 1)
{
SaveContact(&con); // 将联络人信息存储到文件中
break;
}
else if (m == 2)
{
printf("已退出......\n");
break;
}
else
printf("输入不合法,请重新输入\n");
}
ContactDestroy(&con); // 将通讯录销毁
return 0;
}
总结:
通讯录是基于顺序表实现的
很多方法都是可以直接调用顺序表的功能去帮助实现的