C语言实现通讯录项目

       通讯录项目虽然比较简单,但是其包含的知识面比较广,大家可以当作一次阶段性学习测验,检测一下自己对于C语言基础掌握得怎么样,当然若是接触C语言没有多久的小伙伴也可以复制一下代码,尝试阅读一下,提升自己兴趣,当然我也不是大佬,同样是学者一位,这个项目同样是我对我自己能力检验。

通信录要求:

存放好友信息:名字、电话、性别、住址、年龄。

有8个功能分别为

0. 退出通信录
1. 增加信息
2. 删除信息
3. 查找信息
4. 修改信息
5. 显示信息
6. 整理信息
7. 保存信息

        首先,咱们对于一个项目实现不能将功能写在一个文件当中,分文件操作相当有必要,我这次项目就分为启动文件,功能文件,以及声明所用的头文件。

 头文件讲解:

        创建一个结构体struct PeoInfo用于存放个人信息,在创建另一个结构体struct Cont_Book作为我们的通讯录,该结构体运用了动态内存开辟相关的知识。

        使用动态内存原因:我们不希望通讯录创建的大小被定死,需要一个能够灵活存储的空间,既不浪费空间,也不会因为空间不足而存放不了数据。

        后面的枚举只是为了让之后的选着语句让我们在开发时更方便书写,没有太多实际意义。

        最后则是函数声明,想要跨文件使用函数必备内容。

#ifndef __Contact_H__
#define __Contact_H__

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


#define MAX_NAME 20			//名字
#define MAX_SEX  6			//性别
#define MAX_TELE 12			//电话
#define MAX_ADDR 20			//地址
#define DEFAULT_SZ 3		//通讯录初始长度

//个人信息
struct PeoInfo
{
	char name[MAX_NAME];
	int  age;
	char sex[MAX_SEX];
	char tele[MAX_TELE];
	char addr[MAX_ADDR];
};

//通讯录
struct Cont_Book
{
	int size;		        //当前已经拥有的数量
	int capacity;	        //当前通讯录的最大容量
	struct PeoInfo * data;	//动态内存开辟空间
};

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

//功能函数声明
void Init_Cont(struct Cont_Book* ps);					//初始化
void Add_Contact(struct Cont_Book* ps);					//增加
void Show_Contact(const struct Cont_Book* ps);			//显示
void Del_Contact(struct Cont_Book* ps);					//删除
void Search_Contact(const struct Cont_Book* ps);		//查找
void Modify_Contact(struct Cont_Book* ps);				//修改
void Sort_Contact(struct Cont_Book* ps);				//排序
void Destroy_Contact(struct Cont_Book* ps);				//销毁
void Save_Contact(struct Cont_Book* ps);				//保存
void Load_Contact(struct Cont_Book* ps);				//读取

#endif

启动文件讲解:

        整个项目通过输入input选择功能,也利用input决定循环的退出条件。

        因为我们希望程序不管怎样都能够执行一次,所以使用do while循环,在循环体之前需要提前创建好通讯录结构体变量con方便之后函数使用,然后需要对整个通讯录的内容初始化操作,也就是为我们的通讯录建立起始环境,初始化会在之后讲解。

        在循环体内部搭建一个选择语句分别对应我们需要实现的功能。其选择的ADD、DEL等就是我之前在头文件当中建立的枚举内容,你们也可以直接写为数字。

#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 Cont_Book con;		//包含data指针,size,capacity
	//初始化通讯录
	Init_Cont(&con);
	do
	{
		//显示界面
		menu();
		printf("请选择功能》");
		scanf("%d", &input);
		//功能
		switch (input)
		{
		case ADD://增加
			Add_Contact(&con);
			break;
		case DEL://删除
			Del_Contact(&con);
			break;
		case SEARCH://查找
			Search_Contact(&con);
			break;
		case MODIFY://更改
			Modify_Contact(&con);
			break;
		case SHOW://显示
			Show_Contact(&con);
			break;
		case SORT://排序
			Sort_Contact(&con);
			break;
		case SAVE://保存
			Save_Contact(&con);
			break;
		case EXIT://退出
			Save_Contact(&con);
			Destroy_Contact(&con);
			printf("退出程序\n");
			break;
		default://输入错误
			printf("输入错误,请重新输入\n");
			break;
		}

	} while (input);

	return 0;
}

功能函数讲解:

       (1)初始化函数

        在启动文件当中我们调用了初始化函数,而初始化的功能表示我们需要对通讯录开辟空间,以及对起始计数值赋值。

        进入函数之后需要开辟动态内存,所以我们需要利用calloc()函数,其中DEFAULT_SZ宏定义为3,表示初始创建的内存大小能够装下3个人的信息。注意对动态内存指针需要有保护,即判断开辟空间是否正确。

        然后将计数值size赋值为0,将空间容量标记记为3。

        Load_Contact(ps)其为文件操作,表示读取并写入进通讯录已经存在的通讯录信息,后面我会单独讲解。

//声明
void Load_Contact(struct Cont_Book* ps);
//初始化
void Init_Cont(struct Cont_Book* ps)
{ 
    //开辟动态内存
	ps->data = (struct PeoInfo*)calloc(DEFAULT_SZ, sizeof(struct PeoInfo));
	if (ps->data == NULL)
	{
		return;
	}
	ps->size = 0;
	ps->capacity = DEFAULT_SZ;
	//加载文件信息
	Load_Contact(ps);
}

         (2)检测通讯录的容量大小

        在进行增加操作之前需要判断已经开辟的动态内存空间是否足够,如果不够则扩容,足够不做任何操作退出。

        扩容动态内存空间需要利用realloc()函数,传入参数为被扩容的动态内存空间地址,和被扩建之后的内存大小,该函数设计为每次扩容两个struct PeoInfo结构体大小的内存空间。注意realloc()的使用不能为自己对自己扩建。

        例如:int* p = realloc(p, 40);

        这样写在扩建失败时会丢失原来的地址,让我们再也找不到这一片空间,除非整个项目关闭才能删除这一片空间的内容,具体内容需要你们自己去了解啦^ . ^

        正确写法就是写一个中间变量存储新空间,在新空间扩建成功之后再赋值给我们原来哪一个变量。注意容量需要加二。

//检测容量
void Check_Capacity(struct Cont_Book* ps)
{
	if (ps->size == ps->capacity)
	{
        //扩展通讯录大小
		struct PeoInfo* ptr = realloc(ps->data, (ps->capacity + 2)*sizeof(struct PeoInfo));
		if (ptr != NULL)
		{
			ps->data = ptr;
			ps->capacity += 2;
			printf("扩容成功\n");
		}
		else
		{
			printf("扩容失败\n");
		}
	}
}

        (3)添加信息函数

        该函数太简单了,就是利用scanf()输入我们的个人信息,在结束时对计数值size加一表示通讯录当中多一个联系人。

//添加
void Add_Contact(struct Cont_Book* ps)
{
    //检测容量是否足够
	Check_Capacity(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");

}

        (4)显示信息函数

        显示有两种情况,在通讯录中没有内容时不用显示,需要要提示通讯录为空;有内容时通过计数值size作为循环结束条件,输出每一个联系人的信息。

//显示
void Show_Contact(const struct Cont_Book* ps)
{
	if (ps->size == 0)
		printf("Empty Contact_Book\n");
	else
	{
        //显示格式
		printf("%-10s\t%-4s\t%-6s%-12s%-20s\n", "名字", "年龄", "性别", "电话", "地址");
		int i = 0;
		for (i = 0; i < ps->size; i++)
		{
            显示内容
			printf("%-10s\t%-4d\t%-6s%-12s%-20s\n",
				ps->data[i].name,
				ps->data[i].age,
				ps->data[i].sex,
				ps->data[i].tele,
				ps->data[i].addr);
		}
	}
}

        (5)查找下标函数

        删除、查找、修改这三个功能都需要找到通讯录当中特定的某一个联系人,所以我们特地封装一个查找下标的函数。

        作为找下标函数的前提就是需要知道是依靠什么来匹配的。该函数中利用成员的名字匹配,你们可以改为其它内容,这里并没有硬性条件。所以参数的参数应该为通讯录以及成员名字。

        该函数也是利用计数值size遍历匹配,通过strcmp()函数比较通讯录中的名字和我们输入的函数。这里需要注意用法,当完全匹配时strcmp函数返回值为0。找到对应值返回下标,没找到返回-1,用于其它函数操作。

//找成员下标
static int Find_Name_PS(const struct Cont_Book* ps, char name[MAX_NAME])
{
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
        //利用成员名字查找
		if (strcmp(ps->data[i].name, name) == 0)
		{
			//找到,返回下标
			return i;
		}
	}
	//没找到,返回-1
	return -1;
}

        (6)删除信息函数

        添加一个判断,在通讯录中没有数据信息时不对通讯录操作。通讯录中有信息时,通我们之前的查找下标函数找到对应值然后删除数据。删除数据有两种方法(就我而言),两种操作并没有区别,复杂程度相同,你们可以任意选择。

        第一种是将最后一个联系人填补在被删除联系人的位置,不过该方法会改变我们之前的排序方式,虽然我们有排序手段,但是会多一步操作。

        第二种是将被删除联系人后的所有联系人向前移动一位,既不改变原本排序方式,又删除了指定联系人的数据。本次函数的实现就是通过第二种方式实现。

        通过之前利用查找下标函数返回的下标作为循环起始条件,表示被删除人之后的哪一位联系人,以计数值size-1作为循环结束条件,因为已经被删除了一个人那么只需要循环size-1-pos次就能够完成操作。

        删除成功之后需要对计数值size减一操作,让其与整个通讯录对齐。

//删除
void Del_Contact(struct Cont_Book* ps)
{
	char name[MAX_NAME];
	if (ps->size == 0)
		printf("没有可删除数据\n");
	else
	{
		printf("请输入被删除人的名字》");
		scanf("%s", name);
		int pos = Find_Name_PS(ps, name);    //找下标
		int j = 0;
		if (pos == -1)
			printf("没有对应成员\n");
		else
		{
			for (j = pos; j < ps->size -1; j++)
			{
				ps->data[j] = ps->data[j + 1];
			}
			printf("删除成功\n");
			ps->size--;
		}
	}
}

        (7)查找信息函数

        该函数的实现功能与显示函数几乎相等,只是取消了循环,加入了显示特定人的下标。就能够实现单独某人的信息显示。

       

//查找
void Search_Contact(const struct Cont_Book* ps)
{
	char name[MAX_NAME];
	if (ps->size == 0)
		printf("没有可查找数据\n");
	else
	{
		printf("请输入被查找人的名字》");
		scanf("%s", name);
		int pos = Find_Name_PS(ps, name);       //找下标
		int j = 0;
		if (pos == -1)
			printf("没有对应成员\n");
		else
		{
			printf("%-10s\t%-4s\t%-6s%-12s%-20s\n", "名字", "年龄", "性别", "电话", "地址");
			printf("%-10s\t%-4d\t%-6s%-12s%-20s\n",
				ps->data[pos].name,
				ps->data[pos].age,
				ps->data[pos].sex,
				ps->data[pos].tele,
				ps->data[pos].addr);
		}
	}
}

        (8)修改信息函数

        该函数同样是通过名字查找下标然后找对对应联系人信息,然后逐步输入新的数据,中间没有其它新的内容,不做赘述。

//修改
void Modify_Contact(struct Cont_Book* ps)
{
	char name[MAX_NAME];
	if (ps->size == 0)
		printf("没有可修改数据\n");
	else
	{
		printf("请输入被修改人的名字》");
		scanf("%s", name);
		int pos = Find_Name_PS(ps, name);    //找下标
		int j = 0;
		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);
		}
	}
}

        (9)排序信息函数

        排序操作我利用了qsort()函数实现操作,具体操作请看我博客内写好的关于qsort()函数的使用,以及仿写一个有相同功能的排序函数。中间有用到回调函数相关知识。

//作为排序的回调函数
int Cmp_Stu_Age(const void* e1, const void* e2)
{
	return ((struct PeoInfo*)e1)->age - ((struct PeoInfo*)e2)->age;
}

//排序
void Sort_Contact(struct Cont_Book* ps)
{
	if (ps->size <= 1)
		printf("没有可排序数据\n");
	else
	{
        //传参内容(通讯录,排序个数,每一个数据字节长度,回调函数名(比较方式))
		qsort(ps->data, ps->size, sizeof(ps->data[0]), Cmp_Stu_Age);
	}
	printf("排序成功\n");
}

        (10)保存通讯录数据

        该函数利用的文件操作相关的函数,首先创建一个文件,用于存放联系人信息,对于存放文件的指针需要判断是否成功创建。利用循环以计数值size为结束条件,将所有的信息存入进文件当中。最后关闭文件。

//保存通讯录数据
void Save_Contact(struct Cont_Book* ps)
{
	//保存文件信息
	FILE* pf = fopen("contact.dat", "wb");
	if (pf == NULL)
	{
        //报文件操作失败原因
		printf("Save_Contact:%s\n", strerror(errno));
		return;
	}
	//存入数据
	int i = 0;
	for (i = 0; i < ps->size; i++)
	{
		//保存通讯录信息
		fwrite(&(ps->data[i]), sizeof(struct PeoInfo), 1, pf);
	}
	fclose(pf);
	pf = NULL;
}

        (11)加载文件当中的联系人信息函数

        功能:当我们文件当中有数据,那么我们会先将这些数据放入我们的通讯录当中。中间利用到了fread()函数,读取文件当中的数据。利用中间变量temp将得到的数据放入通讯录当中,中间调用我们之前自己封装的Check_Capacity()函数在容量不够时增加容量。因为fread()函数在检测到文件没有数据时会返回0,所以利用该函数作为循环体的循环条件。

//加载文件信息
void Load_Contact(struct Cont_Book* ps)
{
    //中间变量
	struct PeoInfo temp = { 0 };
	//读取文件路径
	FILE* pf = fopen("contact.dat", "rb");
	if (pf == NULL)
	{
		printf("Load_Contact:%s\n", strerror(errno));
		return;
	}
	//读取通讯录信息
	while (fread(&temp, sizeof(struct PeoInfo), 1, pf))
	{
        //检车容量
		Check_Capacity(ps);
		ps->data[ps->size] = temp;
		ps->size += 1;
	}
	fclose(pf);
	pf = NULL;
}

        (12)通讯录使用完毕,销毁开辟的动态内存空间

        销毁空间即表示每一次动态内存的开辟都对应一个free()函数操作,防止空间一直占用内存。

//销毁空间
void Destroy_Contact(struct Cont_Book* ps)
{
	free(ps->data);
	ps->data = NULL;
}

结果:

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值