C语言-综合案例:通讯录
传送门:C语言-第九章-加餐:文件位置指示器与二进制读写
目录
第一节:思路整理
第二节:代码编写
2-1.通讯录初始化
2-2.功能选择
2-3.增加 和 扩容
2-4.查看
2-5.查找
2-6.删除
2-7.修改
2-8.退出
第三节:测试
下期预告:
第一节:思路整理
创建三个文件,main.c用来测试程序,Function.c用来存放函数定义,Address.h用来存放函数声明、全局变量和各种宏。
通讯录的需求是能够长久的存储信息,这就需要用到文件操作,每次运行通讯录程序时从文件读取数据,每次关闭程序时就把数据重新写入到文件中。
存储数据的容器:
可以定义一个结构体类型 PeopleInfo,每有一个人就用这个类型存储它的所有信息;再定义一个结构体类型 AddressBook,它用来存储所有 PeopleInfo,并且还有通讯录的容量和已存储数量,便于管理。
Address.h
struct PeopleInfo // 一个人的信息
{char _name[20]; // 名字int _age; // 年龄char _number[20]; // 号码
};
struct AddressBook // 通讯录
{struct PeopleInfo* _PeoPle; // 所有人的信息int _capacity; // 通讯录的容量int _size; // 通讯录当前存储的数量
}addressBook;
其他问题我们在写代码的过程中一一呈现。
第二节:代码编写
我们把通讯录的各种功能一一实现:
2-1.通讯录初始化
通讯录初始化包括设定通讯录的初始容量、从文件获取信息:
Address.h
#define DEL_CAPACITY 10 // 默认初始容量
Function.c
void AddressInit()
{FILE* pf = fopen(".//AddressBook.txt","rb"); // 二进制读方式打开if (pf == NULL){perror("文件打开失败\n");return;}addressBook._capacity = DEL_CAPACITY;addressBook._PeoPle = (struct PeopleInfo*)malloc(addressBook._capacity*sizeof(struct PeopleInfo)); // 开辟空间if (addressBook._PeoPle == NULL)return;addressBook._size = 0;// 从文件中读取数据while (0 != fread(addressBook._PeoPle + addressBook._size, sizeof(struct PeopleInfo), 1, pf)){addressBook._size++;if (addressBook._size == addressBook._capacity)Expansion(); // 扩容函数,之后实现}fclose(pf);
}
2-2.功能选择
通讯录初始化完成后,就编写代码实现对各种功能的选择:
Function.c
void printMenu()
{printf("***********************************************\n");printf("***************1.增加*****2.删除***************\n");printf("***************3.查找*****4.修改***************\n");printf("***************5.查看*****0.退出***************\n");printf("***********************************************\n");
}void Select()
{printMenu(); // 打印菜单int select;while (1){printf("请选择:>");scanf("%d", &select);switch (select){case 1:ADD(); // 增加break;case 2:DEL(); // 删除break;case 3:Find();// 查找break;case 4:MOD(); // 修改 break;case 5:DIS(); // 查看break;case 0:EXIT();// 退出return 0;default:printf("非法的请求,请重新输入:>");}}
}
switch 中的函数在之后一一实现。
2-3.增加 和 扩容
Function.c
void ADD()
{struct PeopleInfo person;printf("请输入姓名:>");scanf("%s", person._name);printf("请输入年龄:>");scanf("%d", &person._age);printf("请输入号码:>");scanf("%s", person._number);// 把个人信息添加到通讯录memmove(addressBook._PeoPle+addressBook._size,&person,sizeof(person));addressBook._size++;printf("添加成功!\n"); // 提示信息if (addressBook._size == addressBook._capacity)Expansion(); // 扩容
}
如果满了就扩容:
Address.h
#define EXP_CAPACITY 5 // 每次扩容的大小
Function.c
void Expansion()
{// 使用realloc保留原来的内容struct PeoPle* tmp = (struct PeoPle*)realloc(addressBook._PeoPle, addressBook._capacity + EXP_CAPACITY);if (tmp != NULL)addressBook._PeoPle = tmp;elseprintf("扩容失败!\n");// 更新容量addressBook._capacity = addressBook._capacity + EXP_CAPACITY;printf("扩容成功!当前容量:%d\n",addressBook._capacity); // 提示信息
}
2-4.查看
查看功能可以查看已经添加的联系人的所有信息:
Function.c
void DIS()
{printf("姓名 年龄 号码\n");for (int i = 0; i < addressBook._size; i++){printf("%s %d %s\n",addressBook._PeoPle[i]._name, addressBook._PeoPle[i]._age, addressBook._PeoPle[i]._number);}
}
2-5.查找
查找是根据名字找到指向他信息的下标,找不到返回-1:
Function.c
int Find(const char* name)
{for (int i = 0; i < addressBook._size; i++){if (strcmp(addressBook._PeoPle[i]._name, name) == 0)return i;}printf("该联系人不存在!\n");return -1;
}
Select 函数中的case 3的部分做如下修改:
case 3:printf("请输入要查找的联系人:>");char name[20]; scanf("%s",name);int i = Find(name);// 查找// 打印他的信息printf("姓名 年龄 号码\n");printf("%s %d %s\n", addressBook._PeoPle[i]._name, addressBook._PeoPle[i]._age, addressBook._PeoPle[i]._number);break;
2-6.删除
删除是根据名字删除指定的联系人,所以可以复用查找函数:
Function.c
void DEL()
{printf("请输入要删除的联系人:>");char name[20];scanf("%s",name);int i = Find(name);if (i >= 0){// i 位置之后先前覆盖一位,就可以删除它了for (; i < addressBook._size - 1; i++){addressBook._PeoPle[i] = addressBook._PeoPle[i + 1];}addressBook._size--;printf("删除成功!\n");}
}
2-7.修改
修改也是先根据名字找到相应下标,然后再修改,所以也要复用查找函数:
Fuction.c
void MOD()
{printf("请输入要修改的联系人:>");char name[20];scanf("%s", name);int i = Find(name);if (i >= 0){printf("请修改姓名:>");scanf("%s", addressBook._PeoPle[i]._name);printf("请修改年龄:>");scanf("%d", &(addressBook._PeoPle[i]._age));printf("请修改号码:>");scanf("%s", addressBook._PeoPle->_number);printf("修改成功!\n");}
}
2-8.退出
程序退出时需要把数据存储到文件中,然后把堆区开辟的空间释放掉:
Function.c
void EXIT()
{// 存储数据FILE* pf = fopen(".//AddressBook.txt","wb"); // 二进制写方式打开if (pf == NULL){perror("文件打开失败\n");return;}for (int i = 0; i < addressBook._size; i++){fwrite(addressBook._PeoPle+i,sizeof(struct PeopleInfo),1 , pf);}fclose(pf);// 释放堆区空间free(addressBook._PeoPle);
}
至此代码已经编写完毕了,可以测试程序了。
第三节:测试
先添加3个人的信息,最后输入0退出消息:
打开文件看里面是否有内容:
因为是二进制读写,所以看不懂;
再次运行程序,看看文件中的信息能不能正确读取:
最后测试删除、修改、查看就可以了,这里不再掩饰。
每个文件的完整代码如下:
main.c
#include "Address.h"
int main()
{AddressInit(); // 初始化Select(); // 打印菜单并选择return 0;
}
Address.h
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define DEL_CAPACITY 10 // 默认初始容量
#define EXP_CAPACITY 5 // 每次扩容的大小
struct PeopleInfo // 一个人的信息
{char _name[20]; // 名字int _age; // 年龄char _number[20]; // 号码
};
struct AddressBook // 通讯录
{struct PeopleInfo* _PeoPle; // 所有人的信息int _capacity; // 通讯录的容量int _size; // 通讯录当前存储的数量
}addressBook;void printMenu(); // 打印菜单
void Select(); // 打印菜单并选择
void AddressInit(); // 初始化通讯录
void Expansion(); // 扩容
void ADD(); // 增加
void DIS(); // 查看
int Find(const char* name); // 查找
void DEL(); // 删除
void MOD(); // 修改
void EXIT(); // 保存+释放
Function.c
#include "Address.h"
void AddressInit()
{FILE* pf = fopen(".//AddressBook.txt","rb"); // 二进制读方式打开if (pf == NULL){perror("文件打开失败\n");return;}addressBook._capacity = DEL_CAPACITY;addressBook._PeoPle = (struct PeopleInfo*)malloc(addressBook._capacity*sizeof(struct PeopleInfo)); // 开辟空间if (addressBook._PeoPle == NULL)return;addressBook._size = 0;// 从文件中读取数据while (0 != fread(addressBook._PeoPle + addressBook._size, sizeof(struct PeopleInfo), 1, pf)){addressBook._size++;if (addressBook._size == addressBook._capacity)Expansion(); // 扩容函数,之后实现}fclose(pf);
}void printMenu()
{printf("***********************************************\n");printf("***************1.增加*****2.删除***************\n");printf("***************3.查找*****4.修改***************\n");printf("***************5.查看*****0.退出***************\n");printf("***********************************************\n");
}void Select()
{printMenu(); // 打印菜单int select;while (1){printf("请选择:>");scanf("%d", &select);switch (select){case 1:ADD(); // 增加break;case 2:DEL(); // 删除break;case 3:printf("请输入要查找的联系人:>");char name[20]; scanf("%s",name);int i = Find(name);// 查找// 打印他的信息printf("姓名 年龄 号码\n");printf("%s %d %s\n", addressBook._PeoPle[i]._name, addressBook._PeoPle[i]._age, addressBook._PeoPle[i]._number);break;case 4:MOD(); // 修改 break;case 5:DIS(); // 查看break;case 0:EXIT();// 退出return 0;default:printf("非法的请求,请重新输入:>");}}
}void ADD()
{struct PeopleInfo person;printf("请输入姓名:>");scanf("%s", person._name);printf("请输入年龄:>");scanf("%d", &person._age);printf("请输入号码:>");scanf("%s", person._number);// 把个人信息添加到通讯录memmove(addressBook._PeoPle+addressBook._size,&person,sizeof(person));addressBook._size++;printf("添加成功!\n"); // 提示信息if (addressBook._size == addressBook._capacity)Expansion(); // 扩容
}void Expansion()
{// 使用realloc保留原来的内容struct PeopleInfo* tmp = (struct PeopleInfo*)realloc(addressBook._PeoPle, addressBook._capacity + EXP_CAPACITY);if (tmp != NULL)addressBook._PeoPle = tmp;elseprintf("扩容失败!\n");// 更新容量addressBook._capacity = addressBook._capacity + EXP_CAPACITY;printf("扩容成功!当前容量:%d\n",addressBook._capacity); // 提示信息
}void DIS()
{printf("姓名 年龄 号码\n");for (int i = 0; i < addressBook._size; i++){printf("%s %d %s\n",addressBook._PeoPle[i]._name, addressBook._PeoPle[i]._age, addressBook._PeoPle[i]._number);}
}int Find(const char* name)
{for (int i = 0; i < addressBook._size; i++){if (strcmp(addressBook._PeoPle[i]._name, name) == 0)return i;}printf("该联系人不存在!\n");return -1;
}void DEL()
{printf("请输入要删除的联系人:>");char name[20];scanf("%s",name);int i = Find(name);if (i >= 0){// i 位置之后先前覆盖一位,就可以删除它了for (; i < addressBook._size - 1; i++){addressBook._PeoPle[i] = addressBook._PeoPle[i + 1];}addressBook._size--;printf("删除成功!\n");}
}void MOD()
{printf("请输入要修改的联系人:>");char name[20];scanf("%s", name);int i = Find(name);if (i >= 0){printf("请修改姓名:>");scanf("%s", addressBook._PeoPle[i]._name);printf("请修改年龄:>");scanf("%d", &(addressBook._PeoPle[i]._age));printf("请修改号码:>");scanf("%s", addressBook._PeoPle->_number);printf("修改成功!\n");}
}void EXIT()
{// 存储数据FILE* pf = fopen(".//AddressBook.txt","wb"); // 二进制写方式打开if (pf == NULL){perror("文件打开失败\n");return;}for (int i = 0; i < addressBook._size; i++){fwrite(addressBook._PeoPle+i,sizeof(struct PeopleInfo),1 , pf);}fclose(pf);// 释放堆区空间free(addressBook._PeoPle);
}
下期预告:
下一次是第十章,将学习预编译相关知识
相关文章:

C语言-综合案例:通讯录
传送门:C语言-第九章-加餐:文件位置指示器与二进制读写 目录 第一节:思路整理 第二节:代码编写 2-1.通讯录初始化 2-2.功能选择 2-3.增加 和 扩容 2-4.查看 2-5.查找 2-6.删除 2-7.修改 2-8.退出 第三节:测试 下期…...
XWiki中添加 html 二次编辑失效
如果直接在 XWiki 中添加 html, 例如 修改颜色, 新窗口打开主页面等功能, 首次保存是生效的. 如果再次编辑, 则失效, 原因是被转换成了 Markdown 的代码, 而 Markdown 不支持. 解决这个问题可以使用 HTML 宏. 在 XWiki 中使用 Markdown 1.2 语法时,默认 Markdown …...

外贸|基于Java+vue的智慧外贸平台系统(源码+数据库+文档)
外贸|智慧外贸平台|外贸服务系统 目录 基于Javavue的智慧外贸平台系统 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂码农|毕设布道师&…...

Elasticsearch:无状态世界中的数据安全
作者:来自 Elastic Henning Andersen 在最近的博客文章中,我们宣布了支持 Elastic Cloud Serverless 产品的无状态架构。通过将持久性保证和复制卸载到对象存储(例如 Amazon S3),我们获得了许多优势和简化。 从历史上…...

动手学习RAG:迟交互模型colbert微调实践 bge-m3
动手学习RAG: 向量模型动手学习RAG: BGE向量模型微调实践]()动手学习RAG: BCEmbedding 向量模型 微调实践]()BCE ranking 微调实践]()GTE向量与排序模型 微调实践]()模型微调中的模型序列长度]()相似度与温度系数 本文我们来进行ColBERT模型的实践,按惯例ÿ…...

springboot 整合quartz定时任务
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、pom的配置1.加注解 二、使用方法1.工程图2.创建工具类 三、controller 实现 前言 提示:这里可以添加本文要记录的大概内容: 提示&a…...

erlang学习: Mnesia Erlang数据库3
Mnesia数据库删除实现和事务处理 -module(test_mnesia). -include_lib("stdlib/include/qlc.hrl").-record(shop, {item, quantity, cost}). %% API -export([insert/3, select/0, select/1, delete/1, transaction/1,start/0, do_this_once/0]). start() ->mnes…...

善于善行——贵金属回收
在当今社会,贵金属回收已成为一项日益重要的产业。随 着科技的不断进步和人们对资源可持续利用的认识逐渐提高,贵金属回收的现状也备受关注。 目前,贵金属回收市场呈现出蓬勃发展的态势。一方面,贵金属如金、银、铂、钯等在众多领…...

用CSS 方式设置 table 样式
在现代Web开发中,使用CSS来设置table的样式是一种常见且强大的方法,它能让你的表格数据既美观又易于阅读。下面我将通过一个示例来展示如何使用现代CSS技巧来美化表格。 效果图 HTML 结构 首先,我们定义一个基本的HTML表格结构:…...
Elasticsearch7.x 集群迁移文档
一、集群样例信息 集群名称:escluster-ali-test 1、源集群:(source_cluster) 节点IP节点名称节点角色是否为master节点10.200.112.149es2.gj1.china-job.cndata,master是10.200.112.151es1.gj1.china-job.cndata,master否10.200.112.153es…...

高空抛物检测算法的应用场景解析
高空抛物事件频发,对公众安全构成严重威胁。无论是居民区还是商业中心,从高层建筑中丢弃物品都可能导致人员伤亡和财产损失。传统的监控手段多以事后追溯为主,无法在事发时及时预警和干预。为应对这一难题,视觉分析技术的发展为高…...
Leetcode 无重复字符的最长子串
算法思想: 滑动窗口:通过 start 和 end 来维护一个滑动窗口,start 指向当前窗口的起点,end 是当前窗口的末尾。滑动窗口中的字符都是无重复的。哈希表 charIndexMap:用于存储每个字符及其最近一次出现的位置。更新起始…...

用命令行的方式启动.netcore webapi
用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为: dotnet 项目启动文件.dll --urls"ht…...

Spring6详细学习笔记(IOC+AOP)
一、Spring系统架构介绍 1.1、定义 Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)。Spring官网 Spring是一款主流的Java EE 轻量级开源框架,目的是用于简化Java企业级引用的开发难度和开发周期。从简单性、可测试性和松耦…...
@RequestMapping 基于哪个库进行通信
RequestMapping 是 Spring Framework 中用于处理 HTTP 请求的注解,主要用于定义控制器方法的请求映射。它并不直接基于某个特定的通信库,而是依赖于 Spring MVC 框架的核心功能。 1. Spring MVC RequestMapping 是 Spring MVC 的一部分,Spr…...

GPIO(General Purpose Input/Output)输入/输出
GPIO最简单的功能是输出高低电平;GPIO还可以被设置为输入功能,用于读取按键等输入信号;也可以将GPIO复用成芯片上的其他外设的控制引脚。 STM32F407ZGT6有8组IO。分别为GPIOA~GPIOH,除了GPIOH只有两个IO,其余每组IO有…...

两个pdf合并成一个pdf,这些pdf合并小技巧了解下
在日常工作和学习中,我们经常会遇到需要将多个PDF文件合并成一个文件的情况。这不仅可以提高文件管理的效率,还能让信息展示更加集中和便捷。今天就来给大家分享几种非常简单便捷的PDF合并小技巧,一起来学习下吧。 方法一:WPS WP…...

Transformer学习(2):自注意力机制
回顾 注意力机制 自注意力机制 自注意力机制中同样包含QKV,但它们是同源(Q≈K≈V),也就是来自相同的输入数据X,X可以分为 ( x 1 , x 2 , . . , x n ) (x_1,x_2,..,x_n) (x1,x2,..,xn)。 而通过输入嵌入层(input embedding),…...

分类预测|基于粒子群优化径向基神经网络的数据分类预测Matlab程序PSO-RBF 多特征输入多类别输出 含基础RBF程序
分类预测|基于粒子群优化径向基神经网络的数据分类预测Matlab程序PSO-RBF 多特征输入多类别输出 含基础RBF程序 文章目录 一、基本原理1. 粒子群优化算法(PSO)2. 径向基神经网络(RBF)PSO-RBF模型流程总结 二、实验结果三、核心代码…...
【React】Vite 构建 React
项目搭建 vite 官网:Vite 跟着文档走即可,选择 react ,然后 ts swc。 着重说一下 package-lock.json 这个文件有两个作用: 锁版本号(保证项目在不同人手里安装的依赖都是相同的,解决版本冲突的问题&am…...

XML Group端口详解
在XML数据映射过程中,经常需要对数据进行分组聚合操作。例如,当处理包含多个物料明细的XML文件时,可能需要将相同物料号的明细归为一组,或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码,增加了开…...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...
C++ 基础特性深度解析
目录 引言 一、命名空间(namespace) C 中的命名空间 与 C 语言的对比 二、缺省参数 C 中的缺省参数 与 C 语言的对比 三、引用(reference) C 中的引用 与 C 语言的对比 四、inline(内联函数…...

uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现录音机应用
1. 项目配置与权限设置 1.1 配置module.json5 {"module": {"requestPermissions": [{"name": "ohos.permission.MICROPHONE","reason": "录音需要麦克风权限"},{"name": "ohos.permission.WRITE…...

ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...

云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...