【数据结构】 顺序表的应用 - 通讯录的实现

news2024/11/17 8:18:08

0. 前言

上一期博客中,我们已经学习了顺序表是什么,以及顺序表相关接口的实现,了解了如何实现顺序表的插入和删除等功能,那么在这期博客,我们可以基于顺序表来实现一个通讯录,在通讯录当中能实现联系人的增、删、查改等功能,接下来就让我们一起来实现通讯录吧!

1. 实现通讯录前的分析

注意:同学们需要掌握C语言中结构体、动态内存管理、顺序表、⽂件操作等相关知识。

在实现通讯录的代码前我们要先思考在通讯录项目中能实现什么样的功能

🌟🌟大致我们可以思考以下几个方面

(1)至少能够存储100个人的通讯信息
(2)能够保存用户信息:名字、性别、年龄、电话、地址等
(3)增加联系人信息
(4)删除指定联系人
(5)查找制定联系人
(6)修改指定联系人
(7)显示联系人信息

同时由于在之前的顺序表中使用的是动态顺序表

所以在实现通讯录项目中也是在动态顺序表基础上改造成我们的通讯录。 

2. 通讯录流程设计

我们可以像以前一样,将通讯录拆分为不同的文件进行设计。

我们将代码拆分为三块,如下图所示,分别是:

contact.h -- 用于写相关函数声明。

contact.c -- 用来写模块相关函数实现。

test.c -- 用于通讯录整体功能测试。

当然,以为是基于顺序表改造的通讯录,

我们还会用到之前写的顺序表的功能实现的两个文件

3. 通讯录的实现 

3.1 定义联系人的结构

联系人信息至少应该包含:名字、性别、年龄、电话、地址等

 由于顺序表的底层就是数组,所以我们就是要利用数组来实现如以下所示的结构

在通讯录中由于我们要存储的是多个联系人的信息,因此要定义一个结构体来存储联系人的信息
以下定义结构体struct PersonInfo来存储联系人的信息,并且使用typedef将该结构体重命名为Info
并且在PersonInfo中姓名,性别,电话,住址每个数组的大小可以用#define来定义。

#pragma once
#define  _CRT_SECURE_NO_WARNINGS 1

//通讯录数据类型
#define NAME_MAX 100
#define GENDER_MAX 10
#define TEL_MAX 12
#define ADDR_MAX 100

//通讯录数据类型
typedef struct PersonInfo
{
	char name[NAME_MAX];
	int age;
	char gender[GENDER_MAX];
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}Info;

3.2 定义通讯录顺序表 

基于顺序表实现通讯录,顺序表的元素类型SLDatatype就不再是int,而是包含每个联系人信息的结构体Info。 

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "Contact.h"

typedef Info SLDataType;

typedef struct SeqList
{
	SLDataType* arr;//存储数据的底层结构 
	int capacity;//记录顺序表的空间大小 
	int size;//记录顺序表当前有效的数据个数 
}SL;//将struct Seqlist重命名为SL

3.3 通讯录菜单与主函数设计

我们的通讯录肯定是需要有一个菜单的,这样用户才能选择对通讯录执行什么样的操作,这个菜单必须包含所有通讯录功能,也包含退出。

//通讯录菜单
void menu()
{
	printf("*****************通讯录***************\n");
	printf("*******1.添加联系人  2.删除联系人*****\n");
	printf("*******3.修改联系人  4.查找联系人*****\n");
	printf("*******5.查看通讯录  0.  退 出  ******\n");
	printf("**************************************\n");
}

主函数中,实现了一个简单的通讯录操作菜单选择功能。

首先,声明了一个整型变量 op并初始化为 -1 ,用于存储用户的操作选择。

还声明了一个 Contact 类型的变量 con 创建一个通讯录,并调用 ContactInit(&con) 函数对其进行初始化。 然后,通过一个 do-while 循环来不断展示菜单,并获取用户的操作选择。 在循环内部,先调用 menu() 函数显示菜单,提示用户输入操作选项。通过 scanf("%d", &op) 读取用户输入的整数。 接下来,使用 switch 语句根据用户输入的 op值执行不同的操作。

 当 op 为 1 时,调用 ContactAdd(&con)函数执行添加联系人的操作。

当 op为 2时,调用 ContactDel(&con)函数执行删除联系人的操作。  

当 op 为 3 时,调用 ContactModify(&con)函数执行修改联系人的操作。 

当 op 为 4 时,调用 ContactFind(&con) 函数执行查找联系人的操作。

当 op 为 5 时,调用 ContactShow(&con) 函数执行显示联系人的操作。 

当 op 为 0 时,输出提示信息表示退出通讯录。

最后,在循环结束后,调用 ContactDesTroy(&con) 来销毁通讯录。

如果 op 是其他值,则输出输入错误的提示信息,

让用户重新选择。 最后,只要 op 不等于 0 ,就会继续循环执行上述操作。

int main()
{
	int op = -1;
	Contact con;
	ContactInit(&con);

	do {
		menu();
		printf("请选择您的操作:\n");
		scanf("%d", &op);

		//要根据对应的op执行不同的操作
		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:
			printf("输入错误,请重新选择您的操作!\n");
			break;
		}
	} while (op != 0);

	ContactDesTroy(&con);
	return 0;
}

3.4 初始化 销毁 

在以上完成联系人结构体的定义以及对顺序表内数组类型的更改,

接下来就可以来实现通讯录初始化和销毁的函数了

在此先在contact.h内声明初始化函数以及销毁函数,由于在此要对通讯录内容进行更改,所以要进行传址调用,两个函数的参数都是结构体指针

下面是函数声明

void ContactInit(contact* pcon);//初始化通讯录
void ContactDestory(contact* pcon);//销毁通讯录

声明完之后就是在contact.c内实现以上两个函数,

在此通讯录的初始化以及销毁内就可以直接调用之前顺序表的初始化以及销毁函数了 

只不过我们传的是通讯录类型地址pcon,非常的方便。不需要重新实现了。

//初始化和销毁
void SLInit(SL* ps) 
{
	ps->arr = NULL; //不是int 而是PersonInfo类型 
	ps->size = ps->capacity = 0;
}

void SLDestroy(SL* ps) 
{
	assert(ps);

	if (ps->arr) 
    {
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

//通讯录的初始化和销毁
void ContactInit(Contact* pcon)
{
	SLInit(pcon);
}

void ContactDesTroy(Contact* pcon) 
{
	SLDestroy(pcon);
}

3.5 通讯录的显示 

通讯录的展示就是将通讯录的信息都打印出来,实质就是要遍历一般数组
为了使我们通讯录每个人的信息格式好看,我们printf打印每个人信息时,占位符中的%后加入的整数宽度限制来让打印出的通讯录更有顺序,这里我只是空格隔开,没有追求格式,同学们实现时可以自行调整。

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->arr[i].name,
			pcon->arr[i].gender,
			pcon->arr[i].age,
			pcon->arr[i].tel,
			pcon->arr[i].addr
		);
	}
}

3.6 通讯录各功能实现

在以上完成了通讯录的初始化和销毁接下来我们就来实现通讯录增、删、查、改的功能了

3.6.1 通讯录内增加联系人 

在此要实现通讯录中联系人的增加先在contact.h内对增加联系人函数进行声明,由于在此要对通讯录内容进行更改,所以要进行传址调用,函数的参数是结构体指针

void ContactAdd(Contact* pcon);通讯录内增加联系人

接下来就是在contact.c内完成添加联系人的函数

在此先创建一个结构体 Info info;,并且使用scanf来读取用户输入的此联系人的各信息存储在结构体info内,再调用顺序表中的插入函数将该结构体info插入到数组内,在此使用的是尾插函数,也可以使用其他插入方法

//增加联系人到通讯录
void ContactAdd(Contact* pcon) 
{
	//创建联系人结构体变量
	Info info;

	printf("请输入联系人姓名:\n");
	scanf("%s", info.name);
	printf("请输入联系人年龄:\n");
	scanf("%d", &info.age);
	printf("请输入联系人性别:\n");
	scanf("%s", info.gender);
	printf("请输入联系人电话:\n");
	scanf("%s", info.tel);
	printf("请输入联系人住址:\n");
	scanf("%s", info.addr);

	//保存数据到通讯录(顺序表)
	SLPushBack(pcon, info);
}

插入元素就是对顺序表尾插的操作

//顺序表的尾部插入
void SLPushBack(SL* ps, SLDataType x) 
{
	//断言--粗暴的解决方式
	//assert(ps != NULL);
	assert(ps);

	//if判断--温柔的解决方式
	//if (ps == NULL) {
	//	return;
	//}

	//空间不够,扩容
	SLCheckCapacity(ps);

	//空间足够,直接插入
	ps->arr[ps->size++] = x;
	//ps->size++;
}

扩容:

void SLCheckCapacity(SL* ps) 
{
	if (ps->size == ps->capacity) 
    {
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		if (tmp == NULL) 
        {
			perror("realloc fail!");
			exit(1);
		}
		//扩容成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

3.6.2 通讯录内删除联系人

在此要实现通讯录中联系人的删除先在contact.h内对删除联系人函数进行声明,由于在此要对通讯录内容进行更改,所以要进行传址调用,函数的参数是结构体指针

void ContactDel(Contact* pcon);//通讯录内删除联系人

在声明完函数之后就是对该函数的实现,但在删除通讯录内的联系人要通讯录中存在要删除的联系人才能删除,所以在函数内还要先判断,但之后通讯录的其他功能可能还要用到查找联系人是否存在

所以可以直接在创建一个判断相关联系人是否存在的函数,在此根据的是名字来查找联系人

存在该联系人就返回相应的数组下标,不存在就返回-1

int FindByName(Contact* pcon, char name[]) 
{
	for (int i = 0; i < pcon->size; i++)
	{
		if (strcmp(pcon->arr[i].name, name) == 0) 
        {
			//找到了
			return i;
		}
	}
	return -1;
}

接下来就是在contact.c内完成删除联系人的函数

在此函数内先定义一个char类型的数组name,大小为NAME_MAX。用scanf将用户输入的联系人姓名存放在该数组内,之后再将指针con与name作为FindbyName的参数,通过FindbyName函数的返回值来得到要删除的联系人是否存在。若返回值小于0则说明该联系人不存在,之后就直接退出函数ContactDel,否则就调用顺序表中的任意位置的删除函数SLErase,这时findIndex就是要删除的数组下标

void ContactDel(Contact* pcon) 
{
	//删除之前一定要先查找
	//找到了,可以删除
	//找不到,不能执行删除
	printf("请输入要删除的联系人姓名:\n");
	char name[NAME_MAX];
	scanf("%s", name);

	int findIndex = FindByName(pcon, name);
	if (findIndex < 0) 
    {
		printf("要删除的联系人不存在!\n");
		return;
	}
	//执行删除操作
	SLErase(pcon, findIndex);
	printf("联系人删除成功!\n");
}

删除就是顺序表中指定元素删除操作

//删除指定位置数据
void SLErase(SL* ps, int pos) 
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	//pos以后的数据往前挪动一位
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//ps->arr[i-2] = ps->arr[i-1];
	}
	ps->size--;
}

3.6.3 通讯录内修改联系人

在此要实现通讯录中联系人的修改先在contact.h内对修改联系人函数进行声明,由于在此要对通讯录内容进行更改,所以要进行传址调用,函数的参数是结构体指针

void ContactModify(Contact* pcon);//通讯录内修改联系人

接下来就是在contact.c内完成修改联系人的函数

在该函数内和删除联系人函数一样也是先在此函数内先定义一个char类型的数组name,大小为NAME_MAX。用scanf将用户输入的联系人姓名存放在该数组内,之后再将指针con与name作为FindbyName的参数,通过FindbyName函数的返回值来得到要修改的联系人是否存在。若返回值小于0则说明该联系人不存在,之后就直接退出函数ContactDel。否则就使用scanf将用户输入的信息存放到原来联系人的数组位置

void ContactModify(Contact* pcon) 
{
	//修改之前要先查找
	//找到了,执行修改操作
	//没有找到,不能执行修改操作

	char name[NAME_MAX];
	printf("请输入要修改的联系人姓名:\n");
	scanf("%s", name);

	int findIndex = FindByName(pcon, name);
	if (findIndex < 0) 
	{
		printf("要修改的联系人不存在!\n");
		return;
	}
	printf("请输入姓名:\n");
	scanf("%s", pcon->arr[findIndex].name);
	printf("请输入年龄:\n");
	scanf("%d", &pcon->arr[findIndex].age);
	printf("请输入性别:\n");
	scanf("%s", pcon->arr[findIndex].gender);
	printf("请输入电话:\n");
	scanf("%s", pcon->arr[findIndex].tel);
	printf("请输入地址:\n");
	scanf("%s", pcon->arr[findIndex].addr);

	printf("联系人修改成功!\n");
}

3.6.4 通讯录内查找联系人

 在此要实现通讯录中联系人的修改先在contact.h内对修改联系人函数进行声明,在此虽然查找联系人未对通讯录内数据进行修改,但在此还是将结构体作为函数的参数,原因通讯录其他函数都是传地址在此也保持一致性

void ContactFind(Contact* pcon);//在通讯录内查找联系人

接下来就是在contact.c内完成查找联系人的函数

和前面一样也是先在此函数内先定义一个char类型的数组name,大小为NAME_MAX,然后,提示用户输入要查找的姓名,并通过 scanf 函数获取输入。

接下来,调用 FindByName 函数在通讯录中查找该姓名对应的索引 findIndex 。

如果 findIndex 小于 0,说明未找到该联系人,输出提示信息并直接返回。

如果找到了,即 findIndex 非负,打印出联系人的详细信息,包括姓名、性别、年龄、电话和住址。

void ContactFind(Contact* pcon) 
{
	char name[NAME_MAX];
	printf("请输入要查找的用户姓名:\n");
	scanf("%s", name);

	int findIndex = FindByName(pcon, name);
	if (findIndex < 0) 
	{
		printf("该联系人不存在!\n");
		return;
	}
	//找到了,打印一下查找的联系人信息
	printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");
	printf("%s %s %d %s %s\n",
		pcon->arr[findIndex].name,
		pcon->arr[findIndex].gender,
		pcon->arr[findIndex].age,
		pcon->arr[findIndex].tel,
		pcon->arr[findIndex].addr
	);
}

 4. 通讯录读取历史数据和保存数据

在之前已经实现了通讯录的各功能的数据只能在程序运行窗口打开的时候进行通讯录的增、删、查、改等功能。在关闭窗口后对通讯录进行的各项操作都不会保存,那么要怎么样才能让我们设计的通讯录在每次开始之前都读取之前的信息,在结束后都保存通讯录的信息呢?

在之前文件操作章节中讲解了如何将程序数据输出到文件中,将文件数据输入到程序当中,所以在通讯录中我们就可以用到文件操作的相关函数来实现通讯录数据的保存与读取。

4.1读取历史数据

要读取历史数据就要在通讯录每次初始化之后就输入文件的信息到所创建的通讯录中,也就是将输入到数组当中。

在此先在我们创建的程序的文件夹中创建一个contact.txt文件,再使用fopen以读的方式打开文件,再创建一个Info info的变量,之后在使用到fread以二进制的形式输入文件的信息到创建的info中,再将info尾插到数组当中。

void ContactRead(Contact* pcon)//从文件中读取历史数据
{
	FILE* pf = fopen("contact.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	Info info;
	while (fread(&info, sizeof(Info), 1, pf))
	{
		SLPushBack(pcon, info);
	}
	printf("成功读取历史数据到通讯录中\n");
}

 在通讯录初始化函数中调用SLInit函数后调用ContactRead就可以实现历史数据的读取

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

4.2 保存数据

要实现通讯录每次在退出程序后都能将数据保留,这就需要在每次销毁通讯录前将通讯录内的数据,也就是数组的所有元素都输出到contact.txt文件内。

要把数组的所有元素都输出到contact.txt文件内就需要先以写的方式打开文件,后在循环的使用fwrite将数组的数据以二进制的形式输出到文件当中

void SaveContact(Contact* con) 
{
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL) 
	{
		perror("fopen error!\n");
		return;
	}
	//将通讯录数据写⼊⽂件
	for (int i = 0; i < con->size; i++)
	{
		fwrite(con->a + i, sizeof(Info), 1, pf);
	}
	printf("通讯录数据保存成功!\n");
}

在通讯录销毁函数中调用SLDestroy函数前调用SaveContact就可以实现数据输出到文件当中,也就将数据保留了下来

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

5. 通讯录完整代码 

注:在Seqlist.h和Seqlist.c内的查找和打印顺序表与以上代码不兼容,运行时会使得程序崩溃,所以将这些部分注释掉 

Contact.h

#pragma once
#define  _CRT_SECURE_NO_WARNINGS 1

//通讯录数据类型
#define NAME_MAX 100
#define GENDER_MAX 10
#define TEL_MAX 12
#define ADDR_MAX 100

//通讯录数据类型
typedef struct PersonInfo
{
	char name[NAME_MAX];
	int age;
	char gender[GENDER_MAX];
	char tel[TEL_MAX];
	char addr[ADDR_MAX];
}Info;

//使用顺序表的前置声明
struct SeqList;

typedef struct SeqList Contact;
//通讯录提供的操作 
//通讯录的初始化和销毁

void ContactInit(Contact* pcon);//实际初始化的还是顺序表
void ContactDesTroy(Contact* pcon);

//增加、删除、修改、查找、查看通讯录
void ContactAdd(Contact* pcon);
void ContactDel(Contact* pcon);
void ContactModify(Contact* pcon);
void ContactFind(Contact* pcon);
void ContactShow(Contact* pcon);

 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 Info SLDataType;

typedef struct SeqList
{
	SLDataType* arr;//存储数据的底层结构 
	int capacity;//记录顺序表的空间大小 
	int size;//记录顺序表当前有效的数据个数 
}SL;

//初始化和销毁
void SLInit(SL* ps);
void SLDestroy(SL* ps);
void SLPrint(SL* ps);//保证接口的一致性


//顺序表头部 尾部插⼊
void SLPushBack(SL* ps, SLDataType x);
void SLPushFront(SL* ps, SLDataType x);

//顺序表的头部 尾部删除
void SLPopBack(SL* ps);
void SLPopFront(SL* ps);

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

//在顺序表中查找x
int SLFind(SL* ps, SLDataType x);

Seqlist.c 

#include"SeqList.h"
//初始化和销毁

void SLInit(SL* ps) 
{
	ps->arr = NULL; //不是int 而是Info类型 
	ps->size = ps->capacity = 0;
}

void SLCheckCapacity(SL* ps) 
{
	if (ps->size == ps->capacity) 
	{
		int newCapacity = ps->capacity == 0 ? 4 : 2 * ps->capacity;
		SLDataType* tmp = (SLDataType*)realloc(ps->arr, newCapacity * sizeof(SLDataType));
		if (tmp == NULL) 
		{
			perror("realloc fail!");
			exit(1);
		}
		//扩容成功
		ps->arr = tmp;
		ps->capacity = newCapacity;
	}
}

//顺序表的头部/尾部插入
void SLPushBack(SL* ps, SLDataType x) 
{
	//断言--粗暴的解决方式
	//assert(ps != NULL);
	assert(ps);

	//if判断--温柔的解决方式
	//if (ps == NULL) 
	// {
	// 
	//	return;
	//}

	//空间不够,扩容
	SLCheckCapacity(ps);

	//空间足够,直接插入
	ps->arr[ps->size++] = x;
	//ps->size++;
}
void SLPushFront(SL* ps, SLDataType x) 
{
	assert(ps);

	//判断是否扩容
	SLCheckCapacity(ps);

	//旧数据往后挪动一位
	for (int i = ps->size; i > 0; i--) //i = 1
	{
		ps->arr[i] = ps->arr[i - 1]; //ps->arr[1] = ps->arr[0]
	}
	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);
	assert(pos >= 0 && pos <= ps->size);

	SLCheckCapacity(ps);

	//pos及之后的数据往后挪动一位,pos空出来
	for (int i = ps->size; i > pos; i--)
	{
		ps->arr[i] = ps->arr[i - 1]; //ps->arr[pos+1] = ps->arr[pos]
	}
	ps->arr[pos] = x;
	ps->size++;
}
//删除指定位置数据
void SLErase(SL* ps, int pos) 
{
	assert(ps);
	assert(pos >= 0 && pos < ps->size);

	//pos以后的数据往前挪动一位
	for (int i = pos; i < ps->size - 1; i++)
	{
		ps->arr[i] = ps->arr[i + 1];//ps->arr[i-2] = ps->arr[i-1];
	}
	ps->size--;
}

//在顺序表中查找X
//int SLFind(SL* ps, SLDataType x) 
//{
//	//加上断言对代码的健壮性更好
//	assert(ps);
//	for (int i = 0; i < ps->size; i++)
//	{
//		if (ps->arr[i] == x) 
//	{
//			return i;
//		}
//	}
//	return -1;
//}

void SLDestroy(SL* ps) 
{
	assert(ps);

	if (ps->arr) 
    {
		free(ps->arr);
	}
	ps->arr = NULL;
	ps->size = ps->capacity = 0;
}

void SLPrint(SL* ps) 
{
	for (int i = 0; i < ps->size; i++)
	{
		printf("%d ", ps->arr[i]);
	}
	printf("\n");
}

Contact.c 

#include"Contact.h"
#include"SeqList.h"

//通讯录的初始化和销毁
//SL* ps
void ContactInit(Contact* pcon) {
	SLInit(pcon);
}

void ContactDesTroy(Contact* pcon) {
	SLDestroy(pcon);
}

//增加、删除、修改、查找、查看通讯录
void ContactAdd(Contact* pcon) 
{
	//创建联系人结构体变量
	Info info;

	printf("请输入联系人姓名:\n");
	scanf("%s", info.name);
	printf("请输入联系人年龄:\n");
	scanf("%d", &info.age);
	printf("请输入联系人性别:\n");
	scanf("%s", info.gender);
	printf("请输入联系人电话:\n");
	scanf("%s", info.tel);
	printf("请输入联系人住址:\n");
	scanf("%s", info.addr);

	//保存数据到通讯录(顺序表)
	SLPushBack(pcon, info);
}

int FindByName(Contact* pcon, char name[]) {
	for (int i = 0; i < pcon->size; i++)
	{
		if (strcmp(pcon->arr[i].name, name) == 0) {
			//找到了
			return i;
		}
	}
	return -1;
}


void ContactDel(Contact* pcon) {
	//删除之前一定要先查找
	//找到了,可以删除
	//找不到,不能执行删除
	printf("请输入要删除的联系人姓名:\n");
	char name[NAME_MAX];
	scanf("%s", name);

	int findIndex = FindByName(pcon, name);
	if (findIndex < 0) {
		printf("要删除的联系人不存在!\n");
		return;
	}
	//执行删除操作
	SLErase(pcon, findIndex);
	printf("联系人删除成功!\n");
}

void ContactModify(Contact* pcon) 
{
	//修改之前要先查找
	//找到了,执行修改操作
	//没有找到,不能执行修改操作

	char name[NAME_MAX];
	printf("请输入要修改的联系人姓名:\n");
	scanf("%s", name);

	int findIndex = FindByName(pcon, name);
	if (findIndex < 0) 
	{
		printf("要修改的联系人不存在!\n");
		return;
	}
	printf("请输入姓名:\n");
	scanf("%s", pcon->arr[findIndex].name);
	printf("请输入年龄:\n");
	scanf("%d", &pcon->arr[findIndex].age);
	printf("请输入性别:\n");
	scanf("%s", pcon->arr[findIndex].gender);
	printf("请输入电话:\n");
	scanf("%s", pcon->arr[findIndex].tel);
	printf("请输入地址:\n");
	scanf("%s", pcon->arr[findIndex].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->arr[i].name,
			pcon->arr[i].gender,
			pcon->arr[i].age,
			pcon->arr[i].tel,
			pcon->arr[i].addr
		);
	}
}

void ContactFind(Contact* pcon) 
{
	char name[NAME_MAX];
	printf("请输入要查找的用户姓名:\n");
	scanf("%s", name);

	int findIndex = FindByName(pcon, name);
	if (findIndex < 0) 
	{
		printf("该联系人不存在!\n");
		return;
	}
	//找到了,打印一下查找的联系人信息
	printf("%s %s %s %s %s\n", "姓名", "性别", "年龄", "电话", "住址");
	printf("%s %s %d %s %s\n",
		pcon->arr[findIndex].name,
		pcon->arr[findIndex].gender,
		pcon->arr[findIndex].age,
		pcon->arr[findIndex].tel,
		pcon->arr[findIndex].addr
	);
}

void SaveContact(Contact* con) 
{
	FILE* pf = fopen("contact.txt", "wb");
	if (pf == NULL) 
	{
		perror("fopen error!\n");
		return;
	}
	//将通讯录数据写⼊⽂件
	for (int i = 0; i < con->size; i++)
	{
		fwrite(con->a + i, sizeof(Info), 1, pf);
	}
	printf("通讯录数据保存成功!\n");
}

void ContactRead(Contact* pcon)//从文件中读取历史数据
{
	FILE* pf = fopen("contact.txt", "rb");
	if (pf == NULL)
	{
		perror("fopen");
		return;
	}
	Info info;
	while (fread(&info, sizeof(Info), 1, pf))
	{
		SLPushBack(pcon, info);
	}
	printf("成功读取历史数据到通讯录中\n");
}

TestContact.c 

//#include"Contact.h"  //在SeqList.h文件中已经包了Contact.h
#include"SeqList.h"

//通讯录菜单
void menu()
{
	printf("*****************通讯录***************\n");
	printf("*******1.添加联系人  2.删除联系人*****\n");//ctrl+d
	printf("*******3.修改联系人  4.查找联系人*****\n");//ctrl+d
	printf("*******5.查看通讯录  0.  退 出  ******\n");//ctrl+d
	printf("**************************************\n");
}
int main()
{
	int op = -1;
	//创建通讯录结构对象
	Contact con;
	ContactInit(&con);
	do {
		menu();
		printf("请选择您的操作:\n");
		scanf("%d", &op);

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

	} while (op != 0);
	//销毁通讯录
	ContactDesTroy(&con);
	return 0;
}

大家可以根据上述思路和提供的源码,自己练习哦~

以上就是本篇的所有内容了,希望对你的学习有所帮助~

对于我博客中内容有疑问的话,欢迎大家在评论区提问,发表你的看法

我们下期再见!👋👋

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2046543.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【代码随想录训练营第42期 Day27打卡 贪心Part1 - LeetCode 455.分发饼干 376. 摆动序列 53. 最大子序和

目录 一、贪心 二、题目与题解 题目一&#xff1a;455.分发饼干 题目链接 题解&#xff1a;排序双指针贪心 题目二&#xff1a;376. 摆动序列 题目链接 题解&#xff1a;贪心 题目三&#xff1a;53. 最大子序和 题目链接 题解1&#xff1a;暴力&#xff08;失败&…

解决生产环境服务启动失败:一次远程Bug排查与修复历程

一、问题现象 同事没事一直给服务器断电&#xff08;直接拔插头那种&#xff01;&#xff01;&#xff01;&#xff01;&#xff09; 二、初步排查 首先&#xff0c;我登录到生产服务器&#xff0c;查看服务启动日志。在日志中&#xff0c;我发现了一些异常信息&#xff0c;…

全网独家梳理:数字病理图像的常用存储格式以及格式转换的方法|24-08-17

小罗碎碎念 这一期推文&#xff0c;跟你们分享一些比较底层&#xff0c;并且顶刊中不会涉及但是又至关重要的内容。 我们在做任何一个病理AI的项目前&#xff0c;有两样东西是一定会拿到手的——切片&对应的临床基线表。&#xff08;如果做多组学/多模态的项目&#xff0c;…

宠物空气净化器推荐购买吗?真的能除毛去味吗?

自从做了猫咖店老板&#xff0c;我这生活真的是美滋滋&#xff0c;每天都可以摸到不同品种的可爱的小猫咪&#xff0c;在赚钱养家的同时还能肆意和猫咪贴贴&#xff0c;连朋友都说想和我干一样的工作了。每天接待的顾客也不少&#xff0c;店里面的空气质量也还不错&#xff0c;…

模块一(任务2):SDH系统原理解读

一、PDH与SDH标准速率介绍 OTN网络技术是基于SDH系统和WDM系统设计的&#xff0c;所以学习OTN系统原理必须要掌握SDH和WDM量大系统原理 1、PDH与SDH概念及特点 二、SDH的帧结构 目前通信常见的数据结构有两种&#xff1a; 报文&#xff1a;基于以太网通信的数据结构 帧&am…

真诚巨作:全文一万字教你快速熟悉项目|文心快码帮你快速熟悉~

写在前面 : 上了好多年的学&#xff0c;终于毕业进入职场啦&#xff5e;在公司也有一段时间了&#xff0c;慢慢地也变成了纯正的社畜了。近来开始熟悉部门的项目代码&#xff0c;说实话公司的代码真跟以前接触的不一样&#xff0c;内部各种自建框架&#xff0c;让人看得眼花缭乱…

ARCGIS PRO 要素标注背景色透明度的设置

使用ArcGIS Pro 设置标注背景色的透明度 一、点击标注属性 二、点击符号、注释 三、下拉框选择背景 四、背景符号 五、点击颜色 六、编辑颜色 七、应用

Java语言程序设计基础篇_编程练习题**16.26(模拟:升旗并播放国歌)

目录 题目&#xff1a;**16.26&#xff08;模拟&#xff1a;升旗并播放国歌&#xff09; 习题思路&#xff1a; 代码示例 结果展示 音频来源 题目&#xff1a;**16.26&#xff08;模拟&#xff1a;升旗并播放国歌&#xff09; 创建一个显示升国旗的程序&#xff0c;如图15-14…

还在使用百度翻译?这4款翻译工具也能帮你打破语言壁垒!

是谁像我一样&#xff0c;一提到翻译工具第一想到的就是百度翻译。它可以是用了好久的翻译工具&#xff0c;可是随着学习的深入&#xff0c; 我还是发觉了一些其他的实用翻译工具。比如下面这四款&#xff1a; 1、365翻译工具 直达链接&#xff1a;https://fanyi.pdf365.cn/ …

NLP之transformer:transformer-explainer的简介、安装和使用方法、案例应用之详细攻略

NLP之transformer&#xff1a;transformer-explainer的简介、安装和使用方法、案例应用之详细攻略 目录 相关论文 《Transformer Explainer: Interactive Learning of Text-Generative Models》翻译与解读 transformer-explainer的简介 transformer-explainer的安装和使用方…

尚硅谷MYSQL(12-13章)

第十二章数据类型 比如说tinyint&#xff08;4&#xff09;这个四表示的是那个取值范围的位数 他只是一个显示 比如说int后面应该是int&#xff08;11&#xff09;这是有符号的 无符号int是int&#xff08;10&#xff09; 如果写成int&#xff08;3&#xff09;但是存的数据是…

论文写作新神器!10款可以写论文的人工智能软件

在当今快速发展的数字时代&#xff0c;人工智能&#xff08;AI&#xff09;技术已经渗透到各个领域&#xff0c;包括学术研究和论文写作。为了帮助学者和学生提高写作效率和质量&#xff0c;市场上涌现了许多优秀的AI写作工具。本文将详细介绍10款可以写论文的人工智能软件&…

STM32F103C8T6单片机原理图设计(PCB板)

先了解了以下stm32f103c8t6的引脚使用&#xff1a; 对比过一些原理图&#xff0c;我发现除了特定协议的引脚功能&#xff0c;只要功能正确&#xff0c;可以自己选择连接对应的引脚。可以根据使用的LED/BEEP/DHT11/BH1750/ESP8266等模块的功能对应相应的引脚&#xff1a; WIFI(…

[开源] 安卓系统发送modbus协议到硬件设备下位机

最近是在研究安卓板子上的modbus通信&#xff0c;于是写了这个maven依赖。 项目中主要用到的功能是读写寄存器&#xff0c;所以依赖中重点测试了读写多个寄存器的协议。 另外你可以自定义协议pdu交给程序进行封装及发送。 如果使用中发现了什么问题&#xff0c;可以到仓库添…

DHU OJ 循环结构 整除的尾数

思路及代码 //input T int >0 //input a,b int 0< <10000 10< <100 #include<iostream> #include<iomanip> using namespace std; int main(){int T;cin >> T;while (--T > 0){int a, b;cin >> a >> b; //solution // (a*100…

Tomcat初篇

目录 Tomcat主要特点Tomcat的核心组件Tomcat使用安装Tomcat配置Tomcat启动和停止Tomcat Tomcat工作原理目录结构配置文件性能优化策略 Tomcat Apache Tomcat是一个开源的Servlet容器和Web服务器&#xff0c;广泛用于运行基于Java的Web应用程序。它实现了Java Servlet和JavaSer…

【Web开发手礼】探索Web开发的秘密(十八)-Vue2(4)部门管理页面、路由、打包部署

主要介绍了部门管理页面、路由、打包部署&#xff01;&#xff01;&#xff01; 文章目录 前言 部门管理页面 Vue路由 打包部署 打包 部署 总结 前言 主要介绍了部门管理页面、路由、打包部署&#xff01;&#xff01;&#xff01; 部门管理页面 <template><div>&…

module ‘pkgutil‘ has no attribute ‘ImpImporter‘. Did you mean_ ‘zipimporter‘_

错误详情&#xff1a; Traceback (most recent call last):File "<frozen runpy>", line 198, in _run_module_as_mainFile "<frozen runpy>", line 88, in _run_codeFile "C:\ProgramData\anaconda3\envs\py312\Scripts\mim.exe\__main…

数值计算引擎:搭建远程容器开发环境

Build VS Code Remote Docker Development Environment 大型CAE软件开发技术栈通常依赖多个第三方库&#xff0c;因此从零开始配置开发、编译、运行等环境通常较为繁琐。但随着公司的发展壮大&#xff0c;却经常需要为新加入的成员配备相应的开发环境&#xff1b;另外&#xf…

深入理解 go unsafe

往期精选文章推荐&#xff1a; 深入理解 go mapgo 常用关键字深入理解 Go 数组、切片、字符串深入理解channel深入理解 go context深入 go interface 底层原理深入理解 go reflect深入理解 go unsafe 为什么有go unsafe Go 是支持指针的语言&#xff0c;但是为了保持简洁、安…