目录
0.前言:
经过一段时间的C语言学习,相信大家与我一样都学会了很多C语言的知识,当然知识是需要检验滴。所以我今天来讲解一个经典的项目——通讯录。我采用的编译器是VS2022环境是x86,64位系统。
PS:这次讲解会细化到步(如果已经掌握了可以根据目录进行跳转噢)会从静态存储到动态存储,再写到文件存储。希望大家可以有耐心看完呀。(这里是静态篇其他的后续再补充哩)
1.静态通讯录需要用到的知识点:
1.1 结构体:
①什么是结构体:
结构体是一些值的集合,这些值称为成员变量。结构体的每个成员可以是不同类型的变量。例如下图Peo就是一个结构体,里面放了三个变量。
②结构体的声明
结构的声明方式如下图所示。
PS:这里的struct也属于函数名
当然其中这个“x”变量可以不在这个地方定义,可以到函数中定义,比如这样:
若像图中这样写,那么它就变成了一个匿名结构体。但是这样写有一定的弊端,就是如果你要使用匿名结构体,那就必须在结构体最后定义变量,不能在别的地方定义。
③结构体重命名:
PS:这里的struct Peo是原函数名,Peo是新的结构名。
如若像图中这样写,那么就既完成了结构体的声明也完成了重命名。这个结构体是可以使用的,不需要写两个。但是重命名与声明有点不同,就是最后的Peo不是变量,不能被当成变量使用。所以定义变量要在别处定义。
那么重命名有什么好处呢?重命名可以让后续的定义变得更加精简。在C语言中这样的定义是不被允许的,前面必须加上struct但是重命名后就可以这样定义,从而使代码更加精简。
④结构体的访问
如图我们先建立一个结构体
这里的Peo a[10]={0};就是在对结构体变量进行初始化,使这个数组中的所有内存都清空,便于后续的操作。
接下来我们进行读入操作,结构体变量读数据与普通变量其实没有多少不同,只需要注意一些语法就行。
输出呢也是一样滴
当然除了这种用法,还有一种应用的更多的用法。
也就是定义结构体指针,通过指针进行访问。后面写通讯录进行结构体传参的时候,会大量用到哈。为什么要传址调用呢?直接调用不行么?原因如下:函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
1.2枚举类型:
①什么是枚举?
②枚举类型的定义
如若像这里这样定义,那么Mon的初始值就是0,后面的项逐个加一,Sun的值就是6。
③枚举类型的优点
1.3qsort的基础使用方法
先解释一下为啥要用qsort哈,就是在C语言阶段,如果要进行很多数据的排序,用传统的冒泡、旋转、插入排序……可能会不够快,所以在这里就练习使用一下qsort进行快排。
①qsort的基本参数
PS:需要用到头文件stdlib.h
稍微解释一下这些参数,首先base就是参加排序的数据首地址,num是参与排序的个数 ,size是每个数据的大小,最后那个函数指针这里就不细讲了,我们这里先暂时称为name_cmp后文再展开。
我们这里先制作一下数据便于后续的讲解。(这个数据是指他们的名字分别是9~0)
②name_cmp的讲解
先对排序数据进行一个解释。
再对应到上面的参数,就可以先把参数都填上去。如图:
那么name_cmp到底是什么呢?其实这个东西就是数据交换的规则。我们先看一下官方对这个参数的解释:
我们这里可以简单的认为,升序升序返回<0,降序>0(当然这个不准确)
直接翻译的话是p1<p2时返回<0;
那么这个函数该怎么写呢?
我们先根据函数原型写个框架。想必到这里大家也会有些疑问,比如为什么这里是const void*呢?首先加const是因为参加排序的数据,不应该被更改,增加了安全性,总不能说1 3去排序给我排出个2 4出来吧! 指针是void*类型是因为,qosrt适用于所有数据类型的排序,如果不是void*那么这个函数就写死了,就只能为一种数据服务,而void*可以接收所有指针。(先拿过来再强制转化嘛)
函数框架讲完了,那么中间部分该咋写呢?首先要先明确我们的需求,我们的需求是根据名字来排序,那也就是字符串咯,所以我们掌声有请strcmp函数。
PS:如果不清楚strcmp函数,欢迎去C语言字符串&&内存函数的介绍_wsyjpgs的博客-CSDN博客
初步学习一下。
我们来看strcmp的返回值
好像完美符合我们的要求,所以就直接大胆怼上去。
我们一步一步来
大功告成,我们来使用看一下!
已经成功排好序了。接下来,就用这些理论开始写静态通讯录8。
2.静态通讯录的实现
2.1文件的创建
为了方便功能的实现以及后续的调试,这里将用3个文件实现。分别是源.cpp(用于调试与运行),function.h(用于头文件的包含,函数、变量的声明等),function.cpp(用于功能的实现)
为什么是cpp呢?因为创建的时候忘记改了,不过代码都是用C写的,也没有用到C++无法使用的函数。所以无伤大雅啦!
2.2菜单的打印,以及主函数框架
既然咱们写的是C语言那,就必须要个主函数,这里在源.cpp中写主函数。
我们想实现通讯录的功能,那么是不是应该打印一个菜单告诉使用者,咱们可以实现什么功能,
所以我们先写一个菜单函数
(为什么不对齐呢?这个是字体原因,打印出来是对齐滴)
这里是不是用到了printf函数,所以我们这里要去文件function.h中包含一下头文件
那么我们是不是也要在源中引用一下这个文件,像这样:
现在咱们调用试运行一下。
成功打印出来哩!
接下来我们用枚举类型定义这些功能。
现在所有功能就定义好了,接下来是不是要接收操作者的输入。
接下来用选择结构进行分流。
我们这里当然不难让操作者输入错误就退出程序,所以这里采用一个do-while结构
到这里基本框架就已经打完了,我们进行测试一下。
没有发现问题,我们继续下一步。
2.3通讯录变量定义和数据存储
打好了框架,现在咱们就需要创建一个合适的变量来存储数据。
现在我们到function.h文件中,定义一个结构体。
我们先不着急定义变量,我们先想想,我们需要放多少数据,我这里定义了1000个。
接下来,就是定义数据的类型,方便后续的存储。不过在定义前我们想想,既然有最大存放量,那么是不是也需要知道,现在已经存放的量,进行判断数据是否还放得下,所以我这里又定义了一个结构体,对这两个变量进行封装。
类型定义好了,我们现在去源文件中创建变量:这里我将变量命名为ps
变量建立好了,紧接着是不是要初始化变量。
所以我们先去function.h中声明初始化函数,然后去function.cpp中实现。
既然要在function.cpp中实现,那么就必须包含一下声明,也就是包含一下文件
实现的函数如下:
然后我们去主函数里调用
到这里,我们的前期准备就全部做完啦,可以开始实现功能了。
2.4添加函数与显示函数
我们现在来写第一个功能:添加
注意 :以后的声明都在function.h文件,函数实现都在function.h文件。后面的函数的根据以下步骤进行实现。这样后面可以写的快些。
第一步:声明
为什么这样声明呢?因为结构体传参采用传址调用是最优的,所以这里声明就用结构体指针。函数名ADD就是将上面的add首字母变成大写,这样写函数可以一目了然功能。
第二步:实现
这里的逻辑是一个一个输入,全部输入完后,当前数据量加一。
但是这样写是不准确的,因为我们没有判断数据是否满了,所以我们要写一个函数进行判断。
检查函数:
声明:
实现:
这里的功能应该一目了然,只要当前数据量小于最大存储量就没有慢
接着我们调用这个函数并且用一个数接收一下返回值进行判断,这样就完成了添加函数的实现。
虽然我们的添加函数写完了,但是我们该怎么测试数据是否成功输入呢?
为了方便调试,我们就把显示函数一起写了。
声明:
实现:
这里的-20是让数据对齐,使得打印出来的数据更加美观。
显示函数写完了,我们来测试运行一下。
我们发现数据显示出来了,说明函数功能实现成功。
2.5清除与退出
这里比较简单。我们清除数据的话,直接重新初始化一下就实现了,退出的话,是输入0,根据上面的do-while结构,程序是直接退出的,所以我们只要打印一句话告诉操作者退出就行了。这里就不多赘述。
2.6删除与修改
声明:
实现:
既然是删除,那么原来就得有数据,所以我们要先检查有没有数据。
稍微解释一下,就是sz记录的数据量,如果数据量是0说明没有数据。
接下来进行删除操作,我这里实现的是根据姓名删除,大家想用别的也行,就是下面的代码改一改就可以了。
需要实现删除,就要先查找到这个数据,所以这里我定义了一个函数进行实现。
声明:左边是存放的数据ps,右边是需要查找的名字的首地址。
实现:我这里采用的是遍历
如果没有找到,就需要重新输入,直接return
找到了就进行删除操作
这里我进行解释一下。
所以整个删除函数是这样的:
接下来是修改函数:
声明:
实现:这里自己看8,修改就是查找到数据进行覆盖,与输入的不同就是sz不加1.
2.7名字升序排序
声明:
实现:
这里就不多讲啦,上面的用法已经讲述过了
3.代码概览
①源:
②function.h
③function.cpp
4.结语
到这里静态实现通讯录就已经完成啦,接下来我会发另一篇文章,对上述代码进行改造,从而实现用动态内存存储,实现用文件存储,感谢大家的浏览啦!