C语言实现通讯录-动态版本与文件版本

news2024/12/23 19:08:57

C语言实现通讯录-动态版本与文件版本

  • 1.前言
  • 2.动态版本
    • 2.1联系人信息
      • 之前的
      • 改版:
    • 2.2初始化
      • 之前的
      • 改版
    • 2.3自动扩容
  • 3.文件版本
    • 3.1自动保存
      • 函数实现
      • 效果
    • 3.2打开时加载信息
      • 函数实现
      • 效果

1.前言

在先前的探索中,我构建了一个C语言实现简单的通讯录,它能够存储一定数量的联系人信息。然而,这个版本存在局限性——不仅联系人数目被固定,而且一旦程序关闭,所有的联系人信息都会丢失。

为了克服这些局限性,我决定开发一个更加灵活且持久的通讯录系统。在这篇文章中,我将介绍如何利用C语言中的动态内存分配函数(如realloc)来创建一个能够适应任意数量联系人的通讯录,即动态版本

并且还将展示如何使用文件操作来确保联系人信息即使在程序关闭后也能得以保存,即文件版本

2.动态版本

2.1联系人信息

之前的

#define MAX 100
#define NAME_MAX 20
#define TELE_MAX 12
struct Peo
{
	char name[NAME_MAX];
	int age;
	char tele[TELE_MAX];
};
struct Contact
{
	struct Peo peo[MAX];
	int num;
};

在原始版本中,我使用了一个固定大小的结构体数组来存储联系人信息。这种设计虽然简单,但在实际应用中存在明显的不足之处,尤其是当需要存储的联系人数量不确定时。
为此,我转向使用动态内存分配技术来解决这一问题,因此,不能再用结构体数组,而改为结构体指针

且之前只准备了num来存储当前数量,在动态版本,还需一个变量存储当前最大数量,以免溢出。

改版:

struct Contact
{
	struct Peo* peo;
	int num;
	int now_max;
};

我引入了一个指向Peo结构体指针peo,以及一个新的成员now_max来记录当前分配的最大联系人数量。
这样的改动使得我们的通讯录能够动态地适应不断增加的联系人数量。

2.2初始化

之前的

void init(struct contact* p)
{
	assert(p);
	p->num = 0;
	memset(p->peo, 0, sizeof(p->peo));
}

在静态版本中,我使用memset函数来初始化结构体数组中的每个元素。
然而,在动态版本中,我们需要采用不同的方法来进行初始化。
为此,我使用calloc函数来分配内存,并将所有分配的内存初始化为零。

改版

void Init(struct Contact* p)
{
	assert(p);
	p->num = 0;
	struct Peo*tmp = (struct Peo*)calloc(INIT_NUM, sizeof(struct Peo));
	if (!tmp)
	{
		perror("Init:calloc");
		return;
	}
	p->peo = tmp;
	p->now_max = INIT_NUM;
}

这里,我使用calloc函数来分配足够的内存空间,并将now_max初始化为INIT_NUM,即初始最大联系人数量。
这样的改动使得我们的通讯录在初始化时就能正确地分配所需的内存,并将所有成员初始化为零。
其中,INIT_NUM在头文件定义:

#define INIT_NUM 3

perror
perror可打印错误信息,其头文件是<stdio.h>
"Init:calloc"可改为任意字符串,我这样写的目的是在错误信息打印后,知道是Init函数中的calloc步骤出了问题
可简单验证:

#define INIT_NUM 10000000000000000

运行结果:
在这里插入图片描述

2.3自动扩容

在动态版本中,我们面临的一个重要挑战是如何有效地管理内存,尤其是在联系人数量增加时。
为此,我实现了一个自动扩容机制,当现有的内存空间不足以容纳新增加的联系人时,该机制会自动扩大内存容量。
即,联系人当前数量num与当前最大数量now_max相等时,自动扩容:

void AddPeo(struct Contact* p)
{
	if (p->num == p->now_max)
	{
		struct Peo* tmp = (struct Peo*)realloc(p->peo, (p->now_max + ADD_NUM) * sizeof(struct Peo));
		if (!tmp)
		{
			perror("AddPeo:realloc");
			return;
		}
		p->peo = tmp;
		p->now_max += 2;
		printf("扩容成功\n");
	}
}

在这里,我使用realloc函数来重新分配更大的内存空间,并更新now_max以反映新的最大联系人数量。
通过这种方式,可以确保通讯录始终有足够的空间来存储新的联系人信息,而无需手动干预。
其中,ADD_NUM为扩充的联系人数量,在头文件定义:

#define ADD_NUM 2

printf("扩容成功\n");可便于验证当前程序的正确性:
在这里插入图片描述

注:自动扩容函数只需放入添加联系人函数,因为只有后者会增加联系人数量。

动态版本到此为止。


3.文件版本

3.1自动保存

除了动态管理内存之外,另一个重要的功能是确保联系人信息能够被持久化保存。为此,我实现了文件保存功能,能够在程序结束前自动将所有联系人信息保存到文件中:

case 0:
	Ctrl_S(&con);//文件保存函数
	printf("exit\n");
	break;

使用Ctrl_S命名只为直观,而非我不懂’保存’的英文。

函数实现

void Ctrl_S(struct Contact* p)
{
	assert(p);
	FILE* fp = fopen("contact.txt", "wb");
	if (!fp)
	{
		perror("Ctrl_S:fopen");
		return;
	}
	for (int i = 0; i < p->num; i++)
	{
		fwrite(p->peo + i, sizeof(struct Peo), 1, fp);
	}
	fclose(fp);
	fp = NULL;
	printf("自动保存成功\n");
}

通过使用fwrite函数,我将每个联系人的信息写入到名为contact.txt的文件中。
这样,即使在程序关闭后,所有的联系人信息也能被安全地保存下来,供下次使用。

注:fwrite函数原型:size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );

效果

在这里插入图片描述
在这里插入图片描述

需注意,由于是以二进制形式存储入文件,因此,以文本文档形式打开时会出现意义不明的乱码。

3.2打开时加载信息

函数实现

void Load(struct Contact* p)
{
	assert(p);
	FILE* fp = fopen("contact.txt", "rb");
	if (!fp)
	{
		perror("Load:fopen");
		return;
	}
	struct Peo tmp = { 0 };
	int i = 0;
	while (fread(&tmp, sizeof(struct Peo), 1, fp))
	{
		AddPeo(p);
		p->peo[i] = tmp;
		p->num++;
		i++;
	}
	fclose(fp);
	fp = NULL;
	printf("信息加载成功\n");
}

这里创建了一个临时的结构体,一次读一个联系人的信息,便于接收文件中的信息,并判断文件是否还有信息,如果没有,结束循环。
每次循环开始,需检查是否应该扩容
将此函数置于初始化函数Init()的最后,即可运行程序。

注:fread函数原型:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );


效果

退出时
在这里插入图片描述
打开时
在这里插入图片描述


希望这篇博客能够帮助那些正在学习C语言或对内存管理和文件操作感兴趣的朋友!

本人仅是个C语言初学者,如果你有任何疑问或建议,欢迎随时留言讨论!


相关文章:
C语言指针详解-上
C语言指针详解-下

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

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

相关文章

day37-https实战

1.知识点补充: 四层代理转发数据库 目的: 通过10.0.0.4 使用ssh远程连接到web01 ssh--->10.0.0.4:2222 ---------->172.16.1.7:22 xshell-->创建会话-->10.0.0.4 端口 2222 Linux系统 ssh 10.0.0.4 -p2222作业: 目的: 实现连接10.0.0.4的5555端口转发到后端数据库…

【hot100篇-python刷题记录】【只出现一次的数字】

R5-技巧篇 思路&#xff1a;使用哈希记录次数即可 class Solution:def singleNumber(self, nums: List[int]) -> int:dictdefaultdict(int)for num in nums:dict[num]1for num in nums:if dict[num]1:return num

【hot100篇-python刷题记录】【爬楼梯】

R5-真正的动态规划 动态规划核心&#xff1a; 第i步是怎么来的&#xff08;即动态规划公式&#xff09; 走到第i步阶梯的总方法数sum(走到第i-1步阶梯的总方法数&#xff0c;走到第i-2步阶梯的总方法数) class Solution:def climbStairs(self, n: int) -> int:if n<2:r…

云原生系列 - Nginx(高级篇)

前言 学习视频&#xff1a;尚硅谷Nginx教程&#xff08;亿级流量nginx架构设计&#xff09;本内容仅用于个人学习笔记&#xff0c;如有侵扰&#xff0c;联系删学习文档&#xff1a; 云原生系列 - Nginx(基础篇)云原生系列 - Nginx(高级篇) 一、扩容 通过扩容提升整体吞吐量…

OpenCV resize 的各插值方式的区别与用途

一、resize 函数中 interpolation 参数的区别和用途 cv2.resize 函数中的 interpolation 参数用于指定图像缩放时使用的插值方法。不同的插值方法会影响缩放后图像的质量和处理速度。以下是cv2.INTER_AREA、cv2.INTER_CUBIC、cv2.INTER_NEAREST、cv2.INTER_LINEAR 和 cv2.INTE…

有关应用层面试题有关库的思维导体

面试题目&#xff1a; TCP通信中3次握手和四次挥手&#xff1f; 答&#xff1a; 第一次握手&#xff1a;客户端发送SYN包&#xff08;SYN1, seq0&#xff09;给服务器&#xff0c;并进入SYN_SENT状态&#xff0c;等待服务器返回确认包。第二次握手&#xff1a;服务器接收到S…

什么是Redis大key问题?如何解决?

目录 Key多大算大呢&#xff1f; 识别big key 处理big key Big Key是Redis中存储了大量的数据的Key&#xff0c;不要误以为big key只是表示Key的值很大&#xff0c;他还包括这个Key对应的value占用空间很多的情况&#xff0c;通常在String、list、hash、set、zset等类型中出…

三、Socket多路复用介绍

一、Socket连接方式 二、多路复用 三、Socket连接包含的内容

搭建TestBench,收藏这几条基本框架就够了

Verilog功能模块HDL设计完成后&#xff0c;并不代表设计工作的结束&#xff0c;还需要对设计进行进一步的仿真验证。掌握验证的方法&#xff0c;即如何调试自己的程序非常重要。在RTL逻辑设计中&#xff0c;要学会根据硬件逻辑来写测试程序即写Testbench。Verilog测试平台是一个…

MATLAB中qr函数用法

目录 语法 说明 示例 Q-Less QR 分解 矩阵的完整 QR 分解 置换 QR 分解 用精简 QR 因子求解线性系统 求解稀疏线性系统 求解矩形稀疏线性系统 提示 qr函数的功能是对矩阵进行QR 分解。 语法 R qr(A) [Q,R] qr(A) [Q,R,P] qr(A) [___] qr(A,"econ") […

sklearn转换器和估计器

转换器 实例化一个转换器类 调用fit_transform() 转换器调用有以下几种形式&#xff1a; fit_transform fit transform估计器&#xff08;sklearn机器学习算法的实现&#xff09; 在sklearn中&#xff0c;估计器是一个重要的角色&#xff0c;是一类实现了算法的API 1、用于…

深入理解 C# 中的 dynamic 类型详解与示例

文章目录 1. 什么是 dynamic 类型&#xff1f;2. dynamic 的工作原理3. dynamic 类型的使用4. 使用 dynamic 的场景5. dynamic 的优缺点6. dynamic 类型的注意事项7. 总结 在 C# 编程中&#xff0c;dynamic 类型是一个非常特殊的类型&#xff0c;它在编译时并不会进行类型检查&…

TCP协议中的建立连接机制

目录 客户端与服务器间的三次握手 1、关于SYN_RCVD状态 2、关于系统调用listen的第二个参数 3、为什么服务端操作系统内核中的全连接队列不能太长&#xff1f; 4、服务端操作系统内核中可以没有全连接队列&#xff1f; 客户端与服务器间的三次握手 要想成功创建连接需要客…

【Docker深入浅出】Docker镜像

文章目录 一. Docker镜像简介二. Docker镜像详解1. 镜像和容器的关系2. 镜像通常比较小3. 拉取镜像4. 镜像命名4.1. 镜像仓库服务4.2. 官方和非官方镜像仓库4.3. 镜像的命名和标签 5. 为镜像打多个标签6. 过滤镜像内容6.1. 虚空镜像6.2. 删除虚空镜像6.3. 过滤器与格式化输出 7…

【C++题解】1004 - 编程求1*2*3*...*n

欢迎关注本专栏《C从零基础到信奥赛入门级&#xff08;CSP-J&#xff09;》 问题&#xff1a;1004 - 编程求1 * 2 * 3 * … * n 类型&#xff1a;简单循环 题目描述&#xff1a; 编程求 123⋯n 。 输入&#xff1a; 输入一行&#xff0c;只有一个整数 n(1≤n≤10)&#xf…

监狱单位如何选择适合的FTP传输替代方案?

监狱单位是我国司法体系中的重要组成部分&#xff0c;监狱对于维持社会稳定也有重要作用。监狱的正常运作中&#xff0c;少不了文件的传输。由于监狱的封闭性和特殊性&#xff0c;所有传输到监狱的文件都需要经过严格的审核和登记手续&#xff0c;以确保文件的安全性和合法性。…

Linux 进程介绍

今天给伙伴们分享一下Linux 进程介绍&#xff0c;希望看了有所收获。 我是公众号「想吃西红柿」「云原生运维实战派」作者&#xff0c;对云原生运维感兴趣&#xff0c;也保持时刻学习&#xff0c;后续会分享工作中用到的运维技术&#xff0c;在运维的路上得到支持和共同进步&am…

HBase原理和操作

目录 一、HBase在Zookeeper中的存储元数据信息集群状态信息 二、HBase的操作Web Console命令行操作 三、HBase中数据的保存过程 一、HBase在Zookeeper中的存储 元数据信息 HBase的元数据信息是HBase集群运行所必需的关键数据&#xff0c;它存储在Zookeeper的"/hbase&quo…

C++学习笔记----3、设计专业的C++程序(六)---- 重用既有代码(选择库重用指导一)

当你决定使用库、框架、同事的代码、整个应用或者你自己的代码的时候&#xff0c;要记住选择正确的代码重用的几个指导原则。 1、理解功能与限制 要花时间去熟悉代码。理解其功能与限制还是很重要的。开始阅读文档与公开接口或API。理想情况下&#xff0c;有这些去理解怎么使用…

chromedriver下载地址大全(包括124.*后)以及替换exe后仍显示版本不匹配的问题

Chrome for Testing availability CNPM Binaries Mirror 若已经更新了系统环境变量里的chromdriver路径下的exe&#xff0c;仍显示版本不匹配&#xff1a; 则在cmd界面输入 chromedriver 会跳出version verison与刚刚下载好的exe不匹配&#xff0c;则再输入&#xff1a; w…