一、通讯录简介
通讯录的实现分为三个模块:Contact.h Contact.c test.c
Contact.h:.h为后缀的文件一般放头文件的包含,结构体类型的定义,宏定义,函数的声明等等。
**Contact.c:**完成函数的实现。
**test.c:**测试通讯录功能。
二、结构体类型的定义
通讯录需要记录当前已经存放的联系人的个数以及由每一个联系人信息构成的数组,而对于任何一个联系人都是一个复杂的对象,需要用多个变量去描述这个联系人的特点,例如:需要姓名,性别,年龄等等,所以又需要一个结构体描述。,所以这里需要两个结构体嵌套使用。
#define MAX_CAPACITY 1000 //创建描述联系人信息的结构体 typedef struct peo_info { char name[20]; char sex[10]; int age; char addr[20]; char tele[12]; }peo_info; //创建通讯录的结构体 typedef struct Contact { //通讯录结构体里面嵌套一个保存联系人数据的结构体数组 struct peo_info data[MAX_CAPACITY]; int sz; }Contact;
三、test.c整体测试的框架
#include "contact.h" void menu(void) { printf("*******************************\n"); printf("**** 1.add 2.del ****\n"); printf("**** 3.search 4.mod ****\n"); printf("**** 5.show 6.sort ****\n"); printf("******* 0.exit *******\n"); } int main() { Contact con;//创建通讯录 InitContact(&con);//初始化通讯录 int input = 0;//对通讯录进行的操作 do { menu(); printf("请输入:>"); scanf("%d", &input); switch (input) { case 1: { Add(&con);//添加联系人 break; } case 2: { Del(&con);//删除联系人 break; } case 3: { Search(&con);//查找联系人 break; } case 4: { Mod(&con);//修改联系人 break; } case 5: { Show(&con);//展示联系人 break; } case 6: { Sort(&con);//排序 break; } case 0: { printf("退出通讯录!!!\n"); break; } default: { printf("选择错误,请重新选择!!\n"); break; } } } while (input); return 0; }
四、Conatct.h
#include <stdio.h> #include <assert.h> #include <string.h> #include <stdlib.h> #define MAX_CAPACITY 1000 //创建描述联系人信息的结构体 typedef struct peo_info { char name[20]; char sex[10]; int age; char addr[20]; char tele[12]; }peo_info; //创建通讯录的结构体 typedef struct Contact { struct peo_info data[MAX_CAPACITY]; int sz; }Contact; //函数声明 extern void InitContact(Contact* con); extern void Add(Contact* con); extern void Show(Contact* con); extern void Del(Contact* con); extern void Search(Contact* con); extern void Mod(Contact* con); extern void Sort(Contact* con);
五、通讯录各个函数的实现
5.1 初始化通讯录函数的实现
其实这里很简单,就是把存放联系人的数组初始化为0就行了,已经存放的联系人sz当然也为0
void InitContact(Contact* con) { assert(con); memset(con->data, 0, sizeof(peo_info) * MAX_CAPACITY); con->sz = 0; }
5.2 添加联系人函数的实现
void Add(Contact* con) { assert(con); if (con->sz == MAX_CAPACITY) { printf("通讯录已满,无法添加联系人!\n"); return; } //con->data[con->sz].name的意思是通讯录con指向的data数组 //下标为con->sz对应的peo_info结构体的name成员,由于每添加 //一个数据con->sz就会++,所以下标刚好一一对应。 printf("请输入添加的联系人姓名:>"); scanf("%s", con->data[con->sz].name); printf("请输入添加的联系人性别:>"); scanf("%s", con->data[con->sz].sex); printf("请输入添加的联系人年龄:>"); scanf("%d", &con->data[con->sz].age); printf("请输入添加的联系人电话:>"); scanf("%s", con->data[con->sz].tele); printf("请输入添加的联系人地址:>"); scanf("%s", con->data[con->sz].addr); con->sz++; printf("添加成功\n"); }
5.3 删除联系人函数的实现
int FindPeoInfo(Contact* con, char name[]) { assert(con); int i = 0; for (i = 0; i < con->sz; i++)//遍历查找 { //返回值=0,证明通讯录里面下标为i的联系人的姓名与要查找的人的姓名 //一样,证明找到了,返回i下标 if (strcmp(con->data[i].name, name) == 0) { return i; } } return -1;//循环遍历完都没有找到,那就是没有这个人了,返回-1代表找不到 } void Del(Contact* con) { assert(con); printf("请输入需要删除的联系人的名字:>"); char name[20] = { 0 }; scanf("%s", name); int pos = FindPeoInfo(con, name);//先查找指定联系人,再删除 if (-1 == pos) { printf("要删除的联系人不存在!\n"); return; } int i = 0; for (i = pos; i < con->sz - 1; i++) { //迭代往前挪,后一个数据覆盖前一个,最终把需要删除的位置覆盖掉 con->data[i] = con->data[i + 1]; } con->sz--;//删除一个元素con->sz自然也需要--。 printf("删除成功!\n"); }
5.4 查找联系人函数的实现
int FindPeoInfo(Contact* con, char name[]) { assert(con); int i = 0; for (i = 0; i < con->sz; i++)//遍历查找 { //返回值=0,证明通讯录里面下标为i的联系人的姓名与要查找的人的姓名 //一样,证明找到了,返回i下标 if (strcmp(con->data[i].name, name) == 0) { return i; } } return -1;//循环遍历完都没有找到,那就是没有这个人了,返回-1代表找不到 } void Search(Contact* con) { assert(con); char name[20] = { 0 }; printf("请输入需要查找的联系人的姓名:>"); scanf("%s", name); int pos = FindPeoInfo(con,name); if (pos == -1) { printf("找不到此联系人!\n"); return; } //找到了就按照格式打印出来,‘\t’是制表符 printf("%-20s\t%-10s\t%-5s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "电话", "地址"); printf("%-20s\t%-10s\t%-5d\t%-12s\t%-20s\n", con->data[pos].name, con->data[pos].sex, con->data[pos].age, con->data[pos].tele, con->data[pos].addr); }
5.5 改变联系人信息函数的实现
int FindPeoInfo(Contact* con, char name[]) { assert(con); int i = 0; for (i = 0; i < con->sz; i++)//遍历查找 { //返回值=0,证明通讯录里面下标为i的联系人的姓名与要查找的人的姓名 //一样,证明找到了,返回i下标 if (strcmp(con->data[i].name, name) == 0) { return i; } } return -1;//循环遍历完都没有找到,那就是没有这个人了,返回-1代表找不到 } void Mod(Contact* con) { assert(con); printf("请输入需要修改的联系人的姓名:>"); char name[20] = { 0 }; scanf("%s", name); int pos = FindPeoInfo(con, name); if (pos == -1) { printf("该联系人不存在!\n"); return; } //下面其实就是跟添加联系人信息是一样的逻辑的 //修改其实也是输入信息嘛 printf("请输入新的联系人姓名:>"); scanf("%s", con->data[pos].name); printf("请输入添加的联系人性别:>"); scanf("%s", con->data[pos].sex); printf("请输入添加的联系人年龄:>"); scanf("%d", &con->data[pos].age); printf("请输入添加的联系人电话:>"); scanf("%s", con->data[pos].tele); printf("请输入添加的联系人地址:>"); scanf("%s", con->data[pos].addr); printf("修改成功!\n"); }
5.6 展示联系人信息函数的实现
void Show(Contact* con) { assert(con); int i = 0; //先打印表头,可以更加美观 printf("%-20s\t%-10s\t%-5s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "电话", "地址"); //遍历格式化打印各个联系人信息就好咯 for (i = 0; i < con->sz; i++) { printf("%-20s\t%-10s\t%-5d\t%-12s\t%-20s\n", con->data[i].name, con->data[i].sex, con->data[i].age, con->data[i].tele, con->data[i].addr); } }
5.7 通讯录排序函数的实现
这里大家可以用不同的方式对联系人进行排序,我这里就用qsort对联系人的名字进行的排序
int cmp_by_name(const void* e1, const void* e2) { return strcmp(((peo_info*)e1)->name, ((peo_info*)e2)->name); } void Sort(Contact* con) { assert(con); qsort(con->data, con->sz, sizeof(con->data[0]), cmp_by_name); printf("排序成功\n"); Show(con); }
另外一种写法是我自主实现一个通用的冒泡排序
void swap(char* buf1, char* buf2, size_t width) { size_t i = 0; //数组中的每一个数据占width个字节,我们只需要循环width次就能把两个元素的每一个字节都交换, //最终两个元素的内容也就交换成功了 for (i = 0; i < width; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2)) { //这里是典型的冒泡排序的方法 size_t i = 0; for (i = 0; i < sz - 1; i++) { size_t j = 0; for (j = 0; j < sz - 1 - i; j++) { //这里的cmp函数就是就是比较相邻的两个元素的大小,如果返回值大于0,则证明前一个元素 //比后一个元素大,则需要交换这两个元素。由于这里的base指针的类型是void*,所以我们 //首先需要将它强制类型转换成char*类型的指针,那为什么是转换成char*而不是int*, //double*呢?其实很简单,你试想一下,我们比较完了两个相邻的元素之后是不是需要 //拿后一个和这两个元素中大的元素进行比较大小,但是大家别忘了,指针类型的大小可是决 //定了你指针加1跳过几个字节的啊,整形指针加1跳过一个整形,字符指针加1跳过一个字节 //但是我们并不知道将来这个函数会被用来排序什么类型的数据的啊,但是无论是数目类型的 //数据,它的大小都至少为1个字节吧,所以转换成(char*)类型是最合理的。而参数中的宽 //度又正好能让我们找到下一个元素,只需要再起始地址加上宽度*j就能找到下一个元素了 //所以if语句里面的判断条件应该这样写 if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } int cmp_by_name(const void* e1, const void* e2) { //通过姓名对结构体进行排序,需要用到strcmp函数,依然是返回1,0,-1 return strcmp(((peo_info*)e1)->name, ((peo_info*)e2)->name); } void Sort(Contact* con) { assert(con); //qsort(con->data, con->sz, sizeof(con->data[0]), cmp_by_name); bubble_sort(con->data, con->sz, sizeof(con->data[0]), cmp_by_name); printf("排序成功\n"); Show(con); }
当然,这个排序的方法的性能肯定比不上qsort本来的性能的,所以这个只是作为一个通用的排序的方法的参考。
六、通讯录完整代码汇总
test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "contact.h" void menu(void) { printf("*******************************\n"); printf("**** 1.add 2.del ****\n"); printf("**** 3.search 4.mod ****\n"); printf("**** 5.show 6.sort ****\n"); printf("******* 0.exit *******\n"); } int main() { Contact con; InitContact(&con); int input = 0; do { menu(); printf("请输入:>"); scanf("%d", &input); switch (input) { case 1: { Add(&con); break; } case 2: { Del(&con); break; } case 3: { Search(&con); break; } case 4: { Mod(&con); break; } case 5: { Show(&con); break; } case 6: { Sort(&con); break; } case 0: { printf("退出通讯录!!!\n"); break; } default: { printf("选择错误,请重新选择!!\n"); break; } } } while (input); return 0; }
contact.h
#pragma once #include <stdio.h> #include <assert.h> #include <string.h> #include <stdlib.h> #define MAX_CAPACITY 1000 typedef struct peo_info { char name[20]; char sex[10]; int age; char addr[20]; char tele[12]; }peo_info; typedef struct Contact { struct peo_info data[MAX_CAPACITY]; int sz; }Contact; //函数声明 extern void InitContact(Contact* con); extern void Add(Contact* con); extern void Show(Contact* con); extern void Del(Contact* con); extern void Search(Contact* con); extern void Mod(Contact* con); extern void Sort(Contact* con);
contact.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "contact.h" void InitContact(Contact* con) { assert(con); memset(con->data, 0, sizeof(peo_info) * MAX_CAPACITY); con->sz = 0; } void Add(Contact* con) { assert(con); if (con->sz == MAX_CAPACITY) { printf("通讯录已满,无法添加联系人!\n"); return; } printf("请输入添加的联系人姓名:>"); scanf("%s", con->data[con->sz].name); printf("请输入添加的联系人性别:>"); scanf("%s", con->data[con->sz].sex); printf("请输入添加的联系人年龄:>"); scanf("%d", &con->data[con->sz].age); printf("请输入添加的联系人电话:>"); scanf("%s", con->data[con->sz].tele); printf("请输入添加的联系人地址:>"); scanf("%s", con->data[con->sz].addr); con->sz++; printf("添加成功\n"); } void Show(Contact* con) { assert(con); int i = 0; printf("%-20s\t%-10s\t%-5s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "电话", "地址"); for (i = 0; i < con->sz; i++) { printf("%-20s\t%-10s\t%-5d\t%-12s\t%-20s\n", con->data[i].name, con->data[i].sex, con->data[i].age, con->data[i].tele, con->data[i].addr); } } int FindPeoInfo(Contact* con, char name[]) { assert(con); int i = 0; for (i = 0; i < con->sz; i++) { if (strcmp(con->data[i].name, name) == 0) { return i; } } return -1; } void Del(Contact* con) { assert(con); printf("请输入需要删除的联系人的名字:>"); char name[20] = { 0 }; scanf("%s", name); int pos = FindPeoInfo(con, name); if (-1 == pos) { printf("要删除的联系人不存在!\n"); return; } int i = 0; for (i = pos; i < con->sz - 1; i++) { con->data[i] = con->data[i + 1]; } con->sz--; printf("删除成功!\n"); } void Search(Contact* con) { assert(con); char name[20] = { 0 }; printf("请输入需要查找的联系人的姓名:>"); scanf("%s", name); int pos = FindPeoInfo(con,name); if (pos == -1) { printf("找不到此联系人!\n"); return; } printf("%-20s\t%-10s\t%-5s\t%-12s\t%-20s\n", "姓名", "性别", "年龄", "电话", "地址"); printf("%-20s\t%-10s\t%-5d\t%-12s\t%-20s\n", con->data[pos].name, con->data[pos].sex, con->data[pos].age, con->data[pos].tele, con->data[pos].addr); } void Mod(Contact* con) { assert(con); printf("请输入需要修改的联系人的姓名:>"); char name[20] = { 0 }; scanf("%s", name); int pos = FindPeoInfo(con, name); if (pos == -1) { printf("该联系人不存在!\n"); return; } printf("请输入新的联系人姓名:>"); scanf("%s", con->data[pos].name); printf("请输入添加的联系人性别:>"); scanf("%s", con->data[pos].sex); printf("请输入添加的联系人年龄:>"); scanf("%d", &con->data[pos].age); printf("请输入添加的联系人电话:>"); scanf("%s", con->data[pos].tele); printf("请输入添加的联系人地址:>"); scanf("%s", con->data[pos].addr); printf("修改成功!\n"); } void swap(char* buf1, char* buf2, size_t width) { size_t i = 0; //数组中的每一个数据占width个字节,我们只需要循环width次就能把两个元素的每一个字节都交换, //最终两个元素的内容也就交换成功了 for (i = 0; i < width; i++) { char tmp = *buf1; *buf1 = *buf2; *buf2 = tmp; buf1++; buf2++; } } void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2)) { //这里是典型的冒泡排序的方法 size_t i = 0; for (i = 0; i < sz - 1; i++) { size_t j = 0; for (j = 0; j < sz - 1 - i; j++) { //这里的cmp函数就是就是比较相邻的两个元素的大小,如果返回值大于0,则证明前一个元素 //比后一个元素大,则需要交换这两个元素。由于这里的base指针的类型是void*,所以我们 //首先需要将它强制类型转换成char*类型的指针,那为什么是转换成char*而不是int*, //double*呢?其实很简单,你试想一下,我们比较完了两个相邻的元素之后是不是需要 //拿后一个和这两个元素中大的元素进行比较大小,但是大家别忘了,指针类型的大小可是决 //定了你指针加1跳过几个字节的啊,整形指针加1跳过一个整形,字符指针加1跳过一个字节 //但是我们并不知道将来这个函数会被用来排序什么类型的数据的啊,但是无论是数目类型的 //数据,它的大小都至少为1个字节吧,所以转换成(char*)类型是最合理的。而参数中的宽 //度又正好能让我们找到下一个元素,只需要再起始地址加上宽度*j就能找到下一个元素了 //所以if语句里面的判断条件应该这样写 if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0) { swap((char*)base + j * width, (char*)base + (j + 1) * width, width); } } } } int cmp_by_name(const void* e1, const void* e2) { //通过姓名对结构体进行排序,需要用到strcmp函数,依然是返回1,0,-1 return strcmp(((peo_info*)e1)->name, ((peo_info*)e2)->name); } void Sort(Contact* con) { assert(con); //qsort(con->data, con->sz, sizeof(con->data[0]), cmp_by_name); bubble_sort(con->data, con->sz, sizeof(con->data[0]), cmp_by_name); printf("排序成功\n"); Show(con); }
七、总结
其实这个初阶的通讯录实现起来或许没有你想象的那么简单,但是也没有你想象的那么难,就是先把整个测试的架构建起来,然后就逐一实现每一个函数需要的功能就可以了,初阶的通讯录的分享就到这里,你学会了吗?后续将会出进阶的能够动态开辟空间的通讯录的实现,想看的记得点点关注哦!