挑战全网最细——C语言实现简单通讯录(静态版本)【手把手带你做项目系列】

        前言:今天我们开辟一个新的系列【手把手带你做项目系列】,带大家一起写一个通讯录,力争通过三种方式“静态版本、动态版本、文件存储版本”带领大家领略到项目的规范与严谨不断的进行优化和完善,提高自己的编程素养。

目录

项目要求:

1 静态通讯录的概要

2 静态通讯录接口函数实现

2.1 通讯录的基本结构

2.2 简易菜单

2.3 初始化通讯录

2.4 增加成员

2.5 打印通讯录

 2.6 删除联系人

 2.7 查找联系人

2.8 修改联系人

2.9 排序联系人(按名字)

2.10 完善与改进

3 完整代码


项目要求:

通讯录可以用来存储1000个人的信息,每个人的信息包括:姓名、性别、年龄、电话、住址

基本要求如下:

  1. 添加联系人信息
  2. 删除指定联系人信息
  3. 查找指定联系人信息
  4. 修改指定联系人信息
  5. 显示所有联系人信息
  6. 清空所有联系人
  7. 以名字排序所有联系人

1 静态通讯录的概要

        静态通讯录:使用的是定长数组,即数组的长度不能发生改变。我们可以设置通讯录可以记录的成员个数为1000个。

2 静态通讯录接口函数实现

文件名功能
Contact.c通讯录函数接口的实现
Contact.h通讯录相关的宏定义,头文件,接口函数的声明
test.c函数接口测试

2.1 通讯录的基本结构

通讯录是一个结构体

包含:1.通讯录成员数组,每一个通讯录成员(姓名、地址等)又是一个结构体。

          2.标志通讯录成员个数的变量。

注意:为了方便后序更改通讯录的各种大小,推荐使用宏定义

#define MAX 100
#define MAX_NAME 20   //名字最大长度
#define MAX_SEX 10    //性别最大长度
#define MAX_TELE 12   //电话最大长度
#define MAX_ADDR 30   //地址最大长度 

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

注意:未来如果你还想用这个类型,上面的写法就比较麻烦了,可以用typedef重定义 

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

2.2 简易菜单

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");
	printf("*********************************\n");

}

int main()
{
	int input = 0;
    PeoInfo data[1000];  //存1000人信息,仅仅这样可以吗?
	do
	{
		menu();
		printf("请选择>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			break;
		case 2:
			break;
		case 3:
			break;
		case 4:
			break;
		case 5:
			break;
		case 6:
			break;
		case 0:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

        在上面代码段中,仅仅 PeoInfo data[1000] ;是不够的,因为通讯录涉及增删查改,其剩余变量在发生变化。因此我们使用的时候需要一个变量来记录实际有效的人数信息。

PeoInfo data[1000];
int count = 0; //记录当前人数信息

        如果我们把上面这两个放在main函数实现,是不是会显得有些散乱且麻烦?那我们想是不是可以把这两个封装在一起?

//通讯录
typedef struct Contact
{
	PeoInfo data[MAX];  //存放人的信息
	int count;          //记录当前通讯录中实际人的个数
}Contact;

        诶有办法了,把它俩都放在一个结构体,找起来也会方便不少。

        好,我们再回到刚刚的主函数,此时我们可以这样改写

Contact con;  //通讯录

        创建好之后,我们是不是应该给它初始化一下?毕竟一个人的信息都没有,那我们给它初始化为全0吧。

Contact con = { 0 } //初始化通讯录,这样对吗?

        理论上可行,但是我们不推荐这么写,为什么呢?

        1、我们应该尽量把代码封装成函数的形式,模块化的去工作。

        2、如果我们想对它初始化的内容不能通过大括号的方式初始化,或者说想初始化指定的内容不方便,那我们有函数的方式初始化会方便很多。

	InitContact(&con); //初始化通讯录正确方式,注意我们要改内容应该传地址哦

        结构体传参尽量传地址,这样效率更高。

2.3 初始化通讯录

        我们准备使用 InitContact 这个函数,就去 contact.h 处声明一下。

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

        好,接下来我们去 contact.c 处实现一下函数。

        Tip:到 contact.c 处我们发现它不认识 InitContact 函数,我们要记得在前面添加头文件#include"contact.h"  这样它就记住了。

void InitContact(Contact* pc)
{
	assert(pc);  //断言一下,不能为空指针
	pc->count = 0;
	memset(pc->data, 0, sizeof(pc->data));  //设置内存大小
    //pc->data拿到了通讯录中PenInfo数组的数组名
    //数组名单独放在sizeof内部,计算的是整个数组的大小

    //memset(ps->data, 0, sizeof(PeoInfo) * MAX);	//(写法2)通讯录成员数组初始化为0
}

        我们希望 data 里面全是0,怎么做呢?此时我们就要请出 memset 函数,有小伙伴可能会感到陌生,这里再简述一下 memset 函数。        

void * memset ( void * ptr, int value, size_t num );
功能介绍:内存设置(memory set)是将某一块内存中的内容全部设置为指定的值,这个函数通常为新申请的内存做初始化工作。(以字节为单位初始化)
需要的头文件#include<string.h>

        memset 函数来设置我们的内存非常方便,如果不用 memset 函数,那就要考虑循环来初始化,这样就会比较繁琐。

        写到这里,我们来调试一下看看效果。

         初始化成功了,我们增加一个人的信息试试看吧。

2.4 增加成员

AddContact(&con);  //封装函数
void AddContact(Contact* pc);  //增加联系人到通讯录

          谨记:我们一定要清楚一段代码是怎么从无到有产生的,千万不敢从第一行开始一行一行的“抄代码”,千万不敢一个文件一个文件的写(写完.h 之后写.c),代码是哪里需要去哪里写!

//写法1:直接对通讯录成员数组进行操作
void AddContact(Contact* pc)
{
	assert(pc);
	if (pc->count == 1000)
	{
		printf("通讯录已满,无法增加\n");
		return;
	}
	printf("请输入姓名:\n");
	scanf("%s", pc->data[pc->count].name);
	printf("请输入年龄:\n");
	scanf("%d", &(pc->data[pc->count].age));  //需要取地址,数组不用
	printf("请输入性别:\n");
	scanf("%s", pc->data[pc->count].sex);
	printf("请输入电话:\n");
	scanf("%s", pc->data[pc->count].tele);
	printf("请输入地址:\n");
	scanf("%s", pc->data[pc->count].addr);

	pc->count++;
	printf("增加成功\n");
}

//写法2:先创建一个临时结构体成员,然后赋值后,赋给通讯录结构体数组中ps->size位置
void AddContact(Contact* ps)
{
	PeoInfo tmp = { 0 };
	//先判断通讯录是否满了
	if (ps->size == MAX)
	{
		printf("通讯录已满\n");
	}
	else
	{
		printf("请输入名字:");
		scanf("%s", tmp.name);	//数组名不用&
		printf("请输入年龄: ");
		scanf("%d", &(tmp.age));
		printf("请输入地址:");
		scanf("%s", tmp.addr);
		printf("请输入号码:");
		scanf("%s", tmp.tele);
		printf("请输入性别:");
		scanf("%s", tmp.sex);
 
		ps->data[ps->size] = tmp;
		printf("添加成功\n");
		ps->size++;
	}
}

2.5 打印通讯录

ShowContact(&con); //封装函数
void ShowContact(const Contact* pc);  //打印通讯录的信息
//这一次只是打印,不会更改,因此我们使用const来保护指针。
void ShowContact(const Contact* pc)
{
	assert(pc);
	int i = 0;
	printf("%-20s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
	// -是左对齐的意思,\t是空3格 
    for (i = 0; i < pc->count; i++)
	{
		printf("%-20s\t%-3d\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);
	}
}

        小习惯:当遇到长代码时我们可以回车一下增加可读性。

 2.6 删除联系人

        删除代码封装与前面相同,此处仅展示接口函数。

void DelContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	assert(pc);
	int i = 0;
	if(pc->count == 0)
	{
		printf("通讯录为空,没有信息可以删除\n");
		return;
	}
	printf("请输入要删除人的名字:\n");
	scanf("%s", name);
	//删除
	//1.查找
	int pos = FindByName(pc, name);  //封装查找函数,pos是位置
	if (pos == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}
	//2.删除
	for (i = pos; i < pc->count - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];  //从该位置开始后面的数据往前覆盖
	}
	pc->count--;
	printf("删除成功\n");
}

        我们思考一下,删除一个联系人是不是先要查找联系人?那查找、修改联系人是不是也要先查找联系人?既然查找这个功能用的比较多。我们不妨把查找封装成一个函数。同时我们不希望这个函数在.h文件中声明,只在contact.c 文件中给删除、修改函数提供支持,于是加上static修饰。

static int FindByName(Contact* pc, char name[])   //static代表只给本文件的函数用
{
	assert(pc);
	int i = 0;
	for (i = 0; i < pc->count; i++)
	{
		if (0 == strcmp(pc->data[i].name, name))  //字符串比较函数strcmp
		{
			return i;                             //返回下标
		}
	}
	return -1;
}
补充知识:
int strcmp ( const char * str1, const char * str2 );
功能介绍:字符串比较(string compare)
当s1<s2时,返回为数;当s1=s2时,返回值= 0;当s1>s2时,返回数。 
即:两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止。如:1."A"<"B"          2."A"<"AB"          3."compare"<"computer"
注意事项:该函数只能比较字符串,即可用于比较两个字符串常量,或比较数组和字符串常量,不能比较数字等其他形式的参数。

 运行截图

 2.7 查找联系人

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

}                                               //注意下标变为pos了 

2.8 修改联系人

void ModifyContact(Contact* pc)
{
	assert(pc);
	char name[MAX_NAME] = { 0 };
	printf("请输入要修改人的名字:");
	scanf("%s", name);
	//1.查找
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
	printf("已查找到要修改人的姓名,接下来开始修改\n");
	//2.修改(这个人所有信息重新录入一遍)
	printf("请输入姓名:\n");
	scanf("%s", pc->data[pos].name);
	printf("请输入年龄:\n");
	scanf("%d", &(pc->data[pos].age));  //需要取地址,数组不用
	printf("请输入性别:\n");
	scanf("%s", pc->data[pos].sex);
	printf("请输入电话:\n");
	scanf("%s", pc->data[pos].tele);
	printf("请输入地址:\n");
	scanf("%s", pc->data[pos].addr);
	printf("修改成功\n");
}

2.9 排序联系人(按名字)

void SortContact(Contact* pc)
{
	assert(pc);
	qsort(pc->data, pc->count, sizeof(PeoInfo), cmp_peo_by_name);
	printf("排序成功\n");
}

        我们之前学过利用qsort函数排序,下面简单回顾一下。

void qsort (void* base, size_t num, size_t size,
            int (*compare)(const void*,const void*));
需要的头文件:#include<stdlib.h>
功能介绍: qsort()函数(quick sort)是八大排序算法中的快速排序,能够排序任意数据类型的数组其中包括整形,浮点型,字符串甚至还有自定义的结构体类型。
参数介绍:1.首元素地址base:2.元素个数num 3.一个元素大小size 4.自定义比较函数compare
    compare分析:我们需要告诉qsort函数我们希望数据按照怎么的方式进行比较,比如对于几个字符串,我们可以比较字符串的大小(strcmp),也可以比较字符串的长度(strlen),因此我们要告诉qsort函数我们希望的比较方式,我们就需要传入一个比较函数compar就简写为cmp吧。

 

int cmp_peo_by_name(const void* e1, const void* e2)
{
	return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}   //strcmp的返回值恰好和cmp需要的返回值相同

运行截图:

排序前:

排序后:

2.10 完善与改进

        在前面主函数的switch语句中,每一个case对应一个数字比较简单。但是从工程性的角度,这样是不够规范的,因此我们引入枚举这个概念对代码进行优化,增加代码的可读性与可维护性。

enum Option
{
	EXIT,        //从0开始,每次+1
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};

int main()
{
	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:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}


关于枚举变量的进一步了解,详见博主的另一篇文章:自定义类型详解

3 完整代码

//contact.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<assert.h>
#include<stdlib.h>
#define MAX 100
#define MAX_NAME 10
#define MAX_SEX 10
#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;

//通讯录
typedef struct Contact
{
	PeoInfo data[MAX];  //存放人的信息
	int count;  //记录当前通讯录中实际人的个数
}Contact;


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

//增加联系人到通讯录
void AddContact(Contact* pc);

//打印通讯录的信息
void ShowContact(const Contact* pc);

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

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

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

//排序通讯录中的内容
void SortContact(Contact* pc);



//contact.c
#include"contact.h"

void InitContact(Contact* pc)
{
	assert(pc);
	pc->count = 0;
	memset(pc->data, 0, sizeof(pc->data));  //设置内存大小
}

void AddContact(Contact* pc)
{
	assert(pc);
	if (pc->count == 100)
	{
		printf("通讯录已满,无法增加\n");
		return;
	}
	printf("请输入姓名:\n");
	scanf("%s", pc->data[pc->count].name);
	printf("请输入年龄:\n");
	scanf("%d", &(pc->data[pc->count].age));  //需要取地址,数组不用
	printf("请输入性别:\n");
	scanf("%s", pc->data[pc->count].sex);
	printf("请输入电话:\n");
	scanf("%s", pc->data[pc->count].tele);
	printf("请输入地址:\n");
	scanf("%s", pc->data[pc->count].addr);

	pc->count++;
	printf("增加成功\n");
}


void ShowContact(const Contact* pc)
{
	assert(pc);
	int i = 0;
	printf("%-10s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
	for (i = 0; i < pc->count; i++)
	{
		printf("%-10s\t%-3d\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(Contact* pc, char name[])   //只给本文件的函数用
{
	assert(pc);
	int i = 0;
	for (i = 0; i < pc->count; i++)
	{
		if (0 == strcmp(pc->data[i].name, name))
		{
			return i;
		}
	}
	return -1;
}


void DelContact(Contact* pc)
{
	char name[MAX_NAME] = { 0 };
	assert(pc);
	int i = 0;
	if(pc->count == 0)
	{
		printf("通讯录为空,没有信息可以删除\n");
		return;
	}
	printf("请输入要删除人的名字:\n");
	scanf("%s", name);
	//删除
	//1.查找
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要删除的人不存在\n");
		return;
	}
	//2.删除
	for (i = pos; i < pc->count - 1; i++)
	{
		pc->data[i] = pc->data[i + 1];
	}
	pc->count--;
	printf("删除成功\n");
}

void SearchContact(Contact* pc)
{
	assert(pc);
	char name[MAX_NAME] = { 0 };
	printf("请输入要查找人的名字:");
	scanf("%s", name);
	//1.查找
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要查找的人不存在\n");
		return;
	}
	//2.打印
	printf("%-10s\t%-5s\t%-5s\t%-12s\t%-30s\n", "名字", "年龄", "性别", "电话", "地址");
	printf("%-10s\t%-3d\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);
	//1.查找
	int pos = FindByName(pc, name);
	if (pos == -1)
	{
		printf("要修改的人不存在\n");
		return;
	}
	printf("已查找到要修改人的姓名,接下来开始修改\n");
	//2.修改
	printf("请输入姓名:\n");
	scanf("%s", pc->data[pos].name);
	printf("请输入年龄:\n");
	scanf("%d", &(pc->data[pos].age));  //需要取地址,数组不用
	printf("请输入性别:\n");
	scanf("%s", pc->data[pos].sex);
	printf("请输入电话:\n");
	scanf("%s", pc->data[pos].tele);
	printf("请输入地址:\n");
	scanf("%s", pc->data[pos].addr);
	printf("修改成功\n");
}


int cmp_peo_by_name(const void* e1, const void* e2)
{
	return strcmp(((PeoInfo*)e1)->name, ((PeoInfo*)e2)->name);
}

//按照名字来排序
void SortContact(Contact* pc)
{
	assert(pc);
	qsort(pc->data, pc->count, sizeof(PeoInfo), cmp_peo_by_name);
	printf("排序成功\n");
}



//test.c
#include"contact.h"

enum Option
{
	EXIT,
	ADD,
	DEL,
	SEARCH,
	MODIFY,
	SHOW,
	SORT
};

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");
	printf("*********************************\n");

}

int main()
{
	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:
			printf("退出通讯录\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

 

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值