引言
功能:
- 添加联系人
- 删除联系人
- 查找联系人
- 修改联系人信息
- 显示所有联系人
- 清空所有联系人
- 按名字排序联系人
系统设计
为了实现通讯录的功能,我们首先定义了一个 contact
结构体,包含了一个存储联系人信息的数组和当前已经存储的联系人个数。每个联系人信息是一个 info
结构体,包含姓名、年龄、性别、电话和地址。
typedef struct info {
char name[MAX_NAME];
int age;
char gender[MAX_GENDER];
char tele[MAX_TELE];
char addr[MAX_ADDR];
} info;
typedef struct contact {
info data[MAX_NUM]; // 存储联系人的信息
int sz; // 当前已存放联系人的个数
} contact;
功能实现
1. 初始化联系人列表
首先,我们需要一个初始化函数来清空通讯录,并将联系人数量设置为 0。这个功能由 InitContact
函数实现:
void InitContact(contact* pc) {
assert(pc);
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
2. 添加联系人
通过 AddNewContact
函数,我们可以添加新的联系人信息。每次添加一个联系人,我们都会提示用户输入联系人姓名、年龄、性别、电话和地址,并将其存储到 data
数组中:
void AddNewContact(contact* pc) {
assert(pc);
if (pc->sz == MAX_NUM) {
printf("联系人已满,无法添加!\n");
return;
}
printf("请输入姓名>");
scanf("%19s", pc->data[pc->sz].name);
printf("请输入年龄>");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别>");
scanf("%9s", pc->data[pc->sz].gender);
printf("请输入电话号码>");
scanf("%12s", pc->data[pc->sz].tele);
printf("请输入地址>");
scanf("%29s", pc->data[pc->sz].addr);
pc->sz++;
}
3. 查找联系人
通过姓名查找联系人,我们可以使用 FindName
函数在通讯录中查找对应的联系人。如果找到了,就打印出该联系人的详细信息:
int FindName(const char* str, const contact* pc) {
assert(str);
for (int i = 0; i < pc->sz; i++) {
if (strcmp(str, pc->data[i].name) == 0) {
return i; // 找到联系人,返回其位置
}
}
return -1; // 未找到联系人
}
4. 删除联系人
通过 DeleContact
函数,我们可以删除指定的联系人。删除的过程是将该联系人的后续联系人前移一位,并减少联系人数量:
void DeleContact(contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要删除联系人的姓名>");
scanf("%s", name);
int pos = FindName(name, pc);
if (pos == -1) {
printf("没有该联系人!\n");
return;
}
for (int i = pos; i < pc->sz - 1; i++) {
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功!\n");
}
5. 修改联系人
通过 ModifyContact
函数,我们可以修改联系人的信息。用户选择修改的字段后,程序会提示用户输入新的值并进行更新:
void ModifyContact(contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要修改联系人的姓名>");
scanf("%s", name);
int pos = FindName(name, pc);
if (pos == -1) {
printf("没有该联系人!\n");
return;
}
int input = 0;
do {
menu2(); // 显示修改选项
printf("请选择你要修改的项>");
scanf("%d", &input);
switch (input) {
case 0: {
printf("修改成功!\n");
break;
}
case 1: {
printf("请输入姓名>");
scanf("%s", pc->data[pos].name);
break;
}
case 2: {
printf("请输入年龄>");
scanf("%d", &(pc->data[pos].age));
break;
}
case 3: {
printf("请输入性别>");
scanf("%s", pc->data[pos].gender);
break;
}
case 4: {
printf("请输入电话号码>");
scanf("%s", pc->data[pos].tele);
break;
}
case 5: {
printf("请输入地址>");
scanf("%s", pc->data[pos].addr);
break;
}
default: {
printf("数据错误,请重新输入!\n");
break;
}
}
} while (input);
}
6. 显示所有联系人
通过 DisplayContact
函数,我们可以显示当前所有联系人的信息:
void DisplayContact(const contact* pc) {
printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\n", "名字", "年龄", "性别", "地址", "电话");
for (int i = 0; i < pc->sz; i++) {
printf("%-20s\t%-4S\t%-5d\t%-20s\t%-12s\n", pc->data[i].name,
pc->data[i].gender,
pc->data[i].age,
pc->data[i].addr,
pc->data[i].tele);
}
}
7. 清空所有联系人
如果我们想要清空通讯录中的所有联系人,可以通过 ClearContact
函数实现:
void ClearContact(contact* pc) {
assert(pc);
InitContact(pc); // 重置通讯录,清空所有联系人
printf("所有联系人已清空!\n");
}
8. 按名字排序联系人
最后,我们通过 qsort
函数按姓名排序所有联系人:
int CmpByName(const void* e1, const void* e2) {
return strcmp(((info*)e1)->name, ((info*)e2)->name);
}
void SortContact(contact* pc) {
assert(pc);
qsort(pc->data, pc->sz, sizeof(pc->data[0]), CmpByName);
printf("排序成功!\n");
}
易错点与建议
- 输入安全性:使用
scanf
时没有限制输入长度,可能会导致缓冲区溢出。可以在读取字符串时限制输入长度,比如scanf("%19s", name)
。 - 内存越界检查:在访问数组和指针时,应该时刻注意数组越界的问题,避免读取非法内存。
- 模块化设计:目前我们将所有功能集中在一个文件中,可以考虑将各个功能拆分成多个文件,提高代码的可维护性。
完整代码
我将该项目拆分为了3个文件:
test.c
contact.h
contact.c
contact.h
— 头文件
头文件通常包含全局变量、宏定义、结构体声明、函数原型等。在这个文件中,我们将声明通讯录相关的所有结构体和函数原型。
#pragma once
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
// 常量定义
#define MAX_NAME 20
#define MAX_GENDER 10
#define MAX_TELE 13
#define MAX_NUM 1000
#define MAX_ADDR 30
// 联系人信息结构体
typedef struct info {
char name[MAX_NAME];
int age;
char gender[MAX_GENDER];
char tele[MAX_TELE];
char addr[MAX_ADDR];
} info;
// 通讯录结构体
typedef struct contact {
info data[MAX_NUM]; // 存储联系人的信息
int sz; // 当前已存放联系人的个数
} contact;
// 函数声明
void InitContact(contact* pc);
void AddNewContact(contact* pc);
void FindContact(const contact* pc);
void DisplayContact(const contact* pc);
void DeleContact(contact* pc);
void ModifyContact(contact* pc);
void ClearContact(contact* pc);
void SortContact(contact* pc);
int FindName(const char* str, const contact* pc);
int CmpByName(const void* e1, const void* e2);
void menu();
void menu2();
void test();
contact.c
— 主要功能实现
在这个文件中,我们实现了通讯录的具体功能,包括联系人信息的增删改查、排序等。
#include "contact.h"
// 初始化联系人
void InitContact(contact* pc) {
assert(pc);
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
// 添加联系人
void AddNewContact(contact* pc) {
assert(pc);
if (pc->sz == MAX_NUM) {
printf("联系人已满,无法添加!\n");
return;
}
printf("请输入姓名>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入年龄>");
scanf("%d", &(pc->data[pc->sz].age));
printf("请输入性别>");
scanf("%s", pc->data[pc->sz].gender);
printf("请输入电话号码>");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址>");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
}
// 查找联系人
int FindName(const char* str, const contact* pc) {
assert(str);
for (int i = 0; i < pc->sz; i++) {
if (strcmp(str, pc->data[i].name) == 0) {
return i;
}
}
return -1;
}
void FindContact(const contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要查找联系人的姓名>");
scanf("%s", name);
int pos = FindName(name, pc);
if (pos == -1) {
printf("没有该联系人!\n");
return;
}
printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\n", "名字", "年龄", "性别", "地址", "电话");
printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[pos].name,
pc->data[pos].age,
pc->data[pos].gender,
pc->data[pos].addr,
pc->data[pos].tele);
}
// 显示联系人
void DisplayContact(const contact* pc) {
assert(pc);
printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\n", "名字", "年龄", "性别", "地址", "电话");
for (int i = 0; i < pc->sz; i++) {
printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[i].name,
pc->data[i].age,
pc->data[i].gender,
pc->data[i].addr,
pc->data[i].tele);
}
}
// 删除联系人
void DeleContact(contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要删除联系人的姓名>");
scanf("%s", name);
int pos = FindName(name, pc);
if (pos == -1) {
printf("没有该联系人!\n");
return;
}
for (int i = pos; i < pc->sz - 1; i++) {
pc->data[i] = pc->data[i + 1];
}
pc->sz--;
printf("删除成功!\n");
}
// 修改联系人
void ModifyContact(contact* pc) {
assert(pc);
char name[MAX_NAME] = { 0 };
printf("请输入要修改联系人的姓名>");
scanf("%s", name);
int pos = FindName(name, pc);
if (pos == -1) {
printf("没有该联系人!\n");
return;
}
int input = 0;
do {
menu2(); // 显示修改选项
printf("请选择你要修改的项>");
scanf("%d", &input);
switch (input) {
case 1:
printf("请输入姓名>");
scanf("%s", pc->data[pos].name);
break;
case 2:
printf("请输入年龄>");
scanf("%d", &(pc->data[pos].age));
break;
case 3:
printf("请输入性别>");
scanf("%s", pc->data[pos].gender);
break;
case 4:
printf("请输入电话号码>");
scanf("%s", pc->data[pos].tele);
break;
case 5:
printf("请输入地址>");
scanf("%s", pc->data[pos].addr);
break;
case 0:
printf("修改完成!\n");
break;
default:
printf("无效选项,请重新选择!\n");
}
} while (input != 0);
}
// 清空所有联系人
void ClearContact(contact* pc) {
assert(pc);
InitContact(pc); // 重置通讯录
printf("所有联系人已清空!\n");
}
// 排序联系人(按姓名)
int CmpByName(const void* e1, const void* e2) {
return strcmp(((info*)e1)->name, ((info*)e2)->name);
}
void SortContact(contact* pc) {
assert(pc);
qsort(pc->data, pc->sz, sizeof(pc->data[0]), CmpByName);
printf("排序成功!\n");
}
test.c
— 主程序与测试
在这个文件中,我们将测试所有功能,用户可以选择相应的操作,程序会根据用户输入调用不同的函数。
#include "contact.h"
void menu() {
printf("**************************************\n");
printf("***** 0.退出 1.添加联系人 *****\n");
printf("***** 2.查找联系人 3.显示联系人 *****\n");
printf("***** 4.删除联系人 5.修改联系人 *****\n");
printf("***** 6.清空联系人 7.排序联系人 *****\n");
printf("**************************************\n");
}
void test() {
int input = 0;
contact con;
InitContact(&con); // 初始化通讯录
do {
menu();
printf("请输入数字>");
scanf("%d", &input);
switch (input) {
case 1:
AddNewContact(&con);
break;
case 2:
FindContact(&con);
break;
case 3:
DisplayContact(&con);
break;
case 4:
DeleContact(&con);
break;
case 5:
ModifyContact(&con);
break;
case 6:
ClearContact(&con);
break;
case 7:
SortContact(&con);
break;
case 0:
printf("退出成功!\n");
break;
default:
printf("数据错误,请重新输入!\n");
break;
}
} while (input != 0);
}
int main() {
test();
return 0;
}
通过这种方式,我们将项目分成了多个文件,使得代码更加模块化,便于维护和扩展。在实际开发中,将不同功能封装到不同的源文件中是很常见的做法,可以有效提高代码的可读性和可维护性。