简易通讯录Promax

news2024/11/19 16:39:03

前言:哈喽小伙伴们,我们在前边的文章中已经介绍过了如何用C语言实现一个简易的通讯录,但是我们这个通讯录存在两个问题:

一是通讯录的大小不能自由变化;二是通讯录的信息在程序退出之后就没有了,不能保存。

那么,在我们学习完动态内存管理和文件操作之后,这些问题就迎刃而解啦。

下面我们就来一起实现真正意义上的通讯录。


一.动态内存改进

1.创建联系人改进

在最初的通讯录中,我们是用一个结构体类型的数组来存储信息:

typedef struct Contact
{
	Peomessage data[DATA_MAX];
	int sz;
}Contact;

那么当我们用到动态内存管理时,就不需要数组了,转而需要一个能够指向我们所开辟的空间的Peomessage类型的指针

typedef struct Contact
{
	Peomessage* data;
	int sz;
	int Length;
}Contact;

能够看出,我们这里多了一个Length变量,它的作用是记录通讯录当前的大小,因为我们要根据存取的联系人信息数量是否要大于当前通讯录的大小来对通讯录进行扩充

通讯录都要有一个初始大小,所以我们规定通讯录的初始大小为3,每次扩充时增加2个空间:

#define LENGTH 3
#define ADD_LENGTH 2

 2.初始化改进

初始化通讯录,就是用calloc函数动态开辟一块大小为LENGTH的空间。

按照我们动态开辟空间的步骤即可:

void InitContact(Contact* pc)
{
	assert(pc);
	pc->sz = 0;
	pc->Length = LENGTH;
	pc->data = calloc(pc->Length, sizeof(Peomessage));
	if (pc->data == NULL)
	{
		perror("InitContact->calloc");
		return;
	}
}

将sz初始化为0,Length初始化为LENGTH

一定要注意对内存是否开辟成功的判断


3.增加联系人改进

void AddContact(Contact* pc)
{
	assert(pc);
	if (pc->sz == pc->Length)
	{
		Add_Length(pc);
	}
	printf("请输入名字:");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入电话:");
	scanf("%s", pc->data[pc->sz].tele);
	printf("请输入住址:");
	scanf("%s", pc->data[pc->sz].addr);
	pc->sz++;
	printf("添加成功\n");
}

增加联系人时,我们要进行sz是否等于当前Length的判断,如果相等,说明当前通讯录已满,需要扩充,所以我们就调用扩容函数:

void Add_Length(Contact* pc)
{
	Peomessage* ptr = (Peomessage*)realloc(pc->data, (pc->Length + 2) * sizeof(Peomessage));
	if (ptr == NULL)
	{
		perror("Add_Length->realloc");
		return;
	}
	else
	{
		pc->data = ptr;
		pc->Length += 2;
        printf("扩容成功\n");
	}

}

 既然是扩容,那么就是修改我们已经开辟的空间大小,要用到realloc函数来修改大小,新的大小为Length + 2

这里要注意一点,realloc函数修改空间也可能会失败如果就直接用data指针来接收的话,如果失败,就会返回空指针,那我们原本所存的数据就不复存在了

所以要用一个新的指针ptr来测试,如果不为空,再将ptr赋给data,同时要注意Length要加2

 

来看结果,我们当前已经存了三个人的信息再次选择存信息时,就会显示扩容成功。 


4. 销毁通讯录

在动态内存管理里边我们已经学习过,动态开辟的空间使用之后要及时销毁,所以我们在通讯录退出之时,要用一个销毁函数来释放空间并还原通讯录

case EXIT:
	Destroy_Contact(&con);
	printf("退出通讯录\n");
	break;
void Destroy_Contact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->Length = LENGTH;
}

将指针data释放并置空,同时sz和Length也要还原


这样我们便能够实现通讯录的动态大小变换了。

接下来,我们再用文件操作的相关知识来实现信息的真正存储。


二.文件操作改进

1.写文件

想保存我们已经写过的联系人信息,就要把它们写入文件,那么请小伙伴们思考一下,什么时候写入文件最合适呢???

是不是当我们不需要在进行任何操作,要退出通讯录的时候呀

所以这个时候我们就需要一个写文件的函数WriteContact来实现:

case EXIT:
	WriteContact(&con);
	Destroy_Contact(&con);
	printf("退出通讯录\n");
	break;

那么小伙伴们再来思考一下,我们该怎么写文件呢???

我们已经知道,要写的数据是一个结构体含有多种数据类型,那对于我们已经学习过的文件读写函数,是不是用二进制读写函数是最好的,因为二进制不分数据类型

void WriteContact(Contact* pc)
{
	assert(pc);
	FILE* pf = fopen("Contact.txt", "wb");
	if (pf == NULL)
	{
		perror("WriteContact->fopen");
		return;
	}
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(Peomessage), 1, pf);
	}
	fcolse(pf);
	pf = NULL;
}

因为我们读入的数据可能不止一个,所以要使用for循环来实现全部读写。

这时候当我们的程序文件目录下就会生成一个Contact.txt文件,打开内容为:

这里虽然没有对齐,但是我们不用管,因为我们并不是要通过文件来看通讯录信息,而是通过程序来看的文件只是一个后台,而真正展现给用户的是程序运行窗口

这样我们就实现了数据保存到文件的操作。


你以为这样就结束了吗,太天真了,当我们再次运行程序时,结果为:

通讯录依然为空,这是为什么呢???

 因为虽然我们的文件确实还存在,内容也存在,但是我们的内存空间不存在了,我们的代码重启是开辟一个新的空间,新的空间中并没有我们已经写入的文件的数据

所以在此之前,我们需要先将文件里的数据输入到我们开辟的新的空间中去


2.读文件

读文件的操作,需要在我们对文件进行任何操作之前,也就是在初始化的时候就完成

读二进制文件就要用到fread函数了,注意,因为我们的通讯录初始大小仅为3,但是文件中已经存放的数据可能要多余3个,所以要提前进行判断是否需要扩容,所以我们要先用一个临时指针ptr来接收信息,随后再赋值给data

来看代码实现:

void ReadContact(Contact* pc)
{
	assert(pc);
	FILE* pf = fopen("Contact.txt", "rb");
	if (pf == NULL)
	{
		perror("ReadContact -> fopen");
		return;
	}
	Peomessage ptr = { 0 };
	while (fread(&ptr, sizeof(Peomessage), 1, pf))
	{
		if (pc->sz == pc->Length)
		{
			Add_Length(pc);
		}
		pc->data[pc->sz] = ptr;
		pc->sz++;
	}
}

这个时候来看结果,当我们运行代码,并选择显示通讯录5时,我们已经有数据了,

然后我们再添加一位联系人信息并关闭,再次打开时,就多了一个:

 至此,我们就完成了通讯录Promax的实现。


三.完整代码展示

1.Contact.h

#pragma once
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>

#define NAME_MAX 20
#define ADDR_MAX 30
#define DATA_MAX 100 
#define LENGTH 3
#define ADD_LENGTH 2
//选项
enum Function
{
	EXIT,//默认从0开始
	ADD,
	DEL,
	SEARCH,
	REVISE,
	SHOW,
	SORT,
	EMPTY
};
enum Type
{
	QUIT,
	NAME,
	TELE,
	ADDR
};
//联系人信息
typedef struct Peomessage
{
	char name[NAME_MAX];
	char tele[12];
	char addr[ADDR_MAX];
}Peomessage;
//原通讯录
//typedef struct Contact
//{
//	Peomessage data[DATA_MAX];
//	int sz;
//}Contact;
//改进通讯录
typedef struct Contact
{
	Peomessage* data;
	int sz;
	int Length;
}Contact;
//初始化通讯录
void InitContact(Contact* pc);
//增加联系人信息
void AddContact(Contact* pc);
//显示联系人信息
void ShowContact(Contact* pc);
//查找联系人信息
void SearchContact(Contact* pc);
//删除联系人信息
void DelContact(Contact* pc);
//修改联系人信息
void ReviseContact(Contact* pc);
//排序联系人信息
void SortContact(Contact* pc);
//清空所有联系人
void EmptyContact(Contact* pc);
//摧毁通讯录
void Destroy_Contact(Contact* pc);
//写文件
void WriteContact(Contact* pc);
//读文件
void ReadContact(Contact* pc);

 2.Contact.c

#include"Contact.h"
void Add_Length(Contact* pc);
//读文件
void ReadContact(Contact* pc)
{
	assert(pc);
	FILE* pf = fopen("Contact.txt", "rb");
	if (pf == NULL)
	{
		perror("ReadContact -> fopen");
		return;
	}
	Peomessage ptr = { 0 };
	while (fread(&ptr, sizeof(Peomessage), 1, pf))
	{
		if (pc->sz == pc->Length)
		{
			Add_Length(pc);
		}
		pc->data[pc->sz] = ptr;
		pc->sz++;
	}
}
// 改进初始化
void InitContact(Contact* pc)
{
	assert(pc);
	pc->sz = 0;
	pc->Length = LENGTH;
	pc->data = calloc(pc->Length, sizeof(Peomessage));
	if (pc->data == NULL)
	{
		perror("InitContact->calloc");
		return;
	}
	ReadContact(pc);
}
//扩容通讯录
void Add_Length(Contact* pc)
{
	Peomessage* ptr = (Peomessage*)realloc(pc->data, (pc->Length + 2) * sizeof(Peomessage));
	if (ptr == NULL)
	{
		perror("Add_Length->realloc");
		return;
	}
	else
	{
		pc->data = ptr;
		pc->Length += 2;
	}

}
//增加联系人信息
void AddContact(Contact* pc)
{
	assert(pc);
	//增加容量
	if (pc->sz == pc->Length)
	{
		Add_Length(pc);
	}
	printf("请输入名字:");
	scanf("%s", pc->data[pc->sz].name);
	printf("请输入电话:");
	scanf("%s", pc->data[pc->sz].tele);
	printf("请输入住址:");
	scanf("%s", pc->data[pc->sz].addr);
	pc->sz++;
	printf("添加成功\n");
}
//显示联系人信息
void ShowContact(Contact* pc)
{
	assert(pc);
	if (pc->sz == 0)
	{
		printf("通讯录为空\n");
		return;
	}
	printf("%-20s%-12s%-30s\n", "姓名", "电话", "住址");
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-20s%-12s%-30s\n", pc->data[i].name, pc->data[i].tele, pc->data[i].addr);
	}
}
//通过名字查找
int FindByName(Contact* pc, char* name)
{
	assert(pc && name);
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(name, pc->data[i].name) == 0)
		{
			return i;
		}
	}
	return -1;
}
//查找联系人信息
void SearchContact(Contact* pc)
{
	assert(pc);
	char name[NAME_MAX];
	printf("请输入要查找人的名字:");
	scanf("%s", name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("找不到该联系人\n");
		return;
	}
	printf("%-20s%-12s%-30s\n", "姓名", "电话", "住址");
	printf("%-20s%-12s%-30s\n", pc->data[ret].name, pc->data[ret].tele, pc->data[ret].addr);
}
//删除联系人信息
void DelContact(Contact* pc)
{
	assert(pc);
	char name[NAME_MAX];
	printf("请输入要删除联系人的名字:");
	scanf("%s", name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("该联系人信息不存在\n");
		return;
	}
	//删除联系人
	int i = 0;
	for (i = ret; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;
	printf("删除成功\n");
}
void menu2()
{
	printf("*******************************\n");
	printf("***** 0.exit       1.name *****\n");
	printf("***** 2.tele       3.addr *****\n");
	printf("*******************************\n");
}
//修改联系人信息
void ReviseContact(Contact* pc)
{
	assert(pc);
	char name[NAME_MAX];
	printf("请输入要修改联系人的名字:");
	scanf("%s", name);
	int ret = FindByName(pc, name);
	if (ret == -1)
	{
		printf("该联系人信息不存在\n");
		return;
	}
	int input;
	do
	{
		menu2();
		printf("请选择要修改的信息类型:");
		scanf("%d", &input);
		switch (input)
		{
		case QUIT:
			printf("退出修改\n");
			break;
		case NAME:
			printf("请输入名字:");
			scanf("%s", pc->data[ret].name);
			printf("修改成功\n");
			break;
		case TELE:
			printf("请输入电话:");
			scanf("%s", pc->data[ret].tele);
			printf("修改成功\n");
			break;
		case ADDR:
			printf("请输入地址:");
			scanf("%s", pc->data[ret].addr);
			printf("修改成功\n");
			break;
		default:
			printf("选择错误,请重新选择\n");
			break;
		}
	} while (input);
}
//排序联系人信息
int compare(const void* a, const void* b)
{
	return strcmp(((Peomessage*)a)->name, ((Peomessage*)b)->name);
}
void SortContact(Contact* pc)
{
	assert(pc);
	qsort(pc->data, pc->sz, sizeof(pc->data[0]), compare);
	printf("排序成功\n");
}
//清空所有联系人
void EmptyContact(Contact* pc)
{
	assert(pc);
	pc->sz = 0;
	memset(pc->data, 0, sizeof(pc->data));
	printf("已清空通讯录\n");
}
//还原通讯录
void Destroy_Contact(Contact* pc)
{
	assert(pc);
	free(pc->data);
	pc->data = NULL;
	pc->sz = 0;
	pc->Length = LENGTH;
}

//写文件
void WriteContact(Contact* pc)
{
	assert(pc);
	FILE* pf = fopen("Contact.txt", "wb");
	if (pf == NULL)
	{
		perror("WriteContact->fopen");
		return;
	}
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(Peomessage), 1, pf);
	}
	fclose(pf);
	pf = NULL;
}

3. test.c

#include "Contact.h"
void menu1()//菜单
{
	printf("*********************************\n");
	printf("***** 1.add        2.del    *****\n");
	printf("***** 3.search     4.revise *****\n");
	printf("***** 5.show       6.sort   *****\n");
	printf("***** 7.empty      0.exit   *****\n");
	printf("*********************************\n");
}
int main()
{
	Contact con;
	//初始化通讯录
	InitContact(&con);
	int input;
	do {
		menu1();
		printf("请选择->:");
		scanf("%d", &input);
		switch (input)
		{
		case EXIT:
			WriteContact(&con);
			Destroy_Contact(&con);
			printf("退出通讯录\n");
			break;
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case REVISE:
			ReviseContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EMPTY:
			EmptyContact(&con);
			break;
		default:
			printf("输入错误,请重新输入\n");

		}
	} while (input);
	return 0;
}

四.总结

每次写完一个大项目时,我都会为自己感到自豪,因为自己在不断地进步。

希望小伙伴们也能够不断地奋斗,争取早日拿到自己心意的offer!

最后还是不要忘记一键三连呀!

我们下期再见啦!!!

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

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

相关文章

【C语言】进阶——文件操作

目录 前言 1.什么是文件 程序文件&#xff1a; 数据文件&#xff1a; 文件名&#xff1a; 2.文件的打开和关闭 2.1文件指针 2.2文件指针使用 2.3文件的打开和关闭 2.4 文件的使用方式 2.5文件的顺序读写 ✌字符输出函数fputc ✌字符输入函数fgetc ✌文本行输…

【重拾C语言】十三、动态数据组织(一)动态变量(malloc、calloc、realloc、free)

目录 前言 十三、动态数据组织 13.1 动态数据组织 13.2 动态变量 malloc函数 calloc函数 realloc函数 free函数 程序实例 前言 C语言中的动态数据组织是指在程序运行时根据需要动态地分配内存空间来存储数据。这允许程序在运行时根据实际需求来创建、修改和释放数据结…

【数字IC设计】VCS门级网表仿真

本文参考自文章。 除了RTL仿真以外&#xff0c;在IC设计过程中还会进行门级网表的仿真。当设计代码功能仿真通过之后&#xff0c;我们便使用Design Compiler工具对其进行逻辑综合&#xff0c;将RTL代码转化为由与、或、非等门电路和触发器组成的电路&#xff0c;称为门级网表(n…

25栈和队列-理解栈和队列

目录 LeetCode之路——232. 用栈实现队列 分析&#xff1a; LeetCode之路——225. 用队列实现栈 分析&#xff1a; 栈&#xff08;Stack&#xff09;和队列&#xff08;Queue&#xff09;是两种基本的数据结构&#xff0c;它们在计算机科学中用于不同的目的。以下是它们的定…

【传输层协议】UDP/TCP结构特点与原理(详解)

文章目录 1. UDP1.1 UDP结构1.2 UDP特点1. 无连接2. 不可靠3. 面向数据报4. 缓冲区5. 大小受限6. 无序性 2. TCP2.1 TCP结构2.2 TCP特点1. 有连接2. 可靠性3. 面向字节流4. 拥塞控制5. 头部开销 2.3 TCP原理1. 确认应答&#xff08;安全机制&#xff09;2. 超时重传&#xff08…

Ceph分布式存储的简单介绍与Ceph集群的部署搭建

文章目录 1. 存储的概述1.1 单机存储设备1.1.1 DAS&#xff08;直接附加存储&#xff09;1.1.2 NAS&#xff08;网络附加存储&#xff09;1.1.3 SAN&#xff08;存储区域网络&#xff09; 1.2 单机存储的缺陷1.3 分布式存储&#xff08;软件定义的存储 SDS&#xff09;1.4 分布…

【计算机网络笔记】数据交换之报文交换和分组交换

系列文章目录报文交换分组交换存储-转发报文交换 vs 分组交换总结 系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 报文交换 报文&#xff1a;源&#xff08;应用&#xff09;发送的信息整体。比如一个文件、一…

Transformer 中 Positional Encoding 实现

参考博文&#xff1a; https://www.cnblogs.com/nickchen121/p/16470736.html 解决问题 位置编码的主要目的是确保模型能够理解序列中的元素之间的相对位置和顺序&#xff0c;从而更好地捕捉到语义信息。在Transformer模型中&#xff0c;位置编码通常与词嵌入&#xff08;w…

前端小知识之【浏览器内核】

目录 &#x1f31f;前言&#x1f31f;PC端浏览器内核&#x1f31f;Trident内核&#x1f31f;Gecko内核&#x1f31f;WebKit内核(Chromium)&#x1f31f;Blink内核 &#x1f31f;移动端浏览器内核&#x1f31f;应用&#x1f31f;写在最后 &#x1f31f;前言 通常所谓的浏览器内…

docker安装nessus

注册地址:https://zh-tw.tenable.com/products/nessus/nessus-essentials 临时邮箱:http://24mail.chacuo.net/ 帮助文档:https://docs.tenable.com/nessus/Content/DeployNessusDocker.htmdocker pull tenableofficial/nessusdocker run --name "my-nessus" -d -p 8…

【Go入门】编程语言比较:Golang VS Python

Golang&#xff1a;最佳人工智能语言&#xff0c;性能优于 Python 本节是学习go的引入&#xff0c;为了了解Python与go编程语言间比较。后续会完成相关课程&#xff0c;并分享笔记。 如今&#xff0c;世界各地有数百万用户使用 Golang 作为机器学习和人工智能的编程语言。 最好…

算法通过村第十四关-堆|白银笔记|经典问题

文章目录 前言在数组中寻找第K大的元素堆排序原理合并K个排序链表总结 前言 提示&#xff1a;想要从讨厌的地方飞出来&#xff0c;就得有藏起来的翅膀。 --三岛由纪夫《萨德侯爵夫人》 这里我们主要看一下经典的题目&#xff0c;这三个题目来说都是堆的热点问题。重点再理解处理…

Qt不能安装自己想要的版本,如Qt 5.15.2

使用在线安装工具安装Qt5.15.2时&#xff0c;发现没有Qt 5的相关版本&#xff0c;只有Qt 6的版本&#xff0c;这时选择右边的Archive&#xff0c;再点击筛选&#xff0c;这时就会出现之前的Qt版本。

vscode插件路径转移C盘之外盘

改变vscode系统路径 最近C盘路径不够了&#xff0c;网上的工具使用没那么精细&#xff0c;还不如自己手动看每个文件夹大小。在整理过长遇到vscode插件路径转移&#xff0c;方法如下&#xff1a; 桌面图标右键点击属性 改变–extensions-dir后面参数就可以了。

Web3 整理React项目 导入Web3 并获取区块链信息

上文 WEB3 创建React前端Dapp环境并整合solidity项目&#xff0c;融合项目结构便捷前端拿取合约 Abi 我们用react 创建了一个 dapp 项目 并将前后端代码做了个整合 那么 我们就来好好整理一下 我们的前端react的项目结构 我们在 src 目录下创建一个 components 用来存放我们的…

Python学习----Day07

函数 函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码段。函数能提高应用的模块性&#xff0c;和代码的重复利用率。你已经知道Python提供了许多内建函数&#xff0c;比如print()。但你也可以自己创建函数&#xff0c;这被叫做…

C++ 程序员入门需要多久,怎样才能学好?

文章目录 C学习方案有哪些推荐的在线教程或学习资源可以帮助我学习C编程&#xff1f;你能给我一些关于C内存管理的进阶学习资源吗&#xff1f; AI解答 C学习方案 C是一种功能强大且广泛应用的编程语言&#xff0c;作为一个初学者&#xff0c;学习C需要一定的时间和努力。学习…

【java学习—七】对象的实例化过程(33)

文章目录 1. 简单类对象的实例化过程2. 子类对象的实例化过程 1. 简单类对象的实例化过程 2. 子类对象的实例化过程

YOLO目标检测——打电话数据集【含对应voc、coco和yolo三种格式标签】

实际项目应用&#xff1a;安全监控、智能驾驶、人机交互、智能城市数据集说明&#xff1a;YOLO目标检测数据集&#xff0c;真实场景的高质量图片数据&#xff0c;数据场景丰富。使用lableimg标注软件标注&#xff0c;标注框质量高&#xff0c;含voc(xml)、coco(json)和yolo(txt…

快速学习MyBatisPlus

文章目录 前言一、条件构造器和常用接口1.wapper介绍2.QueryWrapper&#xff08;1&#xff09;组装查询条件&#xff08;2&#xff09;组装排序查询&#xff08;3&#xff09;组装删除查询&#xff08;4&#xff09;条件优先级&#xff08;5&#xff09;组装select子句&#xf…