【C语言进阶】【小项目】实现一个通讯录【C语言知识点汇总项目】通过这个项目,掌握C语言重要知识点
【C语言进阶】【小项目】实现一个通讯录【C语言知识点汇总项目】通过这个项目,掌握C语言重要知识点
欢迎来到#西城s的博客,今天,博主带着大家用C实现一个通讯录!干货满满不要错过噢!
作者: #西城s
这是我的主页:@小小Programmer
在食用这篇博客之前,博主在这里介绍一下其它高质量的编程学习栏目:
数据结构专栏:数据结构 这里包含了博主很多的数据结构学习上的总结,每一篇都是超级用心编写的,有兴趣的伙伴们都支持一下吧!
算法专栏:算法 这里可以说是博主的刷题历程,里面总结了一些经典的力扣上的题目,和算法实现的总结,对考试和竞赛都是很有帮助的!
力扣刷题专栏:Leetcode想要冲击ACM、蓝桥杯或者大学生程序设计竞赛的伙伴,这里面都是博主的刷题记录,希望对你们有帮助!
先赞后看好习惯 打字不容易,这都是很用心做的,希望得到支持你 大家的点赞和支持对于我来说是一种非常重要的动力。看完之后别忘记关注我哦!️️️
这个小项目几乎包含了C语言学习阶段所有知识点,实现了这个项目,我们的C语言,就没问题了!
本篇为不收藏必后悔篇~
我们要实现的通讯录的功能:
- 可以动态开辟空间存放联系人的信息。
- 联系人的信息包括名字、性别、年龄、电话、地址。
- 同时,我们要实现通讯录中:增加,删除,查找,修改联系人的功能,打印联系人的功能。
- 联系人的信息可以通过文件进行保存。
实现过程目录
前言
实现这种类似的简易通讯录有很多种方式,这里博主选用的是顺序表来进行实现,其实博主认为,使用链表是最好的,但是我们如果是刚学习完C语言的伙伴,应该是还没有开始学习数据结构的,所以这里我们用最简单的——顺序表,也就是结构体数组来实现这个通讯录。
当然,如果对链表有兴趣的伙伴,可以通过博主提供的传送门过去食用~
【链表】单链表的介绍和基本操作(C语言实现)【保姆级别详细教学】
【链表】双向链表的介绍和基本操作(C语言实现)【保姆级别详细教学】
当然,它们也收录在博主:数据结构这个专栏里面。
当然,在本期博客中,我们要所用到的核心知识有:动态内存的管理,结构体和文件。博主以前总结过这些知识点,这里提供传送门,如果伙伴们在阅读的时候遇到困难,不妨食用完博主这几篇总结再来实现这个小项目,这几篇博客同样是干货满满噢!
【算法】【C语言进阶】C语言字符串操作宝藏级别汇总 strtok函数 strstr函数该怎么用?【超详细的使用解释和模拟实现】
【文件】C语言文件操作及其使用总结篇【初学者保姆级别福利】
【动态内存】C语言动态内存管理及使用总结篇【初学者保姆级福利】
实现通讯录总体框架
当然,我们实现的时候采用分模块实现的方式。
创建3个源文件:
test.c
总体测试源文件(main函数的位置)
contact.c
实现接口
contact.h
接口的声明
首先我们来看通讯录的结构
#define NAME_MAX 20 //一个联系人名字的最大空间
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
#define DEFAULT_SZ 3 //默认初始容量大小-后面有需要会接着增容
//一个人的信息的结构
typedef struct PeoInfo {
char name[NAME_MAX];//名字
char sex[SEX_MAX];//性别
int age;//年龄
char tele[TELE_MAX];//电话
char addr[ADDR_MAX];//地址
}PeoInfo;
//通讯录结构
//顺序表实现
typedef struct Contact {
PeoInfo* data;
int sz;//记录当前通讯录里面有几个人
int capacity;//记录最大容量
}Contact;
//实现的结构名称
//让代码的可读性提升了
enum Option {//利用枚举,给接口所对应的数字换成有意义的单词,提升可读性
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
确定好总体结构之后,我们回到test.c文件里面,开始创建通讯录。
#include"contact.h"
//通讯录
//自己写一个小目录
void menu() {
printf("----------------------------\n");
printf("* THE MENU *\n");
printf("----------------------------\n");
printf("* 1.add *\n");
printf("* 2.del *\n");
printf("* 3.search *\n");
printf("* 4.modify *\n");
printf("* 5.sort *\n");
printf("* 6.print *\n");
printf("* 0.exit *\n");
printf("----------------------------\n");
}
void _test() {
int _input = 0;
//创建通讯录
Contact con;
//初始化通讯录
//所以我们要改造这个初始化函数,给它三个struct的空间
_initContact(&con);//这里同时也要把东西加载进去
do {
system("cls");//这个是格式优化,我们可以暂时不管
menu();
printf("there is %d PeoInfos in the Contact\n", con.sz);
printf("----------------------------\n");
printf("please choose:>");
scanf("%d", &_input);
switch (_input)
{
case ADD://ADD,DEL这些博主都通过枚举的方式,给它们赋好值了
_addContact(&con);//增加联系人
break;
case DEL:
_delContact(&con);//删除联系人
break;
case SEARCH:
_searchContact(&con);//查找联系人
break;
case MODIFY:
_modifyContact(&con);//修改联系人
break;
case SORT:
//_sortByAge(&con);//按照年龄排序-这个博主就不带大家实现了,其余的结构博主都给大家写好了,这个可以自己尝试一下
break;
case PRINT:
_printContact(&con);
break;
case EXIT:
//改成动态版本之后,要一个接口来销毁通讯录
//退出通讯录之前,我们还需要将内容保存一下
_saveContact(&con);
_destroyContact(&con);
printf("exit contact\n");
system("pause");
exit(-1);
break;
default:
printf("err\n");//选择错误
system("pause");
break;
}
} while (_input);
}
int main() {
_test();
return 0;
}
我们可以测试一下我们的目录:
当然,写到这里,我们的实现接口是还没有写的,等一下我们要一一实现!
核心接口实现
初始化通讯录接口
初始化注意事项:
- 首先我们需要开辟出3(一开始设定好的默认值)的空间,全部置为0
- 然后,我们还要加载以前保存下来的文件(
contact.bat
)里的内容- 加载的时候,要检查当前通讯录的容量是否够放,所以我们还需要一个检查容量,并在不够时增容的一个函数:
_checkCapacity()
,这个博主在后面的讲解带大家实现,在这里大家先写好,我们一会儿实现。
//加载通讯录
void _loadContact(Contact* pc) {
//打开文件
FILE* pf = fopen("contact.dat", "rb");
if (pf == NULL) {
perror("_loadContact::fopen");
return;
}
//读文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf)) {
//放进去
_checkCapacity(pc);//检查容量是否够放
pc->data[pc->sz] = tmp;
pc->sz++;
}
//关闭文件
fclose(pf);
pf = NULL;
}
//改造成文件的版本
void _initContact(Contact* pc) {
assert(pc);
pc->sz = 0;
pc->capacity = DEFAULT_SZ;//创建默认个数的空间先
pc->data = (PeoInfo*)malloc(pc->capacity * sizeof(PeoInfo));
if (pc->data == NULL) {
perror("init::malloc");
exit(-1);
}
memset(pc->data, 0, pc->capacity * sizeof(PeoInfo));
//加载文件信息到通讯录
_loadContact(pc);
}
增加联系人接口
思路很简单,一个一个信息输入,放进去即可,但是要注意:增加的时候先检查容量。
//增加联系人
void _addContact(Contact* pc) {
assert(pc);
//满了要增容
_checkCapacity(pc);
//录入信息
printf("please input the name:");
scanf("%s", pc->data[pc->sz].name);
printf("please input the age:");
scanf("%d", &(pc->data[pc->sz].age));
printf("please input the sex:");
scanf("%s", pc->data[pc->sz].sex);
printf("please input the tele_num:");
scanf("%s", pc->data[pc->sz].tele);
printf("pleae input the address:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;//通讯录人物数量记得++
printf("----------------------------\n");
printf("successfully added!\n");
printf("----------------------------\n");
printf("\n");
system("pause");
}
检查并增加容量接口
同样:思路非常的简单,不够了,再开辟一个即可!
这里注意realloc()
函数的使用!
//增容
void _checkCapacity(Contact* pc) {
if (pc->sz == pc->capacity) {
PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
if (tmp != NULL) {
pc->data = tmp;
}
pc->capacity += 2;
printf("contact is successfully enlarged!\n");
printf("---------------------------------\n");
}
}
打印接口
一个简单的顺序表的遍历即可实现,当然我们要花点功夫让它们打印得更美观!
//打印通讯录
void _printContact(const Contact* pc) {
assert(pc);
//打印
int i = 0;
printf("\n");
printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++) {
printf("%-20s %-5d %-5s %-12s %-30s\n", pc->data[i].name, pc->data[i].age,
pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
}
printf("\n");
system("pause");
}
查找,删除,修改联系人信息接口
这几个接口我们写在一起,因为删除,修改,我们总要找到这个人吧,所以其实它们都需要用到一个叫做 通过联系人名字查找联系人的一个接口。
思路也非常的简单,但是有很多细节,我们在写的时候注意一下即可,具体我们看注释。
//通过名字查找人的信息
//如果找到这个人,返回在顺序表中的下标i,如果没找到,返回-1
int _findByName(const Contact* pc, char* name) {
assert(pc);
int i = 0;
for (i = 0; i < pc->sz; i++) {
if (strcmp(pc->data[i].name, name) == 0) {
return i;
}
}
return -1;
}
//删除指定联系人信息
void _delContact(Contact* pc) {
assert(pc);
if (pc->sz == 0) {//里面没有联系人,不能删除
printf("null Contact! err!\n");
system("pause");
return;
}
//删除
//1.找到
char name[NAME_MAX] = { 0 };
printf("please input the name:");
scanf("%s", name);
int pos = _findByName(pc, name);//找到了,返回下标;找不到,返回负一
if (pos == -1) {//找不到
printf("cannot find the PeoInfo,err!");
system("pause");
return;
}
//2.删除
//这里要稍微分析清楚,是sz-1,不要越界了
for (int j = pos; j < pc->sz - 1; j++) {
pc->data[j] = pc->data[j + 1];
}
pc->sz--;//这个不要忘记了
printf("successfully deleted\n");
system("pause");
}
//查找一个人的信息
void _searchContact(const Contact* pc) {
char name[NAME_MAX] = { 0 };
printf("please input the name:>");
scanf("%s", name);
int pos = _findByName(pc, name);
if (pos == -1) {
printf("cannot find the PeoInfo,err!");
return;
}
printf("--------------------------------------------------\n");
printf("the info is:\n");
printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-20s %-5d %-5s %-12s %-30s\n", pc->data[pos].name, pc->data[pos].age,
pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr);
printf("--------------------------------------------------\n");
system("pause");
}
void _modifyContact(Contact* pc) {
printf("whose Info do you want to modify:\n");
char name[NAME_MAX] = { 0 };
scanf("%s", name);
int pos = _findByName(pc, name);//找一下这个人,看能不能找到
if (pos == -1) {//找不到
printf("cannot find the PeoInfo,err!\n");
printf("--------------------------------------------------\n");
system("pause");
return;
}
//找到了-修改
//修改
printf("--------------------------------------------------\n");
printf("you are modifing %s's info\n", pc->data[pos].name);
printf("please input the name:");
scanf("%s", pc->data[pos].name);
printf("please input the age:");
scanf("%d", &(pc->data[pos].age));
printf("please input the sex:");
scanf("%s", pc->data[pos].sex);
printf("please input the tele_num:");
scanf("%s", pc->data[pos].tele);
printf("pleae input the address:");
scanf("%s", pc->data[pos].addr);
printf("\n");
printf("successfully modified!\n");
printf("--------------------------------------------------\n");
printf("\n");
system("pause");
}
保存联系人信息、退出通讯录接口
这里我们又要用到文件的知识了,把当前通讯录的信息写到文件里去。
//保存通讯录
void _saveContact(const Contact* pc) {
FILE* pf = fopen("contact.dat", "wb");//以二进制的形式写进去
if (pf == NULL) {
perror("_saveContact::fopen");
return;
}
//写文件
for (int i = 0; i < pc->sz; i++) {//一个一个写进去
fwrite(pc->data+i,sizeof(PeoInfo),1,pf);
}
printf("sucessfully saved!\n");
printf("--------------------------------------------------\n");
//关闭文件
fclose(pf);
pf = NULL;
}
//销毁通讯录
void _destroyContact(Contact* pc) {
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
}
完整代码展示
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
//通讯录
//改成动态版本
void menu() {
printf("----------------------------\n");
printf("* THE MENU *\n");
printf("----------------------------\n");
printf("* 1.add *\n");
printf("* 2.del *\n");
printf("* 3.search *\n");
printf("* 4.modify *\n");
printf("* 5.sort *\n");
printf("* 6.print *\n");
printf("* 0.exit *\n");
printf("----------------------------\n");
}
void _test() {
int _input = 0;
//创建通讯录
Contact con;
//初始化通讯录
//所以我们要改造这个初始化函数,给它三个struct的空间
_initContact(&con);//这里同时也要把东西加载进去
do {
system("cls");
menu();
printf("there is %d PeoInfos in the Contact\n", con.sz);
printf("----------------------------\n");
printf("please choose:>");
scanf("%d", &_input);
switch (_input)
{
case ADD:
_addContact(&con);
break;
case DEL:
_delContact(&con);
break;
case SEARCH:
_searchContact(&con);
break;
case MODIFY:
_modifyContact(&con);
break;
case SORT:
//_sortByAge(&con);//按照年龄排序
break;
case PRINT:
_printContact(&con);
break;
case EXIT:
//改成动态版本之后,要一个接口来销毁通讯录
//改造成文件版本
_saveContact(&con);
_destroyContact(&con);
printf("exit contact\n");
system("pause");
exit(-1);
break;
default:
printf("err\n");
system("pause");
break;
}
} while (_input);
}
int main() {
_test();
return 0;
}
contact.h
#pragma once
#include<stdio.h>
#include<memory.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
#define NAME_MAX 20 //一个联系人名字的最大空间
#define SEX_MAX 5 //性别
#define TELE_MAX 12 //电话
#define ADDR_MAX 30 //地址
#define DEFAULT_SZ 3 //默认初始容量大小-后面有需要会接着增容
//一个人的信息
typedef struct PeoInfo {
char name[NAME_MAX];
char sex[SEX_MAX];
int age;
char tele[TELE_MAX];
char addr[ADDR_MAX];
}PeoInfo;
//静态版本
#if 0
typedef struct Contact {
PeoInfo data[MAX];//可以存放1000个人的信息
int sz;//记录通讯录种已经保存的信息个数
}Contact;
#endif
typedef struct Contact {
PeoInfo* data;//可以存放
int sz;
int capacity;//记录最大容量
}Contact;
//实现的结构名称
//让代码的可读性提升了
enum Option {
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
SORT,
PRINT
};
void _initContact(Contact* pc);
void _addContact(Contact* pc);
void _printContact(const Contact* pc);
void _delContact(Contact* pc);
void _searchContact(const Contact* pc);
void _modifyContact(Contact* pc);
void _sortByAge(Contact* pc);
void _destroyContact(Contact* pc);
void _saveContact(const Contact* pc);
contact.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"contact.h"
//增容
void _checkCapacity(Contact* pc) {
if (pc->sz == pc->capacity) {
PeoInfo* tmp = (PeoInfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(PeoInfo));
if (tmp != NULL) {
pc->data = tmp;
}
pc->capacity += 2;
printf("contact is successfully enlarged!\n");
printf("---------------------------------\n");
}
}
//初始化通讯录(静态)
#if 0
void _initContact(Contact* pc) {
assert(pc);
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
#endif
//动态版本初始化
#if 0
void _initContact(Contact* pc) {
assert(pc);
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
pc->data = (PeoInfo*)malloc(pc->capacity * sizeof(PeoInfo));
if (pc->data == NULL) {
perror("init::malloc");
exit(-1);
}
memset(pc->data, 0, pc->capacity * sizeof(PeoInfo));
}
#endif
//加载通讯录
void _loadContact(Contact* pc) {
//打开文件
FILE* pf = fopen("contact.dat", "rb");
if (pf == NULL) {
perror("_loadContact::fopen");
return;
}
//读文件
PeoInfo tmp = { 0 };
while (fread(&tmp, sizeof(PeoInfo), 1, pf)) {
//放进去
_checkCapacity(pc);
pc->data[pc->sz] = tmp;
pc->sz++;
}
//关闭文件
fclose(pf);
pf = NULL;
}
//改造成文件的版本
void _initContact(Contact* pc) {
assert(pc);
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
pc->data = (PeoInfo*)malloc(pc->capacity * sizeof(PeoInfo));
if (pc->data == NULL) {
perror("init::malloc");
exit(-1);
}
memset(pc->data, 0, pc->capacity * sizeof(PeoInfo));
//加载文件信息到通讯录
_loadContact(pc);
}
//销毁通讯录
void _destroyContact(Contact* pc) {
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
}
//增加联系人
void _addContact(Contact* pc) {
assert(pc);
//if (pc->sz == MAX) {
// printf("Contact is already full,err\n");
// exit(-1);
//}
//满了要增容
_checkCapacity(pc);
//录入信息
printf("please input the name:");
scanf("%s", pc->data[pc->sz].name);
printf("please input the age:");
scanf("%d", &(pc->data[pc->sz].age));
printf("please input the sex:");
scanf("%s", pc->data[pc->sz].sex);
printf("please input the tele_num:");
scanf("%s", pc->data[pc->sz].tele);
printf("pleae input the address:");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("----------------------------\n");
printf("successfully added!\n");
printf("----------------------------\n");
printf("\n");
system("pause");
}
//打印通讯录
void _printContact(const Contact* pc) {
assert(pc);
//打印
int i = 0;
printf("\n");
printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++) {
printf("%-20s %-5d %-5s %-12s %-30s\n", pc->data[i].name, pc->data[i].age,
pc->data[i].sex, pc->data[i].tele, pc->data[i].addr);
}
printf("\n");
system("pause");
}
//通过名字查找人的信息
int _findByName(const Contact* pc, char* name) {
assert(pc);
int i = 0;
for (i = 0; i < pc->sz; i++) {
if (strcmp(pc->data[i].name, name) == 0) {
return i;
}
}
return -1;
}
//删除指定联系人信息
void _delContact(Contact* pc) {
assert(pc);
if (pc->sz == 0) {
printf("null Contact! err!\n");
system("pause");
return;
}
//删除
//1.找到
char name[NAME_MAX] = { 0 };
printf("please input the name:");
scanf("%s", name);
int pos = _findByName(pc, name);//找到了,返回下标;找不到,返回负一
if (pos == -1) {
printf("cannot find the PeoInfo,err!");
system("pause");
return;
}
//2.删除
//这里要稍微分析清楚,是sz-1,不要越界了
for (int j = pos; j < pc->sz - 1; j++) {
pc->data[j] = pc->data[j + 1];
}
pc->sz--;//这个不要忘记了
printf("successfully deleted\n");
system("pause");
}
//查找一个人的信息
void _searchContact(const Contact* pc) {
char name[NAME_MAX] = { 0 };
printf("please input the name:>");
scanf("%s", name);
int pos = _findByName(pc, name);
if (pos == -1) {
printf("cannot find the PeoInfo,err!");
return;
}
printf("--------------------------------------------------\n");
printf("the info is:\n");
printf("%-20s %-5s %-5s %-12s %-30s\n", "姓名", "年龄", "性别", "电话", "地址");
printf("%-20s %-5d %-5s %-12s %-30s\n", pc->data[pos].name, pc->data[pos].age,
pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr);
printf("--------------------------------------------------\n");
system("pause");
}
void _modifyContact(Contact* pc) {
printf("whose Info do you want to modify:\n");
char name[NAME_MAX] = { 0 };
scanf("%s", name);
int pos = _findByName(pc, name);
if (pos == -1) {
printf("cannot find the PeoInfo,err!\n");
printf("--------------------------------------------------\n");
system("pause");
return;
}
//修改
printf("--------------------------------------------------\n");
printf("you are modifing %s's info\n", pc->data[pos].name);
printf("please input the name:");
scanf("%s", pc->data[pos].name);
printf("please input the age:");
scanf("%d", &(pc->data[pos].age));
printf("please input the sex:");
scanf("%s", pc->data[pos].sex);
printf("please input the tele_num:");
scanf("%s", pc->data[pos].tele);
printf("pleae input the address:");
scanf("%s", pc->data[pos].addr);
printf("\n");
printf("successfully modified!\n");
printf("--------------------------------------------------\n");
printf("\n");
system("pause");
}
//保存通讯录
void _saveContact(const Contact* pc) {
FILE* pf = fopen("contact.dat", "wb");//以二进制的形式写进去
if (pf == NULL) {
perror("_saveContact::fopen");
return;
}
//写文件
for (int i = 0; i < pc->sz; i++) {//一个一个写进去
fwrite(pc->data+i,sizeof(PeoInfo),1,pf);
}
printf("sucessfully saved!\n");
printf("--------------------------------------------------\n");
//关闭文件
fclose(pf);
pf = NULL;
}
尾声
看到这里,我们的通讯录小项目就完成了!这里面包含了C语言大部分的重要知识点,对复习是很有帮助的。这也是我们学习C语言来为数不多的一个项目。看完这篇博客,是不是感觉自己的C又行了呢哈哈哈。如果你觉得这篇博客对你有帮助,千万不要忘了点赞关注和收藏后再走噢!