| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> C语言:动态内存分配,来一个动态的通讯录 -> 正文阅读 |
|
[C++知识库]C语言:动态内存分配,来一个动态的通讯录 |
前言:本人为C语言初学者,学识尚浅,研究程度存在很大的局限性,眼界很窄。以下所有观点仅代表个人见解和思路,各位游刃有余的前辈可以给予批评和指正!各位与鄙人同路的学子可相互探讨、发表看法,交换观点! 需要掌握的知识:指针、结构体、动态内存管理 如果你接触过一些简单的项目,比如之前我已经提及的 “三子棋”? “扫雷” 。或者你做过类似的项目,你会发现对于一个项目的实现,主要是功能的实现,也就是所谓的接口函数。而对于之前一开头就写菜单的这种方法,我个人觉得不是特别妥当,其重心还是应该放在函数的实现上,最后才考虑菜单的实现,因为菜单只是把这些函数有逻辑的调用罢了,这一点我还是比较希望能和你产生共鸣。 目录 老样子,我们还是需要将整个程序的实现分为三个大的部分: 1.main.c:负责函数的调用以及和用户的交互 2.contact.c:负责函数的定义 3.contact.h:负责函数的声明和头文件的引用以及结构体的定义 一个通讯录里,首先我们要存放的肯定是人的信息,所以我们要想想对于一个人来讲,有哪些信息?姓名,性别,年龄,电话......数不胜数,那我们就先拿这四个举例。那么就这些而论:姓名、性别、电话,都应该是字符串类型。对于年龄可以采用整型。这样我们就要定义一个结构体把它们给 “包” 起来,方便我们管理。 CONTACT.H对于所声明的结构体,在 main.c 以及 contact.c 里面都要使用,无疑我们肯定要定义到 contact.h? 中。如下所示:
当然,为了命名使用的方便,我们可以 typedef:
就全局来看,头文件起码要有一个 stdio.h stdlib.h,先引用他们:
想想,如果我们要定义的是一个静态的通讯录,那我们需要定一个结构体数组,然后设定大小。如果我们又涉及到对其增删查改,那肯定要有一个能记录此时位置的地方,所以我们还是要整合一个结构体来包含它们。故如果是静态的,起码应该这样定义:
但是毕竟我们需要开辟的是动态空间,把这么大的空间放在栈区实在是有点zz,所以若是我们要把数据放在堆里,那肯定要用到动态内存分配,那上面的 data 我们肯定要改成指针用来接收 malloc 开辟空间的首地址, count 还是不变,想想,还缺什么?我怎么知道我开辟的空间什么时候用完呀?所以每当我们开辟一个空间,必须要有一个变量来记录此刻变量大小,所以最后的定义变成了这样:
?当然,typedef 是必须的(我个人喜欢单独拿出来typedef,当然你也可以直接在结构体上处理)
这些解决了,是时候开始实现功能了。 最基本的:增、删、查、改。 其次还有:初始化、退出、打印等等。 当然如果你感兴趣,可以加上个排序。 所以我们对于增删查改的声明可以先放出来:
可能你在疑惑我为什么只传了 Con*,比如删除哪个?查找哪个?这些我们可以放到函数里面 scanf 接收数据,也可以在外面接收数据传进去,那何必多用一个栈空间呢,不如就在函数里实现,还可以让代码看起来更规范化。 这样来看,contact.h 的内容差不多已经结束了,让我们进入下一板块吧。 CONTACT.C1.InitContact初始化,指针给NULL,其余给0即可,不过多赘述。 当然,记得把 #include "contact.h" 挂在前面
2.AddContact这个得称得上是重头戏,毕竟增加数据我们需要判断是否增容,而动态内存开辟,正是我们动态通讯录的关键。而开辟的前提是什么?是读取到上限的时候。什么时候是读取上限?capacity 记录了最大容量,count 记录了当前位置,如果 count 等于了 capacity 那不就是满了嘛!这在两者同时为零时仍然适用。
大家应该还记得我之前把c->data初始化为 NULL,这时 realloc 的魅力就随之体现,当 realloc 的第一个参数为 NULL 时其功能相当于 malloc,这样我们就用 realloc 一次解决了初次开辟和再次开辟的问题。且我用 temp 指针先接收了 realloc 返回的指针,想想是为什么? 对的,如果开辟失败,那么 realloc 就会返回 NULL,这个时候我们要对 temp 加以判断再赋值给 c->data,这里我们作为一个严厉的父亲,直接用 assert 断言,记得加头文件哦!
当然,这里增容成功的提示在使用中是没必要的,但是调试阶段还是很重要的。 以上步骤都执行完毕,才到了添加的时候,但是说白了,不就是 4 个 printf 提示用户输入然后用四个 scanf 接收嘛,这里实在是没有什么好讲的,具体如下:
? 当然,这里拿姓名举例:c->data[c->count].name 不要绕晕了。我们这里 c 是接收的地址,是 Con* 类型的,Con* 里面有三个东西:data,count,capacity。数据存放在 data 中,所以我们指向的是 data,data 中又有 4 个数据:name,sex,age,tel。因为这里我们是以数组的方式读取,所以后面直接用点操作符了,因为数组方式读取到的是值不是地址。 还有就是唯一取地址的年龄,因为其它三个都是数组,数组名就是首元素地址,而年龄是整型,这里不要忘了取地址!添加一个联系人后 count 也要加上 1 哦! 3.DelContact我们应该如何删除一个联系人?需要把里面的数据清零吗?删除一个联系人,我们不确定用户要删除的是哪一个,如果是最后一个还好说,但是如果是第一个呢,中间的呢?为了保证空间的连续,我们往往需要 ”牵一发而动全身“。例如我删除第一个联系人,那就需要再把数组整个向前移动 1,所以我们发现对于第一个联系人的信息没有删除的必要,因为它会被覆盖。 这里我们要求用户输入需要删除的联系人标号(也就是第几个):
当然,既然是用户输入,我们肯定要判断 flag 的合法性:
这里再次注意,用户输入的是标号,是第几个,而不是下标!下标应该是:flag - 1 故这里我们需要让整个数组从 flag 到 c->count 的位置全部向前移动 1,这时候又有一个问题出现:从前往后移,还是从后往前移?我这里举一个小小的例子,看完你就明白了: 假设这里要删除的是 3 这个元素,我要把 4 和 5 整体前移 1? ?这时候 c->count--; 其最后一个 5 也就相当于失效了,这里我们删除 3 的目的已经达到,从前往后可知是完全可行的,那么从后往前呢? 全变成了 5 ?由此不难得出,从后往前是行不通的。所以我们开始遍历从前往后覆盖: 首先我想给大家先看看循环:
?我们移动就需要知道移动几次,就如上面的例子,我们删除 3,总个数是 5,我们移动了 2 次,不难推到其次数就是:c->count - flag; 但是移动的时候是我们需要的是下标,所以移动操作我们是这样实现的:
这里我们直接对c->data进行覆盖,切忌不要写成了:
这样写多少是带点bing了~ 这里还需要强调非常重要的一点,写好一个接口函数,最好就去测试一下,保证这一步没问题了我们再往下一步走。不然到时候运行出错,不知道是这个的问题还是那个的问题,这一点一定要重视!至于测试过程我就省略了,这需要你们在 main.c 里面进行测试,这也就是不写菜单的好处,测试省略了很多不必要的麻烦。 4.PrintContact其实这个函数按道理我们应该最先写出来的,因为打印出通讯录可以帮助我们更直观的看到通讯录的状态,方便我们测试,打印的个数自然是取决于 c->count 了,这里循环即可!
5.SearchContact其实查找函数如果细细的想,应该是需要返回值的,如果查找成功了就返回下标,如果失败了就返回-1。当然,如果你硬是把打印函数也套在查找函数里,当然可行,但是此函数的通用性就差了。比如别人就是要根据这个返回值干啥干啥的,你就直接帮别人打印出来了,别人没有处理空间。所以这里我们还是要在头文件里修改一下:
既然是搜索,那我们肯定要根据一个玩意儿搜索,比如姓名。(你也可以让用户自行决定,这里演示需要我考虑太复杂的情况)
写到这里我突然想问大家,姓名是字符串,我们如何判断字符串相等? 如果你真的这样写了:
呃,大聪明你好。这样比较的是什么,好好想想。难不成还指望它们的地址相等哦?所以这里头文件里又要填上一项:
这里我们要用字符串比较函数来判断字符串是否相等:
当然,如果你要单独打印一个人的信息,可能又要多出一个函数来打印指定人的信息,不过代码已经是大同小异。 6.ChangeContact如何修改一个联系人信息?说白了就是再录入一遍覆盖就好了,这里我们就需要用户输入标号来定位一下具体联系人:
当然,判断合法性也是必要步骤:
接下来就是简单的覆盖了:
这里还需要注意的一点就是 flag 是标号而不是下标,下标还是要用 flag - 1; 7.Exit我并没有单独写成函数,而是直接在 main.c 中实现。既然是动态内存开辟的空间,那么我们就要遵循三原则:开辟,检查,释放。现在空间使用完毕,我们要做如下步骤:
接下来,如果你想写个菜单,当然没问题。这个得交给你自己了。 就本程序而言,接口函数负责实现功能是重中之重,而菜单只是为了和用户交互,只是负责调用函数的逻辑顺序等,过于简单,如有疑问可移步: 三子棋,其有关于菜单的详细讲解 本次代码上传至码云:通讯录 - 博客搭建 如有任何疑问、错误、表达不当等处请及时评论留言,本人发现会及时修改和回答。 END |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 13:15:04- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |