引言:顺序表本质是一个结构体,而在一个结构体当中可以存储各种各样的数据类型,例如int,char类型,当然也可以是结构体类型(类似于一个嵌套),而通过使用这样的"嵌套",我们就可以用顺序表实现一个比较使用的通讯录小项目
落日熔金,暮云合璧
我以过客之名,祝你前程似锦
1.通讯录总览:
2.前期准备:
3.函数声明和通讯录结构体(Contact.h):
cs
#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;//不能直接typedef SL Contact;需要前置声明,因为在编译这个头文件时,先编译的是Contact.h,然而SL是另外一个头文件里的,此时编译器不知道另外一个头文件,因此会发生报错,不使用SL也是这个道理
//通讯录相关方法
//通讯录的初始化(实际上就是对顺序表初始化)
void ContactInit(Contact* con);
//通讯录的销毁
void ContactDestroy(Contact* con);
//通讯录结构体申请和检验
void SLCheckCapacity(Contact* con);
//通讯录添加数据
void ContactAdd(Contact* con);
//通讯录删除数据
void ContactDel(Contact* con);
//通讯录修改
void ContactModify(Contact* con);
//展示通讯录数据
void ContactShow(Contact* con);
//查找指定联系人
void ContactFind(contact* pcon);
这里值得关注的有两点:
一是对于底层结构体的定义可以在这里直接写出来,因为所谓的通讯录本质上就是我在引言里所阐述的一个数组类型的结构体,至于为什么理解起来那么抽象,还是源于我们在设计程序时对于一个或多个结构体的反复typedef
二就是对于typedef的使用,要非常注意程序编译的顺序,不然在编译器识别某些定义之前就匆匆地typedef势必会导致程序的报错
4.头文件的包含和顺序表结构体(Seqlist.h)
cs
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#ifndef _SEQLIST_H
#define _SEQLIST_H
#endif
#include"Contact.h"
#include<stdio.h>
#include<assert.h>
typedef peoinfo SLDataType;
typedef struct SeqList
{
SLDataType* arr;
int size; // 有效数据个数
int capacity; // 空间容量
}SL; //命名了一个数据类型为struct Seqlist的叫SL的顺序表
这里值得注意的也有两点:
一是对peoinfo的使用 ,从Contact.h可知,peninfo代表的是我们的通讯录结构体(经过typedef的定义,表示一种自定义结构体类型 ),然后在这个顺序表结构体中我们就可以很明显的看到我们将这个顺序表结构体类型作用为一个已知的类型并且定义了一个数组arr作为顺序表结构体的一个组员,由此可知,一个由顺序表搭建的底层具有逻辑连续性的通讯录结构就在慢慢搭建:
二则是对头文件重复包含的预防 ,我在这里提供有有两种解决方法:
1.每个头文件件前加上**#progma once** 顾名思义,只包含一次,
2.每个头文件加上这个结构(紫色部分依据你的头文件名而定)
5.具体函数的实现(Contact.c):
(1)通讯录的初始化:
cs
//通讯录的初始化
void ContactInit(Contact* con)//就相当于SL* con
{
//实际上进行的就是顺序表的初始化
con->arr = NULL;
con->size = con->capacity = 0;
//实际上也就是我在顺序表详解里实现的SLInit函数
}
(2)通讯录的销毁:
cs
//通讯录的销毁
void ContactDestroy(Contact* con)
{
if (con->arr)
{
free(con->arr);
}
con->arr = NULL;
con->size = con->capacity = 0;
//同理,相当于顺序表的SLDestroy函数
}
以上是通讯录的初始化和销毁,对比下面顺序表的初始化和销毁,是不是就更加清晰了?
(3)对通讯录结构体的扩容与检验空间申请成功与否:
cs
//检验,扩容
void SLCheckCapacity(Contact* con)
{
//插入数据前看空间够不够
if (con->capacity == con->size)
{
//申请空间
int newCapacity = ps->capacity == 0 ? 4 : 2 * sizeof(SLDataType);//防止初始化的capacity为0(也可以直接在初始化就申请)
//con->arr = realloc(con->arr, newCapacity * sizeof(SLDataType));
//万一申请空间不成功(malloc申请空间不一定成功)
SL* tmp = (SL*)realloc(con->arr, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc failed!");
exit(1);//直接退出程序
}
//空间申请成功
con->arr = tmp;
con->capacity = newCapacity;
}
}
这里附加一句,由于typedef的原因SL和Contact其实都表示一个意思,即给顺序表(struct SeqList)改的名字
(4)通讯录添加数据:
cs
//通讯录添加数据
void ContactAdd(Contact* con)
{
//获取用户输入的内容:
peoinfo info;//info为数组类型的结构体变量,故不用&
printf("请输入要添加的联系人姓名:\n");
scanf("%s", info.name);
printf("请输入要添加的联系人性别:\n");
scanf("%s", info.gender);
printf("请输入要添加的联系人年龄:\n");
scanf("%d", info.age);
printf("请输入要添加的联系人电话:\n");
scanf("%s", info.tel);
printf("请输入要添加的联系人住址:\n");
scanf("%s", info.addr);
//往通讯录中添加联系人数据
//尾插:
SLPushBack(con, info);
}
注意:这里的尾插代码与顺序表的尾插是一模一样的,方便起见,我也附在下面,不过值得关注的是,这串代码原是放在Seqlist.c文件下的,并且须在Seqlist.h声明一下
cs
//尾插
void SLPushBack(SL* ps, SLDataType x)
{
判断用户传入的是否为空指针
assert(ps);
//插入数据前看空间够不够
if(ps->capacity==ps->size)
{
//申请空间
int newCapacity = ps->capacity == 0 ? 4 : 2*sizeof(SLDataType);//防止初始化的capacity为0(也可以直接在初始化就申请)
//ps->arr = realloc(ps->arr, newCapacity * sizeof(SLDataType));
//万一申请空间不成功(malloc申请空间不一定成功)
SL*tmp =(SL*) realloc(ps->arr, newCapacity * sizeof(SLDataType));
if (tmp == NULL)
{
perror("realloc failed!");
exit(1);//直接退出程序
}
//空间申请成功
ps->arr = tmp;
ps->capacity = newCapacity;
}
ps->arr[ps->size++] = x;
}
(5)通讯录删除数据:
cs
//以名字为目标检索通讯录
int FindByName(Contact* con, char name[])
{
for (int i = 0; i < con->size; i++)
{
if (0 == strcmp(con->arr[i].name, name))
{
//找到了
return i;
}
}
//没有找到
return -1;
}
//通讯录删除数据
void ContactDel(Contact* con)
{
//要删除的数据必须存在,才能执行删除操作
//查找
char name[NAME_MAX];
printf("请输入要删除的联系人姓名:");
scanf("%s", name);
int find = FindByName(con, name);
if (find < 0)
{
printf("不存在,删除失败\n");
return;
}
//要删除的联系人数据存在------>知道了要删除的联系人的下标
SLErase(con, find);
printf("删除成功\n");
}
同样,这里的SLErase指定位置删除函数 与顺序表删除数据如出一辙(附在Seqlist.c文件下的,并且须在Seqlist.h声明)
cs
//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
assert(ps);
assert(!SLIsEmpty(ps));
//pos的范围需要限制
assert(pos >= 0 && pos < ps->size);//不同于插入,pos不能等于size
for (int i = pos; i < ps->size - 1; i++)
{
//最后一次进来的i的数据ps->size-2
ps->arr[i] = ps->arr[i + 1];//ps->a[ps->size-2] = ps->a[ps->size-1]
}
ps->size--;
}
(6)通讯录修改:
cs
//修改联系人
void ContactModify(Contact* pcon) {
char name[NAME_MAX];
printf("请输入要修改的用户名称:\n");
scanf("%s", name);
//获取到的通讯录(顺序表)下标的位置
int find = FindByName(pcon, name);
if (find < 0)
{
printf("要修改的用户不存在!\n");
return;
}
printf("请输入新的用户名称:\n");
scanf("%s", pcon->arr[find].name);
printf("请输入新的用户性别:\n");
scanf("%s", pcon->arr[find].sex);
printf("请输入新的用户年龄:\n");
scanf("%d", &pcon->arr[find].age);
printf("请输入新的用户电话:\n");
scanf("%s", pcon->arr[find].tel);
printf("请输入新的用户地址:\n");
scanf("%s", pcon->arr[find].addr);
printf("修改成功!\n");
}
(7)通讯录数据展示:
cs
//查看通讯录
void ContactShow(Contact* pcon) {
//打印通讯录所有的数据
//先打印表头文字
printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");
for (int i = 0; i < pcon->size; i++)
{
printf("%-4s %-4s %-4d %-4s %-4s\n",
pcon->arr[i].name,
pcon->arr[i].sex,
pcon->arr[i].age,
pcon->arr[i].tel,
pcon->arr[i].addr
);
}
}
(8)查找指定联系人:
cs
//查找指定联系人
void ContactFind(contact* pcon) {
char name[NAME_MAX];
printf("请输入要查找的用户名称:\n");
scanf("%s", name);
int find = FindByName(pcon, name);
if (find < 0) {
printf("该联系人不存在!\n");
return;
}
全文终