
前言
通讯录本质上是顺序表的应用层封装 。底层用动态顺序表管理内存,上层把存储类型从
int换成自定义结构体peoInfo,所有增删改查逻辑复用顺序表接口。理解这篇文章需要先掌握动态顺序表,可参考:顺序表实现
一、项目结构
Contact/
├── SeqList.h // 顺序表声明
├── SeqList.c // 顺序表实现
├── Contact.h // 通讯录声明 & 联系人结构体
├── Contact.c // 通讯录功能实现
└── main.c // 菜单与主循环
设计思路 :
Contact就是SL(顺序表)的别名,SLDateType替换为peoInfo,实现完全复用底层代码。
二、联系人结构体
cpp
// Contact.h
#pragma once
#include <string.h>
#define NAME_MAX 20
#define GENDER_MAX 10
#define TEL_MAX 20
#define ADDR_MAX 100
typedef struct PersonInfo {
char name[NAME_MAX];
char gender[GENDER_MAX];
int age;
char tel[TEL_MAX];
char addr[ADDR_MAX];
} peoInfo;
要点:字段全用定长字符数组,避免指针管理复杂度,适合嵌入顺序表的连续内存。
三、顺序表适配
将顺序表头文件中的数据类型替换:
// SeqList.h(节选)
#include "Contact.h"
typedef peoInfo SLDateType; // 核心改动:把 int 换成 peoInfo
其余顺序表代码(SLInit / SLDestroy / SLPushBack / SLErase)无需改动,直接复用。
四、通讯录功能实现
4.1 初始化 & 销毁
cpp
void ContactInit(Contact* con) {
SLInit(con); // 直接调用顺序表初始化
}
void ContactDestroy(Contact* con) {
SLDestroy(con); // 释放堆内存,防止泄漏
}
4.2 添加联系人
cpp
void ContactAdd(Contact* con) {
peoInfo info;
printf("姓名: "); scanf("%s", info.name);
printf("性别: "); scanf("%s", info.gender);
printf("年龄: "); scanf("%d", &info.age);
printf("电话: "); scanf("%s", info.tel);
printf("地址: "); scanf("%s", info.addr);
SLPushBack(con, info); // 尾插到顺序表
printf("添加成功!\n");
}
注意 :
con本身已经是指针,传给SLPushBack时不需要再取地址。
4.3 按姓名查找(核心辅助函数)
cpp
// 返回下标,找不到返回 -1
int FindByName(Contact* con, char name[]) {
for (int i = 0; i < con->size; i++) {
if (strcmp(con->arr[i].name, name) == 0)
return i;
}
return -1;
}
面试考点 :字符串比较必须用
strcmp,不能用==(比较的是指针地址)。
4.4 删除联系人
cpp
void ContactDel(Contact* con) {
char name[NAME_MAX];
printf("请输入要删除的姓名: ");
scanf("%s", name);
int pos = FindByName(con, name);
if (pos == -1) { printf("联系人不存在\n"); return; }
SLErase(con, pos); // 顺序表按下标删除
printf("删除成功!\n");
}
4.5 修改联系人
cpp
void ContactModify(Contact* con) {
char name[NAME_MAX];
printf("请输入要修改的姓名: ");
scanf("%s", name);
int pos = FindByName(con, name);
if (pos == -1) { printf("联系人不存在\n"); return; }
// 直接通过下标修改结构体字段
printf("新姓名: "); scanf("%s", con->arr[pos].name);
printf("新性别: "); scanf("%s", con->arr[pos].gender);
printf("新年龄: "); scanf("%d", &con->arr[pos].age);
printf("新电话: "); scanf("%s", con->arr[pos].tel);
printf("新地址: "); scanf("%s", con->arr[pos].addr);
printf("修改成功!\n");
}
4.6 查找 & 展示
cpp
void ContactFind(Contact* con) {
char name[NAME_MAX];
printf("请输入要查找的姓名: ");
scanf("%s", name);
int pos = FindByName(con, name);
if (pos == -1) { printf("联系人不存在\n"); return; }
printf("%-10s %-6s %-4s %-12s %s\n", "姓名","性别","年龄","电话","地址");
printf("%-10s %-6s %-4d %-12s %s\n",
con->arr[pos].name, con->arr[pos].gender,
con->arr[pos].age, con->arr[pos].tel,
con->arr[pos].addr);
}
void ContactShow(Contact* con) {
printf("%-10s %-6s %-4s %-12s %s\n", "姓名","性别","年龄","电话","地址");
for (int i = 0; i < con->size; i++) {
printf("%-10s %-6s %-4d %-12s %s\n",
con->arr[i].name, con->arr[i].gender,
con->arr[i].age, con->arr[i].tel,
con->arr[i].addr);
}
}
优化点 :用
%-Ns格式化对齐,打印更整洁,面试官会注意细节。
五、主菜单
cpp
// main.c
#include "Contact.h"
void menu() {
printf("==== 通讯录 ====\n");
printf("1. 添加联系人\n");
printf("2. 删除联系人\n");
printf("3. 修改联系人\n");
printf("4. 查找联系人\n");
printf("5. 展示所有\n");
printf("0. 退出\n");
}
int main() {
Contact con;
ContactInit(&con);
int op = -1;
do {
menu();
printf("请输入操作: ");
scanf("%d", &op);
switch (op) {
case 1: ContactAdd(&con); break;
case 2: ContactDel(&con); break;
case 3: ContactModify(&con); break;
case 4: ContactFind(&con); break;
case 5: ContactShow(&con); break;
case 0: printf("退出\n"); break;
default: printf("输入有误\n"); break;
}
} while (op != 0);
ContactDestroy(&con);
return 0;
}
六、常见面试问题
| 问题 | 答案要点 |
|---|---|
为什么不用 == 比较字符串? |
== 比较指针地址;字符串内容比较用 strcmp |
realloc 失败怎么处理? |
用临时指针接收返回值,为 NULL 则报错退出,不能直接赋给原指针(会丢失原内存) |
SLErase 删除的时间复杂度? |
O(n),需要移动后续所有元素 |
| 通讯录怎么持久化(扩展方向)? | 增加 ContactSave / ContactLoad,用 fwrite / fread 将结构体数组写入二进制文件 |
总结
通讯录 = 顺序表 + 自定义结构体,核心改动只有两处:
SLDateType从int换成peoInfo- 查找时用
strcmp代替==,打印时通过.字段名解引用掌握这个项目,顺序表的泛化思想和结构体应用就都打通了。