【C语言】实现通讯录(文件版本)

简介: 【C语言】实现通讯录(文件版本)

在这之前,博主已经发表过静态版本和动态版本两篇博客文章, 不太了解的小伙伴们,建议可以先看看这两篇,再来学习本篇文章叭!


静态通讯录版本_传送门


动态通讯录版本_传送门


一、为什么使用文件来存储通讯录

我们前面学习结构体时,写了通讯录的程序,当通讯录运行起来的时候,可以给通讯录中增加、删除数据,此时数据是存放在内存中,当程序退出的时候,通讯录中的数据自然就不存在了,等下次运行通讯录程序的时候,数据又得重新录入,如果使用这样的通讯录就很难受。

我们在想既然是通讯录就应该把信息记录下来,只有我们自己选择删除数据的时候,数据才不复存在。

这就涉及到了数据持久化的问题,我们一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库等方式。

使用文件我们可以将数据直接存放在电脑的硬盘上,做到了数据的持久化。

话不多说,咱们直接开讲思路。


二、代码思路

这里,我就不直接展开重新讲解通讯录的基础实现思路了,在实现动态版本的基础之上,改造代码,使数据持久化存储。即在退出程序之前,我们就保存数据到文件当中,在下次运行程序之前,读取我们上次保存到文件中的数据。


test.c文件

#include "contact.h"
void Menu()
{
  printf("********************************\n");
  printf("******   0.exit 1.add     ******\n");
  printf("******  2.delete  3.modify******\n");
  printf("******  4.search 5.show    *****\n");
  printf("******      6.sort         *****\n");
  printf("********************************\n");
}
enum Option
{
  EXIT,
  ADD,
  DELETE,
  MODIFY,
  SEARCH,
  SHOW,
  SORT
};
int main()
{
  Contact con;//创建一个名为con的结构体变量
  InitContact(&con);
  int input = 0;
  do
  {
    Menu();
    printf("请选择:>");
    scanf("%d", &input);
    switch (input)
    {
    case ADD:
      AddContact(&con);
      break;
    case DELETE:
      DeleteContact(&con);
      break;
    case MODIFY:
      ModifyContact(&con);
      break;
    case SEARCH:
      SearchContact(&con);
      break;
    case SHOW:
      ShowContact(&con);
      break;
    case SORT:
      SortContact(&con);
      break;
    case EXIT:
      DestroyContact(&con);
      printf("退出通讯录\n");
      break;
    default:
      printf("输入有误,请重新输入\n");
      break;
    }
  } while (input);
}

在动态版本通讯录里面,我们将最后的test.c文件直接拿过来。


1.保存通讯录信息

我们首先在实现文件版本时,经过了一些通讯录联系人的增删改查等操作,我们退出程序的之前,是不是需要考虑将通讯录里面的信息保存到文件当中去,那么我们直接编写一个SaveContact函数,用来将通讯录信息存储到文件之中。


test.c文件

#include "contact.h"
void Menu()
{
  printf("********************************\n");
  printf("******   0.exit 1.add     ******\n");
  printf("******  2.delete  3.modify******\n");
  printf("******  4.search 5.show    *****\n");
  printf("******      6.sort         *****\n");
  printf("********************************\n");
}
enum Option
{
  EXIT,
  ADD,
  DELETE,
  MODIFY,
  SEARCH,
  SHOW,
  SORT
};
int main()
{
  Contact con;//创建一个名为con的结构体变量
  //加载文件的信息到通讯录中
  InitContact(&con);
  int input = 0;
  do
  {
    Menu();
    printf("请选择:>");
    scanf("%d", &input);
    switch (input)
    {
    case ADD:
      AddContact(&con);
      break;
    case DELETE:
      DeleteContact(&con);
      break;
    case MODIFY:
      ModifyContact(&con);
      break;
    case SEARCH:
      SearchContact(&con);
      break;
    case SHOW:
      ShowContact(&con);
      break;
    case SORT:
      SortContact(&con);
      break;
    case EXIT:
      SaveContact(&con);
      DestroyContact(&con);
      printf("退出通讯录\n");
      break;
    default:
      printf("输入有误,请重新输入\n");
      break;
    }
  } while (input);
}

在contact.h文件中进行声明SaveContact函数,

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 15
#define MAX_ADDR 20
//#define MAX 20 
#define DEFAULT_SZ 3
#define INC_SZ 2
//创建个人信息
typedef struct PeoInfo
{
  char name[MAX_NAME];
  int age;
  char sex[MAX_SEX];
  char tele[MAX_TELE];
  char addr[MAX_ADDR];
}PeoInfo;
静态版本的通讯录
声明一个通讯录结构体
//typedef struct Contact
//{
//  PeoInfo data[MAX];
//  int sz;
//}Contact;
//动态版本的通讯录
typedef struct Contact
{
  PeoInfo* data;//data指向了存放数据的空间
  int sz;//记录通讯录中的有效信息个数
  int capacity;//记录通讯录的容量
}Contact;
//初始化结构体
void InitContact(Contact* pc);
//添加联系人
void AddContact(Contact* pc);
//显示通讯录
void ShowContact(const Contact* pc);
//删除指定联系人
void DeleteContact(Contact* pc);
//查找指定联系人
void SearchContact(const Contact* pc);
//修改指定联系人
void ModifyContact(Contact* pc);
//排序通讯录
void SortContact(Contact* pc);
//销毁通讯录
void DestroyContact(Contact* pc);
//保存通讯录
void SaveContact(Contact* pc);

在contact.c文加中,我们接着实现SaveContact函数


思路:我们按照打开文件、写数据、关闭文件三种顺序步骤写代码。写入数据之前,我们需要打开文件,使用fopen函数,第一个参数为文件的名字(文件路径+文件名+文件后缀),这里我们直接写到当前文件夹之中,即为相对路径,如果你创建的文件到桌面,此时加上绝对路径即可,第二个参数为文件的打开方式(以读的方式打开还是以写的方式打开?),这里我们以"wb"(二进制写)的形式打开。

FILE * fopen ( const char * filename, const char * mode )

我们对pf这个文件指针依旧作出判断,如果指针为NULL,进行报错。


如果指针不为NULL的情况之下,我们将pc指向的sz个有效的数据,用fwrite函数将内存中的数据写入到pf指针所关联的文件之中,


fwrite的使用规则如下:                                        

image.png

描述:C 库函数 size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream)ptr 所指向的数组中的数据写入到给定流 stream 中。

以下是函数的声明:

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

第一个参数 ptr -- 这是指向要被写入的元素数组的指针。

第二个参数 size -- 这是要被写入的每个元素的大小,以字节为单位。

第三个参数 count -- 这是元素的个数,每个元素的大小为 size 字节。

第三个参数 stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输出流

知道了fwrite函数,我们就可以用一个循环将pc->sz个有效的数据存储到文件之中,第一个参数pc->data+i,意思是以data地址开始,偏移量为i的元素指针;第二个用sizeof(PeoInfo),意思是每个元素的大小为一个PeoInfo结构体类型的大小,第三个参数为1,意思是每次写入1个元素,第三个参数为pf文件指针,即我们想要写入的文件之中。

//保存通讯录
void SaveContact(Contact* pc)
{
  //打开文件
  FILE* pf = fopen("contact.dat","wb");
  if (pf == NULL)
  {
    perror("SaveContact::fopen");
    return;
  }
  //写数据
  int i = 0;
  for (i = 0; i < pc->sz; i++)
  {
    fwrite(pc->data + i,sizeof(PeoInfo),1,pf);
  }
  //关闭文件
  fclose(pf);
  pf = NULL;
  //保存成功
  printf("保存成功...\n");
}

2.加载文件的信息到通讯录之中

思考良久,发现文件的信息是需要在初始化通讯的时候,将数据加载到通讯之中,故我们直接写一个LoadContact函数,在InitContact函数里面调用。


加载信息到通讯录之中,说明仍然需要打开文件,这次不是去写了,而是以"rb"(二进制读的方式)去打开。

然后判断FILE*的pf是否为NULL,不为NULL的情况下,然后读文件,这里我们不得不先温习一下与fwrite函数相对应的fread函数,

fread函数的使用规则之下:

image.png

描述:C 库函数 size_t fread(void *ptr, size_t size, size_t count, FILE *stream) 从给定流 stream 读取数据到 ptr 所指向的数组中。

以下是函数的声明:

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

ptr -- 这是指向带有最小尺寸 size*count 字节的内存块的指针。

size -- 这是要读取的每个元素的大小,以字节为单位。

count -- 这是元素的个数,每个元素的大小为 size 字节。

stream -- 这是指向 FILE 对象的指针,该 FILE 对象指定了一个输入流。

返回值:返回成功读取到的元素总数,size_t 是一个无符号的整型数据类型;


一样的道理,我们还是从打开文件、读文件、关闭文件三个步骤写代码,以二进制读的形式打开文件,放入到文件指针pf中,判断pf如果为NULL,则报错,接下来,读文件,思考一下,该怎么读呢?读多少个呢?这时,我们可以看一下fread函数的返回值是读取到的元素个数,没有读取到数的时候就会返回0,所以我们这里可以用一个while循环来写,将fread函数的返回值拿来判断。

然后我们写入参数,我们是不能将pf所关联的文件直接写入到pc所指向的通讯录里面的,为什么呢?因为pc->capacaity默认的容量是3,如果要放入超过3的有效数据呢,就没法放了。所以我们先将数据存放到一个tmp结构体变量里面去,然后考虑是否需要进行对通讯录进行扩容(此时就需要调用CheckContact函数),再将读取到的tmp中的数据存放到以data为起始地址,偏移量为pc->sz之中,每放进一个pc->sz++



contact.c文件

//声明一下,在LoadContact函数内部使用
void CheckContact(Contact* pc);
void LoadContact(Contact* pc)
{
  //打开文件
  FILE* pf = fopen("contact.dat", "rb");
  if (pf == NULL)
  {
    perror("LoadContact::open");
    return;
  }
  //读文件
  PeoInfo tmp = { 0 };
  while (fread(&tmp,sizeof(PeoInfo),1,pf))
  {
    CheckContact(pc);
    pc->data[pc->sz] = tmp;
    pc->sz++;
  }
  //关闭文件
  fclose(pf);
  pf = NULL;
}
//动态版本
//初始化通讯录
void InitContact(Contact* pc)
{
  PeoInfo* ptr= (PeoInfo* )malloc(DEFAULT_SZ * sizeof(PeoInfo));
  if (ptr == NULL)
  {
    printf("通讯录初始化失败::%s", strerror(errno));
    return;
  }
  else
  {
    pc->data = ptr;
    pc->sz = 0;
    pc->capacity = DEFAULT_SZ;
  }
  //加载文件的信息到通讯录中
  LoadContact(pc);
}

3.代码测试

image.png

ctrl+F5 打开输出窗口,我们发现,程序一运行,就显示“增容成功,当前容量为:5”,说明上次保存到文件中的信息共有5个有效的联系人数据,我们选择5显示一下,可以看出有5个联系人的数据,然后再次选择1的时候,程序会进行判断数量不够,继续扩容。

image.png

我们为了方便测试,直接全部给数字4,然后选择0,这里显示"保存成功"。

当我们再次打开输出窗口的时候,里面的数据是完整存放在里面的,我们关闭输出窗口,这些数据不是像之前一样,程序结束就会丢失,而是真正意义的存放在了你写入的“contact.dat”数据文件当中。

image.png

image.png

三、代码整理

ok,文件版通讯录实现就到此为止了,有需要完整源码的小伙伴自取哦~

test.c文件

#define  _CRT_SECURE_NO_WARNINGS 
#include "contact.h"
void Menu()
{
  printf("********************************\n");
  printf("******   0.exit 1.add     ******\n");
  printf("******  2.delete  3.modify******\n");
  printf("******  4.search 5.show    *****\n");
  printf("******      6.sort         *****\n");
  printf("********************************\n");
}
enum Option
{
  EXIT,
  ADD,
  DELETE,
  MODIFY,
  SEARCH,
  SHOW,
  SORT
};
int main()
{
  Contact con;//创建一个名为con的结构体变量
  //加载文件的信息到通讯录中
  InitContact(&con);
  int input = 0;
  do
  {
    Menu();
    printf("请选择:>");
    scanf("%d", &input);
    switch (input)
    {
    case ADD:
      AddContact(&con);
      break;
    case DELETE:
      DeleteContact(&con);
      break;
    case MODIFY:
      ModifyContact(&con);
      break;
    case SEARCH:
      SearchContact(&con);
      break;
    case SHOW:
      ShowContact(&con);
      break;
    case SORT:
      SortContact(&con);
      break;
    case EXIT:
      SaveContact(&con);
      DestroyContact(&con);
      printf("退出通讯录\n");
      break;
    default:
      printf("输入有误,请重新输入\n");
      break;
    }
  } while (input);
}

contact.h文件

#define  _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<errno.h>
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 15
#define MAX_ADDR 20
//#define MAX 20 
#define DEFAULT_SZ 3
#define INC_SZ 2
//创建个人信息
typedef struct PeoInfo
{
  char name[MAX_NAME];
  int age;
  char sex[MAX_SEX];
  char tele[MAX_TELE];
  char addr[MAX_ADDR];
}PeoInfo;
静态版本的通讯录
声明一个通讯录结构体
//typedef struct Contact
//{
//  PeoInfo data[MAX];
//  int sz;
//}Contact;
//动态版本的通讯录
typedef struct Contact
{
  PeoInfo* data;//data指向了存放数据的空间
  int sz;//记录通讯录中的有效信息个数
  int capacity;//记录通讯录的容量
}Contact;
//初始化结构体
void InitContact(Contact* pc);
//添加联系人
void AddContact(Contact* pc);
//显示通讯录
void ShowContact(const Contact* pc);
//删除指定联系人
void DeleteContact(Contact* pc);
//查找指定联系人
void SearchContact(const Contact* pc);
//修改指定联系人
void ModifyContact(Contact* pc);
//排序通讯录
void SortContact(Contact* pc);
//销毁通讯录
void DestroyContact(Contact* pc);
//保存通讯录
void SaveContact(Contact* pc);

contact.c文件

#define  _CRT_SECURE_NO_WARNINGS 
#include"contact.h"
//静态版本
初始化通讯录
//void InitContact(Contact* pc)
//{
//  pc->sz = 0;
//  memset(pc->data, 0, sizeof(pc->data));
//
//}
//声明一下,在LoadContact函数内部使用
void CheckContact(Contact* pc);
void LoadContact(Contact* pc)
{
  //打开文件
  FILE* pf = fopen("contact.dat", "rb");
  if (pf == NULL)
  {
    perror("LoadContact::open\n");
    return;
  }
  //读文件
  PeoInfo tmp = { 0 };
  while (fread(&tmp,sizeof(PeoInfo),1,pf))
  {
    CheckContact(pc);
    pc->data[pc->sz] = tmp;
    pc->sz++;
  }
  //关闭文件
  fclose(pf);
  pf = NULL;
}
//动态版本
//初始化通讯录
void InitContact(Contact* pc)
{
  PeoInfo* ptr= (PeoInfo* )malloc(DEFAULT_SZ * sizeof(PeoInfo));
  if (ptr == NULL)
  {
    printf("通讯录初始化失败::%s", strerror(errno));
    return;
  }
  else
  {
    pc->data = ptr;
    pc->sz = 0;
    pc->capacity = DEFAULT_SZ;
  }
  //加载文件的信息到通讯录中
  LoadContact(pc);
}
//检测通讯录是否满了
void CheckContact(Contact* pc)
{
  //如果通讯录满了 就增容
  if (pc->sz == pc->capacity)
  {
    PeoInfo* ptr= (PeoInfo*)realloc(pc->data,(pc->capacity+ INC_SZ)*sizeof(PeoInfo));
    if (ptr == NULL)
    {
      printf("CheckContact::%s", strerror(errno));
      return;
    }
    else
    {
      pc->data = ptr;
      pc->capacity += INC_SZ;
      printf("增容成功,当前容量为:%d\n", pc->capacity);
    }
  }
  //没满直接跳过以上语句
}
//添加联系人
void AddContact(Contact* pc)
{
  CheckContact(pc);
  /*if (pc->data == pc->sz)
  {
    printf("通讯录已满,无法添加联系人\n");
    return;
  }*/
  printf("请输入姓名:>");
  scanf("%s", pc->data[pc->sz].name);
  printf("请输入年龄:>");
  scanf("%d", &(pc->data[pc->sz].age));
  printf("请输入性别:>");
  scanf("%s", pc->data[pc->sz].sex);
  printf("请输入电话:>");
  scanf("%s", pc->data[pc->sz].tele);
  printf("请输入地址:>");
  scanf("%s", pc->data[pc->sz].addr);
  printf("添加成功\n");
  pc->sz++;
}
//显示通讯录
void ShowContact(const Contact* pc)
{
  printf("%-15s %-5s %-5s %-15s %-20s\n","姓名", "年龄", "性别", "电话", "地址");
  int i = 0;
  for (i = 0; i < pc->sz; i++)
  {
    printf("%-15s %-5d %-5s %-15s %-20s\n", pc->data[i].name, pc->data[i].age,
      pc->data[i].sex,pc->data[i].tele, pc->data[i].addr);
  }
}
//查找姓名=
static int Findname(Contact* pc, char name[])
{
  int i = 0;
  for (i = 0; i < pc->sz; i++)
  {
    if (0 == strcmp(pc->data[i].name, name))
    {
      return i;//找到返回i
    }
  }
  return -1;//没找到返回-1
}
//删除指定联系人
void DeleteContact(Contact* pc)
{
  if (0 == pc->sz)
  {
    printf("通讯录为空,无法删除联系人\n");
      return;
  }
  printf("请输入姓名:>");
  char name[MAX_NAME] = { 0 };
  scanf("%s",name);
  //查找姓名
  int pos = Findname(pc,name);
  //删除
  if (-1 == pos)
  {
    printf("联系人不存在\n");
    return;
  }
  int i = 0;
  for (i = pos; i < pc->sz; i++)
  {
    pc->data[pos] = pc->data[pos + 1];
  }
  printf("删除成功\n");
  pc->sz--;
}
//查找指定联系人
void SearchContact(const Contact* pc)
{
  char name[MAX_NAME] = { 0 };
  printf("请输入姓名:>");
  scanf("%s", name);
  int pos = Findname(pc, name);
  if (-1 == pos)
  {
    printf("联系人不存在\n");
    return;
  }
  printf("%-15s %-5s %-5s %-15s %-20s\n", "姓名", "年龄", "性别", "电话", "地址");
  printf("%-15s %-5d %-5s %-15s %-20s\n", pc->data[pos].name, pc->data[pos].age,
    pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr);
}
//修改指定联系人
void ModifyContact(Contact* pc)
{
  printf("请输入要修改人的姓名:>");
  char name[MAX_NAME] = { 0 };
  scanf("%s", name);
  int pos = Findname(pc, name);
  if (-1 == pos)
  {
    printf("联系人不存在\n");
    return;
  }
  printf("请输入姓名:>");
  scanf("%s", pc->data[pos].name);
  printf("请输入年龄:>");
  scanf("%d", &(pc->data[pos].age));
  printf("请输入性别:>");
  scanf("%s", pc->data[pos].sex);
  printf("请输入电话:>");
  scanf("%s", pc->data[pos].tele);
  printf("请输入地址:>");
  scanf("%s", pc->data[pos].addr);
  printf("修改成功\n");
}
//通过名字来排序
int cmp_by_name(const void* e1,const void* e2)
{
  return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}
//排序通讯录
void SortContact(Contact* pc)
{
  qsort(pc->data, pc->sz,sizeof(PeoInfo),cmp_by_name);
  printf("排序成功\n");
}
//销毁通讯录
void DestroyContact(Contact* pc)
{
  free(pc->data);
  pc->data = NULL;
  printf("释放成功...\n");
}
//保存通讯录
void SaveContact(Contact* pc)
{
  //打开文件
  FILE* pf = fopen("contact.dat","wb");
  if (pf == NULL)
  {
    perror("SaveContact::fopen");
    return;
  }
  //写数据
  int i = 0;
  for (i = 0; i < pc->sz; i++)
  {
    fwrite(pc->data + i,sizeof(PeoInfo),1,pf);
  }
  //关闭文件
  fclose(pf);
  pf = NULL;
  //保存成功
  printf("保存成功...\n");
}


目录
相关文章
|
26天前
|
存储 编译器 C语言
如何在 C 语言中判断文件缓冲区是否需要刷新?
在C语言中,可以通过检查文件流的内部状态或使用`fflush`函数尝试刷新缓冲区来判断文件缓冲区是否需要刷新。通常,当缓冲区满、遇到换行符或显式调用`fflush`时,缓冲区会自动刷新。
|
26天前
|
存储 编译器 C语言
C语言:文件缓冲区刷新方式有几种
C语言中文件缓冲区的刷新方式主要包括三种:自动刷新(如遇到换行符或缓冲区满)、显式调用 fflush() 函数强制刷新、以及关闭文件时自动刷新。这些方法确保数据及时写入文件。
|
1月前
|
存储 C语言
探索C语言数据结构:利用顺序表完成通讯录的实现
本文介绍了如何使用C语言中的顺序表数据结构实现一个简单的通讯录,包括初始化、添加、删除、查找和保存联系人信息的操作,以及自定义结构体用于存储联系人详细信息。
19 2
|
1月前
|
C语言
【C语言】探索文件读写函数的全貌(三)
【C语言】探索文件读写函数的全貌
|
1月前
|
存储 C语言
【C语言】探索文件读写函数的全貌(二)
【C语言】探索文件读写函数的全貌
|
1月前
|
存储 C语言
手把手教你用C语言实现通讯录管理系统
手把手教你用C语言实现通讯录管理系统
|
1月前
|
C语言
【C语言】探索文件读写函数的全貌(一)
【C语言】探索文件读写函数的全貌
|
1月前
|
存储 文件存储 C语言
【C语言】深入了解文件:简明指南
【C语言】深入了解文件:简明指南
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
34 3
|
7天前
|
C语言
c语言调用的函数的声明
被调用的函数的声明: 一个函数调用另一个函数需具备的条件: 首先被调用的函数必须是已经存在的函数,即头文件中存在或已经定义过; 如果使用库函数,一般应该在本文件开头用#include命令将调用有关库函数时在所需要用到的信息“包含”到本文件中。.h文件是头文件所用的后缀。 如果使用用户自己定义的函数,而且该函数与使用它的函数在同一个文件中,一般还应该在主调函数中对被调用的函数做声明。 如果被调用的函数定义出现在主调函数之前可以不必声明。 如果已在所有函数定义之前,在函数的外部已做了函数声明,则在各个主调函数中不必多所调用的函数在做声明
23 6