一、首先我们应该要想一下我们的通讯录中所包含的内容和具有什么功能
(一、)假设我此时设计的通讯录想具有以下这些信息
1.名字
2.电话
3.地址
4.性别
5.年龄
6.身份证号
(二、)假设我设计的通讯录中具有这些功能
1.可以存放1000个好友的信息
2.可以增加好友的信息
3.可以删除指定名字的好友信息
4.查找好友信息
5.修改好友信息
6.打印好友信息
7.排序好友信息
所以接下来就让我们看一下我们应该要怎样去实现这些功能呢?
二、如何将信息放进我的通讯录中(并且记录下来)
1.首先在进行实现功能的前提下,我应该要先创建出一个可以存放我的好友信息的结构体类型的数组出来
2.所以我们就要通过前一遍博客,有关结构体基础和进阶的内容,进行这步的结构体的创建,所以我通过创建一个结构体,我就拥有了放1000个好友信息的空间(此时有了这个空间,我就才可以去实现我的通讯录功能,去完成一个属于自己的通讯录的搭建)
3.在进行结构体创建的过程中,我还要注意的是(在我进行通讯录的录入时),我应该要把这些我录入的信息给记录下来,这样我才可以对这些信息进行我要进行的相应的操作处理(比如删除,替换等)
三、我们应该如何对创建的结构体数组进行初始化和一些小细节的改进
一、对数组初始化的一些总结和
1.首先当我拥有了一个存放数据的数组时,此时并不清楚里面是什么,我就应该对其进行初始化(让其内部所有元素都是0),这样就有利于我存放数和记录数据
2.接下来就是要实现一个初始化的代码,此时我应该要把这个函数包装到main函数外部去实现,所以我这边应该进行一个传参和对参数的接收
3.传参时应该用一个通讯录变量去接收,例:struct Contact con;此时我就可以利用con变量进行传参了
4.我把初始化函数写成 Initcontact(&con),用 void Initcontact(struct Contact* ps)进行接收
5.所以此时我就可以得到函数的基本结构(除了函数实现以外的)
6.所以代码如下:
void Initcontact(struct Contact* ps)//这边进行一个初始化,我就可以用到 一个库函数叫(memset),这个库函数就是用来对内存进行改变的一个库函数而已 { memset(ps->data, 0, sizeof(ps->data));//void* memset(void* ptr, int value, size_t num);这个就是memset的用法啦 ps->num = 0;//这个就是为了让我的通讯录中一开始时的元素个数为0,而不是一个未知的个数,这样才不会出问题(所以这边就直接进行一个赋值的操作就行啦) }
(1.)这边也就设计到一个memset函数的使用方法(用来实现初始化的关键)
二、对整个函数进行的一些小细节的改进
1.这些改进虽然是可以被替代的,但是这些细节都是非常重要的知识点的运用(非必要不替换)
2.首先当我们在定义struct Contact data[1000] ,这个结构体中有1000个元素的时候,这个自定义的结构体中的成员变量是数组的时候,此时数组的大小为了可以在今后的使用中便与更改(例:此时我的通讯录中已有1000个元素了,放不下东西了,所以此时进行改的时候就不方便),所以此时我可以定义一个数组元素的最大值,例:struct Contact data[MAX] ;然后在代码的上方定义我的MAX(例:#define MAX 1000),这样写不仅方便我修改数组元素的最大值,也可以增加我数组的可读性;所以在我们定义 char name[20];也可以写成(char name[MAX_NAME],在代码的上面定义一个#define MAX_NAME 20),这样就可以使我的代码非常的规范,易操作
四、我应该如何实现这些功能
对应二标题中的信息和功能来讲
1.首先这边我要创建一个可以存放1000和好友信息的结构体
(1.)所以代码如下
#include<stdio.h> struct Peoinfor { char name[20];//1.姓名 char tele[12];//2.电话 char sex[5]; //3.性别 char add[20]; //4.地址 int age; //5.年龄 char id[20]; //6.身份证 //但这边一定要注意一个点(就是我待会在进行输入这些信息的时候,因为前5个信息都是字符串数组,二我的年龄是整形,然后根据前面只是可知数组在进行传参或者是输入,打印的时候(都是首元素地址的意思),所以待会我是数组才可以直接进行输入,二我的年龄进行输入时一定要记得要有&符号) }; struct Contact { struct Peoinfor data[1000];//这个就是我的1000个好友的信息存放的结构体 int num;//这个就是上述说道的(用来记录我好友信息的一个变量(起了和数组中下标一样的作用)),就是为了可以找到我对应的想要的好友信息 };
进行改进后就是(改进方法和原因见上面细节处理部分)
#define MAX 1000 #define MAX_NAME 20 #define MAX_TELE 12 #define MAX_SEX 5 #define MAX_ADD 30 #define MAX_ID 18 struct Peoinfor { char name[MAX_NAME]; char tele[MAX_TELE]; char sex[MAX_SEX]; char add[MAX_ADD];//这边有一个小知识点,就是此时我创建的name和sex等等都是数组(也就是一个首元素的地址) int age; char id[MAX_ID]; }; struct Contact { struct Peoinfor data[MAX];//这个就是我的1000个好友的信息存放的结构体 int num;//这个就是上述说道的(用来记录我好友信息的一个变量(起了和数组中下标一样的作用)),就是为了可以找到我对应的想要的好友信息 };
此时我就完成了搭建一个通讯录的关键一步(将我的好友信息放进我的通讯录之中),所以接下来我就要使用这个好友信息,进行我的功能实现这一个目标
2.增加好友信息的功能
我要有增加好友信息的功能,所以我应该怎样利用这两个结构体进行,这个功能的实现呢?其实根本不用怕,增加好友信息(不就是往通讯录中输入好友信息的意思吗),所以我只要进行好友信息的输入就可以了
(1.)所以这边我们把这个功能重新分装成一个函数,函数名 Addcontact,参数就是我使用通讯录结构体创建的一个变量,例:struct Contact con;然而为什么是用通讯录结构体变量创建一个变量呢?原因就是因为此时我需要改变的是结构体数组中data中的数据,所以我要对该结构体进行传参的改变,所以函数就写成Addcontact(&con)
(2.)然后把函数包装在外部使用,接收时用一个指针接收或者用一个数组,所以我在接收这个函数的参数时,我应该把代码写成 Addcontact(struct Contact* ps),当然在我这个函数没有返回值的前提下,应该写成void Addcontact(struct Contact* ps),所以接下来这个函数内部的内容就轮到应该如何去实现这个函数了
(3.)所以这个的具体实现内容就如下代码:
void Addcontact(struct Contact* ps) { if (ps->num == MAX)//这步的意思就非常清楚,就是为了防止我的通讯录满了(同时假如没满,也就可以很好的成为我的一个else的进入条件) { printf("通讯录已满,放不下,滚!"); } else { //因为此时我的通讯录中一个元素都没有,所以此时我把我的第一个数据放进去的时候,此时这个数据就是放在我的下标为0的那个位置处,第二个元素的下标就是1 ; //所以此时可以得出结论,我的元素是第几个,那么我的num就是第几个(所以此时我的元素就是放在了num这个记录数据的变量的数字上)例:我要放第一个元素时,此时num为0,我放进去的元素的下边就是0,当我放第二个元素时,此时的num就为1,此时我放进去的数字的下标就是1 //所以这边可以得出一个很重要的结论就是:我放进去的数据的下标与我的num有着很关键的联系(所以此时的num就是我元素的下标,所以可以直接把num拿来当下标使用) printf("请输入名字:"); scanf("%s", ps->data[ps->num].name);//ps->data,这个的意思就是我用我的指针找到了我的数组(就是我存放1000个数据的那个数组),有了这个数组,我就可以找下标(就是num(因为此时的num就是这个data数组的一个下标)) printf("请输入电话:"); scanf("%s", ps->data[ps->num].tele);//ps->data[ps->num],这个代码的意思就是我找到了data数组中的某一个元素了 .name这个的意思就是我把这个东西放在这个数组中元素的(名字)里面,[ps->num]这个的意思就是我此时想找的下标,有了这个下标,我就才能找到那个元素 printf("请输入性别:"); scanf("%s", ps->data[ps->num].sex); printf("请输入地址:"); scanf("%s", ps->data[ps->num].add); printf("请输入年龄:"); scanf("%d", &(ps->data[ps->num].age));//因为这个不是数组,所以要去地址 printf("请输入身份证:"); scanf("%s",ps->data[ps->num].id); //关键一步 ps->num++;//这步的意思就是我录入完一个人的信息了,此时想要录入下一个人的信息,我就要再或得一个下标(也就是num++),这样我才可以录入下一个人的信息(准确的找到data数组中的那个元素(切记这里的元素(1-1000)一直都是在我的data数组中,我只是想要拿来用而已,并且拿来用的关键就是我的下标(num))) printf("添加成功\n"); } }
(4.)这个代码的意思注释是非常清楚的可以看明白的,每个点我都进行了注释,所以自己认真看吧!比较难理解的就是那个输入时的元素位置是比较难找的(它的具体原理就是,找到我要储存这个地址的一个空间 ,例:ps->data[ps->num].name,这个ps->data[ ].name的具体意思就是找到ps指针指向的我的(struct Contact结构体中的struct Peoinfor data[MAX]中的struct Peoinfor结构体中的name这个结构体成员变量),然而我是怎样可以直接从struct Contact这个结构体中直接找到(struct Peoinfor这个结构体中的内容呢?),原理就是我利用了ps->num另一个ps指针(指向struct Contact另一个变量的同一个指针),此时这个指针指向了两个关键的变量(所以我就可以通过ps->num这个表达式),成功找到我struct Contact中的struct Peoinfor中的成员变量了(name),(所以此时我ps->num,ps指针指向的那个num此时就有一点像是我们数组中的下标(起下标的作用,可以通过下标来找到我需要的元素)),所以此时这个num就有一点像是我struct Contact这个(结构体类型数组的下标),所以我们就可以很好的理解应该要如何找到我对应的空间去存储我的对应的信息了
(5.) 但这边还一定要注意一个点(就是我待会在进行输入这些信息的时候,因为前5个信息都是字符串数组,而我的年龄是整形,然后根据前面只是可知数组在进行传参或者是输入,打印的时候(都是首元素地址的意思),所以待会我是数组才可以直接进行输入(首元素地址),而我的年龄进行输入时一定要记得要有&符号)
(6.)以上就是我在进行输入功能实现的一系列要注意的问题
3.此时搞定了初始化和输入功能,接下来我们看一下如何实现(显示功能)
(1.)就是当我添加了一些好友信息在我的通讯录中时,当我想要看一下具体的好友信息时,我就应该要把它显示出来,我才能看,对吧!
(2.)所以同理增加函数的实现,我的显示函数就只是进行内容的改变,函数传参的知识同理可依,所以下来让我们一起看一下代码:
void Showcontact(const struct Contact* ps) { if (ps->num == 0) { printf("通讯录为空,赶紧去交朋友,添加你的通讯录吧!\n"); } else { int i = 0; for (i = 0; i < ps->num; i++)//这步就是为了打印出我的num中的数据,表示我此时通讯录中有几个元素(有几个朋友),所以此时写成这样 i < ps->num;是最好的 { printf("%-20s\t %-12s\t %-5s\t %-5s\t %-4s\n", "名字", "电话", "性别", "地址", "年龄","身份证号");//并且这边这个/t的意思就是一个空格的意思 printf("%-20s\t %-12s\t %-5s\t %-5s\t %-4d\t %-20s\n", ps->data[i].name, ps->data[i].tele, ps->data[i].sex, ps->data[i].add, ps->data[i].age); } } }
(3.)总体的原理都在代码里,这个就是如何实现我的显示通讯录的功能,唯一要注意的就只有两点
(一、)i <ps ->num(此时ps指针指向的num表示的就是我此时通讯录中具有的好友信息的个数,所以我如果想要把通讯录中的所有好友信息打印出来那么我就一定要写这个i< ps->num,就是表示 此时我要循环0-num次,依次把通讯录中的元素打印出来)
(二、)printf(“%-20s\t %-12s\t %-5s\t %-5s\t %-4d\n”)这个的打印(这个的打印就是为了按间隔打印的同时同时可以打印出我的所有好友信息的内容(名字 电话 性别 地址 年龄 身份证))
4.接下来就是第四个功能(删除功能)
(1.)当我不要这个好友信息时,可以进行删除
(2.)具体外包装函数原理一致,关键就是内部的代码实现的区别,所以代码如下:
void Delecontact(struct Contact* ps) { if (ps->num == 0) { printf("无可删除信息\n"); } else if(ps->num>0) { char name[MAX_NAME]; printf("请输入要删除人的名字:"); scanf("%s",&name); int pos = Findbyname(ps, name);//就是为了更好的设置返回值(返回-1,不存在,不然就存在) if (pos == -1) { printf("要删除的人不存在\n"); } else { //删除数据 //根据上面那个for循环可以得知,假如有这个元素的话,此时它的下标就是i,所以我只要把此时那个为i的下标删除就行(通过的是从后向前交换的方法) int j = 0; for (j = pos; j < ps->num - 1; j++)//这个j < ps->num-1就需要一个图例了,不然不好理解 { ps->data[j] = ps->data[j + 1]; } ps->data[j] = ps->data[j + 1];//这个ps->num--好理解(就是因为我删除了一个元素,所以此时要减减一下,不然就会出现两个重复的信息(就是最后那个信息会重复,因为向前交换时,我的最后一个信息会被访问两次,所以一定要减减)) ps->num--; printf("删除成功\n"); } } }
(3.)这个就是删除功能,但我在函数内部又重新定义了一个函数int pos = Findbyname(ps, name);这个函数的作用就是找到和我输入名字相同的在通讯录中的那个数据,这样找到了这个数据我才可以对其进行改变(删除、替换、查找等功能都能实现,所以这边我就把这个函数拿到外部来重新实现,这样待会在写别的与要寻找通讯录中数据有关的函数时(这些函数就都可以直接调用这个函数了,不用再一次重新实现,这样就可以很好的解决我的代码冗余的问题))
(4.)所以Findbyname函数的实现如下:
static int Findbyname(struct Contact* ps, char name[MAX_NAME])//这边可以加上一个static(此时加上了这个static就可以让我的这个查找函数不会与外界有联系,只与我这个文件中的函数有关,只在这个文件中起作用) { int i = 0; for (i = 0; i < ps->num; i++)//假如此时num=5 { if (strcmp(ps->data[i].name, name) == 0)//ps->data[i].nam;这个的意思就是我找到了数组中(也就是通讯录中)下标为i的那个人的名字 //且此时要记住我已经具有了找到data数组中任意一个成员的名字,所以此时我要做的就是(判断我输入的名字和data数组中的名字的比较),但是不能用(==),因为此时是两个字符串进行比较(所以可以用到的是字符串函数(strcmp)) { return i; } } return -1; if (i == ps->num) { printf("要删除的信息不存在\n"); } }
关键就是在于如何找到我通讯录中和我输入的这个名字是否相同,(切记这边不敢用(==)来判断,因为此时的name是一个字符串,所以不能直接进行比较,我应该要对每一个字符进行比较),所以这边就会用到有个库函数(strcpy),这个就是比较两个字符串是否相同的关键,具体用法:(strcmp(ps->data[i].name, name) 此时通讯录中的名字和我输入的名字进行比较,如果相同就会返回0,不相同就返回非0,然后通过这个 if语句if (strcmp(ps->data[i].name, name) == 0),return i;这个操作此时我就可以很顺利的拿到此时这个和我输入名字相同的的那个通讯录中的好友信息的下标(也就是位置),然后此时我就可以利用这个下标找到我需要的那个信息(此时就能对其进行各种操作,无论是删除还是替换还是显示就都可以)
(5.)所以按现在的删除来说,我就要把找到的这个信息删除(就要利用下标进行操作),用的方法就是用后面的数据覆盖前面的数据(最后导致其被覆盖,就是消失)
(6.)for (j = pos; j < ps->num - 1; j++),这个的意思就是进行我上述所说的覆盖操作,因为pos此时是函数Findbyname的返回值(也就是我找到的我需要的的那个信息的下标),所以pos就是代表我的要删除的元素的下标,所以用覆盖的方法就是用后一个信息对其进行覆盖,所以这个for循环的意思就是(拿到我下标到通讯录-1的之间的所有的信息)然后对其进行覆盖ps->data[j] = ps->data[j + 1];并且这个表达式的意思就是对其覆盖的意思;然而ps->num–这个是比较不好理解的,所以这里附上一幅图:
这幅图就可以充分帮助我们了解这几个表达式的意思
5.第五个功能就是查找功能了
这个功能与删除的查找是一样的,都是用到了Findbyname这个函数而已,所以直接看代码:
void Searchcontact(const struct Contact* ps) { char name[MAX_NAME]; printf("请输入要查找的名字:"); scanf("%s\n",&name); //然后因为查找的过程跟刚刚那个 删除信息的那个过程是一样的(都是先输入一个名字,然后判断num中是否有这个名字) //出现代码冗余的问题(不好)(所以此时去除冗余的一个好办法就是(再重新分装一个新的函数,然后剩下需要用到这个函数的东西,都重新去调用这个函数就行)) int pos = Findbyname(ps, name); if (pos == -1) { printf("找不到该信息\n"); } else { printf("%-20s\t %-12s\t %-5s\t %-5s\t %-4s\n", "名字", "电话", "性别", "地址", "年龄"); printf("%-20s\t %-12s\t %-5s\t %-5s\t %-4d\n", ps->data[pos].name, ps->data[pos].tele, ps->data[pos].sex, ps->data[pos].add, ps->data[pos].age); } }
不过就只是多了打印这一步而已,别的都没什么好说的,都是正常的步骤
6.第六个功能是替换功能
这个功能还是一个道理,不过就是找到这个好友信息,然后进行替换而已
所以看代码:
} void Modifycontact(struct Contact* ps) { char name[MAX_NAME]; printf("请输入你要修改的名字\n"); scanf("%s",&name); int pos = Findbyname(ps, name); if (pos == -1) { printf("找不到该名字"); } else//此时这个else的意思表示我已经找到了这个通讯录中的这个人的信息(因为我找到了下标i),所以此时我就找到了这个人的所有元素(所以此时我就可以把这个下标中的所有信息给修改一下),因为我现在是有了这个位置,所以我很牛,所以我想改就可以改 { printf("请输入名字:"); scanf("%s", ps->data[pos].name);//ps->data,这个的意思就是我用我的指针找到了我的数组(就是我存放1000个数据的那个数组),有了这个数组,我就可以找下标(就是num(因为此时的num就是这个data数组的一个下标)) printf("请输入电话:"); scanf("%s", ps->data[pos].tele);//ps->data[ps->num],这个代码的意思就是我找到了data数组中的某一个元素了 .name这个的意思就是我把这个东西放在这个数组中元素的(名字)里面,[ps->num]这个的意思就是我此时想找的下标,有了这个下标,我就才能找到那个元素 printf("请输入性别:"); scanf("%s", ps->data[pos].sex); printf("请输入地址:"); scanf("%s", ps->data[pos].add); printf("请输入年龄:"); scanf("%d", &(ps->data[pos].age)); } }
关键就是这句话:else此时这个else的意思表示我已经找到了这个通讯录中的这个人的信息(因为我找到了下标i),所以此时我就找到了这个人的所有元素(所以此时我就可以把这个下标中的所有信息给修改一下),因为我现在是有了这个位置,所以我很牛,所以我想改就可以改;(所以这就实现了我的替换功能,反正通讯录的设计就是离不开输入和输出)
7.第七个功能就是排序功能
(1.)代码如下:
void Sortcontact(struct Contact* ps) { for (int i = 0; i < ps->num; i++) { for (int j = 0; j < ps->num - i - 1; j++) { if (strcmp(ps->data[j].name, ps->data[j + 1].name) > 0) { struct Peoinfor temp; temp = ps->data[j]; ps->data[j] = ps->data[j + 1]; ps->data[j + 1] = temp; } } } }
8.第八个功能就是清除功能
(1.)代码如下:
void Emptycontact(struct Contact* ps) { ps->num = 0; printf("清空成功!!!\n\n"); }
五、我应该如何选择使用这些功能
1.使用以下功能就是用到了一个分支语句(switch case语句),具体代码如下:
#define _CRT_SECURE_NO_WARNINGS //用于测试 #include"contact.h" void menu() { printf("=====================================================\n"); printf("====== 1.add ========== 2.del ==============\n"); printf("====== 3.search ========== 4.modify ==============\n"); printf("====== 5.show ========== 6.sort ==============\n"); printf("====== 0.exit =============7.empty ==============\n"); printf("=====================================================\n"); } int main() { int input = 0; //因为是通讯录,所以此时我还要有一个变量用来记录一下我的通讯录此时拥有的个人信息(就是有几个信息) /* int num = 0; //并且通讯录里面还要有容量(此时的容量就不需要我自己进行定义,因为此时这个MAX就已经是我的容量了) //创建通讯录(里面含有1000个人的信息): //struct Peoinfor con[1000];//这个位置是可以改变的,所以这个位置我可以用一个(define定义的类型) struct Peoinfor con[MAX]; //在c语言中,建立一个东西,位置时,都是要进行初始化的(所以下面就是一个初始化函数,把通讯录进行初始化) InitContact(con) */ //因为上述原因,我就不使用两个变量的那种类型了,我就直接进行一个结构体的声明,这样我就可以把两个变量直接变成一个变量使用(利用的就是以前学的那个结构体的嵌套使用) //所以我就可以利用刚刚创建的那个结构体直接创建一个新的变量,利用这一个单独的变量,我就可以直接进行对我的通讯录的改变 //所以这边应该写成 struct Contact con;//此时因为我的这个结构体中不仅含有 (1000个元素的数组)struct Peoinfor con[1000];也含有int num = 0;(此时我数组中记录元素个数的变量)(此时我就拿到了我想要的两个关键的变量,有了这两个变量,就可以使我的传参变的更加的方便,就不会像刚刚那样,要进行两个变量的传参,现在我只要进行一个变量的传参就行了) //这边弄了这么多的东西,主要就是为了下面这几个自定义函数的实现变得更加的方便 //所以此时就应该要进行我的第一个自定义函数(初始化函数)的实现了,初始化其实就是把我通讯录中所有的元素都给改成0,就叫初始化 //1. Initcontact(&con); do { menu(); printf("请输入你的选择:"); scanf("%d",&input); switch (input) { case add://这边的case有一个缺点(导致我们代码的可读性不是很好,所以就会用到前面学的那个枚举类型,所以这个就是一个枚举类型的高级应用) Addcontact(&con);//因为此时我是需要对通讯录进行增加,所以必须要对其进行去地址才可以改变,不然改变不了,但是这边写两个传参(比较繁杂,所以 这边我要把它进行重新分装,把con和num重新分装到一个东西的内部才好) break; case del: Delecontact(&con); break; case search://此时根据枚举的作用,我就使我的search具有了所用(代表数字3的作用,很好的增强了我代码的可读性) Searchcontact(&con); break; case modify: Modifycontact(&con); break; //添加了元素,此时我就应该想办法打印出来看一下 case show: Showcontact(&con); break; case sort: Sortcontact(&con) break; case empty: Emptycontact(&con) break; case exit: printf("退出该程序"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }
所以以上就是如何对我的函数的各种功能的使用
1.这边用到了一个枚举
(1.)就是上述为什么case后面跟的不是数字,而是函数的名字呢?这就是因为我使用了枚举这个自定义类型
enum Option { exit,//根据枚举的只是//0 add,//1 del,//2 search,//3 modify,//4 show,//5 sort, //6 这样就很好的起到了我的这些函数的顺序的作用(增强了代码的可读性) empty //7 };
就是如注释那样,用了枚举这个函数名就会成为我的数字(这就是枚举的一个用法),增强了我这个switch语句的可读性