带你轻松实现通讯录(C语言版)
文章目录
- 前言
- 通讯录初始化
- 通讯录运行的基本框架和菜单
- 增添联系人
- 删除联系人
- 查找联系人
- 修改联系人信息
- 展示通讯录
- 通讯录联系人个数
- 排序通讯录
- 文件操作储存通讯录信息
- 销毁通讯录
- 整体代码
- Contacts.h
- Contacts.c
- test.c
- 写在最后
前言
学习C语言的小伙伴,相信都要经历实现通讯录这一关吧,接下来就带你手把手实现自己的通讯录!
通讯录初始化
-
整个程序我们需要分三个文件,一个是头文件:
Contacts.h:
用来存放宏,结构体以及函数声明,还有需要用的库函数。一个是Contacts.c:
用来实现各个接口函数的功能,还有一个是test.c:
用来布局功能的框架以及测试代码。 -
首先我们需要两个结构体,一个表示联系人的信息,一个为通讯录的信息:
typedef struct PeoInfo
{char name[NAME_MAX]; // 名字int age; // 年龄char sex[SEX_MAX]; // 性别char adds[ADDS_MAX]; // 地址char tele[TELE_MAX]; // 电话
}Info;typedef struct contact
{Info* data; // data 为Info指针int size; // 存放联系人的个数int capacity; // 存放联系人的空间容量
}Con;
上面类似于NAME_MAX
的东西为宏定义,表示一个联系人的名字最多占多少空间,下面为所有的宏定义:
#define NAME_MAX 20 // 名字的最大长度
#define SEX_MAX 5 // 性别的最大长度
#define ADDS_MAX 20 // 地址的最大长度
#define TELE_MAX 12 // 电话号码的最大长度
#define NEW_SIZE 4 // 每次扩容增加的容量
-
对于初始化由于需要插入数据,牵扯到一个扩容的问题,所以我们先开几个空间,到时候满了再动态扩容,开空间使用
malloc
和calloc
都可以,区别只在于一个没初始化,一个全部初始化0
。 -
并且初始的
size
要为0
,而capacity
则为初始开辟的空间的大小INIT_CAPACITY
,具体初始化代码如下:
// 初始化
void ConInit(Con* pc)
{// 用calloc开辟空间,空间里的数据初始化为0// 先开辟 NEW_SIZE 个 , 后面会使用realloc动态增容Info* tmp = (Info*)calloc(NEW_SIZE, sizeof(Info));if (tmp == NULL){perror("calloc fail");exit(-1);}pc->data = tmp;pc->size = 0;pc->capacity = NEW_SIZE;// 拿之前保存的数据GetConData(pc);
}
当然GetConData(pc)
这一步可以先不看,所以这整个代码就是对通讯录的一个初始化过程。
- 宏定义,与结构体都是定义再在
Contacts.h
头文件当中,除此之外,还有各个函数的接口的声明,所以整个头文件的代码段如下:
#define NAME_MAX 20 // 名字的最大长度
#define SEX_MAX 5 // 性别的最大长度
#define ADDS_MAX 20 // 地址的最大长度
#define TELE_MAX 12 // 电话号码的最大长度
#define NEW_SIZE 4 // 每次扩容增加的容量#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>typedef struct PeoInfo
{char name[NAME_MAX]; // 名字int age; // 年龄char sex[SEX_MAX]; // 性别char adds[ADDS_MAX]; // 地址char tele[TELE_MAX]; // 电话
}Info;typedef struct contact
{Info* data; int size; // 存放联系人的个数int capacity; // 存放联系人的空间容量
}Con;// 初始化
void ConInit(Con* pc);
// 初始化但是不拿之前的数据,销毁当中用
void ConInitNoInfo(Con* pc);// 展示通讯录
void ConShow(Con* pc);// 添加联系人
void ConAdd(Con* pc);// 删除联系人
void ConDel(Con* pc);// 查找联系人
void ConFind(Con* pc);// 修改联系人的信息
void ConModify(Con* pc);// 此时通讯录里联系人的个数
int ConSize(Con* pc);// 销毁通讯录
void ConDestory(Con* pc);// 排序
void ConSort(Con* pc);// 保存通讯录
void SaveContact(Con* pc);// 拿之前保存的通讯录数据
void GetConData(Con* pc);
通讯录运行的基本框架和菜单
-
有了头文件的接口,这里我们设计一个菜单,以便于我们在进行操作的时候以输入数字的方式就可以完成操作,这样很是方便。
-
首先,我们可以先通过枚举 (提高代码可读性) 来规定每一个接口运行的代号数字,整个定义如下:
enum select
{EXIT, // 0 退出ADD, // 1 增加DEL, // 2 删除FIND, // 3 查找MODIFY, // 4 修改SORT, // 5 排序SHOW, // 6 展示通讯录CLEAR, // 7 清屏CONSIZE, // 8 通讯录联系人个数DESTORY // 9 销毁通讯录(要释放)
};
根据此枚举的名字以及对应的数字,我们可以设计这样的一个菜单:
void menu()
{printf("***********************************************\n");printf("********** 1.add 2.del **********\n");printf("********** 3.find 4.modify **********\n");printf("********** 5.sort 6.show **********\n");printf("********** 7.clear 8.ConSize **********\n");printf("********** 9.destory 0.exit **********\n");printf("***********************************************\n");
}
- 然后在
main
函数中使用do while
循环来控制整个程序的运行,在do while
循环里使用switch case
分支语句来控制接口的选项,这样整个框架就差不多了。下面是test.c
文件对整个程序框架建立的代码:
#include "Contacts.h"void menu()
{printf("***********************************************\n");printf("********** 1.add 2.del **********\n");printf("********** 3.find 4.modify **********\n");printf("********** 5.sort 6.show **********\n");printf("********** 7.clear 8.ConSize **********\n");printf("********** 9.destory 0.exit **********\n");printf("***********************************************\n");
}enum select
{EXIT, // 0 退出ADD, // 1 增加DEL, // 2 删除FIND, // 3 查找MODIFY, // 4 修改SORT, // 5 排序SHOW, // 6 展示通讯录CLEAR, // 7 清屏CONSIZE, // 8 通讯录联系人个数DESTORY // 9 销毁通讯录(要释放)
};int main()
{int input = 0;Con con;ConInit(&con);do{// 每次根据菜单进行选择menu();printf("请选择:");scanf("%d", &input);switch (input){case EXIT: // 0SaveContact(&con);printf("退出程序>\n");break;case ADD: // 1ConAdd(&con);break;case DEL: // 2ConDel(&con);break;case FIND: // 3ConFind(&con);break;case MODIFY: // 4ConModify(&con);break;case SORT: // 5ConSort(&con);break;case SHOW: // 6ConShow(&con);break;case CLEAR: // 7system("cls");break;case CONSIZE: // 8printf("现在通讯录里联系人的个数为:%d\n", ConSize(&con));break;case DESTORY: // 9ConDestory(&con);int n = 0;printf("是否需要重新初始化通讯录?\n1.YES : 0.NO >>> ");scanf("%d", &n);if (n){ConInit(&con);printf("重新初始化成功>\n");}else{input = 0;printf("通讯录进程关闭,退出程序>>>>>> \n");}break;default:printf("选择错误,请重新选择>\n");break;}} while (input);return 0;
}
有了这样的框架来控制整个程序的运行,接下来,就是对每一个接口的功能的实现了。
增添联系人
-
有了前面的铺垫,第一步当然就是添加联系人了。
-
添加之前还要进行的操作是看是否需要扩容,因为如果空间添加满了,在添加就会出现越界非法访问的问题了,因此这里要写个扩容函数,使用的是
realloc
,判断条件是如果联系人个数(size)
等于容量(capacity)
,就增加容量。 -
添加联系人是在末尾添加,并且要依次输入该联系人的各个信息。
添加联系人接口代码如下:
// 添加联系人
void ConAdd(Con* pc)
{assert(pc);// 需判断容量够不够用,不够则需要扩容Jud_Exp(pc);printf("请输入联系人的姓名:");scanf("%s", pc->data[pc->size].name);printf("请输入联系人的年龄:");scanf("%d", &pc->data[pc->size].age);printf("请输入联系人的性别:");scanf("%s", pc->data[pc->size].sex);printf("请输入联系人的地址:");scanf("%s", pc->data[pc->size].adds);printf("请输入联系人的电话:");scanf("%s", pc->data[pc->size].tele);pc->size++;printf("添加成功>\n");
}
删除联系人
-
有添加就有删除,删除当然是要指定删除哪位联系人,因此需要输入要删除的联系人的名字,然后再根据名字在通讯录里面找到该联系人,将他删除。
-
删除的方式是通过挪动数组来实现的,以覆盖的形式,依次将后一个联系人数据往前挪动一位(很容易发现,这里的效率不是很高),起到删除的效果,当然最后
size
要减一表示联系人少了一位 。 -
在删除之前也要考虑通讯录里面是否有数据的情况,如果通讯录是空的,那也就没有删的必要了,这里采用
assert
断言直接暴力毒打。 -
如果要删除的联系人在通讯录里面没有找到与之对应的,这时就打印删除失败,程序继续运行。
首先是输入要删除的联系人的名字,通过这个名字查找该联系人是否在通讯录里面存在,如果存在,则执行删除操作,不存在则打印删除失败程序继续运行。由于删除,查找,修改这些接口都要用到查找联系人这个函数,因此这里将这个函数单独抽离出来,实现如下:
// 查找名字
// 删除,修改,查找都要用到此功能,因此单独抽离出来实现一个函数
int Find_Name(Con* pc, char* name)
{assert(name);for (int i = 0; i < pc->size; i++){if (!strcmp(name, pc->data[i].name))return i; // 返回找到的名字的下标}// 没有找到则返回-1return -1;
}
那么整个删除联系人的接口的实现就很清楚了:
// 删除联系人
void ConDel(Con* pc)
{assert(pc && pc->size > 0);char name[NAME_MAX];printf("请输入要删除的联系人信息的名字> ");scanf("%s", name);int pos = Find_Name(pc, name);if (pos != -1){for (int i = pos; i < pc->size - 1; i++){pc->data[i] = pc->data[i + 1];}pc->size--;printf("删除成功>\n");}else{printf("要删除的联系人不存在>\n");}
}
查找联系人
- 查找联系人需要输入你要查找的联系人的名字,前面已经将查找的函数写了,这里只需要通过查找函数的返回值来判断是否找到即可,找到了就将其打印,没有则打印没找到。
函数接口实现:
// 查找联系人
void ConFind(Con* pc)
{assert(pc && pc->size > 0);char name[NAME_MAX];printf("请输入想要查找联系人的名字> ");scanf("%s", name);int pos = Find_Name(pc, name);if (pos != -1){printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[pos].name,pc->data[pos].age,pc->data[pos].sex,pc->data[pos].adds,pc->data[pos].tele);}else{printf("没有找到此联系人>\n");}
}
修改联系人信息
- 有了前面的铺垫,要修改,那还不简单,直接输入要修改的联系人的名字,然后重新输入一遍到这个位置,就ok啦。直接上代码,相信大家一看就懂。
函数接口实现:
// 修改联系人的信息
void ConModify(Con* pc)
{assert(pc && pc->size > 0);char name[NAME_MAX];printf("请输入要修改的联系人的信息的名字> ");scanf("%s", name);// 查找要修改的联系人所在通讯录里的位置int pos = Find_Name(pc, name);if (pos != -1){printf("请修改>\n");printf("请输入联系人的姓名:");scanf("%s", pc->data[pos].name);printf("请输入联系人的年龄:");scanf("%d", &pc->data[pos].age);printf("请输入联系人的性别:");scanf("%s", pc->data[pos].sex);printf("请输入联系人的地址:");scanf("%s", pc->data[pos].adds);printf("请输入联系人的电话:");scanf("%s", pc->data[pos].tele);printf("修改成功>\n");}else{printf("要修改信息的联系人不存在>\n");}
}
展示通讯录
-
展示通讯录就不用多说了吧,直接上代码!
函数接口实现:
// 展示通讯录
void ConShow(Con* pc)
{assert(pc);if (!pc->size){printf("通讯录为空>\n");return;}else{printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\n", "名字", "年龄", "性别", "地址", "电话");for (int i = 0; i < pc->size; i++){printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[i].name,pc->data[i].age,pc->data[i].sex,pc->data[i].adds,pc->data[i].tele);}}
}
通讯录联系人个数
直接上代码!
函数接口实现:
// 此时通讯录里联系人的个数
int ConSize(Con* pc)
{assert(pc);return pc->size;
}
排序通讯录
-
排序通讯录这里稍微麻烦一些,我们可以直接用库函数里的
qsort
来进行排序,这时我们只需要按自己的需求,写出对应的cmp
函数即可。例如按年龄排序,年龄排序又分两种,升序和降序。或者按名字排序,通过比较ASCLL码值来进行排序,也分为升序和降序,不过要注意的是,名字是字符串,需要使用strcmp函数
来进行比较。 -
这里我们自己实现一个排序接口来对通讯录进行排序,底层为冒泡排序模板(哈哈哈哈效率低了)。由于是对一个个结构体进行排序,所以在排序的时候,交换数据需要一个字节一个字节的交换。
下面是自我实现的排序代码:
// 交换的时候一个字节一个字节的换,因为不知道排序的是什么类型的数据
void swap(char* buf1, char* buf2, size_t width)
{assert(buf1 && buf2);while (width--){char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}// 排序, 以冒泡排序为框架
void Bubble_Sort(void* bese, size_t num, size_t width, int (*cmp)(const void*, const void*))
{assert(bese);for (size_t i = 0; i < num - 1; ++i){for (size_t j = 0; j < num - 1 - i; ++j){if (cmp((char*)bese + j * width, (char*)bese + (j + 1) * width) > 0){swap((char*)bese + j * width, (char*)bese + (j + 1) * width, width);}}}
}
- 有了这个排序,可以根据自己的需求来写
cmp
函数,若以名字和年龄排序,那么一共有四种情况,下面是对应四种情况的代码:
// 按名字从小到大排序
int cmp_con_name1(const void* a, const void* b)
{return strcmp(((Info*)a)->name, ((Info*)b)->name);
}
// 按名字从大到小排序
int cmp_con_name2(const void* a, const void* b)
{return strcmp(((Info*)b)->name, ((Info*)a)->name);
}// 按年龄从小到大排序
int cmp_con_age1(const void* a, const void* b)
{return ((Info*)a)->age - ((Info*)b)->age;
}// 按年龄从大到小排序
int cmp_con_age2(const void* a, const void* b)
{return ((Info*)b)->age - ((Info*)a)->age;
}
- 排序代码ok后,接下来就是对排序的选择进行一个梳理与排版,以下是该该功能的接口函数的代码实现:
// 排序
void ConSort(Con* pc)
{assert(pc);printf("请选择何种方式排序:\n");printf("1.按名字排序:\n2.按年龄排序:\n");int n = 0;scanf("%d", &n);if (n != 1 && n != 2){printf("选择错误,退出排序>\n");return;}if (n == 1){int k = 0;printf("请选择如何排序:\n");printf("1.升序\n2.降序\n");scanf("%d", &k);if (k == 1){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name1);printf("排序成功>\n");}else if (k == 2){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name2);printf("排序成功>\n");}else{printf("选择错误>\n");}}else if (n == 2){int k = 0;printf("请选择如何排序:\n");printf("1.升序\n2.降序\n");scanf("%d", &k);if (k == 1){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age1);printf("排序成功>\n");}else if (k == 2){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age2);printf("排序成功>\n");}else{printf("选择错误>\n");}}
}
这里测试一下:
一开始通讯录列表为:
我们按年龄从小到大排序为:
可以看到,此时的确排序成功!
整体的函数接口实现为:
// 按名字从小到大排序
int cmp_con_name1(const void* a, const void* b)
{return strcmp(((Info*)a)->name, ((Info*)b)->name);
}
// 按名字从大到小排序
int cmp_con_name2(const void* a, const void* b)
{return strcmp(((Info*)b)->name, ((Info*)a)->name);
}// 按年龄从小到大排序
int cmp_con_age1(const void* a, const void* b)
{return ((Info*)a)->age - ((Info*)b)->age;
}// 按年龄从大到小排序
int cmp_con_age2(const void* a, const void* b)
{return ((Info*)b)->age - ((Info*)a)->age;
}// 交换的时候一个字节一个字节的换,因为不知道排序的是什么类型的数据
void swap(char* buf1, char* buf2, size_t width)
{assert(buf1 && buf2);while (width--){char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}// 排序, 以冒泡排序为框架
void Bubble_Sort(void* bese, size_t num, size_t width, int (*cmp)(const void*, const void*))
{assert(bese);for (size_t i = 0; i < num - 1; ++i){for (size_t j = 0; j < num - 1 - i; ++j){if (cmp((char*)bese + j * width, (char*)bese + (j + 1) * width) > 0){swap((char*)bese + j * width, (char*)bese + (j + 1) * width, width);}}}
}// 排序
void ConSort(Con* pc)
{assert(pc);printf("请选择何种方式排序:\n");printf("1.按名字排序:\n2.按年龄排序:\n");int n = 0;scanf("%d", &n);if (n != 1 && n != 2){printf("选择错误,退出排序>\n");return;}if (n == 1){int k = 0;printf("请选择如何排序:\n");printf("1.升序\n2.降序\n");scanf("%d", &k);if (k == 1){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name1);printf("排序成功>\n");}else if (k == 2){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name2);printf("排序成功>\n");}else{printf("选择错误>\n");}}else if (n == 2){int k = 0;printf("请选择如何排序:\n");printf("1.升序\n2.降序\n");scanf("%d", &k);if (k == 1){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age1);printf("排序成功>\n");}else if (k == 2){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age2);printf("排序成功>\n");}else{printf("选择错误>\n");}}
}
文件操作储存通讯录信息
-
对文件操作不熟悉的同志可以先看下这篇博客:文件操作。
-
文件操作就是将我们进行一系列操作最终定型的通讯录信息存放在文件当中,且下一次运行程序时,可以将信息从文件拿出来。
-
一共有两个步骤:存和取 ,以二进制方式存,以二进制方式取。
-
存的时候可以在退出程序的那最后一步自动存,也可以多设置一个选项随时存。
-
取的时候在通讯录初始化的时候就自动去文件中寻找信息并取。
分别的函数接口实现为:
存:
// 保存通讯录
void SaveContact(Con* pc)
{assert(pc);// 将信息以二进制形式保存在Contacts.txt文本文档中FILE* pf = fopen("Contact.txt", "wb");if (pf == NULL){perror("Save Con fail");}else{for (int i = 0; i < pc->size; i++){fwrite(pc->data + i, sizeof(Info), 1, pf);}fclose(pf);pf = NULL;printf("保存通讯录数据成功>\n");}
}
取:
// 拿之前保存的通讯录数据
void GetConData(Con* pc)
{assert(pc);FILE* pf = fopen("Contact.txt", "rb");if (pf == NULL){perror("Get data fail");}else{Info tmp = { 0 };int i = 0;while (fread(&tmp, sizeof(Info), 1, pf)){Jud_Exp(pc); // 判断是否要扩容pc->data[i] = tmp; // 每拿一个联系人的信息就放入通讯录pc->size++; // 每get一个计数一次i++; }fclose(pf); // get完后关闭文件pf = NULL;printf("获取之前的通讯录数据成功>\n");}
}
销毁通讯录
-
销毁通讯录相当于是将通讯录里面的信息全部清空,当然我们在销毁过后也可以选择是否初始化通讯录,如果不选则退出程序。
-
注意这里的初始化要单独写一个接口,实际上相当于复制一个,只不过说没有从文件中取数据的那一步(纯纯初始化)。
函数接口代码实现:
// 销毁通讯录
void ConDestory(Con* pc)
{assert(pc);free(pc->data);pc->data = NULL;pc->capacity = 0;pc->size = 0;printf("销毁成功>\n");
}
整体代码
Contacts.h
#define NAME_MAX 20 // 名字的最大长度
#define SEX_MAX 5 // 性别的最大长度
#define ADDS_MAX 20 // 地址的最大长度
#define TELE_MAX 12 // 电话号码的最大长度
#define NEW_SIZE 4 // 每次扩容增加的容量#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>typedef struct PeoInfo
{char name[NAME_MAX]; // 名字int age; // 年龄char sex[SEX_MAX]; // 性别char adds[ADDS_MAX]; // 地址char tele[TELE_MAX]; // 电话
}Info;typedef struct contact
{Info* data; int size; // 存放联系人的个数int capacity; // 存放联系人的空间容量
}Con;// 初始化
void ConInit(Con* pc);
// 初始化但是不拿之前的数据
void ConInitNoInfo(Con* pc);// 展示通讯录
void ConShow(Con* pc);// 添加联系人
void ConAdd(Con* pc);// 删除联系人
void ConDel(Con* pc);// 查找联系人
void ConFind(Con* pc);// 修改联系人的信息
void ConModify(Con* pc);// 此时通讯录里联系人的个数
int ConSize(Con* pc);// 销毁通讯录
void ConDestory(Con* pc);// 排序
void ConSort(Con* pc);// 保存通讯录
void SaveContact(Con* pc);// 拿之前保存的通讯录数据
void GetConData(Con* pc);
Contacts.c
#include "Contacts.h"// 初始化
void ConInit(Con* pc)
{// 用calloc开辟空间,空间里的数据初始化为0// 先开辟 NEW_SIZE 个 , 后面会使用realloc动态增容Info* tmp = (Info*)calloc(NEW_SIZE, sizeof(Info));if (tmp == NULL){perror("calloc fail");exit(-1);}pc->data = tmp;pc->size = 0;pc->capacity = NEW_SIZE;// 拿之前保存的数据GetConData(pc);
}// 初始化但是不拿之前的数据
void ConInitNoInfo(Con* pc)
{// 用calloc开辟空间,空间里的数据初始化为0// 先开辟 NEW_SIZE 个 , 后面会使用realloc动态增容Info* tmp = (Info*)calloc(NEW_SIZE, sizeof(Info));if (tmp == NULL){perror("calloc fail");exit(-1);}pc->data = tmp;pc->size = 0;pc->capacity = NEW_SIZE;
}// 展示通讯录
void ConShow(Con* pc)
{assert(pc);if (!pc->size){printf("通讯录为空>\n");return;}else{printf("%-20s\t%-4s\t%-5s\t%-20s\t%-12s\n", "名字", "年龄", "性别", "地址", "电话");for (int i = 0; i < pc->size; i++){printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[i].name,pc->data[i].age,pc->data[i].sex,pc->data[i].adds,pc->data[i].tele);}}
}// 判断是否要扩容
void Jud_Exp(Con* pc)
{if (pc->capacity == pc->size){Info* tmp = realloc(pc->data, sizeof(Info) * (pc->capacity + NEW_SIZE));if (tmp == NULL){perror("realloc fail");exit(-1);}pc->data = tmp;pc->capacity = pc->capacity + NEW_SIZE;printf("增容成功>\n");}
}// 添加联系人
void ConAdd(Con* pc)
{assert(pc);// 需判断容量够不够用,不够则需要扩容Jud_Exp(pc);printf("请输入联系人的姓名:");scanf("%s", pc->data[pc->size].name);printf("请输入联系人的年龄:");scanf("%d", &pc->data[pc->size].age);printf("请输入联系人的性别:");scanf("%s", pc->data[pc->size].sex);printf("请输入联系人的地址:");scanf("%s", pc->data[pc->size].adds);printf("请输入联系人的电话:");scanf("%s", pc->data[pc->size].tele);pc->size++;printf("添加成功>\n");
}// 查找名字
// 删除,修改,查找都要用到此功能,因此单独抽离出来实现一个函数
int Find_Name(Con* pc, char* name)
{assert(name);for (int i = 0; i < pc->size; i++){if (!strcmp(name, pc->data[i].name))return i; // 返回找到的名字的下标}// 没有找到则返回-1return -1;
}// 删除联系人
void ConDel(Con* pc)
{assert(pc && pc->size > 0);char name[NAME_MAX];printf("请输入要删除的联系人信息的名字> ");scanf("%s", name);int pos = Find_Name(pc, name);if (pos != -1){for (int i = pos; i < pc->size - 1; i++){pc->data[i] = pc->data[i + 1];}pc->size--;printf("删除成功>\n");}else{printf("要删除的联系人不存在>\n");}
}// 查找联系人
void ConFind(Con* pc)
{assert(pc && pc->size > 0);char name[NAME_MAX];printf("请输入想要查找联系人的名字> ");scanf("%s", name);int pos = Find_Name(pc, name);if (pos != -1){printf("%-20s\t%-4d\t%-5s\t%-20s\t%-12s\n", pc->data[pos].name,pc->data[pos].age,pc->data[pos].sex,pc->data[pos].adds,pc->data[pos].tele);}else{printf("没有找到此联系人>\n");}
}// 修改联系人的信息
void ConModify(Con* pc)
{assert(pc && pc->size > 0);char name[NAME_MAX];printf("请输入要修改的联系人的信息的名字> ");scanf("%s", name);int pos = Find_Name(pc, name);if (pos != -1){printf("请修改>\n");printf("请输入联系人的姓名:");scanf("%s", pc->data[pos].name);printf("请输入联系人的年龄:");scanf("%d", &pc->data[pos].age);printf("请输入联系人的性别:");scanf("%s", pc->data[pos].sex);printf("请输入联系人的地址:");scanf("%s", pc->data[pos].adds);printf("请输入联系人的电话:");scanf("%s", pc->data[pos].tele);printf("修改成功>\n");}else{printf("要修改信息的联系人不存在>\n");}
}// 此时通讯录里联系人的个数
int ConSize(Con* pc)
{assert(pc);return pc->size;
}// 销毁通讯录
void ConDestory(Con* pc)
{assert(pc);free(pc->data);pc->data = NULL;pc->capacity = 0;pc->size = 0;printf("销毁成功>\n");
}// 按名字从小到大排序
int cmp_con_name1(const void* a, const void* b)
{return strcmp(((Info*)a)->name, ((Info*)b)->name);
}
// 按名字从大到小排序
int cmp_con_name2(const void* a, const void* b)
{return strcmp(((Info*)b)->name, ((Info*)a)->name);
}// 按年龄从小到大排序
int cmp_con_age1(const void* a, const void* b)
{return ((Info*)a)->age - ((Info*)b)->age;
}// 按年龄从大到小排序
int cmp_con_age2(const void* a, const void* b)
{return ((Info*)b)->age - ((Info*)a)->age;
}// 交换的时候一个字节一个字节的换,因为不知道排序的是什么类型的数据
void swap(char* buf1, char* buf2, size_t width)
{assert(buf1 && buf2);while (width--){char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}// 排序, 以冒泡排序为框架
void Bubble_Sort(void* bese, size_t num, size_t width, int (*cmp)(const void*, const void*))
{assert(bese);for (size_t i = 0; i < num - 1; ++i){for (size_t j = 0; j < num - 1 - i; ++j){if (cmp((char*)bese + j * width, (char*)bese + (j + 1) * width) > 0){swap((char*)bese + j * width, (char*)bese + (j + 1) * width, width);}}}
}// 排序
void ConSort(Con* pc)
{assert(pc);printf("请选择何种方式排序:\n");printf("1.按名字排序:\n2.按年龄排序:\n");int n = 0;scanf("%d", &n);if (n != 1 && n != 2){printf("选择错误,退出排序>\n");return;}if (n == 1){int k = 0;printf("请选择如何排序:\n");printf("1.升序\n2.降序\n");scanf("%d", &k);if (k == 1){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name1);printf("排序成功>\n");}else if (k == 2){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_name2);printf("排序成功>\n");}else{printf("选择错误>\n");}}else if (n == 2){int k = 0;printf("请选择如何排序:\n");printf("1.升序\n2.降序\n");scanf("%d", &k);if (k == 1){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age1);printf("排序成功>\n");}else if (k == 2){Bubble_Sort(pc->data, pc->size, sizeof(pc->data[0]), cmp_con_age2);printf("排序成功>\n");}else{printf("选择错误>\n");}}
}// 保存通讯录
void SaveContact(Con* pc)
{assert(pc);// 将信息以二进制形式保存在Contacts.txt文本文档中FILE* pf = fopen("Contact.txt", "wb");if (pf == NULL){perror("Save Con fail");}else{for (int i = 0; i < pc->size; i++){fwrite(pc->data + i, sizeof(Info), 1, pf);}fclose(pf);pf = NULL;printf("保存通讯录数据成功>\n");}
}// 拿之前保存的通讯录数据
void GetConData(Con* pc)
{assert(pc);FILE* pf = fopen("Contact.txt", "rb");if (pf == NULL){perror("Get data fail");}else{Info tmp = { 0 };int i = 0;while (fread(&tmp, sizeof(Info), 1, pf)){Jud_Exp(pc); // 判断是否要扩容pc->data[i] = tmp; // 每拿一个联系人的信息就放入通讯录pc->size++; // 每get一个计数一次i++; }fclose(pf); // get完后关闭文件pf = NULL;printf("获取之前的通讯录数据成功>\n");}
}
test.c
#include "Contacts.h"void menu()
{printf("***********************************************\n");printf("********** 1.add 2.del **********\n");printf("********** 3.find 4.modify **********\n");printf("********** 5.sort 6.show **********\n");printf("********** 7.clear 8.ConSize **********\n");printf("********** 9.destory 0.exit **********\n");printf("***********************************************\n");
}enum select
{EXIT, // 0 退出ADD, // 1 增加DEL, // 2 删除FIND, // 3 查找MODIFY, // 4 修改SORT, // 5 排序SHOW, // 6 展示通讯录CLEAR, // 7 清屏CONSIZE, // 8 通讯录联系人个数DESTORY // 9 销毁通讯录(要释放)
};int main()
{int input = 0;Con con;ConInit(&con);do{menu();printf("请选择:");scanf("%d", &input);switch (input){case EXIT:SaveContact(&con);printf("退出程序>\n");break;case ADD:ConAdd(&con);break;case DEL:ConDel(&con);break;case FIND:ConFind(&con);break;case MODIFY:ConModify(&con);break;case SORT:ConSort(&con);break;case SHOW:ConShow(&con);break;case CLEAR:system("cls");break;case CONSIZE:printf("现在通讯录里联系人的个数为:%d\n", ConSize(&con));break;case DESTORY:ConDestory(&con);int n = 0;printf("是否需要重新初始化通讯录?\n1.YES : 0.NO >>> ");scanf("%d", &n);if (n){ConInitNoInfo(&con);printf("重新初始化成功>\n");}else{input = 0;printf("通讯录进程关闭,退出程序>>>>>> \n");}break;default:printf("选择错误,请重新选择>\n");break;}} while (input);return 0;
}
写在最后
到了这里,一个简简单单的通讯录就完成了!如果还不能自我实现,那可要好好的练习了!
感谢阅读本小白的博客,错误的地方请严厉指出噢!
相关文章:

带你轻松实现通讯录(C语言版)
文章目录前言通讯录初始化通讯录运行的基本框架和菜单增添联系人删除联系人查找联系人修改联系人信息展示通讯录通讯录联系人个数排序通讯录文件操作储存通讯录信息销毁通讯录整体代码Contacts.hContacts.ctest.c写在最后前言 学习C语言的小伙伴,相信都要经历实现通…...

渗透测试之交换式网络嗅探实验
渗透测试之交换式网络嗅探实验实验目的一、实验原理1.1 网络嗅探器Sniffer的工作原理1.2 网络嗅探器的分类1.3 网络嗅探器Sniffer的作用二、实验环境2.1 操作机器2.2 实验工具Sniffer2.3 安装工具Sniffer三、实验步骤1. 熟悉Sniffer工具的启动2. 进行监听3. 熟悉Sniffer工具的介…...

rust 安装
rust 安装一、需要一个c的环境二、配置环境变量三、开始安装一、需要一个c的环境 安装Visual Studio 二、配置环境变量 Rust需要安装两个东西,一个是rustup,一个是cargo。所以你需要设置两个环境变量来分别指定他们的安装目录。 通过RUSTUP_HOME指定…...
机器学习和深度学习综述
机器学习和深度学习综述 1. 人工智能、机器学习、深度学习的关系 近些年人工智能、机器学习和深度学习的概念十分火热,但很多从业者却很难说清它们之间的关系,外行人更是雾里看花。在研究深度学习之前,先从三个概念的正本清源开始。概括来说…...

SQL零基础入门学习(八)
SQL零基础入门学习(七) SQL 连接(JOIN) SQL join 用于把来自两个或多个表的行结合起来。 下图展示了 LEFT JOIN、RIGHT JOIN、INNER JOIN、OUTER JOIN 相关的 7 种用法。 SQL JOIN SQL JOIN 子句用于把来自两个或多个表的行结合起来,基…...

若依系统如何集成qq邮件发送【超详细,建议收藏】
若依系统的部署博主就不在这儿阐述了,默认大家的电脑已经部署好了若依系统,这里直接开始集成邮件系统,首先我们得需要对qq邮箱进行配置;一套学不会你来打我😀; 一、开启我们的qq邮箱发送邮件的配置 1、先进…...
前端-CSS-zxst
CSS 层叠样式表,为了定义HTML标签的样式 内联样式 在标签内部通过 style 属性设置样式值样式名:样式值;样式名:样式值; 内部样式 在 head 标签内通过 style 标签选择器设置样式,供这个网页上的元素使用 外部样式 在 head 标签内通过 link 标签引入外部…...
合宙Air105|fonts库|mcu.ticks()|LuatOS-SOC接口|官方demo|学习(19):fonts库
基础资料 基于Air105开发板:Air105 - LuatOS 文档 上手:开发上手 - LuatOS 文档 探讨重点 官方fonts库函数介绍以及利用mcu.ticks()计算程序运行周期相关内容的学习及探讨。 软件版本 AIR105:LuatOSAIR105 base 22.12 bsp V0014 32bit …...

成都欢蓬电商:抖音直播卖药灰度测试通告
据报道,近日有MCN机构透露,目前抖音直播卖药为“测试项目,谨慎试跑中”; “仍处于灰度测试,至于测试多久,抖音官方确实没有答复,需要看第一阶段数据,然后定夺,预计4月份会纳入更多机…...
1.1计算机和编成语言
一、C 语言简介历史C 语言最初是作为 Unix 系统的开发工具而发明的。1969年,美国贝尔实验室的肯汤普森(Ken Thompson)与丹尼斯里奇(Dennis Ritchie)一起开发了Unix 操作系统。Unix 是用汇编语言写的,无法移…...
解析 xml 文件 - xml.etree ElementTree
目录1、导入模块 →\rightarrow→ 读取文件 →\rightarrow→ 获取根节点 →\rightarrow→ 获取根节点的标签与属性2、遍历一级子节点、获取子节点的标签 与 属性3、通过索引 获取数据4、Element.findall()、Element.find() - 按照 tag 值查找 子节点5、Element.iter() - 循环迭…...

LeetCode Cookbook 哈希表(collections.Counter()和collections.defaultdict())
好久不更了,这次一鼓作气,学完它! 文章目录LeetCode Cookbook 哈希表30. 串联所有单词的子串36. 有效的数独(很不错的循环题目)49. 字母异位词分组290. 单词规律447. 回旋镖的数量575. 分糖果594. 最长和谐子序列599. …...

spring boot项目中i18n和META-INF.spring下的文件的作用
目录标题一、resource下的文件二、i18n下messages_zh_CN.properties三、spring.factories文件四、org.springframework.boot.autoconfigure.AutoConfiguration.imports一、resource下的文件 org.springframework.boot.autoconfigure.AutoConfiguration.imports ; - …...

3年自动化测试经验,面试连20K都拿不到,现在都这么卷了吗····
我的情况 大概介绍一下个人情况,女,本科,三年多测试工作经验,懂python,会写脚本,会selenium,会性能,会自动化,然而到今天都没有收到一份offer!从2022年11月1…...

Python数据结构与算法篇(四)-- 链表的实现
实现线性表的另一种常用方式就是基于链接结构,用链接关系显式表示元素之间的顺序关联。基于链接技术实现的线性表称为链接表或者链表。 采用链接方式实现线性表的基本想法如下: 把表中的元素分别存储在一批独立的存储块(称为表的结点)里。保…...
【java基础】循环语句、中断控制语句
文章目录循环while循环for循环for each循环中断控制语句breakcontinue带标签的break(相当于goto)循环 在java中有3种循环,分别是while循环,for循环,for each循环 while循环 while循环的形式是 while(condition) statement int i 5;while …...

万字长文带你实战 Elasticsearch 搜索
ES 高级实战 前言 上篇我们讲到了 Elasticsearch 全文检索的原理《别只会搜日志了,求你懂点原理吧》,通过在本地搭建一套 ES 服务,以多个案例来分析了 ES 的原理以及基础使用。这次我们来讲下 Spring Boot 中如何整合 ES,以及如何在 Spring Cloud 微服务项目中使用 ES 来…...

Web网页测试全流程解析论Web自动化测试
1、功能测试 web网页测试中的功能测试,主要测试网页中的所有链接、数据库连接、用于在网页中提交或获取用户信息的表单、Cookie 测试等。 (1)查看所有链接: 测试从所有页面到被测特定域的传出链接。 测试所有内部链接。 测试链…...

初识Python——“Python”
各位CSDN的uu们你们好呀,今天进入到了我们的新专栏噢,Python是小雅兰的专业课,那么现在,就让我们进入Python的世界吧 计算机基础概念 什么是计算机? 什么是编程? 编程语言有哪些? Python背景知…...

LocalDateTime使用
开发中常常需要用到时间,随着jdk的发展,对于时间的操作已经摒弃了之前的Date等方法,而是采用了LocalDateTime方法,因为LocalDateTime是线程安全的。 下面我们来介绍一下LocalDateTime的使用。 时间转换 将字符串转换为时间格式…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...

【JavaEE】-- HTTP
1. HTTP是什么? HTTP(全称为"超文本传输协议")是一种应用非常广泛的应用层协议,HTTP是基于TCP协议的一种应用层协议。 应用层协议:是计算机网络协议栈中最高层的协议,它定义了运行在不同主机上…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
线程与协程
1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指:像函数调用/返回一样轻量地完成任务切换。 举例说明: 当你在程序中写一个函数调用: funcA() 然后 funcA 执行完后返回&…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...

【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...

push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...