用c语言实现通讯录
目录
静态简易通讯录
代码:
功能模块展示:
设计思路:
动态简易通讯录(本质顺序表)
代码:
扩容模块展示:
设计思路:
文件版本通讯录
代码:
文件模块展示:
设计思路:
静态简易通讯录
代码:
//头文件内容#pragma once#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>//声明一个枚举类型增加主程序代码(switch部分)可读性
enum method
{EXIT,ADD,DELETE,FIND,MODIFY,SHOW,DESTROY,SORT
};//定义标识符常量便于对数据大小修改
#define MAX_NAME 20
#define MAX_SEX 6
#define MAX_TELEPHONE 12
#define MAX_ADDRESS 20
#define Max_PEOINFO 1000//定义一个联系人信息结构体类型
typedef struct PeoInfo
{char name[MAX_NAME];char sex[MAX_SEX];int age;char Telephone[MAX_TELEPHONE];char address[MAX_ADDRESS];
}PeoInfo;//创建一个联系人信息结构体数组和计数联系人个数的变量组成联系人结构体
typedef struct Contact
{PeoInfo data[Max_PEOINFO];int sz;
}Contact;//初始化通讯录
void InitContact(Contact* pc);//增加联系人函数
void AddContact(Contact* pc);//展示所有联系人函数
void ShowContact(Contact* pc);//删除联系人函数
void DeleteContact(Contact* pc);//查找联系人函数
void FindContact(Contact* pc);//修改联系人函数
void ModifyContact(Contact* pc);//清空联系人函数
void DestroyContact(Contact* pc);//对联系人排序函数
void SortContact(Contact* pc);//函数实现源文件内容//初始化函数
void InitContact(Contact* pc)
{assert(pc);//利用库函数将整个数组所有元素初始化为0memset(pc->data, 0, sizeof(pc->data));//通讯录没有联系人pc->sz = 0;
}//增加联系人函数
void AddContact(Contact* pc)
{assert(pc);//通讯录已满情况if (pc->sz == Max_PEOINFO){printf("通讯录已满\n");return;}//通讯录未满情况printf("请输入联系人的姓名:>");scanf("%s", pc->data[pc->sz].name);printf("请输入联系人的性别:>");scanf("%s", pc->data[pc->sz].sex);printf("请输入联系人的年龄:>");scanf("%d", &(pc->data[pc->sz].age));printf("请输入联系人的电话:>");scanf("%s", pc->data[pc->sz].Telephone);printf("请输入联系人的地址:>");scanf("%s", pc->data[pc->sz].address);//每增加一个联系人,计数变量加1pc->sz = pc->sz + 1;printf("增加联系人成功\n");
}//为了便于局部调试程序,先实现显示函数
//显示所有联系人
void ShowContact(Contact* pc)
{assert(pc);int i = 0;//打印内容左对齐printf("%-20s\t%-6s\t%-4s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "电话", "地址");for (i = 0; i < pc->sz; i++){printf("%-20s\t%-6s\t%-4d\t%-12s\t%-20s\n",pc->data[i].name,pc->data[i].sex,pc->data[i].age,pc->data[i].Telephone,pc->data[i].address);}
}//删除联系人时我们需要知道联系人在数组中的位置(以名字删除联系人)
//名字检索函数(不需要在头文件中声明)
//此函数无法在有同名联系人时精确找到联系人
int ModifyName(char* str, Contact* pc)
{assert(pc && str);int i = 0;//遍历已经存储了联系人的数组元素寻找第一个名字相符的联系人并返回下标for (i = 0; i < pc->sz; i++){if (0 == strcmp(str, pc->data[i].name))return i;}//找不到return -1;
}//删除指定联系人
//此函数无法在有同名联系人时精确删除联系人
void DeleteContact(Contact* pc)
{assert(pc);//以名字查找指定联系人char name[MAX_NAME] = { 0 };printf("请输入要删除的联系人的名字\n");scanf("%s", name);int num = ModifyName(name, pc);//找到了if (num != -1){int i = 0;for (i = num; i < pc->sz; i++){memcpy(pc->data + i, pc->data + (i + 1), sizeof(PeoInfo));}//删除后计数变量减1pc->sz = pc->sz - 1;printf("删除成功\n");}//没找到elseprintf("删除失败,找不到指定联系人\n");
}//查找指定联系人
//此函数无法在有同名联系人时精确查找联系人
void FindContact(Contact* pc)
{assert(pc);//以名字查找指定联系人char name[MAX_NAME] = { 0 };printf("请输入要查找的联系人的名字\n");scanf("%s", name);//调用上面定义的名字检索函数int num = ModifyName(name, pc);//找到了打印此联系人信息if (num != -1){printf("%-20s\t%-6s\t%-4s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "电话", "地址");printf("%-20s\t%-6s\t%-4d\t%-12s\t%-20s\n",pc->data[num].name,pc->data[num].sex,pc->data[num].age,pc->data[num].Telephone,pc->data[num].address);}//找不到elseprintf("找不到指定联系人\n");
}//修改指定联系人
void ModifyContact(Contact* pc)
{assert(pc);//以名字查找指定联系人char name[MAX_NAME] = { 0 };printf("请输入要修改的联系人的名字\n");scanf("%s", name);//调用名字检索函数int num = ModifyName(name, pc);//找到后重新输入此联系人信息if (num != -1){printf("请输入新的姓名\n");scanf("%s", pc->data[num].name);printf("请输入新的性别\n");scanf("%s", pc->data[num].sex);printf("请输入新的年龄\n");scanf("%d", &(pc->data[num].age));printf("请输入新的电话\n");scanf("%s", pc->data[num].Telephone);printf("请输入新的地址\n");scanf("%s", pc->data[num].address);printf("修改联系人成功\n");}//找不到elseprintf("找不到指定联系人\n");
}//清空所有联系人
void DestroyContact(Contact* pc)
{assert(pc);//调用初始化函数将联系人结构体初始化InitContact(pc);printf("联系人已清空\n");
}//拓展功能
//以名字对所有联系人升序排序
void SortContact(Contact* pc)
{assert(pc);int i = 0;//选择排序for (i = 0; i < pc->sz - 1; i++){int j = i;for (j = i; j < pc->sz; j++){if (strcmp(pc->data[i].name, pc->data[j].name) > 0){PeoInfo tmp;memcpy(&tmp, pc->data[i].name, sizeof(PeoInfo));memcpy(pc->data[i].name, pc->data[j].name, sizeof(PeoInfo));memcpy(pc->data[j].name, &tmp, sizeof(PeoInfo));}}}printf("排序完成\n");
}//主程序源文件内容//菜单
void menu()
{printf("****** 1.ADD 2.DELETE ********\n");printf("****** 3.FIND 4.MODIFY ********\n");printf("****** 5.SHOW 6.DESTROY********\n");printf("****** 7.SORT 0.EXIT ********\n");
}void test()
{//定义一个通讯录结构体变量Contact con;//初始化InitContact(&con);enum method input = 0;do{menu();printf("请选择你的操作\n");scanf("%d", &input);switch (input){case ADD:AddContact(&con);break;case DELETE:DeleteContact(&con);break;case FIND:FindContact(&con);break;case MODIFY:ModifyContact(&con);break;case SHOW:ShowContact(&con);break;case DESTROY:DestroyContact(&con);break;case SORT:SortContact(&con);break;case EXIT:printf("程序已退出\n");break;default:printf("输入错误,请重新输入:>\n");break;}} while (input);
}
int main()
{test();return 0;
}
功能模块展示:
增加功能:

删除功能:

查找功能:

修改功能:

拓展功能:

设计思路:
要实现一个简易版静态通讯录,无非是在实现增删查改的基础上进行进一步的功能拓展,因此设计的重点是在增删查改的功能实现上,静态版本存储通讯录内容的是定长数组,因此要实现这些功能,我们需要围绕数组下标和已经存储的联系人个数作为文章。
只要知道已经存储的联系人个数,增加联系人就会变得轻而易举。而删除,查找,修改联系人时都必须先找到要操作的联系人,因此我们需要先实现一个检索联系人接口供它们调用,此接口实现的越精确,删查改就会越精确。
为了方便对数组下标进行操作,将计数联系人个数的变量和记录联系人信息的数组都作为通讯录结构体的成员,计数变量从0开始,每增加一个联系人就加1,删除减1,这样我们就会得到通讯录的实时大小从而可以非常容易的操作通讯录,实现通讯录的功能也就变的非常简单。
动态简易通讯录(本质顺序表)
代码:
动态版本的代码只需在静态版本上稍作修改即可。
//头文件增加和修改的内容//初始容量
#define INIT_SZ 3
//每次扩容增加容量
#define ADD_SZ 2//修改后通讯录结构体
typedef struct Contact
{//PeoInfo data[Max_PEOINFO];//用结构体指针代替结构体数组PeoInfo* data;int sz;//增加一个记录通讯录容量的变量int capacity;
}Contact;//函数实现源文件增加和修改的内容//动态版本初始化函数
void InitContact(Contact* pc)
{assert(pc);//先开辟INIT_SZ个空间pc->data = (PeoInfo*)malloc(INIT_SZ * sizeof(PeoInfo));//开辟失败if (pc->data == NULL){perror("InitContact");return;}pc->sz = 0;//记录容量pc->capacity = INIT_SZ;
}//增加联系人时需要先检查容量是否已满,若满则增加容量
//空间动态增长(无需在头文件中声明)
int mallocContact(Contact* pc)
{assert(pc);//已满if (pc->sz == pc->capacity){//扩容PeoInfo* ptr = (PeoInfo*)realloc(pc->data, (pc->capacity + ADD_SZ) * sizeof(PeoInfo));//扩容失败if (ptr == NULL){perror("mallocContact");return 0;}//扩容成功else{pc->data = ptr;pc->capacity += ADD_SZ;printf("扩容成功\n");return 1;}}//未满return 1;
}//动态版本增加函数
void AddContact(Contact* pc)
{assert(pc);//扩容失败if (0 == mallocContact(pc)){return;}//未满或扩容成功printf("请输入联系人的姓名\n");scanf("%s", pc->data[pc->sz].name);printf("请输入联系人的性别\n");scanf("%s", pc->data[pc->sz].sex);printf("请输入联系人的年龄\n");scanf("%d", &(pc->data[pc->sz].age));printf("请输入联系人的电话\n");scanf("%s", pc->data[pc->sz].Telephone);printf("请输入联系人的地址\n");scanf("%s", pc->data[pc->sz].address);pc->sz = pc->sz + 1;printf("增加联系人成功\n");}//动态版本清空函数
void DestroyContact(Contact* pc)
{assert(pc);free(pc->data);pc->data = NULL;pc->sz = 0;pc->capacity = 0;printf("联系人已清空\n");
}
扩容模块展示:

设计思路:
静态版本的通讯录十分死板,需要存储的数据少了就会存在空间浪费,多了又会不够,因此衍生出动态版本来解决这一问题,当存储空间不够时就向内存申请新的空间来增加容量,更加灵活。
为了实现实时向内存申请空间,需要一个变量用来记录通讯录结构体的容量,当存储的联系人个数等于容量时就需要进行扩容,同时由于通讯录的大小是动态变化的,因此用结构体指针替代结构体数组来维护存储空间。
由于每次扩容后的存储空间依然是连续的,因此在没有发生访问越界时可以像操作数组一样操作指针,静态版本的大多数代码都无需修改。
文件版本通讯录
代码:
在动态版本上稍作修改。
//函数实现源文件增加和修改的内容//容量检查函数声明
int mallocContact(Contact* pc);//初始化时需要先读取文件contact.dat的内容到data中去
void LoadContact(Contact* pc)
{//打开文件FILE* pf = fopen("contact.dat", "rb");//若路径下没有此文件,则打开文件失败if (pf == NULL){perror("LoadContact");return;}PeoInfo tmp = { 0 };//读文件while (fread(&tmp, sizeof(PeoInfo), 1, pf)){//扩容判断if (0 == mallocContact(pc))return;pc->data[pc->sz] = tmp;//每读取一个计数变量加1pc->sz++;}//关闭文件fclose(pf);pf = NULL;
}//动态版本初始化函数
void InitContact(Contact* pc)
{assert(pc);pc->data = (PeoInfo*)malloc(INIT_SZ * sizeof(PeoInfo));if (pc->data == NULL){perror("InitContact");return;}pc->sz = 0;pc->capacity = INIT_SZ;//载入文件内容LoadContact(pc);
}//将通讯录信息以二进制形式保存到contact.dat文件中
void SaveContact(Contact* pc)
{//打开文件(若路径下没有此文件,则新建一个此文件)FILE* pf = fopen("contact.dat", "wb");if (pf == NULL){perror("SaveContact");return;}int i = 0;for (i = 0; i < pc->sz; i++){//写入文件fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);}//关闭文件fclose(pf);pf == NULL;printf("通讯录内容已保存到contact.dat文件中\n");
}//主程序源文件修改内容case EXIT://在退出程序时将内存中的信息保存到文件中去SaveContact(&con);printf("程序已退出\n");break;
文件模块展示:






设计思路:
无论是静态版本还是动态版本,联系人信息都是存储在内存中的,一旦程序退出运行,这些信息也会随之消失,为了长久存储这些信息,衍生出了文件版本的通讯录,即利用文件操作函数将存储在内存中的信息存储到文件中长久保存。
我们只需要在动态版本的基础上增加一个读取文件函数和写入文件函数,在程序开始时先将文件中的信息读取到内存中,在程序退出时再将内存中的数据写回文件中即可。
相关文章:
用c语言实现通讯录
目录 静态简易通讯录 代码: 功能模块展示: 设计思路: 动态简易通讯录(本质顺序表) 代码: 扩容模块展示: 设计思路: 文件版本通讯录 代码: 文件模块展示&#x…...
AI大模型技术揭秘-参数,Token,上下文和温度
深入理解 AI 大模型:参数、Token、上下文窗口、上下文长度和温度 人工智能技术的飞速发展使AI大模型大放异彩,其中涉及的“参数”、“Token”、“上下文窗口”、“上下文长度”及“温度”等专业术语备受瞩目。这些术语背后究竟蕴含何意?它们如何影响AI大模型的性能?一起揭开…...
攻防世界-fakebook题目__详解
1.打开题目先用dirsearch工具扫描一波,扫出来了robots.php目录,然后访问robots.txt 目录,发现了有一个备份文件 ,访问备份文件,下载内容 文件的大致内容如下 里面有一个curl_exec这个函数容易造成ssrf攻击的漏洞 我…...
Ubuntu 18.04下普通用户的一次提权过程
Ubuntu 18.04下普通用户的一次提权过程 一.背景介绍:二.主要调试过程:三.相关命令:1.设置BMC密码,获取BMC IP2.找一台ubuntu搭建TFTP服务,用来替换grub.cfg文件3.从调试服务器的/boot/grub/grub.cfg中提取出recovery mode的配置,简化并生成新的配置文件grub.cfg,放在tftp服务的…...
接口和抽象类:如何使用普通类模拟接口和抽象类
目录 1.引言 2.抽象类和接口的定义与区别 3.抽象类和接口存在的意义 4.模拟实现抽象类和接口 5.抽象类和接口的应用场景 1.引言 在面向对象编程中,抽象类和接口是两个经常被提及的语法概念,也是面向对象编程的四大特性,以及很多设计模式…...
【文档智能】实践:基于Yolo三行代码极简的训练一个版式分析模型
一、数据集 本文以开源的CDLA数据集做为实验,CDLA是一个中文文档版面分析数据集,面向中文文献类(论文)场景。包含以下10个label: 数据集下载地址:https://github.com/buptlihang/CDLA 数据集是labelme格式…...
聚观早报 | 深蓝G318价格发布;比亚迪方程豹豹3官图发布
聚观早报每日整理最值得关注的行业重点事件,帮助大家及时了解最新行业动态,每日读报,就读聚观365资讯简报。 整理丨Cutie 6月15日消息 深蓝G318价格发布 比亚迪方程豹豹3官图发布 夸克App升级高考AI搜索 iOS 18卫星通信实测 Redmi K70…...
如何实现内网穿透?快解析-免费内网穿透工具
在现如今的ipv4时代,随着上网电脑及其他智能设备越来越多,公网IP地址出现了枯竭的情况。近几年,内网穿透这个词被不断提及,这也是在无公网IP环境下实现异地访问的一种可行办法,下面我就给大家介绍一下内网穿透的原理。…...
【python-AI篇】人工智能技能树思维导图
大致总结一下得出如下思维导图,如不完善日后迭代更新 1. python基础三方库 1.1 科学计算库 ---- numpy库 1.2 科学计算库 ---- Scipy库 1.3 数据分析处理库 ---- pandas库 1.4 可视化库 ---- matplotlib库 1.5 可视化库 ---- seaborn库 1.6 机器学习和数据挖掘库 …...
Vue的computed大致细节
computed computed具体实现流程computer的执行顺序 computed 具体实现流程 computer内部首先是标准化参数然后调用runner函数进行依赖收集设置dirty为true创建副作用函数,具体如下 const runner effect(getter,{//延迟执行lazy:true,//标记为computed effect 用…...
第5章:模型预测控制(MPC)的代码实现
1. 建立 QP 模型: 1.1 车辆模型: 注:使用车辆横向动力学模型 纵向动力学模型(误差模型) 1.2 QP 问题模型: 注:详细推导见 笔记100:使用 OSQP-Eigen 对 MPC 进行求解的方法与代码-…...
论文学习day01
1.自我反思的检索增强生成(SELF-RAG) 1.文章出处: Chan, C., Xu, C., Yuan, R., Luo, H., Xue, W., Guo, Y., & Fu, J. (2024). RQ-RAG: Learning to Refine Queries for Retrieval Augmented Generation. ArXiv, abs/2404.00610. 2.摘…...
Github入门教程,适合新手学习(非常详细)
前言:本篇博客为手把手教学的 Github 代码管理教程,属于新手入门级别的难度。教程简单易操作,能够基本满足读者朋友日常项目寄托于 Github 平台上进行代码管理的需求。Git 与 Github 是一名合格程序员 coder 必定会接触到的工具与平台&#x…...
C# OpenCvSharp 代数运算-add、scaleAdd、addWeighted、subtract、absdiff、multiply、divide
在C#中使用OpenCvSharp进行图像处理时,理解和合理使用各种图像操作函数可以帮助我们实现许多实际应用中的需求。下面,我将详细介绍每个函数的使用,并给出与实际应用项目相关的示例,包括运算过程和运算结果。 1. add 函数 作用 将两幅图像进行相加,可以达到图像融合的目的…...
为什么说Python 是胶水语言?
"Python 是胶水语言"这一说法是指它很擅长将不同的程序或代码库连接在一起,能够让来自不同编程语言或框架的组件无缝协作。Python 具有丰富的库和简单的语法,使得它可以轻松调用其他语言编写的程序或使用不同技术栈的模块。 以下是几个…...
GitLab教程(二):快速上手Git
文章目录 1.将远端代码克隆到本地2.修改本地代码并提交到远程仓库3.Git命令总结git clonegit statusgit addgit commitgit pushgit log 首先,我在Gitlab上创建了一个远程仓库,用于演示使用Gitlab进行版本管理的完整流程: 1.将远端代码克隆到本…...
结构体知识点
基本概念 结构体是一种自定义变量类型,类似于枚举需要自己定义。 它是数据和函数的集合。 在结构体中,可以声明各种变量和方法。 基本语法 1.结构体一般写在namespace语句块中。 2.结构体关键字struct struct 自定义结构体名 {//第一部分//变量//…...
C# —— 显示转换
显示转换: 通过一些方法可以将其他数据类型转换为我们想要的数据类型 1.括号强转 作用: 一般情况下 将高精度的类型转换为低精度 // 语法: 变量类型 变量名 (转换的变量类型名称) 变量; // 注意: 精度问题 范围问题 sbyte sb 1; short s 1; int …...
zip加密txt文件后,暴力破解时会有多个解密密码可以打开的疑问??
最近在做一个关于zip压缩文件解密的测试,发现通过暴力解密时,会有多个解密密码可以打开,非常疑惑,这里做个问题,希望能有大佬解惑。 1、首先在本地创建一个113449.txt的文件,然后右键txt文件选择压缩&…...
css入门宝典
3.1.4 通配符选择器 语法 : *{} 作用 : 让页面中所有的标签执行该样式,通常用来清除间距 例子 : *{ margin: 0; //外间距 padding: 0; //内间距 } 一 CSS基本语法 1基础知识 1.1概述 Css (层叠样式表)是种格式化网页的标准方式, 用于控制设置网页的样式ÿ…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...
