前言:
本章没有使用链表!
C语言从入门到精通(鹏哥带你C语言从入门到精通,谭浩强C语言教程C语言程序设计C语言修仙C语言考研计算机二级专升本C语言期末突软考二级C语言考研C语言C语言)_哔哩哔哩_bilibili
本文很长,看不懂可以去B站看鹏哥的,讲的非常好,但细节需要自己完善,加油💪💪
通讯录的每个联系人是由
名字、性别、年龄、电话、住址
等等
再由每个联系人组成一张列表完成通讯录
注:文中都是拆分的,文尾才是源代码!!
一、基本框架
完成框架:
因为要多次操作,所以需要循环控制操作方法
通讯录的操作方法有很多(
增删查改最基础
)
1.增加 2.删除 3.查找 4.修改 5.排序 6.打印
所以在头文件中用枚举常量很合适,因为枚举常量可以对应输入数值
然后就是主函数的框架部分
void menu()
{
//菜单
printf("*****************************\n");
printf("**** 通讯录v1.0 ****\n");
printf("**** 1.增加 2.删除 ****\n");
printf("**** 3.查找 4.修改 ****\n");
printf("**** 5.排序 6.打印 ****\n");
printf("**** 0.退出 ****\n");
printf("*****************************\n");
}
int main()
{
int input = 0;
do
{
menu(); //菜单
printf("请输入:>");
scanf("%d", &input);
getchar(); //吸收换行(\n)
switch (input)
{
case Add: //增加
printf("增加\n");
break;
case Del: //删除
printf("删除\n");
break;
case Find: //...
printf("查找\n");
break;
case Change:
printf("修改\n");
break;
case Sort:
printf("排序\n");
break;
case Print:
printf("打印\n");
break;
case Exit:
printf("退出\n");
break;
default:
printf("输入错误\n");
break;
}
} while (input);
}
完成主要框架后,可以进行封装函数了
首先建立联系人的结构体,方便寻找成员,然后枚举几个最大值方便以后维护
然后建立
通讯录
结构体来存储
联系人
、
联系人个数
。
因为我不知道你有多少个联系人,所以采用动态开辟内存
来构造通讯录所占空间,因此通讯录中还需要
存储一个变量
来确定当前通讯录的容量以便于
扩容
。
(1) 封装第一个函数:初始化通讯录(记得在头文件声明)
因为要动态开辟内存,所以首先需要
初始化通讯录函数
来进行开辟空间
记得引用 <string.h>头文件
然后就可以声明通讯录并且初始化了。。
(2)封装增加联系人函数
因为是动态开辟内存,所以可能会存满需要扩容的情况,所以
先把编写
扩容函数
,用于判断是否需要扩容的情况,再考虑增加
联系人。realloc 函数可以重新开辟一块空间并且可以将原空间的值
拷贝到新开辟的空间,然后释放原空间,所以重新开辟空间用这个很合适。
接着封装增加联系人,增加一个继续增加的判断条件,这一部分整体还是很简单的。
(3)封装打印函数
有了增加方法,那就把打印函数写出来,就可以进行查看是否写入成功
尝试输入一个联系人
尝试输入四个联系人
看来,开辟空间成功了但是这个通讯录的格式很丑,全是右对齐的,
所以我全给搞成左对齐了(其实就是把”%20s”改成”%-20s”)
(4)封装删除联系人函数
删除联系人,首先你
得有联系人
,所以需要判断通讯录是否还有联系人
然后你还得
找到你想删掉的联系人
,然后将他删掉。
我加了一个判断防止不小心按到的情况。
因为需要找到联系人,但是后边修改、查找都需要寻找联系人
所以封装一个寻找联系人的函数,返回值为该联系人的下标
接着封装删除联系人函数
//删除联系人
void DeleteData(list* pc)
{
if (pc->sz == 0) //当通讯录没有任何联系人时,直接返回
{
printf("还没有联系人呢!\n");
return;
}
char check = 0; //判断是否误操作
printf("确定要进行删除操作吗(N取消):>");
scanf("%c", &check);
CheckCapcity(pc);
if (check != 'n' && check != 'N')
{
char name[MAX_NAME] = { 0 }; //获得删除的人名
printf("请输入要删除的联系人:>");
scanf("%s", name);
int pos = FindContact(pc, name); //接收返回值
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");
}
else
{
printf("取消删除\n");
return;
}
}
这是目前的通讯录,现在要把
“我是例外”
删除 看看能不能成功
删除成功了,那试试能不能删除
“王五”
,毕竟通讯录里可没有王五
可行!
(5)封装查找联系人函数
查找函数前面写了,那直接找到并打印出来即可(这个最简单)
因为前面都写过了,复制过来稍微修改即可
这是当前通讯录的人员,我现在想找到
“2”
,就试试
可以找到,那看看能不能找到
“4”
看来出了一点点小问题 ,但问题不大,修改后继续。。
(6)封装修改联系人函数
要修改联系人前需要先找到要修改的联系人,然后再选择修改方式
再进行修改
//修改联系人
void ChangeData(list* pc)
{
char check = 0;
printf("确定要修改联系人吗(N取消):>");
scanf("%c", &check);
if (check == 'n' || check == 'N')
{
printf("取消修改\n");
return;
}
//寻找联系人
char name[MAX_NAME] = { 0 };
printf("请输入要修改的联系人名字:>");
scanf("%s", name);
int pos = FindContact(pc, name);
if (pos == -1)
{
printf("通讯录中没有这位\n");
return;
}
//修改联系人
int ch = 0; //修改标志位
printf("请输入修改类别:1.姓名 2.性别 3.年龄 4.电话 5.地址:>");
scanf("%d", &ch);
if (ch == 1)
{
printf("请输入修改后的姓名:>");
scanf("%s", pc->data[pos].name);
}
else if (ch == 2)
{
printf("请输入修改后的性别:>");
scanf("%s", pc->data[pos].sex);
}
else if (ch == 3)
{
printf("请输入修改后的年龄:>");
scanf("%d",&(pc->data[pos].age)); //注意 取地址(&)
}
else if (ch == 4)
{
printf("请输入修改后的电话:>");
scanf("%s", pc->data[pos].phone);
}
else if (ch == 5)
{
printf("请输入修改后的地址:>");
scanf("%s", pc->data[pos].address);
}
else
{
printf("输入错误!\n");
return;
}
printf("修改成功\n");
}
试试行不行
不小心把张三性别写成女了,看看能不能修改
看来可以修改
(7)封装排序联系人函数
排序可以按照名字排序,也可以按照性别排序,也可以按照年龄排序
也可以按照电话排序,和按照地址排序。
但这有点多(我懒),所以借助<stdlib.h>头文件中的qsort函数来排序吧
选自:
C 库函数 – qsort() | 菜鸟教程 (runoob.com)
其中重要的就是compar(两个元素间的比较函数),其实就是用一个函数
传两个参数,返回他们的差值,观察差值是大于零还是等于零或者小于零
选择:
qsort – C++ Reference (cplusplus.com)
所以我们需要先写出5种不同的函数指针,用于比较不同的结构体成员
接着封装排序联系人函数
看看能不能行
目前通讯录,接着按名字排序
可以看到李四跑上去了,因为strcmp是根据首字母的ASCII码值排序的。
李四的拼音首字母是“l”,王五的拼音首字母是“w”,l是小于w的,所以李四在上面
接着按性别排序
可以看到李四跑到底下去了,是因为男的拼音首字母 “n” 是小于女的拼音首字母也是“n”
所以比较第二个字母nan中的“a”、nv中的“v”,显然a小于v
所以李四在后面。接着排序年龄
比较整形就没什么可以说的了,那个小,那个在前面
接着比较电话
注意不要把电话当作整形,这里设置的是字符串,所以还是按照strcmp的比较方式比较。
接着比较地址
还是按照strcmp的比较方式比较 。
到此基本完成了通讯录,剩下一些细节修修补补
比如删除联系人到多少的时候,重新开辟空间,节省空间浪费
又比如在修改联系人的时候,提示选中的是那个联系人
或者修饰一下提示语,让它更显眼。
然后。。
就是关闭时保存数据,下次启动时继续对数据操作,这才是通讯录
二、完善框架
(1)维护内存防止浪费
首先在退出程序时,释放动态开辟的内存空间,防止内存泄漏
所以写一个退出通讯录的函数
然后是删除联系人过多时,重新开辟空间
其实就是把CheckCapcity(扩容函数)函数稍微修改一下
多加一个条件,当容量-数量>8,也就是容量空余了8个,就重新开辟一块
空间,当然也可以增加一些宏变量方便以后维护
(2)细节修饰
先将要修改的人提示出来然后修饰
先从网上拷一点制表符
/*
┌ ─ ┐
│ │
└ ─ ┘
*/
然后将每一个有取消、没有等提示语句加上,例如
printf("┌────────┐\n");
printf("│取消退出│\n");
printf("└────────┘\n");
printf("┌───────────────┐\n");
printf("│还没有联系人呢!│\n");
printf("└───────────────┘\n");
(3)退出后保存
现在程序还是运行在内存上,程序退出后,系统就会回收内存空间。
所以想要保存数据,就需要将数据转移到硬盘上去。
所以在要退出前,将数据保存到文件中,在下次初始化时读出来即可。
写入操作
读取操作
以下为gif演示
三、代码展示
源代码展示:
1.Contact.h 文件
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
/******头文件定义*******/
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
/******头文件定义*******/
/********全局变量*******/
enum Contact_tem
{
MAX_NAME = 20, //最大姓名字符数
MAX_SEX = 10, //最大性别字符数
MAX_PHONE = 15, //最大电话字符数
MAX_ADDRESS = 20 //最大地址字符数
};
enum Input
{
Exit, //退出 值0
Add, //增加 值1
Del, //删除 值2
Find, //查找 值3
Change, //修改 值4
Sort, //排序 值5
Print //打印 值6
};
/********全局变量*******/
/*******结构体定义*******/
typedef struct Contact
{
char name[MAX_NAME]; //姓名
char sex[MAX_SEX]; //性别
int age; //年龄
char phone[MAX_PHONE]; //电话
char address[MAX_ADDRESS]; //地址
}Contact;
typedef struct PhoneBook
{
Contact* data; //联系人
int sz; //联系人个数
int Capcity; //当前容量
}list;
/*******结构体定义*******/
/*******函数声明********/
//初始化通讯录
void InitData(list* pc);
//增加联系人
void AddData(list* pc);
//打印
void PrintData(list* pc);
//删除联系人
void DeleteData(list* pc);
//查找联系人
void FindData(list* pc);
//修改联系人
void ChangeData(list* pc);
//排序联系人
void SortData(list* pc);
//退出通讯录
void ExitData(list* pc);
//保存数据
void SaveData(list* pc);
/*******函数声明********/
2.Contact.c 文件
#include"Contact.h"
/**************备用函数区****************/
//扩容
void CheckCapcity(list* pc)
{
Contact* ptr = NULL; //用于接收重新获得的空间
if (pc->Capcity == pc->sz)
{
ptr = (Contact*)realloc(pc->data,(pc->Capcity + 2) * sizeof(Contact)); //重新开辟空间
if (ptr == NULL)
{
perror("CheckCapcity"); //开辟失败时报错并退出
return;
}
pc->data = ptr; //没有错误时将新开辟的空间地址赋予pc->data
pc->Capcity += 2; //重新计算当前容量
}
if (pc->Capcity - pc->sz > 8) //删除扩容
{
ptr = (Contact*)realloc(pc->data, (pc->sz + 2) * sizeof(Contact)); //重新开辟空间为pc->sz+2 个联系人的空间
if (ptr == NULL)
{
perror("CheckCapcity");
return;
}
pc->data = ptr;
pc->Capcity = pc->sz + 2; //重新开辟内存容量为pc->sz+2个
}
}
//查找联系人 通讯录 需要寻找的联系人
int FindContact(list* pc, char* str)
{
for (int i = 0; i < pc->sz; i++) //遍历通讯录
{
if (!(strcmp(pc->data[i].name, str))) //两个字符串对比使用strcmp函数
{
return i; //存在该联系人返回i
}
}
return -1; //不存在返回-1
}
//结构体排序 -- 姓名
int cmp_by_name(const void* e1, const void* e2)
{
//void*指针不能解引用操作,所以需要转换成需要的类型才能继续
return strcmp(((Contact*)e1)->name, ((Contact*)e2)->name); //字符串的比较方式需要用strcmp函数实现
}
//结构体排序 -- 性别
int cmp_by_sex(const void* e1, const void* e2)
{
//void*指针不能解引用操作,所以需要转换成需要的类型才能继续
return strcmp(((Contact*)e1)->sex, ((Contact*)e2)->sex);
}
//结构体排序 -- 年龄
int cmp_by_age(const void* e1, const void* e2)
{
//void*指针不能解引用操作,所以需要转换成需要的类型才能继续
return ((Contact*)e1)->age - ((Contact*)e2)->age; //整形相减即可
}
//结构体排序 -- 电话
int cmp_by_phone(const void* e1, const void* e2)
{
//void*指针不能解引用操作,所以需要转换成需要的类型才能继续
return strcmp(((Contact*)e1)->phone, ((Contact*)e2)->phone);
}
//结构体排序 -- 地址
int cmp_by_address(const void* e1, const void* e2)
{
//void*指针不能解引用操作,所以需要转换成需要的类型才能继续
return strcmp(((Contact*)e1)->address, ((Contact*)e2)->address);
}
/**************备用函数区****************/
/*
┌ ─ ┐
│ │
└ ─ ┘
*/
/**************主要函数区****************/
//初始化通讯录
void InitData(list* pc)
{
pc->sz = 0; //初始化时没有联系人
pc->Capcity = 3; //初始容量为3
pc->data = (Contact*)malloc(sizeof(Contact) * 3); //开辟一块内存为3个联系人大小的通讯录
if (pc->data == NULL) //开辟失败返回并报错
{
perror("InitData");
return;
}
FILE* pf = fopen("text.dat", "a+"); //将文件以可读可写的方式打开,若文件不存在,则新建一个文件
if (pf == NULL)
{
perror("fopen"); //当文件打开失败时报错并退出
return;
}
Contact tem = { 0 }; //定义一个变量来变相读取fread的返回值\
// 接收变量 变量大小 接收个数 文件流 fread每读取一次就会返回剩下多少个元素
while (fread(&tem, sizeof(Contact), 1, pf))
{
CheckCapcity(pc); //判断是否需要扩容
pc->data[pc->sz] = tem; //将原数据覆盖
pc->sz++; //元素个数增加
}
fclose(pf); //关闭文件
}
/*
printf("┌────────┐\n");
printf("│取消增加│\n");
printf("└────────┘\n");
*/
void AddData(list* pc)
{
char check = 0; //判断是否继续添加
printf("确定要添加联系人吗(N取消):>");
scanf("%c", &check);
if (check == 'n' || check == 'N')
{
//取消添加
printf("┌────────┐\n");
printf("│取消增加│\n");
printf("└────────┘\n");
return;
}
//继续添加
CheckCapcity(pc); //判断扩容
printf("请输入姓名:>");
scanf("%s", pc->data[pc->sz].name); //姓名
printf("请输入性别:>");
scanf("%s", pc->data[pc->sz].sex); //性别
printf("请输入年龄:>");
scanf("%d", &(pc->data[pc->sz].age)); //年龄
printf("请输入电话:>");
scanf("%s", pc->data[pc->sz].phone); //电话
printf("请输入住址:>");
scanf("%s", pc->data[pc->sz].address); //地址
pc->sz++;
}
void PrintData(list* pc)
{
printf("%-20s %-10s %-6s %-15s %-20s\n", "姓名", "性别", "年龄", "电话", "地址"); //分类
printf("-------------------------------------------------------------------------\n");
for (int i = 0; i < pc->sz; i++)
{
printf("%-20s %-10s %-6d %-15s %-20s\n",
pc->data[i].name, //姓名
pc->data[i].sex, //性别
pc->data[i].age, //年龄
pc->data[i].phone, //电话
pc->data[i].address //地址
);
printf("-------------------------------------------------------------------------\n");
}
}
//删除联系人
void DeleteData(list* pc)
{
if (pc->sz == 0) //当通讯录没有任何联系人时,直接返回
{
printf("┌───────────────┐\n");
printf("│还没有联系人呢!│\n");
printf("└───────────────┘\n");
return;
}
char check = 0; //判断是否误操作
printf("确定要进行删除操作吗(N取消):>");
scanf("%c", &check);
CheckCapcity(pc);
if (check != 'n' && check != 'N')
{
char name[MAX_NAME] = { 0 }; //获得删除的人名
printf("请输入要删除的联系人:>");
scanf("%s", name);
int pos = FindContact(pc, name); //接收返回值
if (pos == -1)
{
printf("┌──────────────────┐\n");
printf("│通讯录中没有这位!│\n"); //检查是否存在该联系人
printf("└──────────────────┘\n");
return;
}
for (int i = pos; i < pc->sz -1; i++) //最后一位不需要移位,防止越界访问
{
pc->data[i] = pc->data[i + 1]; //让后面一位覆盖前面一位
}
pc->sz--; //让最后一位联系人不能访问
printf("┌────────┐\n");
printf("│删除成功│\n");
printf("└────────┘\n");
}
else
{
printf("┌────────┐\n");
printf("│取消删除│\n");
printf("└────────┘\n");
return;
}
}
//查找联系人
void FindData(list* pc)
{
char check = 0;
printf("确定要查找联系人吗(N取消):>");
scanf("%c", &check);
if (check == 'n' || check == 'N')
{
printf("┌────────┐\n");
printf("│取消查找│\n");
printf("└────────┘\n");
return;
}
//寻找联系人
char name[MAX_NAME] = { 0 };
printf("请输入要查找的联系人:>");
scanf("%s", name);
int pos = FindContact(pc,name);
if (pos == -1)
{
printf("┌──────────────────┐\n");
printf("│通讯录中没有这位!│\n"); //检查是否存在该联系人
printf("└──────────────────┘\n");
return;
}
//打印联系人
printf("%-20s %-10s %-6s %-15s %-20s\n", "姓名", "性别", "年龄", "电话", "地址"); //分类
printf("-------------------------------------------------------------------------\n");
printf("%-20s %-10s %-6d %-15s %-20s\n",
pc->data[pos].name, //姓名
pc->data[pos].sex, //性别
pc->data[pos].age, //年龄
pc->data[pos].phone, //电话
pc->data[pos].address //地址
);
printf("-------------------------------------------------------------------------\n");
}
//修改联系人
void ChangeData(list* pc)
{
char check = 0;
printf("确定要修改联系人吗(N取消):>");
scanf("%c", &check);
if (check == 'n' || check == 'N')
{
printf("┌────────┐\n");
printf("│取消修改│\n");
printf("└────────┘\n");
return;
}
//寻找联系人
char name[MAX_NAME] = { 0 };
printf("请输入要修改的联系人名字:>");
scanf("%s", name);
int pos = FindContact(pc, name);
if (pos == -1)
{
printf("┌──────────────────┐\n");
printf("│通讯录中没有这位!│\n"); //检查是否存在该联系人
printf("└──────────────────┘\n");
return;
}
printf("当前选中: < %s >\n", pc->data[pos].name);
//修改联系人
int ch = 0; //修改标志位
printf("请输入修改类别:1.姓名 2.性别 3.年龄 4.电话 5.地址:>");
scanf("%d", &ch);
if (ch == 1)
{
printf("请输入修改后的姓名:>");
scanf("%s", pc->data[pos].name);
}
else if (ch == 2)
{
printf("请输入修改后的性别:>");
scanf("%s", pc->data[pos].sex);
}
else if (ch == 3)
{
printf("请输入修改后的年龄:>");
scanf("%d",&(pc->data[pos].age)); //注意 取地址(&)
}
else if (ch == 4)
{
printf("请输入修改后的电话:>");
scanf("%s", pc->data[pos].phone);
}
else if (ch == 5)
{
printf("请输入修改后的地址:>");
scanf("%s", pc->data[pos].address);
}
else
{
printf("┌────────┐\n");
printf("│输入错误│\n");
printf("└────────┘\n");
return;
}
printf("┌────────┐\n");
printf("│修改成功│\n");
printf("└────────┘\n");
}
//排序联系人
void SortData(list* pc)
{
char check = 0;
printf("确定要排序吗(N取消):>");
scanf("%c", &check);
if (check == 'n' || check == 'N')
{
printf("┌────────┐\n");
printf("│取消排序│\n");
printf("└────────┘\n");
return;
}
//排序
//函数指针数组,将每一个函数指针组成数组,有需要请自行百度
int (*pf[6])(const void*, const void*) =
{ NULL,cmp_by_name,cmp_by_sex,cmp_by_age,cmp_by_phone,cmp_by_address};
int tmp = 0;
printf("请选择排序方式:1.姓名 2.性别 3.年龄 4.电话 5.地址:>");
scanf("%d", &tmp);
// 排序位置 元素个数 单个元素字节数 元素对比方式
qsort(pc->data, pc->sz, sizeof(pc->data[0]), pf[tmp]); //排序函数
printf("┌────────┐\n");
printf("│排序成功│\n");
printf("└────────┘\n");
}
void ExitData(list* pc)
{
char check = 0;
printf("确定要退出吗(N取消):>");
scanf("%c", &check);
if (check == 'N' || check == 'n')
{
printf("┌────────┐\n");
printf("│取消退出│\n");
printf("└────────┘\n");
return;
}
free(pc->data); //释放内存
printf("┌────┐\n");
printf("│退出│\n");
printf("└────┘\n");
}
//保存数据
void SaveData(list* pc)
{
FILE* pf = fopen("text.dat", "w"); //以只写的形式打开文件,如果文件不存在,创建一个新的文件
if (pf == NULL)
{
perror("fopen"); //打开失败时报错
return;
}
// 写入数据 单个数据大小 写入多少 文件流
fwrite(pc->data, sizeof(Contact), pc->sz, pf);
fclose(pf); //关闭文件
}
/**************主要函数区****************/
3.text.c 文件
#include"Contact.h"
void menu()
{
//菜单
printf("*****************************\n");
printf("**** 通讯录v1.0 ****\n");
printf("**** 1.增加 2.删除 ****\n");
printf("**** 3.查找 4.修改 ****\n");
printf("**** 5.排序 6.打印 ****\n");
printf("**** 0.退出 ****\n");
printf("*****************************\n");
}
int main()
{
int input = 0;
list con; //定义通讯录
InitData(&con); //初始化通讯录
do
{
menu(); //菜单
printf("请输入:>");
scanf("%d", &input);
getchar(); //吸收换行(\n)
switch (input)
{
case Add: //增加
AddData(&con);
break;
case Del: //删除
DeleteData(&con);
break;
case Find: //...
FindData(&con);
break;
case Change:
ChangeData(&con);
break;
case Sort:
SortData(&con);
break;
case Print:
PrintData(&con);
break;
case Exit:
SaveData(&con);
ExitData(&con);
break;
default:
printf("输入错误\n");
break;
}
} while (input);
}
如有错误,敬请指出,多谢🙏