文章目录
通讯录
通讯录对于我们来说都不陌生,在我们早些年使用老人机的时候就认识到了,随着智能手机的普及通讯录变得花里胡哨和更加好看了。但是功能还是不变的,通讯录能够保存人的信息,比如名字、年龄、性别、电话、住址。
那么今天让我们来模拟实现通讯录的一些功能吧。
1.菜单与主框架
我们首先要搞个菜单模块,把我们想要实现的功能打印出来让我们看到,然后我们再去做选择。
框架: 我们定义三个文件,一个是头文件 Contact.h ,一个是Contact.c,一个是test.c
在头文件Contact.h 中我们把定义信息结构体,声明函数和引用库函数的头文件。
在源文件Contact.c 中实现我们定义的函数功能。
在源文件test.c 中放主函数和搭建主要框架。
所以我们先写test.c 中的main函数和框架,代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
//包含我们定义的头文件
#include "Contact.h"
void menu() //菜单函数
{
printf("**********************************\n");
printf("*** 通讯录管理系统 ***\n");
printf("**********************************\n");
printf("*** 0.退出通讯录 ***\n");
printf("*** 1.增加联系人信息 ***\n");
printf("*** 2.删除联系人信息 ***\n");
printf("*** 3.查找联系人信息 ***\n");
printf("*** 4.修改联系人信息 ***\n");
printf("*** 5.显示联系人信息 ***\n");
printf("*** 6.排序联系人信息 ***\n");
printf("**********************************\n\n");
}
int main()
{
Contact con;
//因为要改变栈上变量con的内容,所以要传地址实现
InitContact(&con);//初始化函数
LoadContact(&con);//加载保存联系人函数
int input = 0;
//do while 循环,实现调用各种函数
do
{
menu();
printf("请输入你的选择:");
scanf("%d", &input);
switch (input)
{
case EXIT:
ConserveContact(&con);
DestoryContact(&con);
printf("退出通讯录\n");
break;
case ADD:
AddContact(&con);
break;
case SUB:
SubContact(&con);
break;
case SEARCH:
SearchContact(&con);
break;
case MODIFY:
break;
case SHOW:
ShowContact(&con);
break;
case SORT:
SortContact(&con);
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
2.定义信息结构体
当我们把框架写好了,接下来就是怎么描述一个人的信息呢?要用整形、字符型还是double型;这样都不行,因为一个人的信息有很多,单独靠一种类型是描述不了的。
所以我们要自定义类型,用到了结构体类型struct people 来描述人的信息;在结构体中我们可以定义数组来保存名字、电话、住址等信息,定义整形来保存年龄等等。
而定义了描述一个人的结构体类型还不行,我们要存放若干个人的信息呀,所以我们还要创建描述一个人结构体的指针,然后再所以内存开辟函数,我们要保存多少人就开辟够容纳的空间。
但是怎么知道开辟的空间不够了,要增容才行呢?所以我们再定义一个通讯录结构体类型
struct Contact,在通讯录结构体中放描述人的结构体指针struct people* 和统计人总数与记录开辟空间大小。
所以头文件Contact.h 就可以写出来了,代码如下:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
// 定义枚举类型,方便辨别case入口
enum p
{
EXIT,
ADD,
SUB,
SEARCH,
MODIFY,
SHOW,
SORT
};
// 名字、电话等数组元素最大个数
#define NAME_MAX 15
#define SEX_MAX 15
#define TELE_MAX 15
#define ADDR_MAX 15
// 把人的信息写成一个结构体类型
typedef struct people
{
char name[NAME_MAX]; //名字
int age; //年龄
char sex[SEX_MAX]; //性别
char tele[TELE_MAX]; //电话
char addr[ADDR_MAX]; //住址
}people;
struct Contact
{
//创建一个信息结构体指针
struct people* arr;
int sz;
int capacity;
};
typedef struct Contact Contact;
//函数声明如下
//初始化通讯录
void InitContact(Contact* pc);
//显示联系人信息
void ShowContact(Contact* pc);
//增加联系人信息
void AddContact(Contact* pc);
//删除联系人信息
void SubContact(Contact* pc);
//查找联系人信息
void SearchContact(Contact* pc);
//修改联系人信息
void ModifyContact(Contact* pc);
//排序联系人信息
void SortContact(Contact* pc);
//保存联系人信息
void ConserveContact(Contact* pc);
//加载联系人信息
void LoadContact(Contact* pc);
//释放内存函数
void DestoryContact(Contact* pc);
图片分析如下:
3.初始化结构体
当我们创建好一个通讯录变量con ,因为是在栈区上创建的,所以里面存放的是随机值。那第一步就先进行初始化,把里面的信息结构体指针初始化为空指针,下标和容量都初始化为0。代码如下:
void InitContact(Contact* pc)
{
pc->arr = NULL;
pc->capacity = 0;
pc->sz = 0;
}
4.增加联系人信息
我们知道怎么实现描述一个人的信息,接下来就是主要的功能模块实现了,
注意一点: 在增加联系人的过程中,我们要注意下标与容量相等时要扩容才行。
代码如下:
//扩容函数
people* BuyNewCapacity(Contact* pc)
{
//当容量为0时就为2,其它就扩2倍
pc->capacity = pc->capacity > 0 ? 2 * pc->capacity : 2;
people* tmp = (people*)realloc(pc->arr, pc->capacity * sizeof(people));
if (tmp == NULL)
{
printf("内存开辟失败\n");
exit(-1);
}
//返回扩容的起始地址
return tmp;
}
void AddContact(Contact* pc)
{
if (pc->capacity == pc->sz)
{
people* tmp = BuyNewCapacity(pc);
pc->arr = tmp;
}
printf("请输入名字:");
scanf("%s", pc->arr[pc->sz].name);
printf("请输入年龄:");
scanf("%d", &pc->arr[pc->sz].age);
printf("请输入性别:");
scanf("%s", pc->arr[pc->sz].sex);
printf("请输入电话:");
scanf("%s", pc->arr[pc->sz].tele);
printf("请输入地址:");
scanf("%s", pc->arr[pc->sz].addr);
pc->sz++; //增加一个信息,下标也要自增1
printf("添加成功\n");
}
5.删除联系人信息
删除联系人我们有三种情况,第一是正常删除,第二是通讯录一个人也没有就不能删除了,第三是找不到这个信息也删除不了。所以我们写一个找联系人的函数,找到了就返回下标,找不到就返回 -1。
代码如下:
int FindContact(Contact* pc)
{
char name[NAME_MAX] = { 0 };
printf("请输入名字:");
scanf("%s", name);
int i = 0;
//遍历一遍
for (i = 0; i < pc->sz; i++)
{
//利用库函数来判断字符串是否相等
if (strcmp(name, pc->arr[i].name) == 0)
{
return i;
}
}
return -1;
}
void SubContact(Contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录为空,不可再删除\n");
return;
}
// 通讯录不为空就查找联系人
int pos = FindContact(pc);
if (pos == -1)
{
printf("你要删除的信息不存在\n");
return;
}
else
{
//找到了,就一个一个往前面挪
int i = 0;
for (i = pos; i < pc->sz - 1; i++)
{
pc->arr[i] = pc->arr[i + 1];
}
// 要把下标自减1
pc->sz--;
printf("删除成功\n");
}
}
6.查找联系人信息
如果通讯录为空就直接返回不用找了,如果查找函数返回-1就表示没有此人的信息,如果找到了就把这个人的信息打印出来即可。
代码如下:
void SearchContact(Contact* pc)
{
if (pc->sz == 0)
{
printf("通讯录为空,找不到\n");
return;
}
int pos = FindContact(pc);
if (pos == -1)
{
printf("此联系人不存在\n");
return;
}
else
{
printf("%-15s %-15s %-15s %-15s %-15s\n",
"名字", "年龄", "性别", "电话", "地址");
printf("%-15s %-15d %-15s %-15s %-15s\n",
pc->arr[pos].name,
pc->arr[pos].age,
pc->arr[pos].sex,
pc->arr[pos].tele,
pc->arr[pos].addr);
}
}
7.修改联系人信息
找到了就重新输入一遍即可,代码如下:
void ModifyContact(Contact* pc)
{
int pos = FindContact(pc);
if (pos == -1)
{
printf("此联系人不存在\n");
return;
}
else
{
printf("请输入名字:");
scanf("%s", pc->arr[pos].name);
printf("请输入年龄:");
scanf("%d", &pc->arr[pos].age);
printf("请输入性别:");
scanf("%s", pc->arr[pos].sex);
printf("请输入电话:");
scanf("%s", pc->arr[pos].tele);
printf("请输入地址:");
scanf("%s", pc->arr[pos].addr);
printf("修改成功\n");
}
}
8.显示联系人信息
这个也很简单,显示就是打印出来即可,代码如下:
void ShowContact(Contact* pc)
{
int i = 0;
printf("%-15s %-15s %-15s %-15s %-15s\n",
"名字", "年龄", "性别", "电话", "地址");
for (i = 0; i < pc->sz; i++)
{
printf("%-15s %-15d %-15s %-15s %-15s\n",
pc->arr[i].name,
pc->arr[i].age,
pc->arr[i].sex,
pc->arr[i].tele,
pc->arr[i].addr);
}
printf("\n");
}
9.排序联系人信息
这个就要用到我们的qsort库函数了,这个库函数能排序很多种类型的信息,详解请看这篇博文:qsort函数的解析 其中通讯录我们针对的是对名字或者年龄的排序。有兴趣的小伙伴也可以增加其他种类的排序,除了年龄外都是对字符串之间的排序。
代码如下:
int compar1(const void* a, const void* b)
{
return strcmp(((people*)a)->name, ((people*)b)->name);
}
int compar2(const void* a, const void* b)
{
return ((people*)a)->age - ((people*)b)->age;
}
void SortContact(Contact* pc)
{
int input = 0;
do
{
printf("请选择要排序的类型:\n");
printf("1.名字大小排序 2.年龄大小排序 0.退出排序\n");
scanf("%d", &input);
switch (input)
{
case 1:
qsort(pc->arr, pc->sz, sizeof(people), compar1);
printf("名字大小排序成功\n");
break;
case 2:
qsort(pc->arr, pc->sz, sizeof(people), compar2);
printf("年龄大小排序成功\n");
break;
case 0:
printf("退出排序\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
}
10.保存和加载联系人信息
当我们输入联系人信息结束通讯录后,如果结束前不保存,那么下一次再执行程序就没有上一次输入的信息了,这样不就是一次性的通讯录么,这样的通讯录谁都不敢用呀。
所以我们要在程序结束前保存信息到文件里面,然后重新执行程序在初始化阶段把文件里面信息加载到内存当中。文件使用可以参考这一篇博文:C语言中的文件操作
代码如下:
//保存联系人信息
void ConserveContact(Contact* pc)
{
//以二进制写的方式打开文件
FILE* pf = fopen("contact.txt", "wb");
if (pf == NULL)
{
perror("erron ");
return;
}
int i = 0;
//把信息保存到文件当中
for (i = 0; i < pc->sz; i++)
{
fwrite(pc->arr + i, sizeof(people), 1, pf);
}
fclose(pf);
pf = NULL;
}
//加载联系人信息
void LoadContact(Contact* pc)
{
FILE* pf = fopen("Contact.txt", "rb");
if (pf == NULL)
{
perror("erron ");
exit(-1);
}
// 因为初始化还没有开辟空间
// 先开辟一点大小的空间
if (pc->capacity == pc->sz)
{
people* tmp = BuyNewCapacity(pc);
pc->arr = tmp;
}
// 从文件中把信息加载到变量当中
while (fread(pc->arr + pc->sz, sizeof(people), 1, pf))
{
pc->sz++;
//如果空间满了就增容
if (pc->capacity == pc->sz)
{
people* tmp = BuyNewCapacity(pc);
pc->arr = tmp;
}
}
fclose(pf);
pf = NULL;
}
11.释放内存空间
当我们结束通讯录之前除了要保存联系人的信息外,还要把在堆上开辟的内存空间还给操作系统。代码如下:
void DestoryContact(Contact* pc)
{
free(pc->arr);
pc->arr = NULL;
pc->capacity = 0;
pc->sz = 0;
}
程序执行的部分结果:
以上就是我的通讯录全部内容了,其中前面我只有把头文件Contact.h 和源文件test.c 放在同一段代码块中,实现函数功能的源文件Contact.c 文件我拆开来分析了。如果想要方便拷贝Contact.c 文件的内容,阔以移步到gitee上获取。
三个源文件内容链接:
https://gitee.com/fait-juyuan/c-language/tree/master/test_10_4/test_10_4