我会将代码都直接写在下面,我都注释的十分清楚,一般正常人都能看懂的。
一,静态通讯录
静态通讯录存在于栈区,众所周知,栈区的空间是十分宝贵的,若通讯录太大,不光会造成栈区内存的大片浪费,而且可能使程序无法运行。
但是,也不是不能写。
写之前需要了解的知识
1.模块的概念
一个头文件和一些源文件可以组成一个模块,封装在一起,保密性更好,私密性更强。
2.宏相关的知识
宏是一种很实用的方法,一定要了解宏的使用,再一些大程序的编译中,宏的出现使得更改更方便,当然还有一些相关的好处我在这就不深讲了,等寒假回家我会针对C语言所有的一些细节方面的深究探索出一个系列。
3.结构体相关的知识
因为在我们的日常生活中,是充满了各种各样的不同元素的,所以数组的知识是明显不够用的。还有涉及到结构体指针的一些东西,甚至链表,这些东西在现在以至于之后数据结构的学习中都有着很大的作用。
4.函数相关的知识
结构体指针传参,是传值还是传地址,函数的定义调用
5.指针
指针的操作是必不可少的,因为编程的实质就是对数据的操作,对数据的操作必不可少的部分就是对内存的管理了。毫无疑问,指针是最方便的工具,没有之一。
6.循环和分支结构的基本内容
如果你已经掌握了这些知识,恭喜你,你可以开始写这个静态通讯录了。
好的,那么我们直接上代码。
一,contact.h文件的实现
#pragma once #include<stdio.h> #include<string.h> #include<stdlib.h> //使改数据更加方便 #define MAX_NAME 20 #define MAX_SEX 10 #define MAX_TELE 12 #define MAX_ADDR 20 #define MAX_DATA 1000 //定义用户信息结构 typedef struct Peoinfo { char name[MAX_NAME];//名字 char sex[MAX_SEX];//性别 int age;//年龄 char tele[MAX_TELE];//电话 char addr[MAX_ADDR];//地址 }Peoinfo; //通讯录结构体 typedef struct Contact { Peoinfo data[MAX_DATA];//用户个人信息 int sz;//用户个数 }Contact; //初始化结构体 void InitContact(Contact* pc); //增加用户信息 void Addcontact(Contact* pc); //删除用户信息 void Delcontact(Contact* pc); //查找用户信息 void Searchcontact(Contact* pc); //修改用户信息 void Modifycontact(Contact* pc); //按姓氏首字母排序 void Sortcontact(const Contact* pc); //输出所有用户信息 void Printcontact(const Contact* pc); //通过名字查找用户 int Findbyname(Contact* pc, const char *p); //排序算法 void swap(Peoinfo* pc, Peoinfo* ppc);
二,contact.c文件的实现
#define _CRT_SECURE_NO_WARNINGS 1 #include"contact.h" //初始化通讯录的函数 void InitContact(Contact* pc) { pc->sz = 0; //将结构体内的元素进行初始化,利用memset函数,可以将结构体内的成员定义为任何我想要的值 memset(pc->data, 0, sizeof(pc->data)); //也可以之间pc->data[MAX_DATA]={0}; } //增加用户信息 void Addcontact(Contact* pc) { //如果数据已经存放完毕 if (pc->sz == MAX_DATA) { printf("存满了,放不下了!\n"); return; } //初始化用户信息 printf("请输入名字:>\n"); scanf("%s", pc->data[pc->sz].name); printf("请输入年龄:>\n"); scanf("%d", &(pc->data[pc->sz].age)); printf("请输入性别:>\n"); scanf("%s", pc->data[pc->sz].sex); printf("请输入电话:>\n"); scanf("%s", pc->data[pc->sz].tele); printf("请输入地址:>\n"); scanf("%s", pc->data[pc->sz].addr); pc->sz++; printf("增加成功!\n"); } //程序要求中并没有这一要求,但是,很多程序要求都需要查找这一操作 //为了是程序的重复性降低,我们把他封装起来 //这里为什么要给两个参数? //没有两个参数怎么知道找什么 //该函数只在该文件中使用,为了程序的私密性,放到数据段中。 static int Findbyname(Contact* pc, const char* p) { for (int i = 0; i < pc->sz; i++) { if (strcmp(pc->data[i].name, p) == 0) { return i;//返回对应值的下标 } } return -1; } //删除用户信息 void Delcontact(Contact* pc) { char name[MAX_NAME]=0; //若,通讯录为空,则无对象可删 if (pc->sz == 0) { printf("通讯录为空,无删除对象!\n"); return; } //定义变量传参 printf("请输入要删除人的名字:>\n"); scanf("%s", name); int pos = Findbyname(pc, name); //常见的数组删除操作,该数组元素之后的元素集体前移,最后再将总共需要处理的元素总数-1; 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"); } //查找某用户信息 void Searchcontact(Contact* pc) { char name[MAX_NAME]; int pos = Findbyname(pc, name); if (pos == -1) { printf("通讯录中不存在该用户!\n"); } else { //左对齐输出 printf("%-20s\t%-5d\t%-5s\t%-12s\t%-10s\t\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr); printf("打印成功!\n"); } } //修改某用户信息 void Modifycontact(Contact* pc) { char name[MAX_NAME]; //初始化 printf("请输入修改人的名字:>\n"); scanf("%s", name); Findbyname(pc, name); int pos= Findbyname(pc, name); if (pos == -1) { printf("要修改的人不存在!\n"); return; } //修改用户信息 printf("请输入名字:>\n"); scanf("%s", pc->data[pos].name); printf("请输入年龄:>\n"); scanf("%d", &(pc->data[pos].age)); printf("请输入性别:>\n"); scanf("%s", pc->data[pos].sex); printf("请输入电话:>\n"); scanf("%s", pc->data[pos].tele); printf("请输入地址:>\n"); scanf("%s", pc->data[pos].addr); printf("修改成功\n"); } //按姓氏首字母排序 void Sortcontact( Contact* pc) { int index = 0; //选择法排序 for (int i = 1; i < pc->sz; i++) { if (strcmp(pc->data[index].name, pc->data[i].name) > 0) { swap(&(pc->data[index]), &(pc->data[i])); index = i; } } printf("排序完毕!\n"); } //输出所有用户信息 void Printcontact(const Contact* pc) { //打印标题 printf("%-20s\t%-5s\t%-5s\t%-12s\t%-10s\t\n", "姓名", "年龄", "性别", "电话", "地址"); //打印数据 for (int i = 0; i < pc->sz; i++) { printf("%-20s\t%-5d\t%-5s\t%-12s\t%-10s\t\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr); } printf("打印成功!\n"); } //排序算法 void swap(Peoinfo* pc, Peoinfo* ppc) { Peoinfo* ptr=NULL; *ptr = *pc; *pc = *ppc; *ppc = *ptr; } //输出菜单 void menu() { printf("***********************************\n"); printf("*********1.add 2.del***********\n"); printf("******3.search 4.modify***********\n"); printf("*********5.sort 6.print*********\n"); printf("*********** 0.exit **************\n"); printf("***********************************\n"); }
三,test.c文件的实现
#define _CRT_SECURE_NO_WARNINGS 1 //静态通讯录 //按姓名查询某人的信息 // 修改信息 // 按姓氏首字母排序 // 输出通讯录里面的所有信息 // 增加用户信息 //删除用户信息 // 一共存储多少人的信息 //。。。。。 #include"contact.h" //定义一个枚举类型,用来增加程序可读性 enum options { EXIT, ADD, DEL, SEARCH, MODIFY, SORT, PRINT }; int main() { int option; //创建通讯录 Contact con; //初始化用户信息 InitContact(&con); //用while的话会使程序更复杂,在实现这种多选择的每种情况都会有相应操作,会根据输入的某个值而做出相应的操作 do { //输出菜单 menu(); //初始化数据 printf("请输入选择:>"); scanf("%d", &option); //根据不同的情况做出不同的操作 switch (option) { case ADD: Addcontact(&con);//增加 break; case DEL: Delcontact(&con);//删除 break; case SEARCH: Searchcontact(&con);//查找并输出 break; case MODIFY: Modifycontact(&con);//修改 break; case SORT: Sortcontact(&con);//排序 break; case PRINT: Printcontact(&con);//输出所有 break; case EXIT: printf("程序结束!\n");//如果遇到0,程序结束 break; default: printf("选择错误,请重新选择!\n");//若输入的是其他的数据,则重新选择 break; } } while (option); return 0; } /*.c文件和.h文件是一个大的模块,这个通讯录中,有两个.c文件,二者之间的 功能不同,contact.c文件中存放着关于整个项目要用到的函数的定义。而test.c 文件存放着主体函数的执行,用于测试程序的可行性。两个.c文件都是需要引 contact.h的头文件的。*/
看到这里,我相信大家已经大概的代码看完了,那么问题来了,为什么要写两个.c文件,回答这个问题之前,我们先来逐个分析一下这些文件,
contact.h文件:用作函数或者其他数据类型的定义
contact.c文件:用作函数的封装,函数的定义,即实现主函数或者说想要解决问题的步骤函数
test.c文件:用来写程序代码的主体,测试代码的可行性,那么为什么不将两个.c文件放到一起呢?程序越大,代码越长,如果都写在一起当然是可行的,但是翻阅起来却成了麻烦,为了避免这些,我们最好的习惯还是将程序代码的主体与函数的封装写到不同的文件之中。
二,动态通讯录
上面的开头就说到了静态通讯录的不足之处,为了解决上面所说的不足,我们想到了其他的一些方法来解决。
1.针对上面说占用栈区过大空间的问题、
我们现在选择换成使用堆区,也就是动态内存的分配。
2.针对浪费空间过多的问题
我们使用realloc函数,对已开辟的空间进行增容,且每次增容一点点,避免用户不多,但是存储空间很多的尴尬局面。
3.针对通讯录一退出数据就被销毁的问题(因为数据在栈区,程序一结束,空间就会被释放,数据就会被销毁)
退出通讯录以后,将数据储存到外存中(硬盘)。进入通讯录时,便从文件中读取数据
需要掌握的知识
上面说过的知识是肯定少不了的,因为动态通讯录是比静态通讯录难很多的
1.动态内存分配
malloc,calloc、realloc,free这几个内存处理函数一定是要搞得非常清楚的,这些在我之前写过的深究C语言4.链表和结构
(3条消息) 深究C语言4.链表和结构体_喜欢吃豆的博客-CSDN博客
https://blog.csdn.net/m0_63309778/article/details/121981368
中是有过很深的介绍的。
2.文件的相关函数和操作
文件的操作是很复杂的一个体系,一定要搞得很清晰
好了,话不多说,直接上代码
一,contact.h文件实现
#pragma once #include<stdio.h> #include<string.h> #include<stdlib.h> //使改数据更加方便 #define MAX_NAME 20 #define MAX_SEX 10 #define MAX_TELE 12 #define MAX_ADDR 20 #define MAX_DATA 1000 #define DEFAULT_SZ 3 #define INC_SZ 2 //定义用户信息结构 typedef struct Peoinfo { char name[MAX_NAME];//名字 char sex[MAX_SEX];//性别 int age;//年龄 char tele[MAX_TELE];//电话 char addr[MAX_ADDR];//地址 }Peoinfo; //通讯录结构体 typedef struct Contact { Peoinfo* data;//用户个人信息 int sz;//记录的是通讯录中有效用户信息的个数 int capacity;//记录当前通讯录的容量 }Contact; //初始化结构体 void InitContact(Contact* pc); //增加用户信息 void Addcontact(Contact* pc); //删除用户信息 void Delcontact(Contact* pc); //查找用户信息 void Searchcontact(Contact* pc); //修改用户信息 void Modifycontact(Contact* pc); //按姓氏首字母排序 void Sortcontact(const Contact* pc); //输出所有用户信息 void Printcontact(const Contact* pc); //通过名字查找用户 int Findbyname(Contact* pc, const char* p); //排序算法 void swap(Peoinfo* pc, Peoinfo* ppc); //使用完后销毁 void DestoryContact(Contact* pc); //检测增容问题 void CheckContact(Contact* pc); //储存到文件中 void SaveContact(Contact* pc); //加载文件内容到通讯录 void LoadContact(Contact* pc);
二,contact.c文件的实现
#define _CRT_SECURE_NO_WARNINGS 1 #include"contact.h" //初始化通讯录的函数 void InitContact(Contact* pc) { pc->sz = 0; //如果想对开辟的空间初始化就用calloc //pc->data = (Peoinfo*)calloc(DEFAULT_SZ, sizeof(Peoinfo)); //动态申请内存 pc->data = (Peoinfo*)malloc(DEFAULT_SZ * sizeof(Peoinfo)); //若无法正常打开 if (pc->data == NULL) { perror("InitContact"); return; } //初始容量为3 pc->capacity = DEFAULT_SZ; LoadContact(pc); } //增加用户信息 void Addcontact(Contact* pc) { //如果数据已经存放完毕 //考虑增容 CheckContact(pc); //初始化用户信息 printf("\n请输入名字:>\n"); scanf("%s", pc->data[pc->sz].name); printf("请输入年龄:>\n"); scanf("%d", &(pc->data[pc->sz].age)); printf("请输入性别:>\n"); scanf("%s", pc->data[pc->sz].sex); printf("请输入电话:>\n"); scanf("%s", pc->data[pc->sz].tele); printf("请输入地址:>\n"); scanf("%s", pc->data[pc->sz].addr); pc->sz++; printf("增加成功!\n"); } //程序要求中并没有这一要求,但是,很多程序要求都需要查找这一操作 //为了是程序的重复性降低,我们把他封装起来 //这里为什么要给两个参数? //没有两个参数怎么知道找什么 //该函数只在该文件中使用,为了程序的私密性,放到数据段中。 static int Findbyname(Contact* pc, const char* p) { for (int i = 0; i < pc->sz; i++) { if (strcmp(pc->data[i].name, p) == 0) { return i;//返回对应值的下标 } } return -1; } //删除用户信息 void Delcontact(Contact* pc) { char name[MAX_NAME]={0}; //若,通讯录为空,则无对象可删 if (pc->sz == 0) { printf("通讯录为空,无删除对象!\n"); return; } //定义变量传参 printf("请输入要删除人的名字:>\n"); scanf("%s", name); int pos = Findbyname(pc, name); //常见的数组删除操作,该数组元素之后的元素集体前移,最后再将总共需要处理的元素总数-1; 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"); } //查找某用户信息 void Searchcontact(Contact* pc) { char name[MAX_NAME]; int pos = Findbyname(pc, name); if (pos == -1) { printf("通讯录中不存在该用户!\n"); } else { //左对齐输出 printf("%-20s\t%-5d\t%-5s\t%-12s\t%-10s\t\n", pc->data[pos].name, pc->data[pos].age, pc->data[pos].sex, pc->data[pos].tele, pc->data[pos].addr); printf("打印成功!\n"); } } //修改某用户信息 void Modifycontact(Contact* pc) { char name[MAX_NAME]; //初始化 printf("请输入修改人的名字:>\n"); scanf("%s", name); Findbyname(pc, name); int pos = Findbyname(pc, name); if (pos == -1) { printf("要修改的人不存在!\n"); return; } //修改用户信息 printf("\n请输入名字:>\n"); scanf("%s", pc->data[pos].name); printf("请输入年龄:>\n"); scanf("%d", &(pc->data[pos].age)); printf("请输入性别:>\n"); scanf("%s", pc->data[pos].sex); printf("请输入电话:>\n"); scanf("%s", pc->data[pos].tele); printf("请输入地址:>\n"); scanf("%s", pc->data[pos].addr); printf("修改成功\n"); } //按姓氏首字母排序 void Sortcontact(Contact* pc) { int index = 0; //选择法排序 for (int i = 1; i < pc->sz; i++) { if (strcmp(pc->data[index].name, pc->data[i].name) > 0) { swap(&(pc->data[index]), &(pc->data[i])); index = i; } } printf("排序完毕!\n"); } //输出所有用户信息 void Printcontact(const Contact* pc) { if (pc->sz == 0) { printf("无用户信息!\n"); return; } //打印标题 printf("%-20s\t%-5s\t%-5s\t%-12s\t%-10s\t\n", "姓名", "年龄", "性别", "电话", "地址"); //打印数据 for (int i = 0; i < pc->sz; i++) { printf("%-20s\t%-5d\t%-5s\t%-12s\t%-10s\t\n", pc->data[i].name, pc->data[i].age, pc->data[i].sex, pc->data[i].tele, pc->data[i].addr); } printf("打印成功!\n"); } //排序算法 void swap(Peoinfo* pc, Peoinfo* ppc) { Peoinfo* ptr = NULL; *ptr = *pc; *pc = *ppc; *ppc = *ptr; } //输出菜单 void menu() { printf("***********************************\n"); printf("*********1.add 2.del***********\n"); printf("******3.search 4.modify***********\n"); printf("*********5.sort 6.print*********\n"); printf("*********** 0.exit **************\n"); printf("***********************************\n"); } //内存增容 void CheckContact(Contact* pc) { //当空间使用完了,即实际用户个数与最大容量想等 if (pc->sz == pc->capacity) { //使用realloc增容 Peoinfo* ptr = (Peoinfo*)realloc(pc->data, (pc->capacity+INC_SZ)*sizeof(Peoinfo)); if (ptr != NULL) { //为了避免大的改动,我们选择仍然用pc->data这个指针,但是pc->data的指向已经改变,需要重新定位 pc->data = ptr; pc->capacity += INC_SZ; printf("\n空间不足,以增容成功\n"); } else { perror("CheckContact"); printf("增加联系人失败!\n"); return; } } } //销毁通讯录 void DestoryContact(Contact* pc) { //释放申请的空间 free(pc->data); //将除用户信息外的所有信息初始化 pc->data = NULL; pc->sz = 0; pc->capacity = 0; } //储存到文件中 void SaveContact(Contact* pc) { //打开文件 FILE* pf = fopen("contact.dat", "w"); //判断 if (pf == NULL) { perror("SaveContact"); return; } //将数据写入文件之中,以二进制方式 for (int i = 0; i < pc->sz; i++) { fwrite(pc->data + i, sizeof(Peoinfo), 1, pf); } //关闭文件 fclose(pf); pf = NULL; } //加载文件内容到通讯录 void LoadContact(Contact* pc) { FILE* pf = fopen("Contact.dat", "r"); if (pf == NULL) { perror("LoadContact"); return; } Peoinfo tmp = { 0 }; while (fread(&tmp, sizeof(Peoinfo), 1, pf)) { //考虑是否需要增容 CheckContact(pc); pc->data[pc->sz] = tmp; pc->sz++; } //关闭文件 fclose(pf); pf = NULL; }
三,test.c文件的实现
#define _CRT_SECURE_NO_WARNINGS 1 //静态通讯录 //按姓名查询某人的信息 // 修改信息 // 按姓氏首字母排序 // 输出通讯录里面的所有信息 // 增加用户信息 //删除用户信息 // 一共存储多少人的信息 //。。。。。 //动态通讯录 //空间消耗过大,且浪费了较多的空间,栈区的空间是很宝贵的,故,我们想到利用动态内存分配来做这一题 // 既然是利用动态分配来做,就必须要考虑一些事情 // 比如,刚开始开始多少空间,考虑合理利用空间,那么就要考虑增容 // 一些部分的写法与其他部分也会有不同 // #include"contact.h" //定义一个枚举类型,用来增加程序可读性 enum options { EXIT, ADD, DEL, SEARCH, MODIFY, SORT, PRINT }; int main() { int option; //创建通讯录 Contact con; //初始化用户信息 InitContact(&con); //用while的话会使程序更复杂,在实现这种多选择的每种情况都会有相应操作,会根据输入的某个值而做出相应的操作 do { //输出菜单 menu(); //初始化数据 printf("请输入选择:>"); scanf("%d", &option); //根据不同的情况做出不同的操作 switch (option) { case ADD: Addcontact(&con);//增加 break; case DEL: Delcontact(&con);//删除 break; case SEARCH: Searchcontact(&con);//查找并输出 break; case MODIFY: Modifycontact(&con);//修改 break; case SORT: Sortcontact(&con);//排序 break; case PRINT: Printcontact(&con);//输出所有 break; case EXIT: //保存信息到文件 SaveContact(&con); //销毁通讯录 DestoryContact(&con); printf("程序结束!\n");//如果遇到0,程序结束 break; default: printf("选择错误,请重新选择!\n");//若输入的是其他的数据,则重新选择 break; } } while (option); return 0; } /*.c文件和.h文件是一个大的模块,这个通讯录中,有两个.c文件,二者之间的 功能不同,contact.c文件中存放着关于整个项目要用到的函数的定义。而test.c 文件存放着主体函数的执行,用于测试程序的可行性。两个.c文件都是需要引 contact.h的头文件的。*/
这就是通讯录的写法。如果有不懂的地方,可以去看看鹏哥的视频,鹏哥是一个很好的老师,东西也都讲的很详细。
C语言从入门到精通(鹏哥带你C语言从入门到精通,谭浩强C语言教程C语言程序设计C语言修仙C语言考研计算机二级专升本C语言期末突软考二级C语言考研C语言C语言)
https://www.bilibili.com/video/BV1U44y1y7xN?p=149
好了,希望大家能真的看懂这些东西,成长的路上总是孤单的,诸君当共勉之。