各位老铁们时隔数日,俺又回来了,今日小试牛刀一把,给大家share 一下,基于顺序表的基础,如何实现通讯录项目???
欲知后事如何,且看以下分析!
目录
目录
一:基本知识点
二:顺序表的初始化,销毁,增·删·检·查等等一系列操作
三:通讯录涉及到的一些增删检查
在讲通讯录如何实现之前,容我先卖个关子。
首先我们需要先了解以下顺序表的相关知识。
1.顺序表是线性表的一种,它在逻辑结构上一定是线性的,但是在物理结构上不一定是线性的。
逻辑结构:也就是我们人为想象的
物理结构:计算机内存存储
2.顺序表是基于数组实现的,但它又不完全同于数组
3.掌握结构体的一些基本常识
4 顺序表是随机存取的,它可以任意访问某一个指定数据
1.结构体的重命名,其实主要就是简化名字,用起来比较方便一些
1.1比如说想把 类型为:struct SeqList 重命名为SL
typedef struct SeqList SL;
1.2 当我们把自己所写的接口给他人使用时,可能就不仅仅局限于我对int 型数据的处理,我 们不妨做一下优化
typedef int SLDataType;
而我们在定义我们数据域的时候需要这样写
SLDataType* a;
这样写的目的以便于可以实现其他类型
ok~~咱话不多,上代码
2.顺序表里面涉及函数的具体实现
2.1)顺序表的初始化
void SLInit(SL *ps)//初始化
{
ps->a = NULL;//因为ps 是指针 ,所以只能用箭头来访问
ps->size = ps->capacity = 0;//支持连等
}
2.2)顺序表的销毁
注意我们的free 只适用于释放动态内存开辟的空间(堆),并且所释放的空间不能为空
所以我们就需要进项判断释放为空
void SLDestroy(SL *ps)//销毁
{
//free 只能适用于动态内存开辟的空间且所free 的空间不能为NULL
if (ps->a)//判断所指的空间是否为空
{
free(ps->a);
ps->a = NULL;
}
ps->capacity = ps->size = 0;
}
2.3)顺序表打印
我们这里只需要遍历整个顺序表即可
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
2.4)顺序表的尾插
2.4.1)顺序表有足够的空间可以插入
这时候我们只需要在 size-1 这个下标对应的位置插入即可
2.4.2)当我们空间不足的时候就需要开辟空间
malloc() :向内存申请空间
realloc() :向内存申请一块连续的空间
calloc():向内存申请空间,并把他进行初始化
这时候,需要思考以下,用哪一个来开辟空间
没错,我们使用realloc()来开辟空间,因为我们不知道自己要具体插入多少个数据,而realloc向内存申请一块连续的空间
因为我们在进行头插一系列操作时,都需要判断是否有空间,所以把判断是否有空间写成一个函数
注意这里有一些小细节:
1)SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(newcapacity));
这样写是不对的,因为sizeof()在求大小的时候是以字节为单位的
2)int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;// 初始化之后我size,capacity是为0
这句必须写,若不写当我开辟空间的时候,这个空间大小是0
void SLCheckCapacity(SL* ps)// 空间容量的检查
{
if(ps->size == ps->capacity)//判断是否有空间
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;// 初始化之后我的size,capacity是为0
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity); // 注意: 头文件的引用;sizeof求空间大小时,是以字节为单位的 这时候所开辟空间的类型SLDataType*
//空间可能会存在开辟失败的
if (tmp == NULL)
{
perror("malloc\n");
}
// 空间开辟成功,进行更新
ps->a = tmp;
ps->capacity = newcapacity;
}
}
尾插函数的具体实现:
void SLPushBack(SL* ps,SLDataType x)//尾插 注意:这里第二个参数是SLDataType型
{
assert(ps); //确保指针有效性
//先确认是否有空间可以插入 不够:扩容 够了:直接插
SLCheckCapacity(ps);
//再进行插入
/*for (int i = 0; i < ps->size; i++)
{
ps->a[i] = x;// 对当前第 i 个位置进行插入
ps->size++;
}注意当我size= 0,这样写是不可以的
*/
ps->a[ps->size++] = x; //这样写直接一步到位 因为也插入了,size也对应更新了
//也可以这样写 ps->a[ps->size] = x;ps->size++;
}
1)这里依然是需要考虑以下是否有空间进行头插
2)如何插入:首先挪动数据——怎么挪?其次进行插入
从前往后挪:
显然是不行的,从前往后挪动会造成数据的覆盖所以从后往前挪
思路有了,那我们就代码见吧
void SLPushFront(SL* ps, SLDataType x)//头插
{
assert(ps);
//先判断空间是否足够
SLCheckCapacity(ps);
// 从后往前移动
for (int i = ps->size-1; i >= 0 ; i--)
{
ps->a[i + 1] = ps->a[i];
}
ps->a[0] = x;
ps->size++;
}
1)首先我们要确保这不是一个空表->只需要ps-size == 0
2)ps->size :表示当前顺序表数据的有效个数
3)尾删我们只需要让size--即可
void SLPopBack(SL* ps)//尾删
{
assert(ps);
//判断是不是空表
/*if (IsEmpty(ps) == 0)
perror(IsEmpty);*///这样写是不对的
assert(!IsEmpty(ps));//assert(IsEmpty(ps)) 这样写是不对的 assert()断言是用来判断里面的表达式是真还是假,只有为假的话,这个断言才会生效
//进行尾删
ps->size--;// 没有必要进行赋值,直接size--就行
}
1.)首先确定这不是一个空表
2.)数据如何移动?最后别忘了让ps->size--;
挪动之后的数数据
核心代码: ps->a[i] = ps->a[i + 1];
void SLPopFront(SL* ps)//头删
{
assert(ps);
//首先确保不是空表,其次挪动数据:从后往前挪
assert(!IsEmpty(ps));//问题同上(88)
for (int i = 0; i size-1 ; i++)
{
//最后一次进来 ps->a[size-1-1] = ps->a[size-1]
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
这个指定位置插入和前面插入基本上如出一辙,只不过我们需要判断插入位置的有效性
assert(pos >= 0 && pos <= ps->size);
void SLInsert(SL* ps, int pos, SLDataType x)//指定位置插入
{
//pos 要在有效的范围内 && 空间是否足够
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
// 进行插入:从后往前移动
for (int i = ps->size-1; i >pos-1 ;i--)
{
// 最后进来 ps->a[pos+1] = ps->a[pos];
ps->a[i + 1] = ps->a[i];
}
ps->a[pos] = x;
ps->size++;
}
我们需要考虑位置有效性以及空间是否足够
void SLEarse(SL* ps, int pos)//指定位置删除
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
// 注意是从后往前移动
for (int i =pos; i < ps->size-1; i++)
{
//最后进来的i : ps->size-2 ps->size-2 = ps->size-1;
ps->a[i] = ps->a[i+1];
}
ps->size--;
}
来到这里我们对顺序表的基本操作是已经完成了,下面我们可以通过这个顺序表来实现通讯录这个小项目
通讯录的底层是顺序表,下面就需要我们对顺序表进行一个封装
首先我们需要先考虑以下到底是使用定长数组还是动态数组???
其次我们需要自定义一个通讯录结构体的类型
#pragma once
#define NAME_MAX 1000
#define SEX_MAX 100
#define TEL_MAX 100
#define ADDR_MAX 100
typedef struct ContactInfo CInfo ;//类型重命名 CInfo
函数的具体实现;
void CInfoAdd(Contact* pcon)//联系人的添加
{
assert(pcon);//头文件引入
CInfo info; //先创建一个通讯录这样类型的一个变量info ,info这个变量表示一个联系人对应的所有数据
printf("请输入要添加联系人的姓名\n");
scanf("%s", info.name);// 数组名表示首元素地址,无需&
printf("请输入要添加联系人的性别\n");
scanf("%s", info.sex);
printf("请输入要添加联系人的电话\n");
scanf("%s", info.tel);
printf("请输入要添加联系人的地址\n");
scanf("%s",info.addr);
printf("请输入要添加联系人的年龄\n");
scanf("%d",&info.age);
//终端输入一个联系人对应的数据后进行插入(尾插)
SLPushBack(pcon,info);// 往哪里插,要插入谁的
}
删除之前,先判断是否为空
ps->size == 0
void CInfoPop(Contact* pcon)//联系人的删除
{
//先判断是否为空,其次删除
assert(pcon);
//SLPopBack(pcon);尾删
SLPopFront(pcon);//头删
}
这里我们先暂时根据用户输入要修改联系人的姓名来操作
首先我们需要先判断此联系人是否存在>其次在对联系人各项具体内容进行修改
这里涉及到strcmp这个函数的应用
int FindByName(Contact* pcon,char name[])
{
assert(pcon);
for (int i = 0; i < pcon->size; i++)
{
//pcon->a[i].name == name;//注意这样写是不对的的
//要判断所查找的姓名和我指定要删除的姓名对应的内容都是字符串:strcmp()用来比较字符串大小的,当2个字符串内容一样,此函数返回0
if(strcmp(pcon->a[i].name,name)== 0)
return i;//直接返回该联系人对应的下标
}
//没有找到
return -1;
}
void CInfoModify(Contact* pcon)//联系人的修改
{
//比如先根据输入联系人姓名来进行修改,首先判断该联系人是否存在
printf("请输入要修改联系人的姓名\n");
char name[NAME_MAX];
scanf("%s",name);//修改之后的内容如何保存?
int find = FindByName(pcon, name);//find :要修改联系人对应的下标
if (find < 0)
{
printf("该联系人不存在\n");
return;//没有必要执行下面的代码
}
//来到这,此联系人是存在的
printf("请输入联系人的姓名\n");
scanf("%s", pcon->a[find].name);
printf("请输入联系人的性别\n");
scanf("%s", pcon->a[find].sex);
printf("请输入联系人的电话\n");
scanf("%s", pcon->a[find].tel);
printf("请输入联系人的地址\n");
scanf("%s", pcon->a[find].addr);
printf("请输入联系人的年龄\n");
scanf("%d", &pcon->a[find].age);
printf("修改联系人已成功\n");
}
思路:先判断该联系人是否存在->若存在则返回此联系人对应的所有信息
int CInfoFind(Contact* pcon)//联系人查找
{
//根据姓名判断该联系人是否存在,再进行打印
printf("请输入要查找联系人的姓名\n");
char name[NAME_MAX];
scanf("%s", name);//修改之后的内容如何保存?
int find = FindByName(pcon, name);//find :要修改联系人对应的下标
if (find < 0)
{
printf("该联系人不存在\n");
return;//没有必要执行下面代码
}
printf("%-5s %-5s %-5s %-5s %-5s\n", "姓名", "性别", "电话", "地址", "年龄");
printf("%-5s %-5s %-5s %-5s %-5d\n", pcon->a[find].name,pcon->a[find].sex,pcon->a[find].tel,pcon->a[find].addr,pcon->a[find].age);
}
其实这个很简单了,我们只需循环遍历即可
void CInfoPrint(Contact* pcon)//联系人打印
{
assert(pcon);
//打印一下我的表头
printf("%-6s", "姓名");// %-20s -20表示指定输出格式为左对齐&&指定位宽为20 %20s: 表示右对齐&&指定位宽20
printf("%-6s", "性别");
printf("%-6s", "电话");
printf("%-6s", "地址");
printf("%-6s\n", "年龄");
for (int i = 0; i < pcon->size; i++)
{
printf("%-5s", pcon->a[i].name);
printf("%-5s", pcon->a[i].sex);
printf("%-10s", pcon->a[i].tel);
printf("%-5s", pcon->a[i].addr);
printf("%-5d\n", pcon->a[i].age);
}
}
来到这里,说明我们基本已经大功告成了,下面我们只需要进行简单的操作,以菜单形式来呈现出来
void ContactMenu()
{
printf("*****************************************************\n");
printf("***************1.添加联系人 2.删除联系人********\n");
printf("***************3.查找联系人 4.修改联系人********\n");
printf("***************5.打印通讯录 6.退出通讯录********\n");
printf("****************************************************\n");
}
#pragma once
#define NAME_MAX 1000
#define SEX_MAX 100
#define TEL_MAX 100
#define ADDR_MAX 100
typedef struct ContactInfo
{
char name[NAME_MAX];
char sex[SEX_MAX];
char tel[TEL_MAX];
char addr[ADDR_MAX];
int age;
}CInfo;
//通讯录的底层是顺序表实现的
typedef struct SeqList Contact;
//函数的声明
void CInfoInit(Contact* pcon);//初始化
void CInfoDestroy(Contact* pcon);//销毁
void CInfoAdd(Contact* pcon);//联系人的添加
void CInfoPop(Contact* pcon);//联系人的删除
void CInfoPrint(Contact* pcon);//联系人打印
void CInfoModify(Contact* pcon);//联系人的修改
int CInfoFind(Contact* pcon);//指定联系人的查找
#include
#include"assert.h"
#include
#include"Contact.h"
void CInfoInit(Contact* pcon)//通讯录的初始化
{
SLInit(pcon);
}
void CInfoDestroy(Contact* pcon)//通讯录的销毁
{
SLDestroy(pcon);
}
void CInfoAdd(Contact* pcon)//联系人的添加
{
assert(pcon);//头文件引入
CInfo info; //先创建一个通讯录这样类型的一个变量info ,info这个变量表示一个联系人对应的所有数据
printf("请输入要添加联系人的姓名\n");
scanf("%s", info.name);// 数组名表示首元素地址,无需&
printf("请输入要添加联系人的性别\n");
scanf("%s", info.sex);
printf("请输入要添加联系人的电话\n");
scanf("%s", info.tel);
printf("请输入要添加联系人的地址\n");
scanf("%s",info.addr);
printf("请输入要添加联系人的年龄\n");
scanf("%d",&info.age);
//终端输入一个联系人对应的数据后进行插入(尾插)
SLPushBack(pcon,info);// 往哪里插,要插入谁的
}
void CInfoPrint(Contact* pcon)//联系人打印
{
assert(pcon);
//打印一下我的表头
printf("%-6s", "姓名");// %-20s -20表示指定输出格式为左对齐&&指定位宽为20 %20s: 表示右对齐&&指定位宽20
printf("%-6s", "性别");
printf("%-6s", "电话");
printf("%-6s", "地址");
printf("%-6s\n", "年龄");
for (int i = 0; i < pcon->size; i++)
{
printf("%-5s", pcon->a[i].name);
printf("%-5s", pcon->a[i].sex);
printf("%-10s", pcon->a[i].tel);
printf("%-5s", pcon->a[i].addr);
printf("%-5d\n", pcon->a[i].age);
}
}
void CInfoPop(Contact* pcon)//联系人的删除
{
//先判断是否为空,其次删除
assert(pcon);
//SLPopBack(pcon);尾删
SLPopFront(pcon);//头删
}
int FindByName(Contact* pcon,char name[])
{
assert(pcon);
for (int i = 0; i < pcon->size; i++)
{
//pcon->a[i].name == name;//注意这样写是不对的的
//要判断所查找的姓名和我指定要删除的姓名对应的内容都是字符串:strcmp()用来比较字符串大小的,当2个字符串内容一样,此函数返回0
if(strcmp(pcon->a[i].name,name)== 0)
return i;//直接返回该联系人对应的下标
}
//没有找到
return -1;
}
void CInfoModify(Contact* pcon)//联系人的修改
{
//比如先根据输入联系人姓名来进行修改,首先判断该联系人是否存在
printf("请输入要修改联系人的姓名\n");
char name[NAME_MAX];
scanf("%s",name);//修改之后的内容如何保存?
int find = FindByName(pcon, name);//find :要修改联系人对应的下标
if (find < 0)
{
printf("该联系人不存在\n");
return;//没有必要执行下面的代码
}
//来到这,此联系人是存在的
printf("请输入联系人的姓名\n");
scanf("%s", pcon->a[find].name);
printf("请输入联系人的性别\n");
scanf("%s", pcon->a[find].sex);
printf("请输入联系人的电话\n");
scanf("%s", pcon->a[find].tel);
printf("请输入联系人的地址\n");
scanf("%s", pcon->a[find].addr);
printf("请输入联系人的年龄\n");
scanf("%d", &pcon->a[find].age);
printf("修改联系人已成功\n");
}
int CInfoFind(Contact* pcon)//联系人查找
{
//根据姓名判断该联系人是否存在,再进行打印
printf("请输入要查找联系人的姓名\n");
char name[NAME_MAX];
scanf("%s", name);//修改之后的内容如何保存?
int find = FindByName(pcon, name);//find :要修改联系人对应的下标
if (find < 0)
{
printf("该联系人不存在\n");
return;//没有必要执行下面代码
}
printf("%-5s %-5s %-5s %-5s %-5s\n", "姓名", "性别", "电话", "地址", "年龄");
printf("%-5s %-5s %-5s %-5s %-5d\n", pcon->a[find].name,pcon->a[find].sex,pcon->a[find].tel,pcon->a[find].addr,pcon->a[find].age);
}
#pragma once
//typedef int SLDataType;
typedef struct ContactInfo SLDataType;//这样子写是不对的:typedef struct CInfo SLDataType
typedef struct SeqList
{
SLDataType* a;//可以实现其他类型
int size;//顺序表的有效个数
int capacity;//顺序表的容量
}SL;// 重命名
//函数的声明
void SLInit(SL* ps);//初始化
void SLPrint(SL* ps);//打印
void SLDestroy(SL* ps);//销毁
void SLPushBack(SL* ps,SLDataType);//尾插
void SLPopBack(SL* ps);//尾删
void SLPushFront(SL* ps,SLDataType x);//头插
void SLPushFront(SL* ps);//头删
void SLInsert(SL* ps,int pos , SLDataType x);//指定位置插入
void SLEarse(SL* ps, int pos);//指定位置删除
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
#include"Contact.h"
#include"assert.h"
#include
#include
#include
//函数的具体实现
void SLInit(SL *ps)//初始化
{
ps->a = NULL;//因为ps 是指针 ,所以只能用箭头来访问
ps->size = ps->capacity = 0;
}
void SLDestroy(SL *ps)//销毁
{
//free 只能适用于动态内存开辟的空间且所free 的空间不能为NULL
if (ps->a)//判断所指的空间是否为空
{
free(ps->a);
ps->a = NULL;
}
ps->capacity = ps->size = 0;//支持连等的
}
void SLPrint(SL* ps)
{
for (int i = 0; i < ps->size; i++)
{
printf("%d ", ps->a[i]);
}
printf("\n");
}
//int SLIsEmpty(SL* ps)
//{
// if (ps->size == 0)
// {
// return 0;//是空表
// }
// else
// return 1;//不是空表
//}
bool IsEmpty(SL* ps)//需要引入头文件
{
assert(ps);
return ps->size == 0;//注意return ps->size == ps->capacity ;是不对的,只是用来判空间是否足够的
}
void SLCheckCapacity(SL* ps)// 空间容量的检查
{
if(ps->size == ps->capacity)//既可以判断是否为空表,也可以判断是否没有空间
{
int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;// 初始化之后我的size,capacity是为0
SLDataType* tmp = (SLDataType*)realloc(ps->a, sizeof(SLDataType) * newcapacity); // 注意: 头文件的引用;sizeof求空间大小时,是以字节为单位的 这时候所开辟空间的类型SLDataType*
//空间可能会存在开辟失败的
if (tmp == NULL)
{
perror("malloc\n");
}
// 空间开辟成功,进行更新
ps->a = tmp;
ps->capacity = newcapacity;
}
}
void SLPushBack(SL* ps,SLDataType x)//尾插 注意:这里第二个参数是SLDataType型
{
assert(ps); //确保指针有效性
//先确认是否有空间可以插入 不够:扩容 够了:直接插
SLCheckCapacity(ps);
//再进行插入
/*for (int i = 0; i < ps->size; i++)
{
ps->a[i] = x;// 对当前第 i 个位置进行插入
ps->size++;
}注意当我size= 0,这样写是不可以的
*/
ps->a[ps->size++] = x; //这样写直接一步到位 因为也插入了,size也对应更新了
//也可以这样写 ps->a[ps->size] = x;ps->size++;
}
void SLPopBack(SL* ps)//尾删
{
assert(ps);
//判断是不是空表
/*if (IsEmpty(ps) == 0)
perror(IsEmpty);*///这样写是不对的
assert(!IsEmpty(ps));//assert(IsEmpty(ps)) 这样写是不对的 assert()断言是用来判断里面的表达式是真还是假,只有为假的话,这个断言才会生效
//进行尾删
ps->size--;// 没有必要进行赋值,直接size--就行
}
void SLPushFront(SL* ps, SLDataType x)//头插
{
assert(ps);
//先判断空间是否足够
SLCheckCapacity(ps);
// 从后往前移动
for (int i = ps->size-1; i >= 0 ; i--)
{
ps->a[i + 1] = ps->a[i];
}
ps->a[0] = x;
ps->size++;
}
void SLPopFront(SL* ps)//头删
{
assert(ps);
//首先确保不是空表,其次挪动数据:从后往前挪
assert(!IsEmpty(ps));//问题同上(88)
for (int i = 0; i size-1 ; i++)
{
//最后一次进来 ps->a[size-1-1] = ps->a[size-1]
ps->a[i] = ps->a[i + 1];
}
ps->size--;
}
void SLInsert(SL* ps, int pos, SLDataType x)//指定位置插入
{
//pos 要在有效的范围内 && 空间是否足够
assert(ps);
assert(pos >= 0 && pos <= ps->size);
SLCheckCapacity(ps);
// 进行插入:从后往前移动
for (int i = ps->size-1; i >pos-1 ;i--)
{
// 最后进来 ps->a[pos+1] = ps->a[pos];
ps->a[i + 1] = ps->a[i];
}
ps->a[pos] = x;
ps->size++;
}
void SLEarse(SL* ps, int pos)//指定位置删除
{
assert(ps);
assert(pos >= 0 && pos <= ps->size);
// 注意是从后往前移动
for (int i =pos; i < ps->size-1; i++)
{
//最后进来的i : ps->size-2 ps->size-2 = ps->size-1;
ps->a[i] = ps->a[i+1];
}
ps->size--;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include"Seqlist.h"
#include"Contact.h"
int option = -1;
void ContactMenu()
{
printf("*****************************************************\n");
printf("***************1.添加联系人 2.删除联系人********\n");
printf("***************3.查找联系人 4.修改联系人********\n");
printf("***************5.打印通讯录 6.退出通讯录********\n");
printf("****************************************************\n");
}
int main()
{
Contact info;//定义一个通讯录类型变量
CInfoInit(&info);//初始化一下通讯录
do
{
ContactMenu();
printf("请输入您的选择\n");
scanf("%d", &option);
switch (option)
{
case 1:CInfoAdd(&info); //注意 case1 这样写是不对的
break;
case 2:
break; CInfoPop(&info);
case 3:CInfoFind(&info);
break;
case 4:CInfoModify(&info);
break;
case 5:CInfoPrint(&info);
break;
case 6:printf("GoodBye\n");
break;
default:printf("输入有误,请重新输入\n");
break;
}
} while (option);
CInfoDestroy(&info);//销毁
return 0;
}
大功告成,我们通讯录这个小项目已经完美完成了,要是各位大佬感觉还不错的话,麻烦给个点个赞,我们互关以一下呗~~~