用C语言实现一个简单通讯录功能:

  • 命令端输入对应指令可执行相应操作
  • 可以实现增、删、查、改、排序、显示、一键清除等功能
  • 每次输入的信息保存到txt文件中
  • 每次打开通讯录自动加载已有的txt文件中的信息

本文主要是针对指针、动态内存管理、以及文件读写操作的综合练习。

目录

声明结构体

构建程序框架

函数创建

InitCon初始化函数

LoadCon加载信息函数

AddCapacity扩容函数

ADD增加信息函数

DEL,CHECK,MODIFY删查改函数

SHOW显示信息函数

DEL ALL一键删除,SaveCon保存通讯录,Distory释放内存:


声明结构体

//通讯录结构体
typedef struct Date
{char name[20];char sex[5];int age;char tel[20];char address[30];
}Date;typedef struct Con
{Date* p;int count;int capacity;
}Con;

其中:

  • 结构体Date是用来存储学生信息的
  • 结构体Con嵌套了一个Date结构体
  • Con中count用来计数,记录当前目录存储学生的个数
  • Con中capacity用来记录当前结构体内存大小(内存不够时申请扩容)

构建程序框架

声明完结构体后,先构建程序框架

//Con.h
#include<stdio.h>
//main.c
void menu()
{printf("******************************************\n");printf("********  1.ADD          2.DEL      ******\n");printf("********  3.SHOW         4.MODIFY   ******\n");printf("********  5.CHECK        6.DEL ALL  ******\n");printf("********  7.SORT         0.EXIT     ******\n");printf("******************************************\n");}
int main()
{int input = 0;Con arr;do{menu();scanf("%d", &input);} while (input);return 0;
}

其中:

  • 创建一个菜单栏
  • 创建一个结构体变量arr
  • do while循环至少执行一次,当intput=0时循环结束

完善框架:

只针对于上述要求,我们需要创建以下函数:

InitCon:初始化通讯录,开辟内存

LoadCon:加载txt中的信息到内存中

ADD,DEL,CHECK,MODIFY:对应增、删、查、改

SHOW:显示全部信息

SORT:对某一项进行排序

DEL ALL:一键清除所有信息

EXIT:退出并保存

Distory:释放内存

完善后的主函数如下: 

int main()
{int input = 0;Con arr;InitCon(&arr);LoadCon(&arr);void (*pf[])(Con*) = {0,add,del,show,modify,check,del_all,sort};do{menu();scanf("%d", &input);if (input == 0){SaveCon(&arr);Distory(&arr);printf("退出!\n");}else if (input > 0 && input < 8){pf[input](&arr);}elseprintf("输入错误,请重新输入!\n");} while (input);return 0;
}

其中:

  • void (*pf[])(Con*)为函数指针数组,其数组中存储的类型void (*)(Con*)的函数指针,关于函数指针以及函数指针数组的知识,在回调函数中对其进行了详细讲解。
  • 通过调用函数指针数组的下标,从而实现调用程序的不同功能。
  • 当input=0时,需要先将内存中的信息保存到txt文件中,然后再释放内存

函数创建

为了日后的维护与管理,所有函数均存入到Con.c中,与主函数分开。

InitCon初始化函数

在程序开始时开辟指定大小的内存,在动态规划中有malloc和calloc两种方式开辟内存,这里使用calloc的形式进行开辟。

//Con.h
#include<assert.h>
#include<string.h>
#include<errno.h>#define INIT_NUM 3//Con.c
void InitCon(Con* pc)
{assert(pc);pc->p = (Date*)calloc(INIT_NUM, sizeof(Date));if (pc->p == NULL){printf("InitCon::%s", strerror(errno));return;}pc->count = 0;pc->capacity = INIT_NUM;
}

其中:

  • 传入参数为结构体指针
  • 需要判断结构体指针不能为空,assert需要包含头文件<assert.h>
  • calloc使用方法:void* calloc (size_t num, size_t size);num为指定个数,size为指定大小,返回值为void*类型的指针
  • 开辟完内存后,最好再对其进行检查,是否为空。
  • strerror(arrno)为打印错误信息,strerror需要包含头文件<string.h>,arrno需要包含头文件<errno.h>
  • 开辟内存时,同时初始化count=0,capcity等于calloc开辟的个数

LoadCon加载信息函数

这里用到了文件指针,对文件进行操作,大致思想为:

  • 如果文件中没有信息则退出
  • 如果文件中存有信息,按行读取
  • 每读取一行,对应的count需要+1
  • 如果读取的过程中,初始开辟的内存不够了,需要对内存进行加载
void LoadCon(Con* pc)
{assert(pc);FILE* pfopen = fopen("con.txt", "r");if (pfopen == NULL){perror("LoadCon");return;}Date tmp = { 0 };while (fread(&tmp, sizeof(Date), 1, pfopen)==1){AddCapacity(pc);pc->p[pc->count] = tmp;pc->count++;}printf("加载成功!\n");fclose(pfopen);pfopen = NULL;
}

其中:

  • FIEL*表示为文件指针,fopen为打开文件操作,‘r’表示以只读的方式打开
  • fread为二进制读取数据(前提是文件中的数据是以二进制存储的)
  • fread的使用方法:size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
  • ptr为读取输出地址,size为一次读取大小,count为读取个数,stream为文件指针,当fread读取成功时,会返回读取个数
  • AddCapacity为自定义函数,检查内存是否够用,不足时扩容
  • 当读取完文件后,记得关闭文件

AddCapacity扩容函数

扩容函数需要完成两个任务:

1、检查内存是否足够

2、内存不足时进行扩容,并将capacity中存储的信息更新

void AddCapacity(Con* pc)
{assert(pc);if (pc->count == pc->capacity){Date* str = (Date*)realloc(pc->p, sizeof(Date)*(pc->capacity+ ADD_NUM));if (pc->p == NULL){printf("AddCapacity::%s", strerror(errno));return;}pc->p = str;pc->capacity += ADD_NUM;printf("增容成功\n");}
}

其中:

  • 当count中的个数等于capacity容量的个数时,便需要进行扩容
  • realloc函数的用法如下:void* realloc (void* ptr, size_t size);ptr为需要扩容的地址,size为扩容后新的空间总大小

ADD增加信息函数

ADD函数比较简单,只需要在每次增加前对其内存进行检查,然后格式化输入信息即可,每次增加一个联系人的信息后,其所对应的count计数也需要进行+1。

void add(Con* pc)
{assert(pc);AddCapacity(pc);printf("请输入名字:>\n");scanf("%s", pc->p[pc->count].name);printf("请输入性别:>\n");scanf("%s", pc->p[pc->count].sex);printf("请输入年龄:>\n");scanf("%d", &(pc->p[pc->count].age));printf("请输入电话:>\n");scanf("%s", pc->p[pc->count].tel);printf("请输入地址:>\n");scanf("%s", pc->p[pc->count].address);printf("输入成功\n");pc->count++;
}

DEL,CHECK,MODIFY删查改函数

以上三个函数需要用到查找函数,先假设所有人的名字不重复,根据人的姓名进行查找,返回其所属的count值。

int FindByName(const Con* pc)
{assert(pc);int i = 0;char input[20] = {0};printf("请输入名字\n");scanf("%s", input);for (i = 0; i <pc->count; i++){if (strcmp(input, pc->p[i].name) == 0){return i;}}return -1;
}

其中:

  • 传参时const保护结构体指针pc,不能通过解引用的方式来更改内容
  • 比较两个字符串用strcmp,需要包含头文件<string,h>,使用方法如下:int strcmp ( const char * str1, const char * str2 );str1和str2为相比较的两个字符串,返回值分为>0,<0 =0,相等时返回0

当查找函数完成后,剩余的删查改函数就简单许多,需要注意的是:删除函数需要更改count,并且使所有人信息往前挪一位。

删除函数

//删除
void del(Con* pc)
{assert(pc);if (pc->count == 0){printf("联系人为空\n");return;}int i = FindByName(pc);if (i != -1){int j = 0;for (j = i; j < pc->count; j++){pc->p[j] = pc->p[j + 1];}pc->count--;printf("删除成功!\n");}elseprintf("没有找到!\n");
}

查找并显示函数

void check(const Con* pc)
{assert(pc);int i = FindByName(pc);if (i != -1 ){printf("%-20s%-10s%-10s%-20s%-30s\n", "姓名", "性别", "年龄", "电话", "地址");printf("%-20s%-10s%-10d%-20s%-30s\n",pc->p[i].name,pc->p[i].sex,pc->p[i].age,pc->p[i].tel,pc->p[i].address);}elseprintf("没有找到!\n");
}

修改函数 

void modify(Con* pc)
{assert(pc);if (pc->count == 0){printf("联系人为空\n");return;}int i = FindByName(pc);if (i != -1){printf("请输入名字:>\n");scanf("%s", pc->p[i].name);printf("请输入性别:>\n");scanf("%s", pc->p[i].sex);printf("请输入年龄:>\n");scanf("%d", &(pc->p[i].age));printf("请输入电话:>\n");scanf("%s", pc->p[i].tel);printf("请输入地址:>\n");scanf("%s", pc->p[i].address);printf("修改成功!\n");}elseprintf("没有找到!\n");
}

SHOW显示信息函数

从第一行开始循环打印,以联系人数量count为限制,打印时为了保持美观,最好设计一个表头。

void show(const Con* pc)
{assert(pc);if (pc->count == 0){printf("联系人为空\n");return;}printf("%-20s%-10s%-10s%-20s%-30s\n", "name", "sex", "age", "tel", "address");int i = 0;for (i = 0; i < pc->count; i++){printf("%-20s%-10s%-10d%-20s%-30s\n", pc->p[i].name, pc->p[i].sex, pc->p[i].age,pc->p[i].tel, pc->p[i].address);}
}

SORT排序函数

使用qsort函数进行排序。

int cmp_by_name(const void* e1, const void* e2)
{return strcmp(((Date*)e1)->name, ((Date*)e2)->name);
}void sort(Con* pc)
{assert(pc);qsort(pc->p, pc->count, sizeof(Date), cmp_by_name);printf("排序成功!\n");
}

其中:

sqort函数使用方法如下:

  • qosrt函数需要包含头文件<stdlib.h>
  • void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));
  • base为排序的对象,num为排序的个数,size为单个排序类型的大小,最后需要给qsort传入一个排序原则的函数,其返回值需要是个整数,根据返回值>0,<0,=0的情况进行排序。
  • qsort适合所有类型的排序,所以传入给qsort的函数,其接收参数的形式必须是void*类型的。
  • cmp_by_name是传入sqort的比大小函数

DEL ALL一键删除,SaveCon保存通讯录,Distory释放内存

DEL ALL一键删除

将通讯录重新初始化,就可以完成一键删除的功能了。

void del_all(Con* pc)
{assert(pc);InitCon(pc);printf("全部删除成功!\n");
}

SaveCon保存通讯录

将内存中的数据以二进制的方式写入到txt文本中。

void SaveCon(Con* pc)
{assert(pc);FILE* pfwrite = fopen("con.txt", "w");if (pfwrite == NULL){perror("SaveCon");return ;}int i = 0;for (i = 0; i < pc->count; i++){fwrite(pc->p + i, sizeof(Date), 1, pfwrite);}printf("保存成功!\n");fclose(pfwrite);pfwrite = NULL;
}

其中:

  • 先创建一个文件指针,以只写的方式打开文件
  • 根据count的计数,按行依次将数据读写到txt文件中
  • fwrite为二进制读写,fwrite使用方法如下:
  • size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
  • ptr为起始的指针位置,size为读取类型的大小,count为读取的个数,stream为文件指针

以二进制的读写打开txt文件后,会是一堆乱码,也可以格式化输出到txt文件中。代码如下:

//格式化输出
fprintf(pfwrite, "%10s%10s%10d%10s%10s\n",
pc->p[i].name, pc->p[i].sex, pc->p[i].age, pc->p[i].tel, pc->p[i].address);

如果要使用格式化输出,在LoadCon加载函数中,信息载入的方式也需要进行修改。

Distory销毁函数

void Distory(Con* pc)
{assert(pc);free(pc->p);pc->p = NULL;
}

直接释放内存,并将指针置空。

在写程序时,主要思想就是:先想好大致框架,每个函数的作用、参数以及返回值,框架构建好后依次完善函数即可。在写程序中,要养成步步调试,写注释的好习惯。

C语言动态规划和文件操作练习——通讯录相关推荐

  1. 【C 语言】文件操作 ( C 语言中的文件操作函数 | 磁盘与内存缓冲区 | 缓冲区工作机制 )

    文章目录 一.C 语言中的文件操作函数 二.磁盘与内存缓冲区 三.缓冲区工作机制 一.C 语言中的文件操作函数 读取 文本文件 可以使用 getc , fgets , fscanf 函数 , 向 文本 ...

  2. Unix/Linux环境C编程入门教程(41) C语言库函数的文件操作详解

     上一篇博客我们讲解了如何使用Linux提供的文件操作函数,本文主要讲解使用C语言提供的文件操作的库函数. 1.函数介绍 fopen(打开文件) 相关函数 open,fclose 表头文件 #in ...

  3. 【C语言复习】C语言中的文件操作

    C语言中的文件操作 写在前面 文件操作 什么是文件 文件的分类 文件名 文件的操作 文件指针 文件的打开和关闭 文件的顺序读写 文件的随机读写 fseek ftell rewind feof 写在前面 ...

  4. C语言详解文件操作(一):文件操作基础概念、按照字符、按照行块、按照格式化和随机位置读写文件

    文章目录 一.文件操作基础概念 二.文件读写:按照字符方式读写 三.文件读写:按行和块读写 四.文件读写:格式化和随机位置 一.文件操作基础概念 C语言中的文件操作的好处:       一个文件通常是 ...

  5. C语言字符串、文件操作常用操作函数,

    C语言字符串常用操作函数 C语言字符串.文件操作常用操作函数 字符串输出 - puts() 字符串输入 - gets() 字符串比较 - strcmp() 字符串复制 - strcpy() 字符串连接 ...

  6. C/C++语言入门篇 -- 文件操作

    最近实在是太忙了,这篇整整就推迟了1个月了,实在是对不起.之前本打算这个模块就结束了,文件操作就不写了,但是文件操作又是一个很重要的东西,而且也刚好能够总结之前我们学习的所有知识.同时也为了将文件操作 ...

  7. C语言目录和文件操作扩展

    文章目录 一.access库函数 二.stat库函数 1.stat结构体 2.stat库函数 三.utime库函数 四.rename库函数 五.remove库函数 六.课后作业 七.获取视频教程 八. ...

  8. SCL语言中的文件操作如何进行?

    在SCL语言中,可以使用内置的文件操作函数来进行文件读写操作.下面是SCL语言中一些常用的文件操作函数: 1. `FOPEN`:打开文件. 2. `FCLOSE`:关闭文件. 3. `FREAD`:从 ...

  9. C语言中的文件操作那些事儿~~

    是时候该学着操作文件了 什么是文件 文件种类 文件名 为什么要使用文件 文件的打开和关闭 文件的顺序读写 fgetc/fputc fgets/gputs fscanf/fprintf fread/fw ...

最新文章

  1. 转载:浅谈软件测试流程
  2. GAN生成对抗网络-CGAN原理与基本实现-条件生成对抗网络04
  3. 循环结构作业c语言,C语言循环结构练习题带答案(最新整理)
  4. java判断斐波那契数列_Java 实例 - 斐波那契数列
  5. vant组件做表格_落户上海!分享心得,再做个积分计算器
  6. python求解典型相关系数_三大相关系数: pearson, spearman, kendall(python示例实现)...
  7. 第二季-专题11-世界一下变大了-MMU
  8. VS2010开发的winform程序在XP系统打不开的原因(与ico图标像素有关)
  9. JVM成神之路(一)---咱们为什么要学习JVM
  10. visual studio工程文件介绍
  11. 1230k倍区间,关于cnt[0]赋值为1的解释
  12. Python 详细教程 —— pyautogui 库 基本用法
  13. 怎么开启windows hypervisor platform,解决hypervisor platform消失无法安装的问题
  14. 互联网时代颠覆的传统行业
  15. 工作-iview的Tooltip+render函数
  16. 翻译翻译什么TMD叫EXPLAIN
  17. 什么样的程序员才能算是一个合格的程序员呢?
  18. code::blocks下载与安装
  19. django迁移数据库乱了,彻底重新再来的步骤
  20. 嵌入式学习——串口通信小试

热门文章

  1. 羽毛球之混双战术要点
  2. 关于利用Klayout查看GDS需要导入工艺库的layer properties file(.lyp)
  3. 工程流体力学笔记暂记43 (收缩喷管中的流动)
  4. 符号熵(附matlab代码)
  5. MAC创建ipv6热点
  6. 腾讯云+CentOS 7.2+python:搭建微信公众号后台入门教程
  7. 伍鸣博士受邀出席徐汇区住房租赁市场研讨会
  8. Lagrange Multiplier Theorem——候选人定理
  9. C++字符串大小写转换
  10. vscode 怎么设置背景图片