【C语言】通讯录(文件) -- 详解

news2024/11/18 3:42:21

⚪前言

前面介绍了【C语言】静态通讯录 -- 详解_炫酷的伊莉娜的博客-CSDN博客【C语言】动态通讯录 -- 详解_炫酷的伊莉娜的博客-CSDN博客。当通讯录运行起来时,可以对通讯录中的数据进行增加、删除或修改等。此时数据是存放在内存中,当程序退出时,通讯录中的数据自然就不存在了,等下次再运行通讯录程序时,数据又需要重新录入。如果使用这样的通讯录就难以达到我们日常所需的要求,既然是通讯录就应该把信息记录下来,只有在我们自己选择删除数据时,数据才不复存在,这就涉及到了数据持久化的问题。一般数据持久化的方法有,把数据存放在磁盘文件、存放到数据库中等方式。使用文件我们可以将数据直接存放在电脑的硬盘上,做到数据的持久化。 


一、实现目标

1、功能

  • 保存 1000 个联系人的信息
  • 添加联系人
  • 删除联系人
  • 修改联系人
  • 查找联系人
  • 排序

2、个人信息(结构体)

  • 名字
  • 年龄
  • 性别
  • 电话
  • 地址

文件版通讯录的要求: 

在原有通讯录(动态版)程序上进行功能的增扩,实现通讯录信息的记录保存(即通讯录(文件版))。 在之前的通讯录上增加两个函数,实现数据可以保存到文件,也可以从文件中加载数据。

  1. 添加一个函数,在退出通讯录的时候把信息到保存到文件中。
  2. 添加一个函数,在通讯录打开的时候,可以把文件中的信息加载到通讯录中。

二、创建文件

  1. test.c(专门测试通讯录的功能)
  2. contact.c(接口的实现)
  3. contact.h(接口的声明)

三、初始页面的实现

1、实现主菜单页面

// test.c

(1)menu()
void menu()
{
	printf("****************************************\n");
	printf("****** 1.add           2.del    ********\n");
	printf("****** 3.search        4.modify ********\n");
	printf("****** 5.show          6.sort   ********\n");
	printf("****** 7.save          0.exit   ********\n");
	printf("****************************************\n");
}

效果图如下:


(2)main()
int main()
{
	int input = 0;
	// 创建通讯录
	struct Contact con;// con就是通讯录,里边包含:data指针和size,capacity
	// 初始化通讯录
	InitContact(&con);

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EXIT:
			SaveContact(&con);
			DestoryContact(&con);// 销毁通讯录-释放动态开辟的内存
			printf("退出通讯录\n");
			break;
		case SAVE:
			SaveContact(&con);
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

增加了一个 “保存” 的文件功能,并在 “退出” 功能后,添加调用 “保存” 功能。


2、在contact.h 中引用所需要的文件和必要的定义

头文件的增添

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
枚举与 switch 语句的联合: 使用枚举 enum 与 switch 语句进行连接,增加代码的可读性。为菜单书写功能函数时,不必再来回看每个数具体对应哪一个函数。
enum Option
{
	EXIT,//0
	ADD,//1
	DEL,//2
	SEARCH,//3
	MODIFY,//4
	SHOW,//5
	SORT,//6
	SAVE//7
};

【整体逻辑】

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once // 避免头文件被重复引用

// #define MAX 1000

#define DEFAULT_SZ 3 // 通讯录初始状态的容量大小

#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>

enum Option
{
	EXIT,//0
	ADD,//1
	DEL,//2
	SEARCH,//3
	MODIFY,//4
	SHOW,//5
	SORT,//6
	SAVE//7
};

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
{
	struct PeoInfo* data; // 存放信息
	int size; // 记录当前已经有的元素个数
	int capacity; // 当前通讯录的最大容量
}Contact;

// 声明函数
// 初始化通讯录
void InitContact(struct Contact* ps);
// 添加联系人
void AddContact(struct Contact* ps);
// 展示通讯录信息
void ShowContact(const struct Contact* ps);
// 删除指定联系人信息
void DelContact(struct Contact* ps);
// 查找指定联系人信息
void SearchContact(const struct Contact* ps);
// 修改指定联系人信息
void ModifyContact(struct Contact* ps);
// 排序通讯录内容(按姓名排)
void SortContact(struct Contact* ps);
// 销毁通讯录
void DestoryContact(Contact* ps);
// 保存文件
void SaveContact(const Contact* ps);
// 加载文件中的信息到通讯录
void LoadContact(Contact* ps);

四、在 contact.c 上实现各个接口函数

1、初始化通讯录

malloc 函数在这里并没有把其数据初始化为 0 ,这里可以使用 memset 函数初始化存放联系人信息的结构体数组 data。

对于动态通讯录的初始化,就是申请一块初始的空间,用来存放信息,如果满了在在此基础上进行扩容。

// 检测当前通讯录的容量
void CheckCapacity(struct Contact* ps)
{
	if (ps->size == ps->capacity)
	{
		// 增容
		struct PeoInfo* ptr = realloc(ps->data, (ps->capacity + 2) * sizeof(PeoInfo));
		if (ptr != NULL)
		{
			ps->data = ptr;
			ps->capacity += 2;
			printf("增容成功!\n");
		}
		else
		{
			printf("增容失败!\n");
		}
	}
}

// 把文件中已经存放的通讯录中的信息加载到通讯录中
void LoadContact(Contact* ps)
{
	PeoInfo tmp = { 0 };
	FILE* pfRead = fopen("contact.dat", "rb");
	if (pfRead == NULL)
	{
		printf("LoadContact::%s\n", strerror(errno));
		return;
	}

	// 读取文件,存放到通讯录
	while (fread(&tmp, sizeof(PeoInfo), 1, pfRead))
	{
		CheckCapacity(ps);
		ps->data[ps->size] = tmp;
		ps->size++;
	}
	fclose(pfRead);
	pfRead = NULL;
}

// 初始化通讯录
void InitContact(struct Contact* ps)
{
	assert(ps);
	ps->size = 0;
	ps->capacity = DEFAULT_SZ;
	ps->data = (struct PeoInfo*)malloc(DEFAULT_SZ * sizeof(struct PeoInfo));
	if (ps->data == NULL)
	{
		perror("InitContact::malloc");
		return;
	}
	memset(ps->data, 0, ps->capacity * sizeof(struct PeoInfo));
	LoadContact(ps); // 把文件中已经存放的通讯录中的信息加载到通讯录中
}

2、添加联系人

先判断通讯录人数是否已满,满了需要扩容,对已经申请过的空间进行扩容,可以使用库函数 realloc 实现。满了一次扩容 2 联系人信息的空间。

// 添加联系人
void AddContact(struct Contact* ps)
{
	assert(ps);

	// 检测当前通讯录的容量
	// 1、如果满了就增加空间
	// 2、如果没满无需任何操作
	CheckCapacity(ps);
	// 增加数据
	printf("请输入名字:>");
	scanf("%s", ps->data[ps->size].name);
	printf("请输入年龄:>");
	scanf("%d", &(ps->data[ps->size].age)); // 年龄是一个整型变量,可以取地址
	printf("请输入性别:>");
	scanf("%s", ps->data[ps->size].sex);
	printf("请输入电话:>");
	scanf("%s", ps->data[ps->size].tele);
	printf("请输入地址:>");
	scanf("%s", ps->data[ps->size].addr);

	ps->size++;
	printf("添加成功!\n");
}

注意由于 age 在这里是一个整型变量,所以要加上 &。 


3、展示通讯录中的信息

// 展示通讯录中的信息
void ShowContact(const struct Contact* ps)
{
	if (ps->size == 0)
	{
		printf("通讯录为空!\n");
	}
	else
	{
		int i = 0;
		// 打印标题
		printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
		// 打印数据
		for (i = 0; i < ps->size; i++)
		{
			printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",
				ps->data[i].name,
				ps->data[i].age,
				ps->data[i].sex,
				ps->data[i].tele,
				ps->data[i].addr);
		}
	}
}

注意:因为只是打印信息,不会修改内容,所以加上 const 更加安全。 

为了让结果更美观,采用了左对齐的方式。printf 中 %-20s 中的 20 是指输出字段的宽度负号表示左对齐,如省略表示右对齐,如果输出的数据位数小于 20,则在数据右端补齐空格。


4、通过姓名查找指定联系人所在的下标

// 通过姓名查找指定联系人所在下标
static int FindByName(const struct Contact* ps, char name[MAX_NAME])
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (0 == strcmp(ps->data[i].name, name))
		{
			return i; // 查找到返回联系人的下标
		}
	}

	return -1; // 找不到返回-1
}

为了消除代码冗余,剥离了公共函数,定义成 static 函数,在本文件内部调用。


5、删除指定联系人信息

先判断通讯录是否为空,如果不为空,再通过 FindByName() 函数查找通讯录中是否有你要删除的联系人,如果有则删除。

原理:把要删除的联系人后面一个人的信息依次从前向后往前移动一位,覆盖掉其信息,然后再将人数 -1。

// 删除指定联系人信息
void DelContact(struct Contact* ps)
{
	char name[MAX_NAME];
	int pos = 0;
	printf("请输入要删除人的名字:>");
	scanf("%s", name);
	// 1、查找要删除的人在什么位置
	// 找到了返回名字所在元素的下标
	// 找不到返回 -1
	pos = FindByName(ps, name);

	// 2、删除
	if (pos == -1)
	{
		printf("要删除的人不存在!\n");
	}
	else
	{
		//删除数据
		int j = 0;
		for (j = pos; j < ps->size - 1; j++)
		{
			// 把要删除的人后面一个人的信息依次从前向后往前移动一位,覆盖掉其信息
			ps->data[j] = ps->data[j + 1];
		}
		ps->size--;
		printf("删除成功!\n");
	}
}

6、查找指定联系人信息

通过 FindByName() 函数查找通讯录中是否有你要查找的联系人,如果查找到,则打印该联系人的信息。 

// 查找指定联系人信息
void SearchContact(const struct Contact* ps)
{
	int pos = 0;
	char name[MAX_NAME];
	printf("请输入要查找人的名字:>");
	scanf("%s", name);
	pos = FindByName(ps, name); // 查找
	if (pos == -1)
	{
		printf("要查找的人不存在!\n");
	}
	else
	{
		printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
		// 打印数据
		printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",
			ps->data[pos].name,
			ps->data[pos].age,
			ps->data[pos].sex,
			ps->data[pos].tele,
			ps->data[pos].addr);
	}
}

 注意:因为只是查找信息,不会修改内容,所以加上 const 更加安全 。


7、修改指定联系人信息

通过 FindByName() 函数查找通讯录中是否有你要修改信息的联系人,如果有则修改。

// 修改指定联系人信息
void ModifyContact(struct Contact* ps)
{
	int pos = 0;
	char name[MAX_NAME];
	printf("请输入要修改人的名字:>");
	scanf("%s", name);
	pos = FindByName(ps, name);
	if (pos == -1)
	{
		printf("要修改人的信息不存在!\n");
	}
	else
	{
		printf("请输入名字:>");
		scanf("%s", ps->data[pos].name);
		printf("请输入年龄:>");
		scanf("%d", &ps->data[pos].age);
		printf("请输入性别:>");
		scanf("%s", ps->data[pos].sex);
		printf("请输入电话:>");
		scanf("%s", ps->data[pos].tele);
		printf("请输入地址:>");
		scanf("%s", ps->data[pos].addr);

		printf("修改完成\n");
	}
}

8、排序通讯录内容 

 

// 排序通讯录内容
// 比较结构体数组中两个元素的姓名成员
int compareStructType_name(const void* elem1, const void* elem2)
{
	// 姓名成员是字符串,用strcmp比较
	return strcmp(((struct PeoInfo*)elem1)->name, ((struct PeoInfo*)elem2)->name);
}

// 以名字排序所有联系人
void SortContact(struct Contact* ps)
{
	// 判断通讯录是否为空
	if (ps->size == 0)
	{
		printf("通讯录为空,无法排序!\n");
		return;
	}
	else
	{
		// 根据姓名成员对结构体数组升序排列
		qsort(ps->data, ps->size, sizeof(ps->data[0]), compareStructType_name);
		printf("以名字排序联系人成功!\n");
	}
}

该函数排序用的是姓名作为标准,也可以自己更换其他的排序方式。


9、销毁通讯录

使用动态内存开辟的空间是需要归还的,当通讯录使用完后是需要归还内存的,也就需要我们销毁通讯录。

// 销毁通讯录
void DestoryContact(Contact* ps)
{
	free(ps->data);
	ps->data = NULL;
	ps->capacity = 0;
	ps->size = 0;
	printf("销毁成功!\n");
}

10、保存文件

// 保存文件
void SaveContact(const Contact* ps)
{
	FILE* pfWrite = fopen("contact.dat", "wb");
	if (pfWrite == NULL)
	{
		printf("SaveContact::%s\n", strerror(errno));
		return;
	}

	// 写通讯录中数据到文件中
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		fwrite(&(ps->data[i]), sizeof(PeoInfo), 1, pfWrite);
	}
	fclose(pfWrite);
	pfWrite = NULL;
}

在这里只是写入文件信息,不会改变通讯录信息,所以这里加上 const。 


五、代码整合

1、test.c

// test.c
#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"

void menu()
{
	printf("****************************************\n");
	printf("****** 1.add           2.del    ********\n");
	printf("****** 3.search        4.modify ********\n");
	printf("****** 5.show          6.sort   ********\n");
	printf("****** 7.save          0.exit   ********\n");
	printf("****************************************\n");
}

int main()
{
	int input = 0;
	// 创建通讯录
	struct Contact con;// con就是通讯录,里边包含:data指针和size,capacity
	// 初始化通讯录
	InitContact(&con);

	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			AddContact(&con);
			break;
		case DEL:
			DelContact(&con);
			break;
		case SEARCH:
			SearchContact(&con);
			break;
		case MODIFY:
			ModifyContact(&con);
			break;
		case SHOW:
			ShowContact(&con);
			break;
		case SORT:
			SortContact(&con);
			break;
		case EXIT:
			SaveContact(&con);
			DestoryContact(&con);// 销毁通讯录-释放动态开辟的内存
			printf("退出通讯录!\n");
			break;
		case SAVE:
			SaveContact(&con);
			break;
		default:
			printf("选择错误!\n");
			break;
		}
	} while (input);
	return 0;
}

2、contact.h

// contact.h
#pragma once // 避免头文件被重复引用

// #define MAX 1000

#define DEFAULT_SZ 3 // 通讯录初始状态的容量大小

#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>

enum Option
{
	EXIT,//0
	ADD,//1
	DEL,//2
	SEARCH,//3
	MODIFY,//4
	SHOW,//5
	SORT,//6
	SAVE//7
};

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
{
	struct PeoInfo* data; // 存放信息
	int size; // 记录当前已经有的元素个数
	int capacity; // 当前通讯录的最大容量
}Contact;

// 声明函数
// 初始化通讯录
void InitContact(struct Contact* ps);
// 添加联系人
void AddContact(struct Contact* ps);
// 展示通讯录信息
void ShowContact(const struct Contact* ps);
// 删除指定联系人信息
void DelContact(struct Contact* ps);
// 查找指定联系人信息
void SearchContact(const struct Contact* ps);
// 修改指定联系人信息
void ModifyContact(struct Contact* ps);
// 排序通讯录内容(按姓名排)
void SortContact(struct Contact* ps);
// 销毁通讯录
void DestoryContact(Contact* ps);
// 保存文件
void SaveContact(const Contact* ps);
// 加载文件中的信息到通讯录
void LoadContact(Contact* ps);

3、contact.c

// contact.c
#define _CRT_SECURE_NO_WARNINGS 1

#include "contact.h"

// 检测当前通讯录的容量
void CheckCapacity(struct Contact* ps)
{
	if (ps->size == ps->capacity)
	{
		// 增容
		struct PeoInfo* ptr = realloc(ps->data, (ps->capacity + 2) * sizeof(PeoInfo));
		if (ptr != NULL)
		{
			ps->data = ptr;
			ps->capacity += 2;
			printf("增容成功!\n");
		}
		else
		{
			printf("增容失败!\n");
		}
	}
}

// 把文件中已经存放的通讯录中的信息加载到通讯录中
void LoadContact(Contact* ps)
{
	PeoInfo tmp = { 0 };
	FILE* pfRead = fopen("contact.dat", "rb");
	if (pfRead == NULL)
	{
		printf("LoadContact::%s\n", strerror(errno));
		return;
	}

	// 读取文件,存放到通讯录
	while (fread(&tmp, sizeof(PeoInfo), 1, pfRead))
	{
		CheckCapacity(ps);
		ps->data[ps->size] = tmp;
		ps->size++;
	}
	fclose(pfRead);
	pfRead = NULL;
}

// 初始化通讯录
void InitContact(struct Contact* ps)
{
	assert(ps);
	ps->size = 0;
	ps->capacity = DEFAULT_SZ;
	ps->data = (struct PeoInfo*)malloc(DEFAULT_SZ * sizeof(struct PeoInfo));
	if (ps->data == NULL)
	{
		perror("InitContact::malloc");
		return;
	}
	memset(ps->data, 0, ps->capacity * sizeof(struct PeoInfo));
	LoadContact(ps); // 把文件中已经存放的通讯录中的信息加载到通讯录中
}

// 添加联系人
void AddContact(struct Contact* ps)
{
	assert(ps);

	// 检测当前通讯录的容量
	// 1、如果满了就增加空间
	// 2、如果没满无需任何操作
	CheckCapacity(ps);
	// 增加数据
	printf("请输入名字:>");
	scanf("%s", ps->data[ps->size].name);
	printf("请输入年龄:>");
	scanf("%d", &(ps->data[ps->size].age)); // 年龄是一个整型变量,可以取地址
	printf("请输入性别:>");
	scanf("%s", ps->data[ps->size].sex);
	printf("请输入电话:>");
	scanf("%s", ps->data[ps->size].tele);
	printf("请输入地址:>");
	scanf("%s", ps->data[ps->size].addr);

	ps->size++;
	printf("添加成功!\n");
}

// 展示通讯录中的信息
void ShowContact(const struct Contact* ps)
{
	if (ps->size == 0)
	{
		printf("通讯录为空!\n");
	}
	else
	{
		int i = 0;
		// 打印标题
		printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
		// 打印数据
		for (i = 0; i < ps->size; i++)
		{
			printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",
				ps->data[i].name,
				ps->data[i].age,
				ps->data[i].sex,
				ps->data[i].tele,
				ps->data[i].addr);
		}
	}
}

// 通过姓名查找指定联系人所在下标
static int FindByName(const struct Contact* ps, char name[MAX_NAME])
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		if (0 == strcmp(ps->data[i].name, name))
		{
			return i; // 查找到返回联系人的下标
		}
	}

	return -1; // 找不到返回-1
}

// 删除指定联系人信息
void DelContact(struct Contact* ps)
{
	char name[MAX_NAME];
	int pos = 0;
	printf("请输入要删除人的名字:>");
	scanf("%s", name);
	// 1、查找要删除的人在什么位置
	// 找到了返回名字所在元素的下标
	// 找不到返回 -1
	pos = FindByName(ps, name);

	// 2、删除
	if (pos == -1)
	{
		printf("要删除的人不存在!\n");
	}
	else
	{
		//删除数据
		int j = 0;
		for (j = pos; j < ps->size - 1; j++)
		{
			// 把要删除的人后面一个人的信息依次从前向后往前移动一位,覆盖掉其信息
			ps->data[j] = ps->data[j + 1];
		}
		ps->size--;
		printf("删除成功!\n");
	}
}

// 查找指定联系人信息
void SearchContact(const struct Contact* ps)
{
	int pos = 0;
	char name[MAX_NAME];
	printf("请输入要查找人的名字:>");
	scanf("%s", name);
	pos = FindByName(ps, name); // 查找
	if (pos == -1)
	{
		printf("要查找的人不存在!\n");
	}
	else
	{
		printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "名字", "年龄", "性别", "电话", "地址");
		// 打印数据
		printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",
			ps->data[pos].name,
			ps->data[pos].age,
			ps->data[pos].sex,
			ps->data[pos].tele,
			ps->data[pos].addr);
	}
}

// 修改指定联系人信息
void ModifyContact(struct Contact* ps)
{
	int pos = 0;
	char name[MAX_NAME];
	printf("请输入要修改人的名字:>");
	scanf("%s", name);
	pos = FindByName(ps, name);
	if (pos == -1)
	{
		printf("要修改人的信息不存在!\n");
	}
	else
	{
		printf("请输入名字:>");
		scanf("%s", ps->data[pos].name);
		printf("请输入年龄:>");
		scanf("%d", &ps->data[pos].age);
		printf("请输入性别:>");
		scanf("%s", ps->data[pos].sex);
		printf("请输入电话:>");
		scanf("%s", ps->data[pos].tele);
		printf("请输入地址:>");
		scanf("%s", ps->data[pos].addr);

		printf("修改完成!\n");
	}
}

// 排序通讯录内容
// 比较结构体数组中两个元素的姓名成员
int compareStructType_name(const void* elem1, const void* elem2)
{
	// 姓名成员是字符串,用strcmp比较
	return strcmp(((struct PeoInfo*)elem1)->name, ((struct PeoInfo*)elem2)->name);
}

// 以名字排序所有联系人
void SortContact(struct Contact* ps)
{
	// 判断通讯录是否为空
	if (ps->size == 0)
	{
		printf("通讯录为空,无法排序!\n");
		return;
	}
	else
	{
		// 根据姓名成员对结构体数组升序排列
		qsort(ps->data, ps->size, sizeof(ps->data[0]), compareStructType_name);
		printf("以名字排序联系人成功!\n");
	}
}

// 销毁通讯录
void DestoryContact(Contact* ps)
{
	free(ps->data);
	ps->data = NULL;
	ps->capacity = 0;
	ps->size = 0;
	printf("销毁成功!\n");
}

// 保存文件
void SaveContact(const Contact* ps)
{
	FILE* pfWrite = fopen("contact.dat", "wb");
	if (pfWrite == NULL)
	{
		printf("SaveContact::%s\n", strerror(errno));
		return;
	}

	// 写通讯录中数据到文件中
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		fwrite(&(ps->data[i]), sizeof(PeoInfo), 1, pfWrite);
	}
	fclose(pfWrite);
	pfWrite = NULL;
}

六、程序运行效果

程序未运行时:(没有 contact.dat 这个文件)

 

 

 

 

 

 

 


退出程序后:

因为这里是以二进制写入,所以会出现乱码的现象,但是不影响程序正常运行。 

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

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

相关文章

不可忽略:冒烟测试到底有多重要?

冒烟测试介绍 冒烟测试一词&#xff0c;来源于电路板测试&#xff1a;电路板拼接或组装完成后&#xff0c;进行通电测试&#xff0c;如果冒烟&#xff0c;则说明存在缺陷。 而软件应用中&#xff0c;对其的定义为&#xff1a;在软件开发过程中的一种针对软件版本包的快速基本…

双亲委派机制,懂吧~ 那什么情况下需要破坏它,知道吗?

一、什么是双亲委派机制&#xff1f; 我们要获得一个类的Class实例&#xff0c;可以采用如下方式&#xff1a; 那么在Class的forName(className)方法中&#xff0c;会根据是谁调用了Class.forName(className)这个方法&#xff0c;那么就获得当时加载了它的那个ClassLoader&…

uniapp选择只选择月份demo效果(整理)

<template><view style"margin-top: 200rpx;"><!-- mode"multiSelector" 多列选择器 --><view><picker :range"years" :value"echoVal" change"yearChange" mode"multiSelector">{…

【面试题】2、Docker和Spring相关

1、Docker是什么&#xff1f; &#xff08;1&#xff09;Docker是一个快速交互、运行应用的技术&#xff0c;可以将程序及其依赖、运行环境一起打包为一个镜像&#xff0c;该镜像可以迁移到任意的Linux操作系统 &#xff08;2&#xff09;运行时利用沙箱机制形成隔离容器&…

【JasperReports笔记04】如何使用Jasper Studio制作父子报表,并且通过Java + Parameters参数填充模板文件

这篇文章&#xff0c;主要介绍如何使用Jasper Studio制作父子报表&#xff0c;并且通过Java Parameters参数填充模板文件。 目录 一、JasperReports实现父子报表 1.1、运行效果 1.2、制作模板 &#xff08;1&#xff09;制作子报表 &#xff08;2&#xff09;制作子报表的…

计算机竞赛 图像检索算法

文章目录 1 前言2 图像检索介绍(1) 无监督图像检索(2) 有监督图像检索 3 图像检索步骤4 应用实例5 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 图像检索算法 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff…

0101读写分离测试-jdbc-shardingsphere-中间件

文章目录 1 前言2、创建SpringBoot程序2.1、创建项目2.2、添加依赖2.3、生成实体类、service与Mapper1.5、配置读写分离 2、测试2.1、读写分离测试2.2、事务测试2.3、负载均衡测试 结语 1 前言 shardingshpere-jdbc定位为轻量级 Java 框架&#xff0c;在 Java 的 JDBC 层提供的…

处理Selenium3+python3定位鼠标悬停才显示的元素

这篇文章主要介绍了Selenium3python3--如何定位鼠标悬停才显示的元素 &#xff0c;文中通过简单代码给大家介绍的非常详细,需要的朋友可以参考下 先给大家介绍下Selenium3python3--如何定位鼠标悬停才显示的元素 定位鼠标悬停才显示的元素&#xff0c;要引入新模块 # coding…

Tableau画图

目录 蝴蝶图 四象图 排序图 盒型图/散点图 圆环图 火柴图 直方图 瀑布图 地理图 面积图 树地图 面积图 条形图 词云图 双轴图 填充地图 tableau2023.2 须知 蝴蝶图 拉好数据之后 创建新字段正负销售额&#xff0c;并拖入第一个颜色标记卡 四象图 智能推荐 散…

使用句子嵌入的无监督文本摘要

一、说明 这是一个AI研究生班的作业练习&#xff0c; 在本文中&#xff0c;我将描述我用来在 Python 中执行文本摘要的方法&#xff0c;这是我在导师分配给我的很棒的任务列表之一。 二、什么是文本摘要&#xff1f; 文本摘要是从一个或多个源中提取最重要的信息以生成特定用户…

华为ENSP网络设备配置实战4(OSPF+BGP+VPN+单臂路由)

题目要求 1、loopback口通过OSPF连通&#xff0c;合理规划OSPF开销&#xff0c;通过设置AR1->AR2->AR4链路&#xff0c;来消除负载链路。 2、AR3、AR4分别与AR1、AR2建立BGP邻居 3、AR3、AR4作为PC机网关设备 4、PC1、PC3由VPN-spi承载&#xff0c;PC2、PC4由VPN-spims承…

【算法挨揍日记】day03——双指针算法_有效三角形的个数、和为s的两个数字

611. 有效三角形的个数 611. 有效三角形的个数https://leetcode.cn/problems/valid-triangle-number/ 题目描述&#xff1a; 给定一个包含非负整数的数组 nums &#xff0c;返回其中可以组成三角形三条边的三元组个数。 解题思路&#xff1a; 本题是一个关于三角形是否能成立…

通过爬虫抓取上市企业利润表并在睿思BI中展示

睿思BI从v5.3开始支持网络爬虫&#xff0c;可以从指定URL抓取表格数据&#xff0c;本示例实现从网络上抓取上市企业招商银行的利润表数据&#xff0c;并在睿思BI中进行展现。 首先&#xff1a;从搜狐财经抓取招商银行利润表数据&#xff0c;操作过程如下&#xff1a; 1.在睿思…

提示词4大经典框架;将AI融入动画工作流的案例和实践经验;构建基于LLM的系统和产品的模式;提示工程的艺术 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 高效提示词的4大经典框架&#xff1a;ICIO、CRISPE、BROKE、RASCEF ICIO 框架 Intruction (任务) &#xff1a;你希望AI去做的任务&am…

软件测试中如何测试算法?

广义的算法是指解决问题的方案,小到求解数学题,大到制定商业策略,都可以叫做算法。而我们 今天讨论的软件测试中的算法,对应的英文单词为Algorithm ,专指计算机处理复杂问题的程序或 指令。 随着最近几年人工智能等领域的快速发展,算法受到前所未有的重视,算法测试也随之兴起。…

异步电机IM-改进的电压模型磁链观测器学习

导读&#xff1a;本期文章主要介绍异步电机的改进型电压模型磁链观测器。传统纯积分形式的积分器在低速区域存在初始值问题和直流偏置问题&#xff0c;所以在实际应用中必须对电压模型进行改进。本期文章中的对电压模型改进是借鉴一篇IEEE中的方法。 如果需要文章中对应的仿真…

gcc make cmake ninja的区别

理清C编译过程用到的工具概念 ref: 知乎 早先学C的时候&#xff0c;因为只需要点击IDE的运行按钮&#xff0c;程序就可以跑起来&#xff0c;写过最复杂的只不过是几个文件的学生管理系统。 现在要重新拾起C&#xff0c;看的项目和之前的不可同日而语&#xff0c;构建系统也复…

最优化方法Python计算:牛顿算法

设函数 f ( x ) f(\boldsymbol{x}) f(x)&#xff0c; x ∈ R n \boldsymbol{x}\in\text{ℝ}^n x∈Rn二阶连续可微&#xff0c;记 g ( x ) ∇ f ( x ) \boldsymbol{g}(\boldsymbol{x})\nabla f(\boldsymbol{x}) g(x)∇f(x)&#xff0c; H ( x ) ∇ 2 f ( x ) \boldsymbol{H}(\…

【数据结构OJ题】用栈实现队列

原题链接&#xff1a;https://leetcode.cn/problems/implement-queue-using-stacks/ 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 用两个栈实现&#xff0c;一个栈进行入队操作&#xff0c;另一个栈进行出队操作。 出队操作&#xff1a; 当出队的栈…

ARFoundation避坑记录

网上很多人说这个要改成可选的&#xff0c;否则如果没有安装arcore就会自动弹窗&#xff0c;但是如果关闭了&#xff0c;确实不会弹窗了&#xff0c;但是检测设备的代码也不能完美执行了&#xff0c;如果设备安装了arcore还好&#xff0c;如果没有安装测无法检测。 如果不想有…