当前位置: 首页 > news >正文

学完顺序表后,用 C 语言写了一个通讯录

目录

一、什么是顺序表?它和普通数组有啥区别?

二、为什么用顺序表做通讯录?

三、项目需求

四、设计思路详解(手把手拆解)

Step 1:定义联系人结构体(contact.h)

Step 2:定义动态顺序表(SeqList.h)

Step 3:顺序表核心函数实现(SeqList.c)

Step 4:通讯录业务逻辑(contact.c)

Step 5:主函数(test.c)

五、初学者高频错误 & 避坑指南

六、顺序表的优缺点总结

七、下一步可以怎么优化?

八、动手挑战!


上一篇博客我们讲解了,数据结构中顺序表的知识点,所以这篇博客用顺序表来实现一个通讯录管理系统 

📌 本文适合:

  • 会基础 C 语言(知道结构体、数组、函数)
  • 刚接触“顺序表”概念
  • 想通过小项目理解数据结构的实际用途

那我们就,开始吧

首先呢,先回顾一下知识点

一、什么是顺序表?它和普通数组有啥区别?

很多同学一听到“顺序表”,就觉得很高大上。其实——
顺序表 = 数组 + 当前长度 + 容量 + 一套操作函数

比如普通数组:

int arr[100];

你只能自己记“现在用了多少个”,自己写循环插入、删除,很容易出错。

顺序表把它封装成一个结构体:

typedef struct Seqlist
{Contact* data;   //指向动态数组的指针int size ;       //容量int capacity;    //数组长度}

这样,增删改查都通过函数操作,安全、清晰、可扩展

这里关于顺序表跟多的内容我们就不多说了,详细知识内容可看《数据结构杂谈》的上一篇博客

二、为什么用顺序表做通讯录?

通讯录的特点:

  • 联系人数量不会爆炸(几十到几百个);
  • 经常要“找张三的电话”(查找频繁);
  • 偶尔增删改。

顺序表的优势正好匹配:

  • 查找快:虽然要遍历,但数据连续,CPU 缓存友好;
  • 内存紧凑:没有链表那种每个节点都要存指针的开销;
  • 实现简单:比链表容易理解,适合入门。

💡 注意:如果未来要做“百万级联系人”,那就要考虑哈希表或数据库了。但对学习阶段,顺序表刚刚好!

三、项目需求

根据 要求,我们的通讯录要支持:

  1. 存储至少 100 人;
  2. 每个联系人包含:姓名、性别、年龄、电话、地址
  3. 功能:添加、删除(按姓名)、查找(按姓名)、修改、显示全部;
  4. 程序关闭后,数据不丢失(保存到文件);
  5. 使用动态顺序表(不够就自动扩容)。

四、设计思路详解(手把手拆解)

Step 1:定义联系人结构体(contact.h)

// contact.h
#pragma once
#include <stdio.h>
#include <string.h>// 定义字段最大长度(避免缓冲区溢出)
#define NAME_MAX 20
#define SEX_MAX  4
#define TEL_MAX  12
#define ADDR_MAX 50// 联系人信息
typedef struct PersonInfo {char name[NAME_MAX];char sex[SEX_MAX];     // "男"/"女"int age;char tel[TEL_MAX];char addr[ADDR_MAX];
} PersonInfo;

✅ 为什么用 char name[20] 而不是 char* name
因为初学者用动态字符串容易内存泄漏。固定长度数组更安全、简单。

Step 2:定义动态顺序表(SeqList.h)

//SeqList.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert>
#include "contact"//把 PersonInfo 作为顺序表的数据类型
typedef PersonInfo SQDataType;typedef struct SeqList
{SQDataType;      //指向动态分配的数组int size;        //当前有效数据的个数int capacity;    //当前数组的容量
} Seqlist;//函数声明
void SeqListInit(SeqList* psl);
void SeqListDestroy(SeqList* psl);
void SeqListPushBack(SeqList* psl, SQDataType x);
void SeqListErase(SeqList* psl, int pos);
int SeqListFind(SeqList* psl, const char* name); // 按姓名找
void SeqListPrint(SeqList* psl);
void CheckCapacity(SeqList* psl); // 检查是否需要扩容

🔍 重点:a 是指针!我们要用 malloc 动态申请内存。

Step 3:顺序表核心函数实现(SeqList.c)

// SeqList.c
#include "SeqList.h"void SeqListInit(SeqList* psl) {assert(psl);psl->a = (SQDataType*)malloc(sizeof(SQDataType) * 4); // 初始容量4psl->size = 0;psl->capacity = 4;
}void CheckCapacity(SeqList* psl) {if (psl->size == psl->capacity) {int newCapacity = psl->capacity * 2;SQDataType* tmp = (SQDataType*)realloc(psl->a, sizeof(SQDataType) * newCapacity);if (tmp == NULL) {perror("realloc failed");exit(-1);}psl->a = tmp;psl->capacity = newCapacity;printf("通讯录扩容成功!当前容量:%d\n", newCapacity);}
}void SeqListPushBack(SeqList* psl, SQDataType x) {assert(psl);CheckCapacity(psl); // 先检查是否要扩容psl->a[psl->size] = x;psl->size++;
}void SeqListErase(SeqList* psl, int pos) {assert(psl);assert(pos >= 0 && pos < psl->size);// 从 pos 开始,后面所有元素前移一位for (int i = pos; i < psl->size - 1; i++) {psl->a[i] = psl->a[i + 1];}psl->size--; // ⚠️ 别忘了减 size!
}int SeqListFind(SeqList* psl, const char* name) {for (int i = 0; i < psl->size; i++) {if (strcmp(psl->a[i].name, name) == 0) {return i; // 找到返回下标}}return -1; // 未找到
}void SeqListPrint(SeqList* psl) {if (psl->size == 0) {printf("通讯录为空!\n");return;}printf("\n%-10s %-4s %-4s %-12s %-20s\n", "姓名", "性别", "年龄", "电话", "地址");printf("------------------------------------------------------------\n");for (int i = 0; i < psl->size; i++) {printf("%-10s %-4s %-4d %-12s %-20s\n",psl->a[i].name, psl->a[i].sex,psl->a[i].age, psl->a[i].tel, psl->a[i].addr);}
}void SeqListDestroy(SeqList* psl) {free(psl->a);psl->a = NULL;psl->size = psl->capacity = 0;
}

Step 4:通讯录业务逻辑(contact.c)

// contact.c
#include "SeqList.h"// 从文件加载历史数据
void LoadContact(SeqList* con) {FILE* pf = fopen("contact.dat", "rb");if (pf == NULL) {printf("首次运行,无历史数据。\n");return;}PersonInfo tmp;while (fread(&tmp, sizeof(PersonInfo), 1, pf)) {SeqListPushBack(con, tmp);}fclose(pf);printf("✅ 历史数据加载成功!\n");
}// 保存到文件
void SaveContact(SeqList* con) {FILE* pf = fopen("contact.dat", "wb");if (pf == NULL) {perror("保存失败");return;}for (int i = 0; i < con->size; i++) {fwrite(&(con->a[i]), sizeof(PersonInfo), 1, pf);}fclose(pf);printf("💾 通讯录已保存到文件!\n");
}// 初始化通讯录(含加载历史)
void InitContact(SeqList* con) {SeqListInit(con);LoadContact(con);
}// 添加联系人
void AddContact(SeqList* con) {PersonInfo info;printf("请输入姓名: "); scanf("%s", info.name);printf("请输入性别(男/女): "); scanf("%s", info.sex);printf("请输入年龄: "); scanf("%d", &info.age);printf("请输入电话: "); scanf("%s", info.tel);printf("请输入地址: "); scanf("%s", info.addr);SeqListPushBack(con, info);printf("✅ 添加成功!\n");
}// 删除联系人
void DelContact(SeqList* con) {char name[NAME_MAX];printf("请输入要删除的姓名: "); scanf("%s", name);int pos = SeqListFind(con, name);if (pos == -1) {printf("❌ 未找到该联系人!\n");return;}SeqListErase(con, pos);printf("✅ 删除成功!\n");
}// 查找联系人
void FindContact(SeqList* con) {char name[NAME_MAX];printf("请输入要查找的姓名: "); scanf("%s", name);int pos = SeqListFind(con, name);if (pos == -1) {printf("❌ 未找到!\n");return;}printf("✅ 找到联系人:\n");printf("%-10s %-4s %-4d %-12s %-20s\n",con->a[pos].name, con->a[pos].sex,con->a[pos].age, con->a[pos].tel, con->a[pos].addr);
}// 修改联系人
void ModifyContact(SeqList* con) {char name[NAME_MAX];printf("请输入要修改的姓名: "); scanf("%s", name);int pos = SeqListFind(con, name);if (pos == -1) {printf("❌ 未找到该联系人!\n");return;}printf("请输入新姓名: "); scanf("%s", con->a[pos].name);printf("请输入新性别: "); scanf("%s", con->a[pos].sex);printf("请输入新年龄: "); scanf("%d", &con->a[pos].age);printf("请输入新电话: "); scanf("%s", con->a[pos].tel);printf("请输入新地址: "); scanf("%s", con->a[pos].addr);printf("✅ 修改成功!\n");
}// 显示全部
void ShowContact(SeqList* con) {SeqListPrint(con);
}// 销毁并保存
void DestroyContact(SeqList* con) {SaveContact(con);SeqListDestroy(con);
}

💾 文件持久化说明:

  • 使用二进制文件 contact.dat
  • 程序启动时自动加载;
  • 退出时自动保存;
  • 即使关机,数据也不会丢!

Step 5:主函数(test.c)

// test.c
#include "SeqList.h"void menu() {printf("\n========== 通讯录管理系统 ==========\n");printf("1. 添加联系人\n");printf("2. 删除联系人\n");printf("3. 查找联系人\n");printf("4. 修改联系人\n");printf("5. 显示所有联系人\n");printf("0. 退出\n");printf("====================================\n");printf("请选择操作: ");
}int main() {SeqList con;InitContact(&con);int choice;do {menu();scanf("%d", &choice);switch (choice) {case 1: AddContact(&con); break;case 2: DelContact(&con); break;case 3: FindContact(&con); break;case 4: ModifyContact(&con); break;case 5: ShowContact(&con); break;case 0: printf("再见!\n"); break;default: printf("❌ 无效选项,请重试!\n");}} while (choice != 0);DestroyContact(&con); // 退出前保存+释放内存return 0;
}

五、初学者高频错误 & 避坑指南

错误正确做法
==比较字符串必须用strcmp(str1, str2) == 0
删除后忘记size--删除后一定要size--,否则显示异常
插入时不检查容量动态顺序表必须在插入前CheckCapacity()
scanf读字符串加&scanf("%s", name)name本身就是地址
忘记保存文件退出前调用SaveContact()

六、顺序表的优缺点总结

优点

  • 实现简单,适合入门;
  • 数据连续,缓存命中率高,遍历快;
  • 内存开销小(无额外指针)。

缺点

  • 中间插入/删除需移动大量元素(O(n));
  • 扩容有性能开销(malloc + memcpy);
  • 2倍扩容可能导致空间浪费(比如容量200,只用了105)。

七、下一步可以怎么优化?

如果联系人超过 1000 个怎么办?

  • 改用链表:插入删除 O(1),但查找变慢;
  • 改用哈希表:查找 O(1),但实现复杂;
  • 加索引:比如按首字母分组;
  • 支持模糊搜索:比如“张”能搜出“张三”“张伟”。

这些,都是你未来要探索的方向!

八、动手挑战!

现在,轮到你了!试试给通讯录加这些功能:

  • 🔍 按电话号码查找
  • 📥 从 CSV 文件批量导入联系人
  • 🎨 美化输出格式(对齐、颜色)
  • 🔒 增加密码保护

🌟 欢迎在评论区贴出你的代码或改进想法!
一起交流,一起进步。数据结构不是背概念,而是用出来才有意义!

           

http://www.dtcms.com/a/475656.html

相关文章:

  • php网站怎么做自适应网站安全狗 服务名
  • 法拍房捡漏与风险排查
  • canvas 特效网站有哪些营销型网站
  • java12
  • CAN信号通信
  • 昌平网站建设哪家强企业网站推广方案在哪里
  • 国外商品网站网站开发服务合同范本
  • mysql 收费 网站建设地图网站建设
  • 【Git学习】初识git:简单介绍及安装流程
  • 在线推广企业网站的方法有wordpress主题文件夹在哪里
  • 华夏润达建设有限公司网站ui设计难吗
  • 阿勒泰建设局网站wordpress技术教程
  • 新知识点背诵
  • 南昌企业建站html5 购物网站
  • 淄博高效网站建设wordpress视频播放
  • 长沙专业网站建设wordpress网站回调域
  • 帝国cms网站搬家网站模板在线制作
  • 中山品牌网站建设推广徐州企业网站排名优化
  • 10.【Linux系统编程】缓冲区详解——库缓冲区 VS 内核缓冲区
  • 做网站模版与定制的区别信通网站开发中心
  • ppt网站模板佟年做网站给KK
  • 女人做绿叶网站相亲拉人深圳网站设计x
  • 凡科网怎么做网站网络域名后缀
  • 企业网站建站 优帮云做网站其实不贵
  • 个人或主题网站建设服装效果图网站
  • 像美团这种网站怎么做的深圳外包软件开发
  • 公司网站是否做地方分站智能展厅设计公司
  • 极速网站建设广州网站优化推荐
  • 网站建设公司的会计分录蒙古文网站建设情况
  • 家具flash网站模板下载wordpress用php版本