C语言之数据结构初见篇(2):顺序表之通讯录的实现

目录

一、基本概念

二、通俗理解

三、这个程序是什么

[1. 数据结构](#1. 数据结构)

[2. 核心功能](#2. 核心功能)

四、通讯录代码的实现

1.多个文件的创建


一、基本概念

顺序表实现的通讯录 是一种基于数组结构的联系人信息管理系统。它将数据结构的理论知识(顺序表)应用到实际场景中(管理联系人信息)。

二、通俗理解

就像一本纸质通讯录

  • 顺序表就像一本有固定页数的本子(数组)

  • 联系人就像本子上记录的一条条信息

  • 添加联系人就像在本子上写新内容

  • 删除联系人就像撕掉或划掉某页内容

  • 查找联系人就像翻看本子找人名

技术角度理解

内存中的样子:

位置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;
}

后面的具体具体解释我们放到下一章里面给大家去讲解,这里我就先公布所有的代码!!!!

相关推荐
lxh01132 小时前
计算右侧小于当前元素的个数 题解
javascript·数据结构·算法
美式请加冰2 小时前
前缀数组的介绍和使用
数据结构·c++·算法
GawynKing2 小时前
图论2 图的数据结构表示
数据结构·图论
祁同伟.3 小时前
【C++】哈希的应用
开发语言·数据结构·c++·算法·容器·stl·哈希算法
每天回答3个问题3 小时前
leetcodeHot100|148.排序链表
数据结构·c++·链表·ue4
我能坚持多久3 小时前
【初阶数据结构08】——深入理解树与堆
数据结构·算法
丶小鱼丶3 小时前
数据结构和算法之【数组】
java·数据结构·算法
承渊政道3 小时前
C++学习之旅【⽤哈希表封装myunordered_map和myunordered_set以及位图和布隆过滤器介绍】
数据结构·c++·学习·哈希算法·散列表·hash-index·图搜索算法
开源盛世!!3 小时前
3.9-3.11学习笔记
数据结构·算法