【C语言】通讯录(静态版本+动态版本+文件版本)层层深入讲解,附有完整源代码

通讯录

由于代码比较长,为了增加可读性,分成了contact.h,contact.c,test.c,分别用来声明函数或者类型,实现函数功能,测试代码

contact.h

我们希望通讯录具有增加联系人,删除联系人,显示联系人,找查联系人,修改联系人,排序的功能,联系人的信息具有名字,年龄,性别,电话,地址的信息

由于每次对通讯录进行操作都要把data数组和存的联系人的个数sz传过去,那干脆把他们两个打包到一个结构体类型struct contact里面

枚举类型是为了增加test.c里面switch语句选项的可读性,默认第一个成员也就是EXIT就是0,那么我们就可以把switch语句里面的0换成EXIT了。

test.c

contact.c

首先把所有data数组的所有内容以及p->sz初始化成0

增加联系人的函数

一次性增加一个联系人的信息

数字表示打印多少位,负号表示左对齐,\t表示插入一个制表符,能够让每一行对齐

在查找联系人,删除联系人,修改联系人信息的函数中,我们都需要先找到某个联系人,为了避免写三份类似的代码,我们这里使用了一个函数。

查找联系人的函数

修改信息的函数

排序的函数

像这样静态版本的通讯录问题还是比较大的,首先就是我们不管存多少个联系人的信息,上来都创建了一个100个元素的数组,这样对内存的开销就比较大,如果我们存的信息少,就浪费了内存,如果存的太多,又要去修改最大容量,在我们学习了动态内存开辟之后,我们可以对通讯录进行如下修改:通讯录刚上来可以存放三个联系人的信息,当通讯录满了之后,自动扩充两个人的名额。于是我们就可以把data数组改成一个指针,为了尽可能少的改动原来的代码,我们把这个指针的名字也叫做data。这与原来数组名的含义都是地址,现在data是一个people*类型的指针,我们可以把使用malloc,realloc函数开辟的内存首地址存到里面去。

那么我们先对以前的contact类型进行以下修改

实际上需要修改的函数只有初始化通讯录的函数和增加联系人的函数。

首先使用malloc开辟一块能存放三个people类型变量的空间,并返回首地址存到data里面去。这里申请了空间由于后面要用,所以并没有及时释放掉,最后关闭通讯录的时候再释放即可。

然后是增加联系人的函数,在增加之前我们先要判断一下通讯录是不是已经满了,我们使用下面的函数来实现这个功能

由于realloc增容可能会失败,因此必须检查,在增容完毕之后要应该更改掉当前的最大容量capacity。

然后在添加联系人的函数中调用这个检查容量的函数

由于以上所有动态内存开辟的空间都没有被释放掉,因此我们在推出这个通讯录的时候应该及时释放掉,在test函数这里调用一个用来释放内存的函数

这个函数也是需要我们自己编写的

这样我们的通讯录就是动态版本的了。

要实现文件版本,首先应改在退出通讯录之前保存一下当前通讯录的信息,使用savecontact函数来实现

以二进制格式+相对路径把通讯录的信息写入了当前代码同一路径中的文件data.txt中,当然如果原本没有这个文件会自动创建一个。文件操作分三步走,打开文件,读写文件,关闭文件。并在test.c中退出当前程序之前调用这个函数,如图

但是我们这样以二进制形式把数据写入文件的时候实际上我们手动点开data.txt这个文件是什么也看不懂的,因为它是以二进制的形式存进去的,但是无需担心,我们可以使用fread函数来读。由于下一次打开程序的时候我们希望上一次保存的信息应该重新加载到通讯录中,因此我们最好在初始化的时候就完成这件事,并通过一个函数loadcontact来完成这项工作

文件操作分三步走,打开文件,读写文件,关闭文件

把fread的返回值也就是成功读取的数据的个数作为while循环的判断条件,如果当前文件中的数据已经读完了,就不用再继续读了,并且我们把读到的内容放到一个临时变量tmp中去,读到的东西是people类型的数据,因此tmp也应该是people类型的,data是一个数组,里面的元素都是people类型的,每次把tmp赋给p->data[p->sz],出循环的时候就读完了文件中的所有信息并且相应修改了sz。同时还按照需求已经进行了扩容。

此时的通讯录就是文件版本了。

奉上文件版本的完整源代码

contact.c

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"
//加载文件信息到通讯录中的函数
void loadcontact(contact*p) {
	FILE* pf = fopen("dada.txt", "rb");
	if (pf == NULL) {
		perror("loadcontact");
		return;
	}
	people tmp = { 0 };//tmp是结构体类型
	while (fread(&tmp, sizeof(people), 1, pf)) {
		check_capacity(p);
		p->data[p->sz] = tmp;
		p->sz++;
	}
	fclose(pf);
	pf = NULL;
}
//初始化通讯录的函数
void initcontact(contact* p) {
	p->data = (people*)malloc(DEFAULT_NUM *sizeof(people));//刚上来容量是3
	if (p->data == NULL) {
		perror("initcontact");
		return;
	}
	p->sz = 0;
	p->capacity = DEFAULT_NUM;
	loadcontact(p);
}
//检查容量是否已满的函数
int check_capacity(contact*p) {
	if (p->capacity == DEFAULT_NUM) {
		//扩大容量
		people* ptr=(people*)realloc(p->data, (DEFAULT_NUM + INC) * sizeof(people));
		//一个变量名为ptr的people*类型指针,用来接收调整之后的空间首地址
		if (ptr == NULL) {
			perror(check_capacity);
			return 0;
		}
		else {
			p->data = ptr;//这个ptr可能是原来的data,也可能不是,增容成功之后赋给data
			p->capacity += INC;
			return 1;
		}
	}
	return 1;//压根不需要增容,也返回1
}
//添加联系人的函数,动态版本
void addcontact(contact* p) {
	int ret = check_capacity(p);
	if (0 == ret) {
		return;
	}
	printf("请输入名字\n");
	scanf("%s", p->data[p->sz].name);
	printf("请输入年龄\n");
	scanf("%d", &(p->data[p->sz].age));
	printf("请输入性别\n");
	scanf("%s", p->data[p->sz].sex);
	printf("请输入电话号码\n");
	scanf("%s", p->data[p->sz].tele);
	printf("请输入地址\n");
	scanf("%s", p->data[p->sz].address);
	p->sz++;
	printf("增加联系人成功\n");
}
//显示联系人的函数
void showcontact(const contact* p) {
	int i = 0;
	for (i = 0; i < p->sz; i++) {
		//打印标题
		printf("%-10s\t,%-4s\t,%-5s\t,%-12s\t,%-30s\n", "名字", "年龄", "性别", "电话", "地址");
		printf("%-10s\t,%-4d\t,%-5s\t,%-12s\t,%-30s\n",
			p->data[i].name,
			p->data[i].age,
			p->data[i].sex,
			p->data[i].tele,
			p->data[i].address
		);
	}
}
//通过名字找查联系人的函数
// 由于在delcontact,searchcontact,modifycontact函数中都需要找查
// 为了避免写三份类似的代码,我们使用函数来实现找查联系人的功能
int find_by_name(contact* p, char name[]) {
	//找查联系人
	int i = 0;
	for (i = 0; i < p->sz; i++) {
		if (strcmp(p->data[i].name, name) == 0) {
			return i;//找到了,返回下标
		}
	}
	return -1;//没有找到,返回-1
}
//删除联系人的函数(暂时不考虑两个人的名字相同的情况)
void delcontact(contact* p) {
	char name[20] = { 0 };
	printf("请输入要删除的人名字\n");
	scanf("%s", name);
	int i = 0;
	int del = 0;//记录要删除的人信息在data数组中的下标
	int flag = 0;
	if (p->sz == 0) {
		printf("通讯录为空,无法删除\n");
	}
	//找查联系人
	del = find_by_name(p, name);
	//删除联系人
	if (del == -1) {
		printf("要删除的人不存在\n");
		return;
	}
	for (i = del; i < p->sz - 1; i++) {
		p->data[i] = p->data[i + 1];//循环用后面的元素覆盖前面的元素
	}
	p->sz--;
	//如果要删除最后一个联系人,也就是下标为sz-1的那个人的信息
	//由于刚上来del就是sz-1,不会进入循环,也就不会覆盖掉最后一个元素
	//但是sz--了,就访问不到最后一个元素了,效果上就好像删除了最后一个联系人
	printf("删除成功\n");
}
//查找某个联系人的函数
void searchcontact(contact* p) {
	char name[20] = { 0 };
	printf("请输入要查找的人的名字\n");
	scanf("%s", name);
	int pos = find_by_name(p, name);//返回的就是要找的这个人的信息在data数组中的下标 
	if (pos == -1) {
		printf("查无此人\n");
		return;
	}
	else {
		//打印这一个人的信息
		printf("%-10s\t,%-4s\t,%-5s\t,%-12s\t,%-30s\n", "名字", "年龄", "性别", "电话", "地址");
		printf("%-10s\t,%-4d\t,%-5s\t,%-12s\t,%-30s\n",
			p->data[pos].name,
			p->data[pos].age,
			p->data[pos].sex,
			p->data[pos].tele,
			p->data[pos].address
		);
	}
}
//修改信息的函数
void modifycontact(contact* p) {
	char name[20] = { 0 };
	printf("请输入要修改信息的人的名字\n");
	scanf("%s", name);
	int pos = find_by_name(p, name);
	if (pos == -1) {
		printf("查无此人\n");
	}
	else {
		printf("请输入名字\n");
		scanf("%s", p->data[pos].name);
		printf("请输入年龄\n");
		scanf("%d", &(p->data[pos].age));
		printf("请输入性别\n");
		scanf("%s", p->data[pos].sex);
		printf("请输入电话号码\n");
		scanf("%s", p->data[pos].tele);
		printf("请输入地址\n");
		scanf("%s", p->data[pos].address);
		printf("修改联系人信息成功\n");
	}
}
//qsort需要的比较大小的函数
int cmp_by_name(void* str1,void* str2) {
	return strcmp(((people*)str1)->name, ((people*)str2)->name);
}
//根据名字对信息排序的函数
void sortcontact(contact* p) {
	qsort(p->data, p->sz, sizeof(p->data[0]), cmp_by_name);
	printf("排序成功\n");
}
void release(contact* p) {
	free(p->data);
	p->data = NULL;//注意是data指向的空间被释放掉,不是p指向的空间被释放掉
	p->capacity = 0;
	p->sz = 0;
}
void savecontact(contact* p) {
	FILE* pf = fopen("data.txt", "wb");
	if (pf == NULL) {
		perror("savecontact");
		return ;
	}
	int i = 0;
	for (i = 0; i < p->sz; i++) {
		fwrite(p->data + i, sizeof(people), 1, pf);
	}
	fcolse(pf);
	pf = NULL;
}

test.c

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include"contact.h"
void menu() {
	printf("******1.add*****2.del***********\n");
	printf("******3.search**4.modify********\n");
	printf("******5.show****6.sort**********\n");
	printf("***********0.exit***************\n");
} 
void test() {
	int input = 0;
	//创建通讯录
	contact con = {0};
	do {
		menu();
		printf("请选择功能\n");
		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);
			release(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("输入错误,请重新选择\n");
		}
	} while (input);
}
int main() {
	test();
	FILE* pf = fopen("data.txt", "r");
	if (pf == NULL) {
		perror("fopen");
	}
	return 0;
}

contact.h

cpp 复制代码
#pragma once
//用于各种函数或者类型的声明
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 100 //通讯录能添加的最大人数
#define DEFAULT_NUM 3//刚上来的默认容量是3
#define INC 2//到达最大容量的时候一次性扩充的个数
typedef struct people {
	char name[20];
	int age;
	char sex[5];
	char tele[12];
	char address[30];
}people;//创建结构体类型并重命名为people
typedef struct contact {
	people* data;//一个名为data的指针变量
	int sz;//用来记录通讯录里面存了几个人的信息了
	int capacity;//记录当前最大容量
}contact;
//枚举类型,增加可读性,默认EXIT就是0
enum OPTION {
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT,
};
void initcontact(contact* p);//初始化通讯录的函数,把通讯录中内容初始化为0
void check_capacity(contact* p);//检查通讯录是否已满的函数
void addcontact(contact* p);//增加联系人的函数
void showcontact(const contact* p);//显示所有联系人的函数,显示并不会修改p指向的内容,因此加了const
void delcontact(contact* p);//删除联系人的函数
void searchcontact(contact* p);//找查某个联系人的函数
void modifycontact(contact* p);//修改信息的函数
void sortcontact(contact* p);//根据名字对信息排序的函数
void release(contact* p);//使用完成之后释放动态申请的那些空间的函数
void savecontact(contact* p);//保存通讯录信息到文件的函数
相关推荐
readmancynn5 分钟前
二分基本实现
数据结构·算法
萝卜兽编程7 分钟前
优先级队列
c++·算法
Bruce小鬼9 分钟前
QT文件基本操作
开发语言·qt
Bucai_不才9 分钟前
【数据结构】树——链式存储二叉树的基础
数据结构·二叉树
2202_7544215414 分钟前
生成MPSOC以及ZYNQ的启动文件BOOT.BIN的小软件
java·linux·开发语言
盼海15 分钟前
排序算法(四)--快速排序
数据结构·算法·排序算法
我只会发热21 分钟前
Java SE 与 Java EE:基础与进阶的探索之旅
java·开发语言·java-ee
一直学习永不止步31 分钟前
LeetCode题练习与总结:最长回文串--409
java·数据结构·算法·leetcode·字符串·贪心·哈希表
懷淰メ31 分钟前
PyQt飞机大战游戏(附下载地址)
开发语言·python·qt·游戏·pyqt·游戏开发·pyqt5
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j