通讯录是C语言中非常简单且实用的小项目,涉及C语言中很多基础知识,能够独立写出通讯录这个小项目,证明自身对C语言知识有了一定的掌握。今天来探讨通讯录三种版本的实现
pragma once 是防止头文件在程序运行时在内存的反复调用
string.h 头文件是调用字符串函数
stdio.h 就不必多说了
assert.h 头文件是在引用assert函数,防止一些指针为NULL,影响程序运行
enum 是用来枚举出通信录八大主功能,增加代码可读性和规范性
在contact.h里面直接将contact.c中大量出现的变量进行定义,之后遇到这些变量就不用重复定义了,提高代码效率
创建一个结构体,存放联系人信息
再创建一个结构体
data [MAX] 是一个能存放1000个Ploinfo(也就是上一个结构体)信息的数组
sz是用来纪录联系人个数
#pragma once
#include
#include
#include
//类型的声明
enum Option
{
EXIT,
ADD,
DEL,
SEARCH,
MODIFY,
PRINT,
CLEAR,
SORT
};
#define MAX 1000
#define NAME_MAX 20
#define SEX_MAX 5
#define ADDR_MAX 30
#define TELE_MAX 12
typedef struct Ploinfo
{
char name[NAME_MAX]; //姓名
char sex[SEX_MAX]; //性别
int age; //年龄
char addr[ADDR_MAX]; //地址
char tele[TELE_MAX]; //电话号码
}Ploinfo;
typedef struct Contact
{
struct Ploinfo data[MAX]; //存放联系人的数组
int sz; //统计联系人的个数
}Contact;
//函数的声明
//contact 初始化
void InitContact(Contact* pc);
//Contact 增加联系人的信息
void AddContact(Contact* pc);
//Contact 打印联系人的信息
void PrintContact(const Contact* pc);//只是打印,不会改变Contact里面的数据,所以可以const来修饰
//Contact 删除指定联系人
void DelContact(Contact* pc);
//Contact 查找指定联系人
void SearchContact(Contact* pc);
//Contact 修改指定联系人信息
void ModifyContact(Contact* pc);
//Contact 通讯录信息排序
void sortContact(Contact* pc);
//Contact 通讯录信息清空
void ClearContact(Contact* pc);
contact.h是我们自主创建的头文件,里面包含了我们运行contact.c需要的所有头文件和变量
在contact.c开头只需要引用这一个contact.h头文件就行了
InitContact函数是用来初始化通讯录的,在添加联系人信息之前,需要把data和sz全都初始化为0
assert是防止pc为NULL
memset是字符串函数,这里的作用是把data [MAX] 里面的空间全部设置为0
AddContact函数是用来添加联系人信息的
在添加之前需确认通讯录空间是否足够,如果不够是无法添加的
可以添加就继续执行下面的语句就行,这里不过多阐述,去看下面的源码
这里唯一需要注意的一点就是在printf语句中 “-” 是靠左,那么不加符号就是靠右
修改和删除指定联系人信息都离不开FindByName函数,因为实现修改和删除是要先找到要删除和修改的元素,如果都找不到,就没有删除和修改的必要了
FindByName函数是如何实现的:
原理是应用for循环遍历data里面的name,每个data都有特点的下标 i ,通过strcmp把要查找的name与data里面的name进行比较,如果相同,strcmp返回0,这里再在for循环里面嵌套一个if语句,如果 0 == strcmp返回的值,那么此时,data的下标就被返回到Del和Modify函数里面,进行删除和修改指定联系人信息,如果遍历到最好找不到这个人,直接跳出for循环,返回 - 1返回Del和Modify函数
Del Modify Search 函数里面都有这个语句
仔细阅读后面的源码就会发现 Del Modify Search 函数 一些部分是相同的
1.需要判断通讯录是否为空
2.需要判断要寻找的联系人是否存在
3.都要调用FindByName
Sort函数是实现联系人信息的排序,联系人信息包含5种,它们的类型有相同也有不同的,我们不能对它们分别排序,虽然如果强行去搞肯定可以实现,但是没什么必要,在我们手机里的通讯录基本上都是以姓名来进行排序,那么我们也用姓名来排序就比较符合实际
姓名是属于char类型的,我们直接用strcmp来实现它们之间的比较,进而进行排序就行
这里是用了双重for循环嵌套一个if语句,在创建一个Ploinfo类型的tmp作为中间变量进行data之间的交换,来实现的排序
#include"contact.h"
//contact初始化
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
memset(pc->data, 0, sizeof(pc->data));
}
//Contact函数的使用 —— 添加联系人
void AddContact(Contact* pc)
{
assert(pc);
if (pc->sz == MAX)
{
printf("通讯录容量已满!无法添加!\n");
return;
}
printf("请输入姓名 =>\n");
scanf("%s", pc->data[pc->sz].name);
printf("请输入性别 =>\n");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入年龄 =>\n");
scanf("%d", &pc->data[pc->sz].age);
printf("请输入电话号码 =>\n");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址 =>\n");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;
printf("添加联系人成功!\n");
}
//Contact 打印联系人的信息
void PrintContact(const Contact* pc)
{
assert(pc);
int i = 0;
printf("%-10s %-5s %-5s %-15s %-20s\n","姓名","性别","年龄","电话","地址");
for (i = 0;i < pc->sz;i++)
{
printf("%-10s %-5s %-5d %-15s %-20s\n", pc->data[i].name, pc->data[i].sex,
pc->data[i].age, pc->data[i].tele, pc->data[i].addr);
}
}
//Contact 删除指定联系人
int FindByName(const Contact* pc, char name[])
{
int i = 0;
//遍历查找
//找到返回下标
//没找到返回 -1
for (i = 0;i < pc->sz;i++)
{
if (0 == strcmp(pc->data[i].name, name))
{
return i;
}
}
return -1;
}
void DelContact(Contact* pc)
{
assert(pc);
if (0 == pc->sz)
{
printf("通讯录为空!无法删除!\n");
return;
}
printf("请输入你要查找的联系人的姓名 =>\n");
char name[NAME_MAX] = { 0 };//初始化
scanf("%s", name);//输入要查找的联系人的姓名
//1、查找
int pos = FindByName(pc, name);
if (-1 == pos)
{
printf("联系人不存在!\n");
return;
}
//2、删除
int j = 0;
for (j = pos;j < pc->sz - 1;j++)
{
pc->data[j] = pc->data[j + 1];
}
pc->sz--;
printf("删除成功!\n");
}
//Contact 查找指定联系人
void SearchContact(Contact* pc)
{
assert(pc);
if (0 == pc->sz)
{
printf("通讯录为空!无法查找!\n");
return;
}
char name[NAME_MAX] = { 0 };
printf("输入你要查找的联系人的姓名=>\n");
scanf("%s", &name);
int pos = FindByName(pc, name);
if (-1 == pos)
{
printf("联系人不存在!\n");
return;
}
printf("%-10s %-5s %-5s %-15s %-20s\n", "姓名", "性别", "年龄", "电话", "地址");
printf("%-10s %-5s %-5d %-15s %-20s\n", pc->data[pos].name, pc->data[pos].sex,
pc->data[pos].age, pc->data[pos].tele, pc->data[pos].addr);
printf("搜索联系人信息成功!\n");
}
//Contact 修改指定联系人信息
void ModifyContact(Contact* pc)
{
assert(pc);
if (0 == pc->sz)
{
printf("通讯录为空!无法修改!\n");
return;
}
//查找
char name[NAME_MAX] = { 0 };
printf("输入你要查找的联系人的姓名=>\n");
scanf("%s", &name);
int pos = FindByName(pc, name);
if (-1 == pos)
{
printf("联系人不存在!\n");
return;
}
//修改
printf("请输入你想修改该联系人的信息 =>\n");
printf("请输入姓名 =>\n");
scanf("%s", pc->data[pos].name);
printf("请输入性别 =>\n");
scanf("%s", pc->data[pos].sex);
printf("请输入年龄 =>\n");
scanf("%d", &pc->data[pos].age);
printf("请输入电话号码 =>\n");
scanf("%s", pc->data[pos].tele);
printf("请输入地址 =>\n");
scanf("%s", pc->data[pos].addr);
printf("修改联系人信息成功!\n");
}
//Contact 通讯录信息排序
void SortContact(Contact* pc)
{
assert(pc);
if (0 == pc->sz)
{
printf("通讯录为空!无法排序!\n");
return;
}
//以名字排序
int i, j;
Ploinfo tmp;
for (i = 0; i < pc->sz - 1; i++)
{
for (j = 0; j < pc->sz - 1 - i; j++)
{
if (0 < strcmp(pc->data[j].name, pc->data[j + 1].name))
{
tmp = pc->data[j];
pc->data[j] = pc->data[j + 1];
pc->data[j + 1] = tmp;
}
}
}
printf("排序成功!\n");
}
//Contact 通讯录信息清空
void ClearContact(Contact* pc)
{
InitContact(pc);
}
这里就是一个菜单界面,来选择功能的,很简单,不做过多阐述
#include "contact.h"
void menu()
{
printf("**********************************************\n");
printf("*** 1.添加(add) 2.删除(del) ***\n");
printf("*** 3.搜索(search) 4.修改(modify) ***\n");
printf("*** 5.排序(sort) 6.打印(print) ***\n");
printf("*** 7.清空(clear) 0.退出(exit) ***\n");
printf("**********************************************\n");
}
void test()
{
int input = 0;
//创建通讯录
Contact cot;
//初始化通讯录
InitContact(&cot);
do
{
menu();
printf("请选择功能 =>\n");
scanf("%d", &input);
switch (input)
{
case EXIT:
printf("退出通讯录\n");
break;
case ADD:
//添加联系人
AddContact(&cot);
break;
case DEL:
DelContact(&cot);
break;
case SEARCH:
SearchContact(&cot);
break;
case MODIFY:
ModifyContact(&cot);
break;
case SORT:
SortContact(&cot);
break;
case PRINT:
PrintContact(&cot);
break;
case CLEAR:
ClearContact(&cot);
default:
printf("输入错误!请重新输入\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
动态版的通讯录就是在上面通讯录基础上进行升级,所谓动态版就是可以动态地改变通讯录的空间,这个版本的优势在于可以很大程度上减少空间的浪费,和避免存储空间不足的情况。
我就不把所有代码搬过来了,只是把通讯录静态版被修改的部分单独放出来说明一下。
其中会涉及动态内存管理的知识,如果是初学C语言的小伙伴需要去了解一下这方面的知识,下去可以了解一下,不了解也没关系,我也会简单的解释一下原理的
#define DEFAULT_SZ 3//初始状态的容量大小
//动态的版本
typedef struct Contact
{
Ploinfo* data; //data不再是数组,而是指定为一个指针,在contact.c中直接向内存申请空间
int sz; //统计联系人的个数
int capacity;//容量
}Contact;
//contact 销毁
void DestroyContact(Contact* pc);
增加 DestroyContact 和 Check_capacity 函数
修改 InitContact 函数
说明都在代码里面
//contact初始化
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;//sz指的是联系人个数,初始化为0
pc->capacity = DEFAULT_SZ;
pc->data = (Ploinfo*)malloc(pc->capacity * sizeof(Ploinfo));//malloc开辟一个动态的空间,含有capacity个联系人信息的空间
if (pc->data == NULL)
{
perror("InitContact::malloc");//如果pc->data是NULL perror会在屏幕上打印出错误信息 InitContact::malloc 这个格式是给计算机减负,能快速定位错误的语句具体在哪个位置
//同时也是一个保险 当你代码运行时发现错误时,能快速锁定错误位置进行修正
return 1;
}
memset(pc->data, 0, pc->capacity * sizeof(Ploinfo));//初始化 前三个信息的位置 都为0
}
//contact销毁
void DestroyContact(Contact* pc)
{
assert(pc);
free(pc->data);//动态开辟了空间,在不需要用通讯录时,需要把空间释放
pc->data = NULL;
pc->capacity = 0;//归零
pc->sz = 0;//归零
}
//检查capacity是否等于sz,也就是在检查空间是否已满,需不需要增加空间
Check_capacity(Contact* pc)
{
assert(pc);
if (pc->capacity == pc->sz)
{
Ploinfo* tmp = (Ploinfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(Ploinfo));//realloc 的目的就是动态增加开始malloc开辟的空间
if (tmp != NULL)
{
pc->data = tmp;
}
pc->capacity += 2;//不要一次只添加一个联系人所占的空间,一次加多点,也不加太多,加两个联系人的空间
}
}
//Contact函数的使用 —— 添加联系人
void AddContact(Contact* pc)
{
//静态版本
/*assert(pc);
if (pc->sz == MAX)
{
printf("通讯录容量已满!无法添加!\n");
return;
}*/
Check_capacity(pc);//添加联系人之前,需要先调用这个函数检查一下空间是否足够,不够就扩容
printf("请输入姓名 =>\n");
scanf("%s", pc->data[pc->sz].name);
printf("请输入性别 =>\n");
scanf("%s", pc->data[pc->sz].sex);
printf("请输入年龄 =>\n");
scanf("%d", &pc->data[pc->sz].age);
printf("请输入电话号码 =>\n");
scanf("%s", pc->data[pc->sz].tele);
printf("请输入地址 =>\n");
scanf("%s", pc->data[pc->sz].addr);
pc->sz++;//添加联系人成功 sz++ 联系人数量增加
printf("添加联系人成功!\n");
}
菜单这里只在退出选项这里添加了DestroyContact 函数
文件版是动态版的升级,在以上两个版本存储的信息在退出通讯录之后都会被销毁无法保存,下次再运行通讯录还是空的,文件版就是可以把第一次输入进通讯录的信息保存到一个指定的文件中去,下次再打开通讯录的时候,信息从指定的文件读入通讯录保存,这就是通讯录文件版。
涉及C语言文件操作的知识。
//Contact 通讯录信息在销毁之前以文件形式保存
void SaveContact(Contact* pc);
Check_capacity(Contact* pc)
{
assert(pc);
if (pc->capacity == pc->sz)
{
Ploinfo* tmp = (Ploinfo*)realloc(pc->data, (pc->capacity + 2) * sizeof(Ploinfo));
if (tmp != NULL)
{
pc->data = tmp;
}
pc->capacity += 2;
}
}
//读取文件
LoadContact(Contact* pc)
{
//1.打开文件
FILE* pf = fopen("C.txt", "rb");
if (pf == NULL)
{
perror("LoadContact::fopen");
return;
}
//2.读取文件
Ploinfo tmp = { 0 };//创建一个存放读取信息的变量
while (fread(&tmp, sizeof(Ploinfo), 1, pf))
{
Check_capacity(pc);//第二次打开通讯录时,sz(联系人个数)和capacity(容量)都为0,从C.txt读取到的信息需要放到data里面去,就必须先开辟空间
pc->data[pc->sz] = tmp;
pc->sz++;
}
//3.关闭文件
}
//contact初始化
void InitContact(Contact* pc)
{
assert(pc);
pc->sz = 0;
pc->capacity = DEFAULT_SZ;
pc->data = (Ploinfo*)malloc(pc->capacity * sizeof(Ploinfo));
if (pc->data == NULL)
{
perror("InitContact::malloc");
return 1;
}
memset(pc->data, 0, pc->capacity * sizeof(Ploinfo));
//关闭通讯录,再次打开,我们需要读取C.txt里面的联系人信息
//再创建一个函数
LoadContact(pc);
}
//contact销毁
void DestroyContact(Contact* pc)
{
assert(pc);
free(pc->data);
pc->data = NULL;
pc->capacity = 0;
pc->sz = 0;
}
//Contact 通讯录信息在销毁之前以文件形式保存
void SaveContact(const Contact* pc)
{
//1.打开文件
//打开一个文件 可以在Contact 的文件路径下创建一个文件 也可以直接fopen一个文件
FILE* ptr = fopen("C.txt", "wb");//我这里是命名的文件是txt的文件后缀名,当然这个后缀名可以随便写 wb是以二进制打开文件
//考虑到ptr可能为NULL,防止这个函数无法正常运行,需要加个保险措施
if (ptr == NULL)
{
perror("SaveContact::fopen");//方便定位错误位置
return;
}
//2.写文件
int i = 0;
for (i = 0;i < pc->sz;i++)
{
fwrite(pc->data + i, sizeof(Ploinfo), 1, ptr);//在销毁通讯录之前,把联系人信息已经保存到 C.txt 里面了
}
//3.关闭文件
fclose(ptr);
ptr = NULL;
}
#include "contact.h"
void menu()
{
printf("**********************************************\n");
printf("*** 1.添加(add) 2.删除(del) ***\n");
printf("*** 3.搜索(search) 4.修改(modify) ***\n");
printf("*** 5.打印(print) 6.清空(clear) ***\n");
printf("*** 7.排序(sort) 0.退出(exit) ***\n");
printf("**********************************************\n");
}
void test()
{
int input = 0;
//创建通讯录
Contact cot;
//初始化通讯录
InitContact(&cot);
do
{
menu();
printf("请选择功能 =>\n");
scanf("%d", &input);
switch (input)
{
case EXIT:
printf("退出通讯录\n");
SaveContact(&cot);//销毁通讯录之前先保存联系人信息
DestroyContact(&cot);
break;
case ADD:
//添加联系人
AddContact(&cot);
break;
case DEL:
DelContact(&cot);
break;
case SEARCH:
SearchContact(&cot);
break;
case MODIFY:
ModifyContact(&cot);
break;
case PRINT:
PrintContact(&cot);
break;
case CLEAR:
ClearContact(&cot);
case 7:
SortContact(&cot);
break;
default:
printf("输入错误!请重新输入\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
通讯录静态涉及C语言知识版:分支与循环语句 结构体 字符串函数 操作符 指针 函数 数组
通讯录动态涉及C语言知识版:分支与循环语句 结构体 字符串函数 操作符 指针 函数 数组
动态内存管理
通讯录动态涉及C语言知识版:分支与循环语句 结构体 字符串函数 操作符 指针 函数 数组
动态内存管理 文件操作
通讯录虽然在现如今已经非常不起眼了,但对于C语言初学者来说,能熟练自主地实现这三种通讯录足以说明C语言功底基本合格了,之后可以尝试去实现更多的C语言小项目,来巩固自身的编程能力和锻炼自己的编程思维。
work hard!!!