C语言实现通讯录

1.针对这个通讯录需要先介绍一下功能,随着我的编写会在这里逐步丰富代码的内容。
最初功能:希望可以在通讯录里面存放1000个好友的信息,包括姓名 年龄 性别 电话 地址这几个功能
首先实现test.c的测试函数,并且我在vs2013中编写代码的时候已经很清楚的标明了每一句语句的意思!

#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");
}
int main()
{
	int input = 0;
	//创建通讯录 
	struct Contact con;  // con就是通讯录,里面包含1000个元素的数和size
	                         //  这里可以使用动态内存malloc进行一个开辟,在后续会修改
	//初始化通讯录
	InitContact(&con);  // 1.传地址效率高  2.结构体传地址才能通过InitContac 改变 strcut Contac里面的内容
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d",&input);
		switch (input)   //  这里我们选择写1,2,3你会发现代码的可读性不高,我不知道这里具体的数字代表着什么意思
		{
		//这里使用了枚举的方法可以很好的避免在后续你不知道自己case中的数字代表什么意思,增加了代码的可读性
		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:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
}

这里我们开始实现每个选项中的功能,在使用每个函数前需要使用头文件Contact.h对每个函数进行声明。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>    // 这里因为Contact.c  和  test.c 文件都是需要引入contact.h 所以直接在这里面来引入头文件
#include<string.h>   //menset的头文件
//在我们写数字的时候要考虑后续的修改和代码的可移植性 所以我们尽量都是用宏定义
#define MAX 1000  
#define MAX_NAME 20
#define MAX_SEX  5
#define MAX_TEL 12
#define MAX_ADDR 30
//关于类型的定义,所以这里我们放在contact.h  一个好友的信息就是一个结构体

struct PeoInfo
{
	char name[MAX_NAME];
	int age;
	char sex[MAX_SEX];
	char tel[MAX_TEL];
	char addr[MAX_ADDR];
};

//枚举的第一个变量是0 要记住   这样就避免了忘记你的选择条件是什么意思!增加了代码的可读性
enum option
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};

//通讯录类型
struct Contact
{
	struct PeoInfo date[MAX];
	int size;
};

//初始化
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);

然后是实现每个具体功能的函数我们放在Contact.c
在实现每个函数的时候会遇见一些难点:
1.对于AddContact函数来说,首先你要判断是否通讯录已经满了,然后在开始往里面添加。
2.对于一个DelContact函数来说,要删除指定联系人,这里实现的方法是比对输入的名字和你通讯录里面的存储的名字,但是你会发现这个函数在删除,查找,修改处都需要,避免代码的多次重复,我们在这里使用了一个FindByName的函数,如果找到了就返回找到了的那个元素下角标,没找到返回-1的这样一个函数,并且使用了static函数进行了封装,对于static函数来说1.可以增加局部变量的生命周期。2.对于全局变量和函数来说,只能够使它在这个源文件中使用,不暴露给外面。
3.修改就是找到了那个人,然后重新输入一遍新的信息。

#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
void InitContact(struct Contact* ps)
{
	memset(ps->date, 0, sizeof(ps->date));  // ps->date 是数组名 放在sizeof()里面就是求出来整个数组的总大小
	ps->size = 0; //设置通讯录最初只有0个元素
}

//在你编写代码前应该就思考以下问题:1.你增加内容,但是容量本身是有可能满了的
void AddContact(struct Contact* ps)
{
	if (ps->size == MAX)
	{
		printf("通讯录已满,无法增加\n");
	}
	else
	{
		 //★★这里其实不是非常的好理解  什么叫增加?增加就是放在已有元素的下一个位置
		//开始的时候里面有0个元素 ,0放在下标为1的地方,第一个元素放在下标为2的地方
		printf("请输入名字:>");
		scanf("%s", ps->date[ps->size].name);
		printf("请输入年龄:>");   //要记住这里的名字是一个变量所以要加&符号
		scanf("%s", &(ps->date[ps->size].age));
		printf("请输入性别:>");
		scanf("%s", ps->date[ps->size].sex);
		printf("请输入电话:>");
		scanf("%s", ps->date[ps->size].tel);
		printf("请输入地址:>");
		scanf("%s", ps->date[ps->size].addr);
		ps->size++;
		printf("添加成功\n");    //  在这里我们虽然报了添加成功但是对于你自己来说你并没有看见是否这一步真的成功了
									//  所以我们在这里直接考虑写ShowContact函数
	}

}

void ShowContact(const struct Contact* ps)   //因为只是显示绝对不会改变ps指向的对象的  所以加一个const
{
	if (ps->size == 0)
	{
		printf("通讯录为空\n");
	}
	else
	{
		int i = 0;
		// 我想在能不能直接在外面打印出来一个标题,姓名 年龄 性别 电话 地址  这样就能更加清楚的展示下面的信息
		printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "姓名","年龄","性别","电话","地址"); 
		//易错点:这里的年龄是一个字符 用%s打印     而下面的则是要用%d的形式进行打印,不然就会在下面报错
		for (i = 0; i < ps->size; i++)
		{
			printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",ps->date[i].name,ps->date[i].age,ps->date[i].sex,ps->date[i].tel,ps->date[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->date[i].name, name))
		{
			return i;
		}
	}
	return -1;
}
void DelContact(struct Contact* ps)    //  这里使用const地下就会报错,因为你说的是ps所指向的位置是不会发生改变的
{
	char name[MAX_NAME];
	printf("请输入要删除人的名字:>");
	scanf("%s", name);
	//找到了就返回名字所在元素的下标    没有找到就返回一个-1
	int pos = FindByName(ps, name);
	//删除

	if (-1 == pos)
	{
		printf("要删除的人不存在\n");
	}
	else
	{
		int j = 0;
		for (j = pos; j<ps->size - 1; j++)
		{
			ps->date[j] = ps->date[j + 1];
		}
		ps->size--;
		printf("删除成功\n");
	}
}

//1.查找要删除的人在什么位置   这一块的代码多次重复,为了是整个代码看着简洁省事我们封装一个函数就是用来查找名字的
//int i = 0;
//for (i = 0; i < ps->size; i++)
//{
//	if (0 == strcmp(ps->date[i].name, name))
//	{
//		break;
//	}
//}
//来到这里有两种情况1.break跳到这里了(删除掉它)   2.你把所有的判别完了,都还没有找到一样的
//	if (i == ps->size)
//	{
//		printf("要删除的人不存在\n");
//	}
//	else
//	{
//		//删除数据    假设 1 2 3 4 5 6 7  
//		//2.删除  用覆盖法  也就是后面的一个元素往前移动
//		int j = 0;
//		for (j = i;j<ps->size-1;j++)
//		{
//			ps->date[j] = ps->date[j + 1];
//		}
//		ps->size--;
//		printf("删除成功\n");
//	}
//}

void SearchContact(const struct Contact* ps)
{
	//此时此刻出现了代码重复,修改,删除,查找这几个函数都是需要先找到指定的联系人
	char name[MAX_NAME];
	printf("请输入要查找的人名字:>");
	scanf("%s", name);
	int pos = FindByName(ps, name);
	if (-1 == pos)
	{
		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->date[pos].name, ps->date[pos].age, ps->date[pos].sex, ps->date[pos].tel, ps->date[pos].addr);
	}
}

void ModifyContact(struct Contact* ps)
{
	char name[MAX_NAME];
	printf("请输入要修改的人名字:>");
	scanf("%s", name);
	int pos = FindByName(ps, name);
	if (-1 == pos)
	{
		printf("要修改的人不存在\n");
	}
	else
	{
		// 修改相当于重新录入一边
		printf("请输入名字:>");
		scanf("%s", ps->date[pos].name);
		printf("请输入年龄:>");   
		scanf("%s", &(ps->date[pos].age));
		printf("请输入性别:>");
		scanf("%s", ps->date[pos].sex);
		printf("请输入电话:>");
		scanf("%s", ps->date[pos].tel);
		printf("请输入地址:>");
		scanf("%s", ps->date[pos].addr);
		printf("修改成功\n");
	}
}

这样我们需要实现的最初的一个通讯录的版本就实现了。但是这里还可以丰富他的功能。
这样的一个struct Contact con 里面创建了一个1000人的struct PeoInfo的结构体和size,在创建的时候我们不管什么就直接创建了1000个struct PeoInfo的结构体,会造成大量的空间浪费,所以我们这里还可以实现malloc的动态开辟,首先开辟3个struct PeoInfo的空间,当发现满了以后自动的增加两个位置,这样我们就需要引入一个capacity(容量),所以在这个结构体中存在一个struct PeoInfo* date 的指针和当前存了几个的size和缪按总共的容量三个内容。下面的代码是新的Contact.c 和 Contact.h 以及实现test.c函数

#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
//这是一个动态内存的版本,因为你会发现你一次性创造出来那么多个会对空间造成一定的浪费,我想要默认情形下只存放这3个空位,当发现满的时候每次增加2个空位
//只有Add函数是需要动态开辟内存的,其他的都基本不需要,还有就是要对开辟的空间进行释放

//我们经常会感慨自己输入进去的信息在关掉的那一瞬间,都不存在了,所以这里也可以把他修改成为一个文件操作的版本,可以使他再关闭以后,下一次打开信息依旧存在
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就是通讯录,里面包含date指针和size还有capacity
	                         //  这里可以使用动态内存malloc进行一个开辟,在后续会修改
	//初始化通讯录
	InitContact(&con);  // 1.传地址效率高  2.结构体传地址才能通过InitContac 改变 strcut Contac里面的内容
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d",&input);
		switch (input)   //  这里我们选择写1,2,3你会发现代码的可读性不高,我不知道这里具体的数字代表着什么意思
		{
		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:
			//在退出前,要销毁动态开辟的内存
			DestroyContact(&con);
			printf("退出通讯录\n");
			break;
		case SAVE:
			SaveContact(&con);
			break;
		case LOAD:
			LoadContact(&con);
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
}

这里还增减了一个文件操作的模块,我们经常会因为输入了很多信息,但是发现再关闭的时候,就不见了,下一次还需要再一次的存储,所以我们保存一下。

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>    // 这里因为Contact.c  和  test.c 文件都是需要引入contact.h 所以直接在这里面来引入头文件
#include<string.h>   //menset的头文件
#include<stdlib.h>   // malloc的头文件
#include<errno.h>
//在我们写数字的时候要考虑后续的修改和代码的可移植性 所以我们尽量都是用宏定义
//#define MAX 1000  
#define MAX_NAME 20
#define MAX_SEX  5
#define MAX_TEL 12
#define MAX_ADDR 30
#define DEFAULT_SZ 3
//关于类型的定义,所以这里我们放在contact.h  一个好友的信息就是一个结构体

typedef struct PeoInfo
{
	char name[MAX_NAME];
	int age;
	char sex[MAX_SEX];
	char tel[MAX_TEL];
	char addr[MAX_ADDR];
}PeoInfo;

//枚举的第一个变量是0 要记住   这样就避免了忘记你的选择条件是什么意思!增加了代码的可读性
enum option
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT,
	SAVE,
	LOAD
};

//通讯录类型
typedef struct Contact
{
	struct PeoInfo* date;
	int size;
	int capacity;// 当前通讯录的最大容量(当size和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 DestroyContact( Contact* ps);
//保存文件
void SaveContact( Contact* ps);
//加载文件信息
void LoadContact(Contact* ps);

这里需要大改的地方主要是InitContact和AddContact函数。

#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
void InitContact(Contact* ps)
{
	ps->date = (PeoInfo*)malloc(3 * sizeof(PeoInfo));   //  既然你动态开辟了一块空间当你不用的时候一定要会给操作系统,要对其进行释放
	if (ps->date == NULL)   // 只要是动态内存开辟的指针一定要进行一下判断,不然在后续你解引用的时候程序会直接崩溃
	{
		return;
	}
	ps->size = 0;
	ps->capacity = DEFAULT_SZ;
}

void CheckCapacity(Contact* ps)
{
	if (ps->size == ps->capacity)
	{
		//增容   这里又忘记了  realloc这个函数要用一个新的指针来接受,并且还要判断这个指针是否为NULL,避免后续的解引用导致程序崩溃
		PeoInfo* ptr = (PeoInfo*)realloc(ps->date, (ps->capacity + 2)*sizeof(PeoInfo));  //  增容一次加2个空间
		if (ptr != NULL)
		{
			ps->date = ptr;
			ps->capacity += 2;
			printf("增容成功\n");
		}
		else
		{
			printf("增容失败\n");
		}
	}
}

//在你编写代码前应该就思考以下问题:1.你增加内容,但是容量本身是有可能满了的
void AddContact(struct Contact* ps)
{
	//检测当前通讯录的容量
	//1.如果满了就增加空间
	//2.如果不满,啥都不干
	CheckCapacity(ps); 
	//要么空间本来就够用就没有满足if语句,要么就是增容成功了,空间已经扩大了
	//增加数据
	printf("请输入名字:>");
	scanf("%s", ps->date[ps->size].name);
	printf("请输入年龄:>");   //要记住这里的名字是一个变量所以要加&符号
	scanf("%d", &(ps->date[ps->size].age));
	printf("请输入性别:>");
	scanf("%s", ps->date[ps->size].sex);
	printf("请输入电话:>");
	scanf("%s", ps->date[ps->size].tel);
	printf("请输入地址:>");
	scanf("%s", ps->date[ps->size].addr);
	ps->size++;
	printf("添加成功\n"); 
}

void ShowContact(const struct Contact* ps)   //因为只是显示绝对不会改变ps指向的对象的  所以加一个const
{
	if (ps->size == 0)
	{
		printf("通讯录为空\n");
	}
	else
	{
		int i = 0;
		// 我想在能不能直接在外面打印出来一个标题,姓名 年龄 性别 电话 地址  这样就能更加清楚的展示下面的信息
		printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n", "姓名","年龄","性别","电话","地址"); 
		//易错点:这里的年龄是一个字符 用%s打印     而下面的则是要用%d的形式进行打印,不然就会在下面报错
		for (i = 0; i < ps->size; i++)
		{
			printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",ps->date[i].name,ps->date[i].age,ps->date[i].sex,ps->date[i].tel,ps->date[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->date[i].name, name))
		{
			return i;
		}
	}
	return -1;
}
void DelContact(struct Contact* ps)    //  这里使用const地下就会报错,因为你说的是ps所指向的位置是不会发生改变的
{
	char name[MAX_NAME];
	printf("请输入要删除人的名字:>");
	scanf("%s", name);
	//找到了就返回名字所在元素的下标    没有找到就返回一个-1
	int pos = FindByName(ps, name);
	//删除

	if (-1 == pos)
	{
		printf("要删除的人不存在\n");
	}
	else
	{
		int j = 0;
		for (j = pos; j<ps->size - 1; j++)
		{
			ps->date[j] = ps->date[j + 1];
		}
		ps->size--;
		printf("删除成功\n");
	}
}

//1.查找要删除的人在什么位置   这一块的代码多次重复,为了是整个代码看着简洁省事我们封装一个函数就是用来查找名字的
//int i = 0;
//for (i = 0; i < ps->size; i++)
//{
//	if (0 == strcmp(ps->date[i].name, name))
//	{
//		break;
//	}
//}
//来到这里有两种情况1.break跳到这里了(删除掉它)   2.你把所有的判别完了,都还没有找到一样的
//	if (i == ps->size)
//	{
//		printf("要删除的人不存在\n");
//	}
//	else
//	{
//		//删除数据    假设 1 2 3 4 5 6 7  
//		//2.删除  用覆盖法  也就是后面的一个元素往前移动
//		int j = 0;
//		for (j = i;j<ps->size-1;j++)
//		{
//			ps->date[j] = ps->date[j + 1];
//		}
//		ps->size--;
//		printf("删除成功\n");
//	}
//}

void SearchContact(const struct Contact* ps)
{
	//此时此刻出现了代码重复,修改,删除,查找这几个函数都是需要先找到指定的联系人
	char name[MAX_NAME];
	printf("请输入要查找的人名字:>");
	scanf("%s", name);
	int pos = FindByName(ps, name);
	if (-1 == pos)
	{
		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->date[pos].name, ps->date[pos].age, ps->date[pos].sex, ps->date[pos].tel, ps->date[pos].addr);
	}
}

void ModifyContact(struct Contact* ps)
{
	char name[MAX_NAME];
	printf("请输入要修改的人名字:>");
	scanf("%s", name);
	int pos = FindByName(ps, name);
	if (-1 == pos)
	{
		printf("要修改的人不存在\n");
	}
	else
	{
		// 修改相当于重新录入一边
		printf("请输入名字:>");
		scanf("%s", ps->date[pos].name);
		printf("请输入年龄:>");   
		scanf("%d", &(ps->date[pos].age));
		printf("请输入性别:>");
		scanf("%s", ps->date[pos].sex);
		printf("请输入电话:>");
		scanf("%s", ps->date[pos].tel);
		printf("请输入地址:>");
		scanf("%s", ps->date[pos].addr);
		printf("修改成功\n");
	}
}

void DestroyContact(Contact* ps)
{
	free(ps->date);
	ps->date = NULL;
}

void SaveContact(Contact* ps)
{
	FILE* pfWrite = fopen("contact.dat", "wb");
	if (pfWrite == NULL)
	{
		printf("%s\n", strerror(errno));
		return;   //函数的定义是void  所以这里返回就好了,不要加0
	}
	//写通讯录中的数据到文件中
	//一个一个的写入
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		fwrite(&(ps->date[i]), sizeof(PeoInfo), 1, pfWrite);
	}
	printf("保存成功\n");
	//关闭文件
	fclose(pfWrite);
	pfWrite = NULL;
}

void CheckCapacity(Contact* ps); 
void LoadContact(Contact* ps)
{
	PeoInfo Tmp = {0};
	FILE* pfRead = fopen("contact.dat","rb");
	if(pfRead == NULL)
	{
		printf("LoadContact:%s\n",strerror(errno));
		return 0;
	}
	//读取文件,放到通讯录中
	//fread 返回的是真实的读取到的数据个数
	while(fread(&tmp,sizeof(PeoInfo),1,pfRead))
	{
		CheckCapacity(ps);
		ps->date[ps->size] = tmp;
		ps->size++;
	}
	fclose(pfRead);
	pfRead = NULL;
	return 0;
}

对于这样一个代码包含了结构体,指针,内存管理,文件操作等C语言的进阶需要的知识点,在后续的日子里还需要不断的回看,回想代码,争取更加丰富功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值