C语言通讯录

        在本博客中,我们将介绍如何使用C语言构建一个基本的通讯录。主要涉及C语言的指针、结构体、动态内存管理、文件操作等方面的知识。我们还将学习如何使用C语言的各种功能和技巧来实现通讯录的各种操作,如添加联系人、编辑联系人、删除联系人和搜索联系人等,并且还会对通讯录进行多个版本的优化。

        无论您是初学者还是有一定编程经验的开发者,通过学习和实践C语言通讯录的构建,你都能够加深对C语言的理解,并提升自己的编程能力。这部分知识的学习和代码实践对后面学习数据结构也有很大帮助。希望你能够通过本博客的学习,掌握C语言通讯录的开发技巧,并能够在实际项目中灵活应用。

        让我们开始学习并构建自己的C语言通讯录吧!

​​​​​​​

 

目录

零、前置知识复习: 

1.结构体:

2.动态内存管理

3.文件操作

4.分文件编写:

一、版本一:静态通讯录

1.结构体初始化

2.添加联系人

3.显示联系人

4.删除联系人

5.查找联系人

6.修改联系人

7.排序

二、版本二:动态顺序表

1.结构体初始化

2.添加联系人

3.销毁通讯录

三、版本三:通讯录与文件

1.通讯录读取文件信息

2.通讯录信息保存在文件

附录:通讯录最终版本代码


零、前置知识复习: 

1.结构体:

详见博客:结构体

通过一个结构体来简单定义联系人信息:

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

typedef struct PeoInfo
{
	char name[MAX_NAME];//姓名
	int age;//年龄
	char sex[MAX_SEX];//性别
	char tele[MAX_TELE];//电话
	char addr[MAX_ADDR];//住址
}PeoInfo;

 结构体传参:只能传结构体地址

原因:

  • 函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
  • 传参时,形参是实参的拷贝,我们要改变实参,只能传递指针。

2.动态内存管理

malloc,realloc等函数的使用,尤其是realloc

详见博客:动态内存管理

3.文件操作

fwite和fread函数的使用

详见博客:文件操作

4.分文件编写:

Contact.h 主要写类型和函数的声明

Contact.c 主要写函数的实现

test.c 通讯录测试

详见博客:文件包含相关博客


具体细节下面介绍:

一、版本一:静态通讯录

这个版本的通讯录是通过大小指定的数组来存储联系人信息。

静态通讯录结构体的实现如下:

//静态版本
typedef struct Contact
{
	PeoInfo data[MAX];
	int sz;
}Contact;

这里我们的Contact结构体中存放的是PeoInfo类型的数组data,并且大小固定为MAX,超出容量后就不能添加联系人了。这也是静态通讯录的一个缺陷。sz是联系人个数。

下面详解各种功能的实现:

1.结构体初始化

//静态版本
void InitContact(Contact* pc)
{
	assert(pc);
	memset(pc->data, 0, sizeof(pc->data));
	pc->sz = 0;
}

使用memset内存函数进行初始化

memset函数使用详见:内存操作函数

2.添加联系人

//静态版本

void AddContact(Contact* pc)
{
	assert(pc);
	if (pc->sz == MAX)
	{
		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);

	pc->sz++;
	printf("成功增加联系人\n");
}

3.显示联系人

void ShowContact(const Contact* pc)
{
	assert(pc);

	int i = 0;
	printf("%-10s\t%-4s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");//预留一定空间,并且左对齐
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-10s\t%-4d\t%-5s\t%-12s\t%-30s\n", 
			pc->data[i].name,
			pc->data[i].age,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].addr);
	}
}

4.删除联系人

删除联系人之前,我们要查找联系人。不管删除需要查找,我们修改联系人也需要查找。所以这里我们可以封装一个按姓名查找的函数。

static int FindByName(const Contact* pc, char name[])
//static修饰函数,只能在该源文件中使用
{
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;//找到了
		}
	}
	return -1;//没找到
}

注意:

  • 函数用static修饰,表示函数由外部链接属性转变为内部链接属性。只能在本源文件中使用。
  • 字符串的比较,要用strcmp函数。

 删除联系人函数:

//静态
void DelContact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空,无法删除\n");
		return;
	}
	char name[MAX_NAME] = {0};
	assert(pc);

	//查找
	printf("请输入要删除的人名字:>");
	scanf("%s", name);
    int del = FindByName(pc,name);
    if(del == -1)
    {
        printf("要删除的人不存在!\n");
        return;
    }

    //删除
    int i=0;
	for (i = del; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;

	printf("成功删除联系人\n");
}

5.查找联系人

void SearchContact(const Contact* pc)
{
	assert(pc);

	char name[MAX_NAME] = { 0 };
	printf("请输入要查找人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要查找的人不存在\n");
	}
	else
	{
		printf("%-10s\t%-4s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
		//打印数据
		printf("%-10s\t%-4d\t%-5s\t%-12s\t%-30s\n",
			pc->data[pos].name,
			pc->data[pos].age,
			pc->data[pos].sex,
			pc->data[pos].tele,
			pc->data[pos].addr);
	}
}

6.修改联系人

void ModifyContact(Contact* pc)
{
	assert(pc);

	char name[MAX_NAME] = { 0 };
	printf("请输入要修改人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
		printf("要修改的人不存在\n");
	else
	{
		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");
	}
}

7.排序

复习:qsort函数的使用

int CmpByName(void* p1, void* p2)
{
	return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}

int CmpByAge(void* p1, void* p2)
{
	return ((PeoInfo*)p1)->age - ((PeoInfo*)p2)->age;
}

void SortContact(Contact* pc) 
{
	int option = 0;
	printf("****1.姓名   2.年龄****\n");
	printf("按着姓名还是年龄排序?>");
	scanf("%d", &option);
	if (option == 1)
	{
		qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByName); 
	}
	else
	{
		qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByAge);
	}
}

二、版本二:动态顺序表

上面我们说过,静态顺序表只用一个数组来存储联系人信息,满了之后就不能扩容,这是一个很大的缺陷,下面我们通过动态内存管理来实现动态通讯录。

动态通讯录结构体如下:

#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30
#define DEFAULT_SZ 3//初始容量
#define INC_SZ 2//扩容添加容量

//动态版本
typedef struct Contact
{
	PeoInfo* data;
	int sz;
	int capacity;
}Contact;

PeoInfo指针用来维护动态开辟的空间。sz是联系人数量。capacity是通讯录容量。 

与静态版本相比,我们只需修改初始化、添加函数,并添加一些其他函数。

1.结构体初始化

//动态版本
void InitContact(Contact* pc)
{
	assert(pc);
	pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
	if (!pc->data)
	{
		perror("InitContact");
		exit(-1);
	}
	pc->sz = 0;
	pc->capacity = DEFAULT_SZ;

}

2.添加联系人

如果容量满了,就可以使用realloc函数进行扩容,我们可以把扩容函数进行封装。

realloc函数的使用细节,详见:动态内存管理

static int CheckCapacity(Contact* pc)
{
	if (pc->sz == pc->capacity)
	{
		PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
		if (!tmp)
		{
			perror("CheckCapacity");
			return 0;
		}
		
	    pc->data = tmp;
		pc->capacity += INC_SZ;
		printf("增容成功\n");
		return 1;
		
	}
	return 1;
}

添加联系人:

//动态版本
void AddContact(Contact* pc)
{
	assert(pc);

	if (CheckCapacity(pc) == 0)
	{
		exit(-1);
	}

	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);

	pc->sz++; 
	printf("成功增加联系人\n");
}

3.销毁通讯录

自己开辟的空间用完后要销毁,我们封装成Destroy函数。

void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

三、版本三:通讯录与文件

我们发现,以上两个版本都是在内存中保存信息,退出程序后通讯录信息就会丢失,无法做到数据持久化,这里就能用到文件了。

1.通讯录读取文件信息

static void LoadContact(Contact* pc)
{
	FILE* pf = fopen("data.txt", "rb");
	if (!pf)
	{
		perror("fopen");
		exit(-1);
	}

	PeoInfo tmp = { 0 };
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	{
		if (!CheckCapacity(pc))
		{
			exit(-1);
		}
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}

	fclose(pf);
	pf == NULL;
}

注意:

static修饰,只在本源文件使用

二进制读写函数的返回值

详见:文件操作

2.通讯录信息保存在文件

void SaveContact(Contact* pc)
{
	FILE* pf = fopen("data.txt", "wb");
	if (!pf)
	{
		perror("fopen");
		exit(-1);
	}

	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
	}

	fclose(pf);
	pf = NULL;
}

附录:通讯录最终版本代码

#pragma once
#include 
#include 
#include 
#include 

#define MAX 100
#define MAX_NAME 20
#define MAX_SEX 5
#define MAX_TELE 12
#define MAX_ADDR 30
#define DEFAULT_SZ 3
#define INC_SZ 2

enum OPTION
{
	EXIT,//0
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};

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;
	int sz;
	int capacity;
}Contact;

//初始化通讯录
void InitContact(Contact* pc);

//增加联系人
void AddContact(Contact* pc);

//显示所有联系人的信息
void ShowContact(const Contact* pc);

//删除指定联系人
void DelContact(Contact* pc);

//查找指定联系人
void SearchContact(const Contact* pc);

//修改指定联系人
void ModifyContact(Contact* pc);

//排序
void SortContact(Contact* pc);

//通讯录销毁
void DestroyContact(Contact* pc);

//保存通讯录信息到文件中
void SaveContact(Contact* pc);
#define _CRT_SECURE_NO_WARNINGS 1
#include "Contact.h"

static void LoadContact(Contact* pc)
{
	FILE* pf = fopen("data.txt", "rb");
	if (!pf)
	{
		perror("fopen");
		return;
	}

	PeoInfo tmp = { 0 };
	while (fread(&tmp, sizeof(PeoInfo), 1, pf))
	{
		if (!CheckCapacity(pc))
		{
			return;
		}
		pc->data[pc->sz] = tmp;
		pc->sz++;
	}

	fclose(pf);
	pf == NULL;
}

静态版本
//void InitContact(Contact* pc)
//{
//	assert(pc);
//	memset(pc->data, 0, sizeof(pc->data));
//	pc->sz = 0;
//}

//动态版本
void InitContact(Contact* pc)
{
	assert(pc);
	pc->data = (PeoInfo*)malloc(DEFAULT_SZ * sizeof(PeoInfo));
	if (!pc->data)
	{
		perror("InitContact");
		return;
	}
	pc->sz = 0;
	pc->capacity = DEFAULT_SZ;

	//文件信息输入到通讯录中
	LoadContact(pc); 
}

静态版本
//
//void AddContact(Contact* pc)
//{
//	assert(pc);
//	if (pc->sz == MAX)
//	{
//		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);
//
//	pc->sz++;
//	printf("成功增加联系人\n");
//}


static int CheckCapacity(Contact* pc)
{
	if (pc->sz == pc->capacity)
	{
		PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + INC_SZ) * sizeof(PeoInfo));
		if (!tmp)
		{
			perror("CheckCapacity");
			return 0;
		}
		else
		{
			pc->data = tmp;
			pc->capacity += INC_SZ;
			printf("增容成功\n");
			return 1;
		}
	}
	return 1;
}

//动态版本
//静态版本有一个致命缺点,就是满了就不能添加了,很明显这种版本是会被淘汰的,所以我们使用动态内存管理的方式开辟空间
void AddContact(Contact* pc)
{
	assert(pc);

	if (CheckCapacity(pc) == 0)
	{
		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);

	pc->sz++; 
	printf("成功增加联系人\n");
}

void ShowContact(const Contact* pc)
{
	assert(pc);

	int i = 0;
	printf("%-10s\t%-4s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");//预留一定空间,并且左对齐
	for (i = 0; i < pc->sz; i++)
	{
		printf("%-10s\t%-4d\t%-5s\t%-12s\t%-30s\n", 
			pc->data[i].name,
			pc->data[i].age,
			pc->data[i].sex,
			pc->data[i].tele,
			pc->data[i].addr);
	}
}

static int FindByName(const Contact* pc, char name[])
//static修饰函数,只能在该源文件中使用
{
	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		if (strcmp(pc->data[i].name, name) == 0)
		{
			return i;//找到了
		}
	}
	return -1;//没找到
}

静态
//void DelContact(Contact* pc)
//{
//	if (pc->sz == 0)
//	{
//		printf("通讯录为空,无法删除\n");
//		return;
//	}
//	char name[MAX_NAME] = {0};
//	assert(pc);
//	//删除
//	printf("请输入要删除的人名字:>");
//	scanf("%s", name);
//
//	//找到要删除的人
//	int i = 0;
//	int del = 0;
//	int flag = 0;
//	for (i = 0; i < pc->sz; i++)
//	{
//		if (strcmp(pc->data[i].name, name) == 0)
//		{
//			del = i;
//			flag = 1; 
//			break;
//		}
//	}
//	if (flag == 0)
//	{
//		printf("要删除的人不存在\n");
//		return;
//	}
//	//删除坐标位del的联系人
//	for (i = del; i < pc->sz-1; i++)
//	{
//		pc->data[i] = pc->data[i + 1];
//	}
//	pc->sz--;
//
//	printf("成功删除联系人\n");
//}

//动态
void DelContact(Contact* pc)
{
	if (pc->sz == 0)
	{
		printf("通讯录为空,无法删除!\n");
		return;
	}
	char name[MAX_NAME] = { 0 };
	assert(pc);

	//查找
	printf("请输入要删除的人的名字:>");
	scanf("%s", name);
	int del = FindByName(pc, name);
	if (del == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}

	//删除
	int i = 0;
	for (i = del; i < pc->sz - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->sz--;

	printf("成功删除联系人\n");
}

void SearchContact(const Contact* pc)
{
	assert(pc);

	char name[MAX_NAME] = { 0 };
	printf("请输入要查找人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要查找的人不存在\n");
	}
	else
	{
		printf("%-10s\t%-4s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
		//打印数据
		printf("%-10s\t%-4d\t%-5s\t%-12s\t%-30s\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)
{
	assert(pc);

	char name[MAX_NAME] = { 0 };
	printf("请输入要修改人的名字:>");
	scanf("%s", name);
	int pos = FindByName(pc, name);
	if (pos == -1)
		printf("要修改的人不存在\n");
	else
	{
		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 CmpByName(void* p1, void* p2)
{
	return strcmp(((PeoInfo*)p1)->name, ((PeoInfo*)p2)->name);
}

int CmpByAge(void* p1, void* p2)
{
	return ((PeoInfo*)p1)->age - ((PeoInfo*)p2)->age;
}

void SortContact(Contact* pc) 
{
	int option = 0;
	printf("****1.姓名   2.年龄****\n");
	printf("按着姓名还是年龄排序?>");
	scanf("%d", &option);
	if (option == 1)
	{
		qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByName); 
	}
	else
	{
		qsort(pc->data, pc->sz, sizeof(PeoInfo), CmpByAge);
	}
}

void DestroyContact(Contact* pc)
{
	free(pc->data);
	pc->data = NULL;
	pc->capacity = 0;
	pc->sz = 0;
}

void SaveContact(Contact* pc)
{
	FILE* pf = fopen("data.txt", "wb");
	if (!pf)
	{
		perror("fopen");
		return;
	}

	int i = 0;
	for (i = 0; i < pc->sz; i++)
	{
		fwrite(pc->data + i, sizeof(PeoInfo), 1, pf);
	}

	fclose(pf);
	pf = NULL;
}
#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("***** 0. exit              *****\n");
	printf("********************************\n");
}

void test()
{
	int input = 0;
	//首先得有通讯录
	Contact con;
	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); 
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误, 重新选择\n");
			break;
		}
	} while (input);
}

int main()
{
	test();
	return 0;
}

你可能感兴趣的:(C语言,c语言,通讯录,动态内存管理,结构体,文件操作,qsort排序)