【顺序表的应用-通讯录的实现】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

一、顺序表的应用

1. 基于动态顺序表实现通讯录

C语言基础要求:结构体、动态内存管理、顺序表、文件操作

1、功能要求

1)至少能够存储100个人的通讯信息

2)能够保存用户信息:名字、性别、年龄、电话、地址等

3)增加联系人信息

4)删除指定联系人

5)查找制定联系人

6)修改指定联系人

7)显示联系人信息

2、代码实现

【思考1】用静态顺序表和动态顺序表分别如何实现

【思考2】如何保证程序结束后,历史通讯录信息不会丢失

二、通讯录的代码实现

1.通讯录的底层结构(顺序表)

(1)思路展示

通讯录底层是顺序表来实现(相当于:通讯录 == 顺序表)

顺序表的底层结构是数组,对数组的封装,实现了常用的增删改查等接口

(2)底层代码实现(顺序表)

Seqlist.h(顺序表的头文件:目录)

#pragma once

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>
#include "Contact.h"
//动态顺序表
//类型重命名(要加分号)
//如果我们要转换类型的话,就可以直接替换int
//typedef int SLDataType;
typedef struct ContactInfo SLDataType;//结构体的类型重命名要加struct
//我们把顺序表的数据类型改成联系人的结构体类型
typedef struct Seqlist
{
	SLDataType* a;//动态顺序表中底层结构中数组中的起始地址(a:指针变量,用来存放下面代码开辟空间的起始地址)
	int size;  //顺序表中有效的数据个数 = 最后一个数据的下一个位置(因为位置的话,有下标0)
	int capacity;  //顺序表中当前的空间大小
}SL;

//对结构体类型初始化
//sl是结构体类型创建的变量
void SLInit(SL* ps);

//销毁顺序表
void SLDestroy(SL* ps);

//头部/尾部 插入或删除
void SLPushBack(SL* ps, SLDataType x);
//我们在顺序表中尾部插入一个SLDataType类型的数据x
void SLPushFront(SL* ps, SLDataType x);//头部插入

void SLPopBack(SL* ps);//尾部删除
void SLPopFront(SL* ps);//头部删除

//打印数据
void SLprint(SL* ps);

//当把顺序表都初始化为0,而没有尾插和头插的话,就直接进行尾删,代码会报错
//判断顺序表是否为空
bool SLIsEmpty(SL* ps);

//在任意位置插入数据
//在指定的位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x);
//删除指定位置的数据
void SLErase(SL* ps, int pos);

//在数据表中查找数据
bool SLFind(SL* ps, SLDataType x);

Seqlist.c(顺序表的源文件:函数的具体实现)

#define _CRT_SECURE_NO_WARNINGS 1

//接口:我们后续提供给其他用户使用的函数或方法
#include "Seplist.h"
void SLInit(SL* ps)
{
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}

//销毁顺序表
void SLDestroy(SL* ps)
{
	//动态内存开辟了空间才能free()释放
	if (ps->a != NULL)
		free(ps->a);
	ps->a = NULL;
	ps->size = ps->capacity = 0;
}


//判断当前顺序表的空间是否足够
void SLCheckCapacity(SL* ps)
{

	if (ps->size == ps->capacity)
	{
		//结构体变量指向的有效数据个数==顺序表当前空间的大小,就得扩容
		//realloc()函数的第二个参数:顺序表当前空间的大小 * 2倍 * SLDataType的类型
		//首先得判断一下顺序表中当前空间的大小是否为0,因为为0的话,0*任何数都是0
		int newcapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->a, newcapacity * sizeof(SLDataType));
		if (tmp == NULL)
		{
			perror("realloc");
			return;
		}
		ps->a = tmp;
		ps->capacity = newcapacity;//顺序表当前空间的大小赋值给capacity,一个空间大小是SLDataType类型的
	}
}

//头部/尾部 插入或删除
void SLPushBack(SL* ps, SLDataType x)
{
	assert(ps != NULL);
	//1:空间足够,直接尾插
	//2:空间不够,扩容
	//直接调用扩容函数
	SLCheckCapacity(ps);

	//直接插入数据
	ps->a[ps->size] = x;
	ps->size++;
}
//头插
void SLPushFront(SL* ps, SLDataType x)
{
	assert(ps != NULL);
	//判断空间是否足够的函数
	//空间不够,扩容
	SLCheckCapacity(ps);
	//空间足够,历史数据向后移一位
	for (size_t i = ps->size; i > 0; i--)
	{
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[0] = x;//第0个位置直接插入x
	ps->size++;//既要增加数据,也要增加空间
}

void SLPopBack(SL* ps)//尾部删除
{
	assert(ps != NULL);
	assert(!SLIsEmpty(ps));
	//SLIsEmpty(ps):如果为真,就进入函数,那么顺序表就为空
	//SLIsEmpty(ps):为真,加!就为假,那么断言执行,否则就不执行
	ps->size--;
}
void SLPopFront(SL* ps)//头部删除
{
	assert(ps);
	assert(!SLIsEmpty(ps));
	//让后面的数据往前挪动一位
	for (size_t i = 0; i < ps->size - 1; i++)
	{
		//如果数组下标为ps->size的话,就越界了
		//因为size为有效数据的个数 = 最后一个数据的下一个位置(有下标为0)
		ps->a[i] = ps->a[i + 1];
	}
	ps->size--;
}

//打印数据
void SLprint(SL* ps)
{
	for (size_t i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->a[i]);
	}
	printf("\n");
}

//判断顺序表是否为空
bool SLIsEmpty(SL* ps)
{
	assert(ps != NULL);
	return ps->size == 0;
	//如果当前没有一个有效的数据,就为空
}

//在任意位置插入数据
//在指定的位置之前插入数据
void SLInsert(SL* ps, int pos, SLDataType x)
{
	//pos的位置对于计算机看来是下标的意思
	assert(ps);
	//不要忘了对pos加以限制
	assert(pos >= 0 && pos <= ps->size);//怕有人会在-100之类的位置插入数据
	//扩容
	SLCheckCapacity(ps);
	//把pos位置及以后的数据往后挪动一位
	//循环条件里的i的初始值是size还是size-1都是可以的,但不同的初始值对应不同的结束条件
	for (size_t i = ps->size; i > pos; i--)
	{
		//最后一个进来的值是pos+1
		ps->a[i] = ps->a[i - 1];
	}
	ps->a[pos] = x;
	ps->size++;
}
//删除指定位置的数据
void SLErase(SL* ps, int pos)
{
	assert(ps);
	assert(!SLIsEmpty(ps));
	//要对pos进行限制
	assert(pos >= 0 && pos < ps->size);
	for (int i = pos; i < ps->size - 1; i++)
	{
		//最后一次进来的数据是ps->size-2(ps->size的话,就越界了)
		ps->a[i] = ps->a[i + 1];//ps->a[size-2] = ps->a[size-1]
	}
	ps->size--;
}

test1.c(顺序表源文件:顺序表的测试)

#define _CRT_SECURE_NO_WARNINGS 1
 
#include "seqlist.h"
 
void SLtest()
{
	SL sl;//定义一个顺序表,sl就是没有初始化
	//SLInit(sl);//我们把sl传递给一个初始化的方法进行初始化
	SLInit(&sl);
	//我们想要把sl初始化的话,要把地址传过去,因为形参是实参的一份临时拷贝
 
 
	//顺序表的具体操作
	//尾插
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);//1 2 3 4
	SLprint(&sl);
	//头插
	SLPushFront(&sl, 5);//5 1 2 3 4 
	SLPushFront(&sl, 6);//6 5 1 2 3 4
	SLPushFront(&sl, 7);//7 6 5 1 2 3 4
	SLprint(&sl);
	//尾删
	SLPopBack(&sl);
	SLprint(&sl);
	SLPopBack(&sl);
	SLprint(&sl);
 
	//销毁顺序表
	SLDestroy(&sl);
}
void SLtest02()
{
	SL sl;
	SLInit(&sl);
	SLPushBack(&sl, 1);
	SLPushBack(&sl, 2);
	SLPushBack(&sl, 3);
	SLPushBack(&sl, 4);
	SLprint(&sl);
	//头删
	SLPopFront(&sl);
	SLprint(&sl);
 
	SLPopFront(&sl);
	SLprint(&sl);	
 
	SLPopFront(&sl);
	SLprint(&sl);
 
 
 
	//到这里顺序表已经没有数据了
	SLPopFront(&sl);
	SLprint(&sl);
 
	//在任意位置插入数据
   //在指定的位置之前插入数据
	SLInsert(&sl, sl.size, 11);
	SLprint(&sl);
 
	//删除指定位置的数据
	SLErase(&sl, 0);
	SLprint(&sl);
 
 
	//在数据表中查找数据
	bool Findret = SLFind(&sl, 3);
	if (Findret == 3)
	{
		printf("找到了!\n");
	}
	else
	{
		printf("找不到!\n");
	}
	//销毁顺序表
	SLDestroy(&sl);
}
 
 
 
int main()
{
	//测试顺序表是否被初始化
	SLtest();
	SLtest02();
	return 0;
}

2.通讯录上层代码实现(通讯录结构)

(1).思路展示

1:因为通讯录底层是顺序表来实现(相当于:通讯录 == 顺序表)

2:所以通讯录首先要创建保存联系人数据的结构体,通讯录的信息类型创建好后,我们可以一键替换到顺序表中,其次再定义通讯录所需的函数实现即可

3:基于前面,我们已经知道,通讯录是基于动态顺序表实现的,所以在实现通讯录功能函数时,只需要在通讯录上层代码的源文件中,加上顺序表的头文件就可以调运顺序表已经实现好的函数来实现通讯录了。

(2).上层代码实现(通讯录)

Contact.h(通讯录的头文件:创建并保存联系人信息的头文件来替换动态顺序表的数据类型,起通讯录所需函数的声明)

#pragma once

//创建保存联系人数据的结构
#define NAME_MAX 100
#define SEX_MAX 10
#define TEL_MAX 15
#define ADDR_MAX 100
//通讯录的信息类型创建好后,我们可以一键替换到顺序表中
//通讯录联系人的信息
typedef struct ContactInfo
{
	char name[NAME_MAX];
	char sex[SEX_MAX];
	int age;
	char tel[TEL_MAX];//电话号码
	char addr[ADDR_MAX];
}CInfo;
//CInfo:是结构体类型重命名,但是只在Contact.h(本文件)内使用,出了文件,别的文件都不认识


//通讯录底层是顺序表来实现(相当于:通讯录 == 顺序表)
//给顺序表起个新名字
typedef struct Seqlist contact;
//为什么Contact.h文件中不包含#include "Seqlist.h"的头文件呢?
//因为我们的Contact.h头文件只是用了"Seqlist.h"的头文件的表面内容,并没有具体使用函数以及一些这个头文件的数据 

//通讯录的创建和销毁
void ContactInit(contact* pcon);
void ContactDestroy(contact* pcon);

//添加联系人(我们可以从终端,也就是屏幕获取,用scanf()函数,所以可以不用传参数)
void ContactAdd(contact* pcon);
//删除联系人
void ContactDel(contact* pcon);
//修改联系人
void ContactModify(contact* pcon);
//查看(展示)通讯录
void ContactShow(contact* pcon);
//查找指定联系人
void ContactFind(contact* pcon);

contact.c(通讯录的源文件:实现通讯录所需的函数功能)

#define _CRT_SECURE_NO_WARNINGS 1
//通讯录的实现文件
#include "Contact.h"
#include "Seplist.h"

//通讯录初始化
void ContactInit(contact* pcon)
{
	SLInit(pcon);

}
//通讯录销毁
void ContactDestroy(contact* pcon)
{
	SLDestroy(pcon);
}

//添加联系人(我们可以从终端,也就是屏幕获取,用scanf()函数,所以可以不用传参数)
void ContactAdd(contact* pcon)
{
	//接下来要获取的信息都是CInfo结构体里要求的数据
	CInfo info;
	//结构体创建变量,用变量来获取终端输入的信息
	printf("请输入联系人姓名:\n");
	scanf("%s", info.name);
	//把输入的名字存放到变量的name结构体成员里
	printf("请输入联系人的性别:\n");
	scanf("%s", info.sex);
	printf("请输入联系人的年龄:\n");
	scanf("%d", &info.age);
	printf("请输入联系人的电话:\n");
	scanf("%s", info.tel);
	printf("请输入联系人的住址:\n");
	scanf("%s", info.addr);

	//联系人数据都获取到了,并保存在结构体info中
	//往通讯录(顺序表)中插入数据
	SLPushBack(pcon, info);//尾插 1:通讯录结构体传过去,2:联系人结构体保存的数据传过去
}

//查找这个联系人到底在不在
int FindByName(contact* pcon,char name[])
{
	for (int i = 0; i < pcon->size; i++)
	{
		if (strcmp(pcon->a[i], name) == 0)
			return i;//返回的是通讯录中数组的下标
	}
	return -1;
}

//删除联系人
void ContactDel(contact* pcon)
{
	//直接强制要求用户使用联系人姓名来查找
	printf("请输入要删除的用户名称:\n");
	char name[NAME_MAX];
	scanf("%s", name);
	int Findidex = FindByName(pcon, name);
	if (Findidex < 0)
	{
		printf("要删除的联系人不存在!\n");
		return;//这个函数就到此位置了
	}
	//找到了,要删除findidex位置的数据
	SLErase(pcon, Findidex);
}
//修改联系人
void ContactModify(contact* pcon)
{
	printf("请输入要修改的联系人名称:\n");
	char name[NAME_MAX];
	scanf("%s", name);
	//获取到通讯录(顺序表)下标的位置
	int find = FindByName(pcon, name);
	if (find < 0)
	{
		printf("要修改的用户名称不存在!\n");
		return;//结束当前的方法运行
		//exit(1);//直接就把程序退出了,是粗暴的行为
	}
	printf("请输入新的用户名称:\n");
	scanf("%s", pcon->a[find].name);
	printf("请输入新的用户性别:\n");
	scanf("%s", pcon->a[find].sex);
	printf("请输入新的用户年龄:\n");
	scanf("%d", &pcon->a[find].age);
	printf("请输入新的用户电话:\n");
	scanf("%s", pcon->a[find].tel);
	printf("请输入新的用户住址:\n");
	scanf("%s", pcon->a[find].addr);

	printf("修改成功!\n");

}
//查看(展示)通讯录
void ContactShow(contact* pcon)
{
	//打印通讯录所有的数据
	//先打印表头文字
	printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
	for (int i = 0; i < pcon->size; i++)
	{
		printf("%s %s %d %s %s\n",
			pcon->a[i].name,
			pcon->a[i].sex,
			pcon->a[i].age,
			pcon->a[i].tel,
			pcon->a[i].addr
		);

	}
}
//查找指定联系人
void ContactFind(contact* pcon)
{
	printf("请输入要查找的用户名称:\n");
	char name[NAME_MAX];
	scanf("%s", name);
	int find = FindByName(pcon, name);
	if (find < 0)
	{
		printf("该联系人不存在!\n");
		return;
	}
	//存在的话,打印当前的联系人
	printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "地址");
	printf("%s %s %d %s %s",
		pcon->a[find].name,
		pcon->a[find].sex,
		pcon->a[find].age,
		pcon->a[find].tel,
		pcon->a[find].addr
	);
}

test.c(通讯录的头文件:用来测试程序运行)

#define _CRT_SECURE_NO_WARNINGS 1
#include "Seplist.h"
#include "Contact.h"


//void contact01()
//{
//	contact con;
//	ContactInit(&con);
//	//往通讯录中插入数据
//	ContactAdd(&con);
//	//ContactAdd(&con);
//	//展示通讯录
//	ContactShow(&con);
//	//ContactDel(&con);
//	//ContactShow(&con);
//	//修改联系人
//	ContactModify(&con);
//	ContactShow(&con);
//	//查找指定联系人
//	ContactFind(&con);
//
//	ContactDestroy(&con);
//}

void menu()
{
	printf("****************   通讯录    ******************\n");
	printf("**********1.添加联系人   2.删除联系人**********\n");
	printf("**********3.修改联系人   4.查找联系人**********\n");
	printf("**********5.查找通讯录   0.退  出   ***********\n");
	printf("***********************************************\n");
}
int main()
{
	int input = 0;
	//定义一个通讯录
	contact con;
	ContactInit(&con);
	do
	{
		menu();
		printf("请选择您的操作!\n");
		scanf("%d", &input);
		switch(input)
		{
		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("goodbye~\n");
			break;
		default:
			printf("输入错误,请重新输入!\n");
			break;
		}
	} while (input != 0);
	ContactDestroy(&con);
	//contact01();
	return 0;
}

3.通讯录代码运行展示


总结

好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。
不积硅步,无以至千里;不积小流,无以成江海。

猜你喜欢

转载自blog.csdn.net/2301_79585944/article/details/134552817