参考教程:黑马程序员——01 通讯录管理系统 - 系统功能介绍及展示_bilibili
注意这个项目其实是想让我用链表实现的,但我没仔细看要求(说实话,一个通讯录要预先分配数十到数百倍于真实使用的空间的内存,想想就恐怖 XP

1. 项目功能介绍

对于一个简单的通讯录管理系统,其主要功能有:

  • 添加联系人:向通讯录中添加新联系人,固定需要添加的信息有:姓名、性别、年龄、联系电话和家庭住址。要求最多支持添加 1000 个联系人。
  • 显示联系人:显示通讯录中所有联系人的信息
  • 删除联系人:按照姓名删除指定的联系人
  • 查找联系人:按照姓名查看指定联系人信息
  • 修改联系人:按照姓名重新修改指定联系人
  • 清空联系人:清空通讯录中所有联系人信息
  • 退出通讯录:退出当前使用的通讯录

该通讯录应该进行具备一定的人机交互特征(命令行下),具体有:

  • 初始的功能菜单:列出以上功能,并用数字代号提示各个功能。用户可以输入代号来访问对应的功能。可以对菜单做一些装饰性元素

  • 不重启切换功能:要求切换功能时保持当前程序运行,而不是让用户一遍遍重启程序。菜单不要重复输出

  • 添加联系人:以问答式方式引导用户输入需要添加的信息。性别一栏可以提供选项,如果要考虑 DEI,也可以让用户自己输入自己的性别。所有信息添加完成后,需要明确提示用户 “添加成功”。

  • 显示联系人:要求格式化输出。建议在一行内输出且只输出一个联系人的信息。如果没有任何联系人信息,应输出 “当前记录为空” 之类的提示信息

  • 删除联系人:以问答式方式确认需要删除的联系人。删除成功后需要说明。如果删除了不存在的联系人,程序也需要提示 “查无此人”。

  • 查找联系人:以问答式方式确认需要查询的联系人。如果查询了不存在的联系人,程序需要提示 “查无此人”。

  • 修改联系人:实质上相当于调用 “添加联系人” 功能。修改成功后需要提示

  • 清空联系人:清空后需要提示。更好的做法是在删除前加上确认信息,防止误删

  • 退出通讯录:建议输出退出语,比如 “欢迎下次使用”


2. 分功能开发

2.1 添加联系人

  • 数据方面

我们使用结构体来存储单个联系人信息,因为我们要存储的信息的类型明显不同,并且应该归属于同一个条目下。

对于联系人信息结构体的集合,我们使用数组存储。

结构体数组之前没有介绍,可参见:C 结构体 | 菜鸟教程下面列出的笔记,以及 C 语言数组、结构体、结构体数组作为函数参数_结构体数组作为函数参数传递 - CSDN 博客

  • 算法方面

使用最经典的scanf输入单个结构体的成员的数据。提示语用printf输出。

单个信息输入完毕后,在main函数中对计数器i++,标示数组元素个数以供索引

不得不说,Clang 的代码诊断对于中文疑似不太友好啊,一直输出warning

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void add_person(info arr[], int i) {
	//input name
	printf("请输入姓名:\n");
	scanf("%s", arr[i].name);
	//input gender
	printf("请输入性别:\n");
	scanf("%s", arr[i].gender);
	//input age
	printf("请输入年龄:\n");
	scanf("%d", &arr[i].age);
	//input phone number
	printf("请输入联系电话:\n");
	scanf("%s", arr[i].phone);
	//input location
	printf("请输入家庭住址:\n");
	scanf("%s", arr[i].location);
}

实机演示:

2.2 显示联系人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void list_person(info arr[], int i) {
	if (i == 0) {
		printf("当前记录为空\n");
	}
	else {
		for (int cnt = 0; cnt < i; cnt++) {
			printf("姓名:%s  \
			性别:%s  \
			年龄:%d  \
			联系电话:%s  \
			家庭住址:%s\n", arr[cnt].name, arr[cnt].gender, arr[cnt].age, arr[cnt].phone, arr[cnt].location);
		}
	}
}

实机演示:

2.3 删除联系人

初始版本仅仅令计数器i--,但在后续测试中发现只考虑到最后一个联系人数据的删除。

为了让任意一个元素均可以被删除,我们可以移动数组内元素,用后续元素把目标元素给覆盖掉,再令计数器i--,防止数组越界

由于我们需要改变实参i的数值,因此传入i的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void delete_person(info arr[], int* i) {
	bool check = 0;
	if (*i == 0) {
		printf("当前通讯录为空\n");
	}
	else {
		printf("请输入将要删除的联系人的姓名:\n");
		char goal[MAX_CHARACTER_LENGTH];
		scanf("%s", goal);
		for (int j = 0; j < *i; j++) {
			if (!strcmp(goal, arr[j].name)) {
				for (int ds = 0; ds < *i - j; ds++) {
					arr[j + ds] = arr[j + (ds + 1)];
				}
				(*i)--;
				check = 1;
				printf("删除成功!\n");
				break;
			}
		}
		if (!check){
			printf("查无此人\n");
		}
	}
}

实机演示:

2.4 查找联系人

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void find_person(info arr[], int i) {
	printf("请输入将要查询的联系人的姓名:\n");
	char goal[MAX_CHARACTER_LENGTH];
	scanf("%s", goal);
	for (int j = 0; j < i; j++) {
		if (!strcmp(goal, arr[j].name)) {
			printf("姓名:%s  \
性别:%s  \
年龄:%d  \
联系电话:%s  \
家庭住址:%s\n", arr[j].name, arr[j].gender, arr[j].age, arr[j].phone, arr[j].location);
		}
		else {
			printf("查无此人\n");
		}
	}
}

2.5 修改联系人

找到目标联系人后调用add_person把原来的联系人覆盖掉就行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void edit_person(info arr[], int i) {
	bool check = 0;
	if (i == 0) {
		printf("当前通讯录为空\n");
	}
	else {
		printf("请输入将要修改的联系人的姓名:\n");
		char goal[MAX_CHARACTER_LENGTH];
		scanf("%s", goal);
		for (int j = 0; j < i; j++) {
			if (!strcmp(goal, arr[j].name)) {
				add_person(arr, j);
				check = 1;
				break;
			}
		}
		if (!check)
		{
			printf("查无此人\n");
		}
	}
}

实机演示:

2.6 清空联系人

1
2
3
4
5
6
7
8
9
10
11
12
void empty_person(int* i) {
	printf("确定要清空通讯录吗?\n"
	"输入'y'确定,输入'n'取消\n");
	char check = getchar();
	if (check == 'y' || check == 'Y') {
		*i = 0;
		printf("已清空!\n");
	}
	else {
		printf("已取消操作\n");
	}
}

2.7 主界面(退出通讯录)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
int main(void) {
	int status;
	int i = 0;
	do
	{
		printf("*****通讯录*****\n");
		printf("*请输入对应代码*\n"
      "* 1.添加联系人 *\n"
      "* 2.显示联系人 *\n"
      "* 3.删除联系人 *\n"
      "* 4.查找联系人 *\n"
      "* 5.修改联系人 *\n"
      "* 6.清空联系人 *\n"
      "* 0.退出通讯录 *\n"
      "****************\n");
		while (scanf("%d", &status) != 1) {
			printf("无法识别功能代码,请重新输入:\n");
			while (getchar() != '\n');
		}
		switch (status) {
		case 1:
			add_person(data, i);
			i++;
			break;
		case 2:
			list_person(data, i);
			break;
		case 3:
			delete_person(data, &i);
			break;
		case 4:
			find_person(data, i);
			break;
		case 5:
			edit_person(data, i);
			break;
		case 6:
			empty_person(&i);
			break;
		case 0:
			printf("欢迎下次使用!\n");
			break;
		default:
			printf("功能不存在。请重试!\n");
		}
        system("pause");
		system("cls");
	} while (status != 0);
	return 0;
}


3. 人机交互优化

3.1 提示非法输入

在主界面中,对于非数字输入,我们将拒绝该输入并要求用户重新输入:

1
2
3
4
while (scanf("%d", &status) != 1) {
	printf("无法识别功能代码,请重新输入:\n");
	while (getchar() != '\n');//将新数据传入缓冲区,让scanf函数读取
}

程序首先检查scanf返回的成功匹配参数的数据个数。当用户输入一个以数字开头的数据时,scanf返回1,程序进入功能代码匹配环节;当用户输入一个以其他字符开头的数据时,scanf返回0,进入该循环。


对于不存在的功能代码,提示 “功能不存在”:

1
2
default:
	printf("功能不存在。请重试!\n");