C/C++实现多版本通讯录,干货满满!

前言

这篇博客将应用C语言实现通讯录管理系统
通讯录要求如下:

通讯录能够存放联系人的复杂信息,包括:姓名、年龄、性别、电话、地址。
通讯录能够提供如下功能:
1、存放1000人信息
2、增加新的联系人信息
3、删除指定联系人信息
4、查找指定联系人信息
5、修改指定联系人信息
6、重新为通讯录排序
7、打印通讯录列表
8、指定位置插入新联系人信息

通讯录将写成项目工程模式,代码将由三部分组成
在这里插入图片描述

通讯录效果初步演示

菜单比较长,为了截图方便点,故先将菜单放在循环外在这里插入图片描述

数据类型设计

创建联系人结构体

如果要存放一个人的复杂信息,包括姓名(name),年龄(age),性别(sex),电话(phone),地址(address)等,单独单独创建变量,或者数组都不好处理,所以首选结构体,我将它命名为People。

//符号常量声明
//优点:便于管理
#define NAME_MAX 20
#define SEX 5
#define PHONE 12
#define ADDRESS 20

//定义联系人结构体并将其重命名为People
//优点:增强代码可读性;减少书写工作量
typedef struct People
{
	char name[NAME_MAX];
	int age;
	char sex[SEX];
	char phone[PHONE];
	char address[ADDRESS];
}People;

创建通讯录结构体

#define MAX 1000
//定义通讯录结构体,重命名为Contact
typedef struct Contact
{
	People data[MAX];//声明联系人数组,形成一个能够存放1000信息的通讯录列表
	int sz;			 //变量sz是用来记录列表中已经存放了几个联系人的信息
}Contact;

为什么要嵌套使用结构体?
当实现比如说增加、删除、打印的功能时,需要一个变量记录联系人数量变动,相较于额外创建一个变量,这种写法能够减少函数传参时参数的个数

初始化通讯录IntContact

在这里插入图片描述
在写函数之前有一个问题:函数该传值还是传址?

在这里很明显传通讯录的地址过去比较好,理由:
1.造成严重的空间浪费。传变量过去函数会再次开辟一个与通讯录大小相同的内存空间,而通讯录所占内存空间的大小为(People结构体的大小 * 1000)
2.我们要能够访问、修改通讯录的内容

代码如下:

//初始化结构体
void IntContact(Contact* con)
{
	assert(con);//断言指针,提高程序的安全性,在地址为空时强行中止程序
	memset(con->data, 0, sizeof(con->data));//初始化结构体数组
	con->sz = 0;//信息位置也要初始化,每当信息数量加一,sz+1.
}

功能设计与实现

添加联系人AddContact

功能介绍:

1.、能够判断通讯录是否满员
2、未满员前能够添加通讯录下成员
该功能位于主框架第一条分支

代码如下:

void AddContact(Contact* con)
{
	assert(con);
	if (con->sz == MAX)//当通讯录容量已达上限
	{
		printf("您的通讯录列表已满员,无法添加\n");
		return;
	}
	printf("请输入姓名:>");
	(void)scanf("%s", con->data[con->sz].name);//date是一个数组,不要忘记加[]
	printf("请输入年龄:>");
	(void)scanf("%d", &con->data[con->sz].age);//字符数组名本身是一个地址,但age不是,要加&
	printf("请输入性别:>");
	(void)scanf("%s", con->data[con->sz].sex);
	printf("请输入电话:>");
	(void)scanf("%s", con->data[con->sz].phone);
	printf("请输入地址:>");
	(void)scanf("%s", con->data[con->sz].address);
	printf("本次输入成功\n");
	
	con->sz++;//记录信息个数
}

控制台打印通讯录PrintContact

该功能位于之框架第六条分支
先写出这个是因为打印能够帮助我们更直观地理解、修改

代码如下:

//打印通讯录
void PrintContact(Contact* con)
{
	assert(con);
	int i = 0;
	printf("%-10s\t%-5s\t%-5s\t%-12s\t%-20s\n", "姓名", "年龄", "性别", "电话号码", "地址");
	//\t 的意思是 :水平制表符。将当前位置移到下一个tab位置。
	//%10s - 表示输出的宽度为10,其他数字同理
	//负号 - 表示从默认的右对齐改为打印数据左对齐
	
	for (i = 0; i < con->sz; i++)
	{
		printf("%-10s\t%-5d\t%-5s\t%-12s\t%-20s\n", 
				con->data[i].name,
				con->data[i].age,
				con->data[i].sex,
				con->data[i].phone,
				con->data[i].address);
		要打印的联系人信息不止一个 -> 循环
	}

查找联系人信息

在实现查找之前,我们来思考一个问题:查找的标准是什么?

通讯录成员的信息有五项,我们似乎有很多标准,但是实际上以年龄、性别、地址为标准查找除的联系人信息是不靠谱的,剩下的只有姓名和电话号码了。
问题来了,你说,如果我还记得电话号码,那我为啥要翻通讯录?
因此,我将姓名定义为查找标准

代码如下:

//查找联系人信息
void SearchContact(Contact* con)
{
	assert(con);
	char name[NAME_MAX] = { 0 };
	printf("请输入要查找的联系人的名字:>");
	(void)scanf("%s", name);
	int pos = 0;			//用变量pos记录查找到的联系人的在数组中的下标
	int i = 0;
	for (i = 0; i < con->sz; i++)
	{
		if (strcmp(name, con->data[i].name) == 0)
		{
			pos = i;
		}
	}
	if (pos == -1)
	{
		printf("您要修改的人的信息不存在\n");
		return;
	}
	else
	{
		printf("您要查找的联系人信息如下\n");
		printf("%-10s\t%-5s\t%-5s\t%-12s\t%-20s\n", 
		"姓名", "年龄", "性别", "电话号码", "地址");
		
		printf("%-10s\t%-5d\t%-5s\t%-12s\t%-20s\n",
			con->data[pos].name,
			con->data[pos].age,
			con->data[pos].sex,
			con->data[pos].phone,
			con->data[pos].address);
	}
}

在这里插入图片描述
修改后如下:

//查找联系人的姓名
static int FindName(char name[], Contact* con)
{
	assert(con);
	int i = 0;
	for (i = 0; i < con->sz; i++)
	{
		if (strcmp(name, con->data[i].name) == 0)
		{
			return i;
		}
	}//有个小缺陷,如果联系人重名了咋办 - 虽然一般来说正常人的通讯录基本不会有同名的
	return -1;//表示找不到
}
//查找联系人信息
void SearchContact(Contact* con)
{
	assert(con);
	char name[NAME_MAX] = { 0 };
	printf("请输入要查找的联系人的名字:>");
	(void)scanf("%s", name);
	int pos = FindName(name, con);
	if (pos == -1)
	{
		printf("您要修改的人的信息不存在\n");
		return;
	}
	else
	{
		printf("您要查找的联系人信息如下\n");
		printf("%-10s\t%-5s\t%-5s\t%-12s\t%-20s\n", "姓名", "年龄", "性别", "电话号码", "地址");
		printf("%-10s\t%-5d\t%-5s\t%-12s\t%-20s\n",
			con->data[pos].name,
			con->data[pos].age,
			con->data[pos].sex,
			con->data[pos].phone,
			con->data[pos].address);
	}
}

修改联系人信息ModifyContact

修改,说白了就是为指定位之的结构体重新赋值
但是修改的前提是,我没们要修改的联系人信息存在,所以要加入判断语句

代码如下:

//修改联系人信息
void ModifyContact(Contact* con)
{
	assert(con);
	char name[NAME_MAX] = { 0 };
	printf("请输入要修改的联系人的名字:>");
	(void)scanf("%s", name);
	int pos = FindName(name, con);
	if (pos == -1)
	{
		printf("您要修改的人的信息不存在\n");
		return;
	}
	else
	{
		printf("已成功为您找到该联系人\n");
		printf("请输入姓名:>");
		(void)scanf("%s", con->data[pos].name);
		printf("请输入年龄:>");
		(void)scanf("%d", &con->data[pos].age);
		printf("请输入性别:>");
		(void)scanf("%s", con->data[pos].sex);
		printf("请输入电话:>");
		(void)scanf("%s", con->data[pos].phone);
		printf("请输入地址:>");
		(void)scanf("%s", con->data[pos].address);
		printf("本次修改成功\n");
	}
}

删除联系人DeleteContact

功能介绍:
输入要删除的联系人的名字,判断该联系人是否存在,存在的话,执行删除
原理就是后面的元素向前覆盖
在这里插入图片描述
代码如下:

void DeleteContact(Contact* con)
{
	assert(con);
	char name[NAME_MAX] = { 0 };

	if (con->sz == 0)
	{
		printf("您的通讯录列表为空,无需删除\n");
		return ;
	}
	printf("请输入您要删除的联系人姓名:>");
	(void)scanf("%s", name);

	int pos = FindName(name, con);

	//printf("%d\n", pos);
	if (pos == -1)
	{
		printf("您要删除的人不存在\n");
		return ;
	}
	else
	{
		int i = 0;
		for (i = pos; i < con->sz-1; i++)
		{
			con->date[i] = con->data[i + 1];
			//想相同类型的结构体能够直接用赋值号
		}
	}
//举例:
//如果pos = 3,con->sz = 5,循环走两次,第五个位置的信息没有被空白覆盖
//但其实也不需要被覆盖,当增加新的联系人时会覆盖掉
	con->sz--;
	printf("删除成功\n");
}

排序通讯录

排序首先要定一个排序的标准,这里我提供了两种标准:年龄,姓名。
为了更好地实现排序,我采用库函数qsort(快排)
代码如下:

//void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
//以姓名为基准进行排序
int SortByName(const void* e1, const void* e2)
{
	assert(e1 && e2);
	return strcmp(((People*)e1)->name, ((People*)e2)->name);
}

//以年龄为基准进行排序
int SortByAge(const void* e1, const void* e2)
{
	assert(e1 && e2);
	return ((People*)e1)->age - ((People*)e2)->age;
}


//重新排序通讯录
void SortContact(Contact* con)
{
	assert(con);
	//printf("测试\n");
	if (con->sz == 0)
	{
		printf("您的通讯录列表为空,无需排序\n");
		return;
	}
	int input = 0;
	printf("请问您要选择哪种顺序来排序通讯录\n");
	printf("输入 1 表示以姓名为标准\n");
	printf("输入 2 表示以年龄为标准\n");
	printf("输入 0 表示不排序\n");
	printf("请问您的选择是:>");
	(void)scanf("%d", &input);
	switch (input)
	{
	case 1:
		//以姓名为基准进行排序
		qsort(con->data, con->sz, sizeof(con->data[0]), SortByName);
		break;
	case 2:
		//以年龄为基准进行排序
		qsort(con->data, con->sz, sizeof(con->data[0]), SortByAge);
		break;
	case 0:
		printf("退出排序功能\n");
		break;
	default:
		printf("输入错误请,重新输入:>");
		break;
	}
	printf("排序成功\n");
}

指定位置插入联系人

插入联系人之前判断通讯录是否满员,有空闲空间就执行插入操作
插入简单来说就是在数组中的某个地方腾出一个位置来放我们想放的信息
在这里插入图片描述
后面的元素都往后挪动为插入信息腾出一个空间,最后通讯录列表中的联系人数量比原来+1,与删除相比是一个相反的操作
代码如下

void InsertContact(Contact* con)
{
	assert(con);
	//判断当前通讯录联系人信息数量是否达到上限
	if (con->sz == MAX)
	{
		printf("您的通讯录列表已满,无法插入\n");
		return;
	}
	int input1 = 0, input2 = 0;
	printf("请问您要在哪个位置进行信息插入:>");
	(void)scanf("%d", &input1);
	printf("请问您要插入几个联系人的信息:>");
	(void)scanf("%d", &input2);

	//在执行插入操作之前要对信息进行移动
	int j = 0;
	for (j = con->sz-1; j >= input1-1; j--)
	{
		con->data[j + input2] = con->data[j];
		con->sz += input2;
	}
	int i = 0;									
	for (i = input1-1; i < input1-1 + input2; i++)
	{
		//执行插入操作
		printf("请输入联系人信息\n");
		printf("请输入姓名:>");
		(void)scanf("%s", con->data[i].name);
		printf("请输入年龄:>");
		(void)scanf("%d", &con->data[i].age);
		printf("请输入性别:>");
		(void)scanf("%s", con->data[i].sex);
		printf("请输入电话:>");
		(void)scanf("%s", con->data[i].phone);
		printf("请输入地址:>");
		(void)scanf("%s", con->data[i].address);
		printf("本次修改成功\n");
	}
}

至此,作为零部件的各功能的函数已经实现好了

主体框架一之switch-case模式

各功能的函数已经实现好了,又怎么少得了主心骨main函数呢,第一种方法,我采取比较常见的多重分支switch - case
代码如下:

//包含自定义头文件
#include "Contact.h"
//打印菜单
void menu(void)
{
	printf("==================================\n");
	printf("===========1.增加联系人==========*\n");
	printf("===========2.删除联系人==========*\n");
	printf("===========3.查找联系人==========*\n");
	printf("===========4.修改联系人==========*\n");
	printf("===========5.排序联系人==========*\n");
	printf("===========6.打印联系人==========*\n");
	printf("===========7.插入联系人==========*\n");
	printf("===========0.退出程序=============\n");
	printf("==================================\n");

}
//定义枚举常量
enum Choose
{
	EXIT1,  //默认值从0开始
	ADD,    //1
	DELETE, //2
	SEARCH, //3
	MODIFY, //4
	SORT,	//5
	PRINT,	//6
	INSERT	//7
};
int main(void)
{
	//定义变量存放指令
	int input = 0;
	do
	{
		menu();//打印菜单
		printf("请选择您要进行的操作:>");
		(void)scanf("%d", &input);
		switch (input)
		{
		case ADD:
			//增加联系人
			break;
		case DELETE:
			//删除联系人
			break;
		case SEARCH:
			//查找联系人信息
			break;
		case MODIFY:
			//修改联系人信息
			break;
		case SORT:
			//重新排序通讯录
			break;
		case PRINT:
			//打印联系人信息
			break;
		case INSERT:
			//在通讯录的某个位置插入某个一个联系人的信息
			break;
		case EXIT:
		printf("退出程序\n");
			break;
		default:
		printf("输入错误,请您重新输入!\n");
			break;
		}
	} while (input);//注意点:do - while后面要加分号

	return 0;
}

根据输入的 input 的值的不同,程序会执行不同的分支,从而实现不同的功能。
枚举的作用在于定义常量,来替代 case 后面的数字,从而增加代码的可读性。

主体框架二之函数指针数组模式

但是,随着通讯录功能的增加,case分支将会越来越多,代码将越来越长,越来越累赘,那么有什么简化的方法吗?
答案是,肯定的。
我们来观察一下各个函数的声明

//增加联系人
void AddContact(Contact* con);
//打印通讯录
void PrintContact(Contact* con);
//删除联系人
void DeleteContact(Contact* con);
//修改联系人信息
void ModifyContact(Contact* con);
//查找联系人信息
void SearchContact(Contact* con);
//重新排序通讯录
void SortContact(Contact* con);
//指定位置插入联系人信息
void InsertContact(Contact* con);

显然,除了函数名不一样外,函数的参数甚至返回类型都是一样的,这样恰好符合构成函数指针数组的条件

void (*pContact[8])(Contact * con)

经过优化后的代码如下:

int main(void)
{
	//定义变量存放指令
	int input = 0;
	//通讯录变量
	Contact con;
	//初始化通讯录
	IntContact(&con);
	do
	{
		menu();//打印菜单
		printf("请选择您要进行的操作:>");
		(void)scanf("%d", &input);

		void (*pContact[8])(Contact * con) = 
		{ NULL, AddContact,DeleteContact,
		SearchContact, ModifyContact, SortContact,
		PrintContact, InsertContact 
		};
//注意点:如果要符合1-7的指令,则要8个元素,函数指针数组首元素存放空指针

		if (input >= 1 && input <= 7)
		{
			pContact[input](&con);
		}
		else if (input == 0)
		{
			printf("退出程序\n");
		}
		else
		{
			printf("您的指令输入错误,请重新输入!\n");
		}

	} while (input);//注意点:do - while后面要加分号

	return 0;
}

看,这样写是不是感觉既简洁,又高大上起来了

优化之动态版本

直接用一个数组来作为通讯录不是不行,但我们仔细想一下,一个1000人的通讯录我们真的能够一下子用完吗,显然是不能的,这样子就会造成一大片空间一直不被使用,对提高内存利用率来说毫无帮助

主框架改动

在这里插入图片描述

动态版通讯录结构体

1.直接使用结构体数组已经不合适,由于malloc返回的是一个指针,所以用一个指针变量来管理
2.变量 sz 记录目前通讯录中联系人的信息数量
3.变量 capacity 记录目前通讯录的最大容量,在通讯录扩容后,该变量也要更新
代码如下:

//结构体Contact存放通讯录 - 动态版本				
typedef struct Contact
{
	People* date;
	int sz;	
	int capacity;
}Contact;

动态版初始化

代码如下:

#define DEFAULT 3//通讯录默认大小
#define INCREASE 2//每次扩容增加的大小
//初始化结构体 - 动态版本
void IntContact(Contact* con)
{
	assert(con);
	con->data = (People*)malloc(DEFAULT * sizeof(People));
	//判断,空间开辟是否成功
	if (con->data == NULL)
	{
		printf("空间开辟失败\n");
		return;
	}
	con->sz = 0;				
	con->capacity = DEFAULT;
}

动态版添加联系人

改动地方不大,在增加到上限时,自动扩容通讯录
代码如下:

增加联系人
void AddContact(Contact* con)
{
	assert(con);
	if (con->sz == con->capacity)//当通讯录容量已达上限,考虑扩容
	{
		printf("通讯录成员已满,程序将为你扩容\n");
		people* str = (People*)realloc(con->data, (con->capacity + INCREASE) * sizeof(People));
		//判断是否成功开辟新的空间
		if (str == NULL)
		{
			printf("扩容失败\n");
			return;
		}
		else
		{
			con->data = str;		  //将新空间的地址交给date管理
			con->capacity += INCREASE;//提高上限
			Sleep(700);//
			printf("扩容成功\n");
		}
	}
	printf("请输入姓名:>");
	(void)scanf("%s", con->data[con->sz].name);//date是一个数组,不要忘记加[]
	printf("请输入年龄:>");
	(void)scanf("%d", &con->data[con->sz].age);//字符数组名本身是一个地址,但age不是,要加&
	printf("请输入性别:>");
	(void)scanf("%s", con->data[con->sz].sex);
	printf("请输入电话:>");
	(void)scanf("%s", con->data[con->sz].phone);
	printf("请输入地址:>");
	(void)scanf("%s", con->data[con->sz].address);
	printf("本次输入成功\n");
	
	con->sz++;//记录信息个数
}

动态版插入联系人

改动与动态版添加功能类似
代码如下:

void InsertContact(Contact* con)
{
	assert(con);
	if (con->sz == con->capacity)//当通讯录容量已达上限,考虑扩容
	{
		printf("通讯录成员已满,程序将为你扩容\n");
		people* str = NULL;
		str = (People*)realloc(con->data, (con->capacity + INCREASE) * sizeof(People));
		//判断是否成功开辟新的空间
		if (str == NULL)
		{
			printf("扩容失败\n");
			return;
		}
		else
		{
			con->data = str;		  //将新空间的地址交给date管理
			con->capacity += INCREASE;//提高上限
			Sleep(700);				  //暂缓0.7秒打印扩容成功
			printf("扩容成功\n");
		}
	}

	int input1 = 0, input2 = 0;
	printf("请问您要在哪个位置进行信息插入:>");
	(void)scanf("%d", &input1);
	printf("请问您要插入几个联系人的信息:>");
	(void)scanf("%d", &input2);

	//在执行插入操作之前要对信息进行移动(数据备份)
	int j = 0;
	for (j = con->sz-1; j >= input1-1; j--)
	{
		con->data[j + input2] = con->date[j];
											 
	}										 
	con->sz += input2;
	//开始插入
	int i = 0;									
	for (i = input1-1; i < input1-1 + input2; i++)
	{
		//执行插入操作
		printf("请输入联系人信息\n");
		printf("请输入姓名:>");
		(void)scanf("%s", con->data[i].name);
		printf("请输入年龄:>");
		(void)scanf("%d", &con->data[i].age);
		printf("请输入性别:>");
		(void)scanf("%s", con->data[i].sex);
		printf("请输入电话:>");
		(void)scanf("%s", con->data[i].phone);
		printf("请输入地址:>");
		(void)scanf("%s", con->data[i].address);
		printf("本次修改成功\n");
	}
}

销毁通讯录

对于动态申请的空间,有两种释放的方法:
1、手动释放
2、关闭程序时自动释放
但是,手动释放动态申请的内存空间任然是一个好习惯,这样子可以避免产生许多bug
代码如下:

//释放通讯录空间 - 动态版本
void DestoryContact(Contact* con)
{
	free(con->data);
	con->data = NULL;
	con->sz = 0;
	con->capacity = 0;
}

C++重构

# 未完待续
虽然看起来这个通讯录已经很完善了,但其实还存在很多缺点
比如说:
1、当我们扩容到100个联系人时,确只用了20个空间,浪费了80个,这时我们自动释放一部分的空间来提高空间利用率。
2、以上内容都是以顺序表的形式存储的,但是我们也能够应用链表的的形式
3、还有查找功能,怎么实现只输入一个字,却要输出含有该字的联系人的信息
…………
问题还用很多,本人的知识储备不断完善时,将会逐渐完善它。

首先说一下“平台更换”是什么意思,C语言版本的通讯录原本是在Windows上用Microsoft Visual Studio 2022写的代码,编译之后运行程序就在控制台(也就是那个黑框框)上看到我们写的菜单,就像下面的图里这样,给人一种很丑的感觉,不过实际上这也正常,因为我们是在Windows操纵系统上运行的,本身我们对于Windows的印象就是图形化界面然后鼠标点点点,这个界面确实有点多次一举。
在这里插入图片描述

而对Linux有了一定了解之后,个人发现通讯录其实更加适合在Linux下运行,就像下面这样
在这里插入图片描述

最后考虑了一下,干脆按照Linux的使用风格用C++对这个小程序进行了重构。

功能演示

在当前通讯录项目的目录下输入./addressbook启动程序,输入一些友好的辅助信息。
在这里插入图片描述

按照提示信息输入help就能够获取帮助手册
在这里插入图片描述

输入add命令增加一个联系人
在这里插入图片描述

输入show显示当前通讯录列表
在这里插入图片描述
通讯录支持同名联系人存在,但是要求电话号码不能相同,search功能是按姓名查询,如果有两个张三,search会同时输出这两个人的信息
在这里插入图片描述

输入delete时,它会先按名字删除,如果存在同姓名的联系人则会再要求输入要删除的联系人的电话号码
在这里插入图片描述
在这里插入图片描述
modify和删除类似,也是先按名字修改,如果存在同名联系人的话就得用电话执行修改哪个联系人的信息,只有一项是会直接进行修改,特别的在于增加了一点措施,只对我们想要改的地方才进行修改:
在这里插入图片描述

Main.cc

#include "AddressBook.hpp"

int main()
{
    EnableScreen();
    std::cout << "欢迎使用通讯录!" << std::endl;
    std::cout << "可用命令: show, add, delete, search, modify, help, quit" << std::endl;
    std::cout << "输入 'help' 查看更多可用命令的详细信息。" << std::endl;
    std::cout << std::endl;

    AddressBook addressbook;
    std::string command_str;

    while (true)
    {
        std::cout << "addressbook> ";
        getline(std::cin, command_str);

        // 输入 "quit" 退出进程
        if (command_str == "quit")
        {
            break;
        }

        // 在通讯录内建指令集command_map中查找command_str是否存在
        // 不存在就输出 "You have entered an incorrect command"
        auto ret_iter = addressbook._command_map.find(command_str);
        if (ret_iter == addressbook._command_map.end())
        {
            LOG(ERROR, "You have entered an incorrect command: %s\n", command_str.c_str());
        }
        // 存在就调用该指令字符串对应的函数方法
        else
        {
            addressbook._command_map[command_str]();
        }
    }

    return 0;
}

AddressBook.hpp

#ifndef __ADDRESSBOOK_HPP__
#define __ADDRESSBOOK_HPP__

#include "Log.hpp"

#include <iostream>
#include <string>
#include <list>
#include <map>
#include <functional>
#include <limits>  // for std::numeric_limits
#include <iomanip> // iomanip、codecvt、locale for character width
#include <codecvt>
#include <locale>
#include <vector>    // for sameNameContacts
#include <algorithm> // for find_if
struct Contact
{
    void IOSet()
    {
        std::cout << "请输入姓名: ";
        getline(std::cin, _name);

        std::cout << "请输入年龄: ";
        std::cin >> _age;
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 处理 \n
        // std::numeric_limits<std::streamsize>::max() 确保清除输入缓冲区中所有字符,直到遇到换行符为止

        std::cout << "请输入性别: ";
        getline(std::cin, _sex);

        std::cout << "请输入电话: ";
        std::cin >> _phone;
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 处理 \n

        std::cout << "请输入地址: ";
        getline(std::cin, _address);
    }

    // 本通讯录的难点之一:控制表格输出格式正确!
    // 一个汉字占2字节,一个英文占1字节
    // 用于显示单个联系人的信息
    void Display(int nameWidth, int ageWidth, int sexWidth, int phoneWidth, int addressWidth) const
    {
        std::cout << "| ";
        PrintField(_name, nameWidth);
        std::cout << " | ";
        PrintField(std::to_string(_age), ageWidth);
        std::cout << " | ";
        PrintField(_sex, sexWidth);
        std::cout << " | ";
        PrintField(std::to_string(_phone), phoneWidth);
        std::cout << " | ";
        PrintField(_address, addressWidth);
        std::cout << " |" << std::endl;
    }
    // 辅助函数,用于打印带有适当填充的字段
    static void PrintField(const std::string &str, int width)
    {
        int display_width = DisplayWidth(str);
        int padding = width - display_width;
        std::cout << str;
        for (int i = 0; i < padding; ++i)
        {
            std::cout << " ";
        }
    }

    // 计算字符串显示宽度的辅助函数
    static int DisplayWidth(const std::string &str)
    {
        int width = 0;
        std::wstring_convert<std::codecvt_utf8<wchar_t>> conv;
        std::wstring wstr = conv.from_bytes(str);
        for (auto ch : wstr)
        {
            if (ch >= 0x4E00 && ch <= 0x9FFF)
            {
                width += 2; // 汉字字符
            }
            else
            {
                width += 1; // ASCII 字符
            }
        }
        return width;
    }

    size_t _age;          // 联系人年龄(根据数据库存储出生日期更好,因为age是会变的修改非常麻烦,看后期好不好改)
    size_t _phone;        // 联系人电话
    std::string _name;    // 联系人姓名
    std::string _sex;     // 联系人性别
    std::string _address; // 联系人住址
    // std::string _email;// 联系人邮件
};

class AddressBook
{
public:
    std::map<std::string, std::function<void(void)>> _command_map; // 公开的允许外部查询的指令集

public:
    AddressBook()
    {
        // 为了 addressbook._command_map[command_str](void); 使用绑定
        // 循序按照使用频率从上往下排
        _command_map = {
            {"show", std::bind(&AddressBook::ShowContact, this)},
            {"add", std::bind(&AddressBook::AddContact, this)},
            {"delete", std::bind(&AddressBook::DeleteContact, this)},
            {"search", std::bind(&AddressBook::SearchContact, this)},
            {"modify", std::bind(&AddressBook::ModifyContact, this)},
            {"help", std::bind(&AddressBook::ShowHelpManual, this)}};
    }
    ~AddressBook()
    {
    }

    void AddContact()
    {
        // LOG(DEBUG, "add a contact\n");

        // 1. 实例化一个联系人对象,然后用IO的方式Set一下
        Contact newContact;
        newContact.IOSet();

        // 2. 本通讯录允许同姓名,故以Contact::_phone为key(这个方案是否合理有待商榷)
        // 遍历,_phone重复插入失败
        for (auto &contact : _contacts)
        {
            if (contact._phone == newContact._phone)
            {
                std::cout << "添加失败,该联系人已存在" << std::endl;
                std::cout << std::endl;
                return;
            }
        }

        // 用于更新每列的最大宽度
        UpdateMaxWidths(newContact);

        // 来到这里一定能成功插入,顺便按名字的ASCII排序
        _contacts.emplace_back(newContact);
        _contacts.sort([](const Contact &contact1, const Contact &contact2)
                       { return contact1._name < contact2._name; });
        std::cout << "联系人添加成功" << std::endl;
        std::cout << std::endl;
    }
    void DeleteContact()
    {
        // LOG(DEBUG, "delete a contact\n");

        // 删除功能采取姓名为主,电话为辅的方式
        std::cout << "请输入你要删除的联系人的姓名: ";
        std::string name;
        getline(std::cin, name);

        // 统计同姓名联系人
        std::vector<std::list<Contact>::iterator> sameNameContacts;
        for (auto it = _contacts.begin(); it != _contacts.end(); ++it)
        {
            if (it->_name == name)
            {
                sameNameContacts.push_back(it);
            }
        }

        // 如果有多个同名联系人,用户输入电话号码删除指定联系人
        if (sameNameContacts.size() > 1)
        {
            std::cout << "检测到存在多个同名联系人" << std::endl;
            DisplayHeader();
            for (const auto &it : sameNameContacts)
            {
                it->Display(maxNameWidth, maxAgeWidth, maxSexWidth, maxPhoneWidth, maxAddressWidth);
            }
            DisplayFooter(sameNameContacts.size());

            std::cout << "请输入你要删除的联系人的电话: ";
            size_t phone;
            std::cin >> phone;
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            auto phoneIt = std::find_if(sameNameContacts.begin(), sameNameContacts.end(),
                                        [&phone](const std::list<Contact>::iterator &it)
                                        { return it->_phone == phone; });
            if (phoneIt != sameNameContacts.end())
            {
                _contacts.erase(*phoneIt);
                std::cout << "联系人删除成功" << std::endl;
                std::cout << std::endl;
            }
            else
            {
                std::cout << "找不到对应电话的联系人" << std::endl;
                std::cout << std::endl;
            }
        }
        // 如果只有一个,直接删除
        else if (sameNameContacts.size() == 1)
        {
            _contacts.erase(sameNameContacts[0]);
            std::cout << "联系人删除成功" << std::endl
                      << std::endl;
        }
        // 没有的话删除失败
        else
        {
            std::cout << "你要删除的联系人不存在" << std::endl;
            std::cout << std::endl;
        }
    }
    void SearchContact()
    {
        // LOG(DEBUG, "search a contact\n");

        // 按姓名查找,直接将所有同姓名的联系人列出来
        // 后续:可以考虑实现多种不同的查找方式
        std::cout << "请输入要查找的联系人姓名: ";
        std::string name;
        getline(std::cin, name);

        std::vector<std::list<Contact>::iterator> foundContacts;
        for (auto it = _contacts.begin(); it != _contacts.end(); ++it)
        {
            if (it->_name == name)
            {
                foundContacts.push_back(it);
            }
        }

        if (foundContacts.empty())
        {
            std::cout << "联系人不存在" << std::endl;
            std::cout << std::endl;
        }
        else
        {
            std::cout << "查找结果如下: " << std::endl;
            DisplayHeader();
            for (const auto &it : foundContacts)
            {
                it->Display(maxNameWidth, maxAgeWidth, maxSexWidth, maxPhoneWidth, maxAddressWidth);
            }
            DisplayFooter(foundContacts.size());
        }
    }
    void ModifyContact()
    {
        // LOG(DEBUG, "modify a contact information\n");

        // 实现逻辑和delete类似
        std::cout << "请输入你要修改的联系人的姓名: ";
        std::string name;
        getline(std::cin, name);

        // 统计同姓名联系人
        std::vector<std::list<Contact>::iterator> sameNameContacts;
        for (auto it = _contacts.begin(); it != _contacts.end(); ++it)
        {
            if (it->_name == name)
            {
                sameNameContacts.push_back(it);
            }
        }

        // 如果有多个同名联系人,用户输入电话号码用于指定修改联系人信息
        if (sameNameContacts.size() > 1)
        {
            std::cout << "检测到存在多个同名联系人" << std::endl;
            DisplayHeader();
            for (const auto &it : sameNameContacts)
            {
                it->Display(maxNameWidth, maxAgeWidth, maxSexWidth, maxPhoneWidth, maxAddressWidth);
            }
            DisplayFooter(sameNameContacts.size());

            std::cout << "请输入你要修改的联系人的电话: ";
            size_t phone;
            std::cin >> phone;
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
            auto phoneIt = std::find_if(sameNameContacts.begin(), sameNameContacts.end(),
                                        [&phone](const std::list<Contact>::iterator &it)
                                        { return it->_phone == phone; });
            if (phoneIt != sameNameContacts.end())
            {
                ModifyContactDetails(**phoneIt);
                _contacts.sort([](const Contact &contact1, const Contact &contact2)
                               { return contact1._name < contact2._name; });
                std::cout << "联系人信息已更新" << std::endl;
                std::cout << std::endl;
            }
            else
            {
                std::cout << "找不到对应电话的联系人" << std::endl;
                std::cout << std::endl;
            }
        }
        // 如果只有一个,直接修改
        else if (sameNameContacts.size() == 1)
        {
            ModifyContactDetails(*sameNameContacts[0]);
            _contacts.sort([](const Contact &contact1, const Contact &contact2)
                           { return contact1._name < contact2._name; });
            std::cout << "联系人信息已更新" << std::endl
                      << std::endl;
        }
        // 没有的话修改失败
        else
        {
            std::cout << "联系人不存在" << std::endl;
            std::cout << std::endl;
        }
    }
    void ShowContact() const
    {
        // LOG(DEBUG, "show the contact list\n");

        if (_contacts.empty())
        {
            std::cout << "通讯录为空" << std::endl;
            std::cout << std::endl;
            return;
        }

        // 输出表头
        DisplayHeader();

        // 输出联系人信息
        for (auto &contact : _contacts)
        {
            contact.Display(maxNameWidth, maxAgeWidth, maxSexWidth, maxPhoneWidth, maxAddressWidth);
        }

        // 输出表尾
        DisplayFooter(_contacts.size());
    }
    void ShowHelpManual()
    {
        std::cout << "帮助文档:" << std::endl;
        std::cout << "show    - 显示联系人列表" << std::endl;
        std::cout << "add     - 添加联系人" << std::endl;
        std::cout << "delete  - 删除联系人" << std::endl;
        std::cout << "search  - 搜索联系人" << std::endl;
        std::cout << "modify  - 修改联系人信息" << std::endl;
        std::cout << "help    - 显示帮助文档" << std::endl;
        std::cout << "quit    - 退出程序" << std::endl;
        std::cout << std::endl;
    }

private:
    // 用于修改联系人的信息
    void ModifyContactDetails(Contact &contact)
    {
        char choice;

        std::cout << "请问名字是否需要修改(y/n): ";
        std::cin >> choice;
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        if (choice == 'y' || choice == 'Y')
        {
            std::cout << "请输入新的名字: ";
            getline(std::cin, contact._name);
        }

        std::cout << "请问年龄是否需要修改(y/n): ";
        std::cin >> choice;
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        if (choice == 'y' || choice == 'Y')
        {
            std::cout << "请输入新的年龄: ";
            std::cin >> contact._age;
            std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        }

        std::cout << "请问性别是否需要修改(y/n): ";
        std::cin >> choice;
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        if (choice == 'y' || choice == 'Y')
        {
            std::cout << "请输入新的性别: ";
            getline(std::cin, contact._sex);
        }

        std::cout << "请问电话是否需要修改(y/n): ";
        std::cin >> choice;
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        if (choice == 'y' || choice == 'Y')
        {
            std::cout << "请输入新的电话: ";
            std::cin >> contact._phone;
        }

        std::cout << "请问地址是否需要修改(y/n): ";
        std::cin >> choice;
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        if (choice == 'y' || choice == 'Y')
        {
            std::cout << "请输入新的地址: ";
            getline(std::cin, contact._address);
        }
    }

    // 用于更新每列的最大宽度
    void UpdateMaxWidths(const Contact &contact)
    {
        maxNameWidth = std::max(maxNameWidth, contact.DisplayWidth(contact._name));
        maxAgeWidth = std::max(maxAgeWidth, contact.DisplayWidth(std::to_string(contact._age)));
        maxSexWidth = std::max(maxSexWidth, contact.DisplayWidth(contact._sex));
        maxPhoneWidth = std::max(maxPhoneWidth, contact.DisplayWidth(std::to_string(contact._phone)));
        maxAddressWidth = std::max(maxAddressWidth, contact.DisplayWidth(contact._address));
    }
    // 用于显示表头
    void DisplayHeader() const
    {
        PrintLine();
        PrintRow("姓名", maxNameWidth, "年龄", maxAgeWidth, "性别", maxSexWidth, "电话", maxPhoneWidth, "地址", maxAddressWidth);
        PrintLine();
    }
    // 用于显示表尾
    void DisplayFooter(size_t count) const
    {
        PrintLine();
        std::cout << "共计 " << count << " 个联系人" << std::endl;
        std::cout << std::endl;
    }
    // 用于打印表格的横线
    void PrintLine() const
    {
        std::cout << "+";
        std::cout << std::string(maxNameWidth + 2, '-') << "+";
        std::cout << std::string(maxAgeWidth + 2, '-') << "+";
        std::cout << std::string(maxSexWidth + 2, '-') << "+";
        std::cout << std::string(maxPhoneWidth + 2, '-') << "+";
        std::cout << std::string(maxAddressWidth + 2, '-') << "+" << std::endl;
    }
    // 用于打印表格中的一行
    void PrintRow(const std::string &name, int nameWidth, const std::string &age, int ageWidth, const std::string &sex, int sexWidth, const std::string &phone, int phoneWidth, const std::string &address, int addressWidth) const
    {
        std::cout << "| " << std::setw(nameWidth + 2) << std::left << name << " | ";
        std::cout << std::setw(ageWidth + 2) << std::left << age << " | ";
        std::cout << std::setw(sexWidth + 2) << std::left << sex << " | ";
        std::cout << std::setw(phoneWidth + 2) << std::left << phone << " | ";
        std::cout << std::setw(addressWidth + 2) << std::left << address << " |" << std::endl;
    }

private:
    std::list<Contact> _contacts; // 底层采用链表的方式管理联系人信息

    int maxNameWidth = 20; // 初始宽度设置为合理的默认值
    int maxAgeWidth = 5;
    int maxSexWidth = 10;
    int maxPhoneWidth = 15;
    int maxAddressWidth = 30;
};

#endif
  • 34
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值