C语言通讯录
开头
这是一篇介绍如何使用C语言实现一个简单通讯录的博客, 我使用的编译器是vs2022.
同时, 我是一名C语言的初学者, 目前学了半年还比较菜, 如果有写不好的地方,希望大佬能够见谅和指点一下. 如果你跟我一样也是初学者, 那希望这篇文章对你有帮助.
程序设计的目标
用c语言实现一个能够存储1000个人的姓名,电话,地址,年龄的通讯录, 并且能够实现以下功能
0,退出程序
1,增加一个人的信息
2,删除指定人信息
3,搜索指定人并显示其信息
4,修改指定人信息
5,按照年龄大小对通讯录进行排序
程序实现的思路
我将程序分为3个文件, 头文件(contact.h)用于声明函数和结构体变量等, 源文件(contact.c)用于实现通讯录增删改查等功能的函数, 源文件(test.c)用于实现程序的整体执行.
test.c
先搭个整体框架. 首先在main()中创建一个结构体变量con(该结构体定义在contact.h)和整型input并初始化为0, 由于该代码一定会执行一次所以我用do…while语句, 利用switch语句来进行选项的选择–选项我使用枚举类型主要是增加可读性. 然后在依次在选项后面添加上我们实现的增删改查等函数.
#define _CRT_SECURE_NO_WARNINGS 1
#include "contact.h"
void menu()
{
printf("******************************\n");
printf("********1.增加 2.删除*********\n");
printf("********3.查找 4.修改*********\n");
printf("********5.打印 6.排序*********\n");
printf("********0.退出****************\n");
}
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIF,
PRINT,
SORT
};
int main()
{
Contact con = { 0 };
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case ADD:
contact_add(&con);
break;
case DEL:
contact_del(&con);
break;
case SEARCH:
contact_sear(&con);
break;
case MODIF:
contact_modi(&con);
break;
case PRINT:
contact_print(&con);
break;
case SORT:
contact_sort(&con);
break;
case EXIT:
printf("已退出\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
}
contact.h
在头文件中定义最大长度的宏以及结构体PeopleInfo用于存放每个的信息,结构体Contact则存放1000个人的信息,同时利用sz统计实际人数.
对于这两个结构体我都使用了typedef进行重命名,主要是为了方便使用.
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#define NAME_MAX 20 // 名字
#define NUM_MAX 15 // 电话
#define ADDR_MAX 20 // 地址
#define MAX 1000 // 人数
typedef struct PeopleInfo // 单个人的信息
{
char name[NAME_MAX];
char number[NUM_MAX];
char adderss[ADDR_MAX];
int age;
}PeopleInfo;
typedef struct Contact
{
PeopleInfo data[MAX];
int sz; // 记录人数
}Contact;
void contact_add(Contact* pc); // 增加一个人的信息
void contact_print(Contact* pc); // 打印信息
void contact_del(Contact* pc); // 删除指定人
void contact_sear(Contact* pc); // 查询指定人并打印其信息
void contact_modi(Contact* pc); // 修改指定人信息
void contact_sort(Contact* pc); // 按照年龄进行排序
contact.c
这一个源文件才是重头戏. 这里我将给出我设计时的一些思路, 希望能够帮助读者理解这个源文件.
void contact_add(Contact* pc) – 增加一个人的信息
首先实现的就是向通讯录增加一个人信息的函数, 这个函数比较简单, 它的输入是结构体指针(&con), 然后配合上一段printf和scanf语句完成信息的存储, 以及增加后实际人数(sz)+1和打印增加成功的提示.
void contact_add(Contact* pc)
{
if (pc->sz == MAX - 1)
{
printf("增加失败,通讯录已满\n");
}
printf("请输入名字:>");
scanf("%s", pc->data[pc->sz].name);
printf("请输入电话号码:>");
scanf("%s", pc->data[pc->sz].number);
printf("请输入地址:>");
scanf("%s", pc->data[pc->sz].adderss);
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->sz].age)); // 注意年龄是整数,要取地址
pc->sz++;
printf("增加成功\n");
}
但是需要注意溢出的情况, 所以我在函数的前头加上了判读语句.
sz代表下标, data[MAX] 的最大下标就是MAX - 1
void contact_print(Contact* pc) – 打印信息
完成了增加函数, 我们需要确认是否真的增加了一个人的信息, 所以第二步就是设计打印的函数. 因为要打印结构体数组和打印人数, 所以该函数中输入是结构体指针(&con), 使用for循环来打印实际人数(sz).
void contact_print(Contact* pc)
{
int i = 0;
printf("%-10s %-10s %-10s %-10s\n", "名字", "号码", "地址", "年龄");
for (i = 0; i < pc->sz; i++)
{
printf("%-10s %-10s %-10s %-10d\n",pc->data[i].name,
pc->data[i].number,
pc->data[i].adderss,
pc->data[i].age); // 注意最后打印的是%d
}
}
printf("%-10s %-10s %-10s %-10s\n", "名字", "号码", "地址", "年龄");
将字符串这样打印主要是为了格式统一, 就是好看点
请注意, 每次增加人后sz都会+1, 也就是说sz下标处是初始化状态(啥都没有), 所以也就不需要打印sz下标对应的值了.
到这里, 程序执行后可以获得这样的结果
这里*对不齐应该是我字体的原因(强迫症犯了)
void contact_del(Contact* pc) – 删除指定人
接下了实现的是删除函数, 它需要用户输入名字来找到要删除的特定人, 由于后续删改查都需要根据找到特定人, 所以在这里再设计一个函数int find_someone_by_name(Contact* pc)
int find_someone_by_name(Contact* pc)
{
char name[NAME_MAX] = { 0 };
int i = 0;
printf("请输入要找人的名字:>");
scanf("%s", name);
for (i = 0; i < pc->sz; i++)
{
if (strcmp(pc->data[i].name, name) == 0)
{
return i; // 返回下标
}
}
return -1;
}
该函数要求用户输入名字并将其储存于name数组中, 然后遍历数组判断输入名字是否存在于结构体, 如果存在就返回i(此时i代表的就是要找的人在结构体的下标), 如果不存在则返回1.
好了, 实现这个函数后接着完成我们的删除函数. 首先, 创建一个临时变量ret用来接收寻找函数的返回值, 如果ret == -1 那就说明没找到这个人, 打印找不到的信息提醒用户, 如果ret != -1 那么进行删除动作. 由于删除完成后会留下一个空数组, 影响打印时的美观, 所以我们直接把删除的那个人后面的数据往前移动一个, 也就是将该人后续数据前移, 直接覆盖这个人. 然后记得在删除后实际人数会减1(sz–), 同时可以加上一个判断条件防止通讯录为空的情况–当然也可以不用,因为通讯录为空时你就不可能找到人.
void contact_del(Contact* pc)
{
if (pc->sz == 0)
{
printf("删除失败,电话簿为空\n");
}
int ret = find_someone_by_name(pc);
if (ret == -1)
{
printf("没有找到这个人\n");
return; // 结束函数
}
else
{
int i = 0;
for (i = ret; i < pc->sz; i++);
{
pc->data[i] = pc->data[i + 1]; // 从获取到的下标开始覆盖前一个数据
}
pc->sz--; // 人数减少
printf("删除成功\n");
}
return;
}
结构体变量之间是可以直接用"="赋值的.
void contact_sear(Contact* pc) – 查询指定人并打印其信息
接下来实现查找这个功能, 同样这个函数也需要找到特定人, 所以继续使用int find_someone_by_name(Contact* pc)
这个函数. 首先创建变量ret来储存返回值, 如果ret == -1 那就说明没找到这个人, 打印找不到的信息提醒用户, 如果ret != -1 那么进行打印动作. 打印的功能可以直接拷贝contact_print
函数, 但需要注意这里只打印一个人, 它的下标为ret.
void contact_sear(Contact* pc)
{
int ret = find_someone_by_name(pc);
if (ret == -1)
{
printf("没有找到这个人\n");
return; // 结束函数
}
else // 找到时打印这个人的信息
{
printf("%-10s %-10s %-10s %-10s\n", "名字", "号码", "地址", "年龄");
printf("%-10s %-10s %-10s %-10d\n", pc->data[ret].name,
pc->data[ret].number,
pc->data[ret].adderss,
pc->data[ret].age); // 注意最后打印的是%d
}
return;
}
void contact_modi(Contact* pc) – 修改指定人信息
既然是修改指定人的信息, 那也同样需要int find_someone_by_name(Contact* pc)
这个函数, 继续我们的老方法. 创建变量ret来储存返回值, 如果ret == -1 那就说明没找到这个人, 打印找不到的信息提醒用户, 如果ret != -1 那么进行修改动作. 修改动作可以直接拷贝contact_add
函数, 但需要注意这里只修改一个人, 它的下标为ret.
void contact_modi(Contact* pc)
{
int ret = find_someone_by_name(pc);
if (ret == -1)
{
printf("没有找到这个人\n");
return; // 结束函数
}
else
{
printf("请输入名字:>");
scanf("%s", pc->data[ret].name);
printf("请输入电话号码:>");
scanf("%s", pc->data[ret].number);
printf("请输入地址:>");
scanf("%s", pc->data[ret].adderss);
printf("请输入年龄:>");
scanf("%d", &(pc->data[ret].age));
printf("修改成功\n"); // 注意这里人数无变化所以sz不用+1
}
return;
}
void contact_sort(Contact* pc) – 按照年龄进行排序
这是最后一个函数, 它的功能是根据年龄大小对结构体数组data[]
进行排序(从小到大). 要实现这个函数我能可以借助一个库函数qsort
这个函数有4个参数, 第一个参数即我们要排序的目标(data), 第二个即排序对象的个数(sz), 第三个即每个元素的长度–大小为字节(sizeof(data[0])), 第四个是一个函数指针, 这里使用的是回调函数, 关于函数指针和回调函数我这里就不展开了, 反正这里就是让你设计一个函数, 它可以比较两个数之间的大小–大于返回>0|小于返回<0|等于返回0.
所以我们先实现比较函数
// 比较函数,用于qsor函数中
int cmp(const void* a, const void* b)
{
PeopleInfo* p1 = (PeopleInfo*)a;
PeopleInfo* p2 = (PeopleInfo*)b;
return p1->age - p2->age;
}
因为传入的指针内容不需要改变, 所以加const更加安全. 使用void*
是为了通用性, 只要你想任何同类型的两数都可以比较, 当然在你知道你要比较的两个数是啥类型时, 你也可以直接写那个类型, 例如PeopleInfo* a
. 这里我使用的是void*
所以还要进行强制类型转换, 这样编译器才知道这两值之间的间隔, 进而可以计算. 计算就非常简单粗暴, 直接返回两数相减的结果, 当然你也可以使用if…else返回1, -1, 0. 如果你想实现反序, 直接在return的值前加上"-", 或者p1和p2位置互换一下即可.
void 类型是不能进行±等运算的
实现了比较函数, 接下来就是实现我们的contact_sort
函数了
void contact_sort(Contact* pc)
{
qsort(pc->data, pc->sz, sizeof(pc->data[0]), cmp);
contact_print(pc);
printf("排序完成\n");
}
注意这里第二个参数是sz, 而不能是MAX. 如果是MAX则会把初始化为0的排在前头, 造成错误.
结语
这个通讯录还有许多可以改进的地方, 比如排序的方式, 修改项的数量等等, 在这里只是给出一些基本的功能, 剩下就交给你自己吧. 总的来说, 这个程序难度并不大, 只是有些麻烦. 同时这也是我第一次写博客, 只能很得劲,不知不觉码了3000个字左右, 有一点小累, 如果你有什么问题或者建议请在评论区给出, 如果觉得对你有帮助也请点个赞让我开心开心o( ̄▽ ̄)ブ.
.