




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第7章 关于函数的高级专题合理使用函数能将程序模块化,大大降低了问题的规模,提高了编码效率,方便以后复用代码。函数的参数传递有传值,传指针和传引用3种方式,从类型的角度上看,参数不仅仅可以是系统内建的数据类型,还可以是数组、结构以及后面要介绍的类对象等等,同时,内存使用注意事项、函数与指针的关系也是学习C+必须迈过的槛。7.1 内存使用错误剖析编写代码时,少不了和内存打交道,很多程序员对此提心吊胆,称内存为“雷区”或“bug集中营”似乎并不过份。即使是久经沙场的老手,有时也难免落入内存错误的陷阱。本节帮助读者了解这些常见的错误,在编程时加以注意,把出错的概率降到最低。7.1.1 内存泄露使用n
2、ew或malloc()动态申请的内存,如果不再使用,应该把它释放掉,为程序节省内存空间,方便后面的使用。在C/C+中,内存管理器不会自动回收不再使用的内存。如果忘记释放不再使用的内存,在程序的运行过程中,这些内存就不能再被使用用,就造成了所谓的“内存泄露”。内存泄露是最为常见的错误,现在的计算机配置比较高,内存容量很大,一两处内存泄露通常不至于让程序崩溃,也不会出现逻辑上的错误,进程退出时,系统会自动释放该进程所有相关的内存,所以内存泄露的后果相对来说并不是灾害性的。但这并不意味着完全没有危险,如果程序规模较大、长时间运行,或者是内存资源相对紧张的场合,内存泄露过多会导致内存耗尽,系统没有后继
3、内存可以使用,程序可能会崩溃。第6章讲过,代码块中声明的局部变量在代码块执行完毕后会自动消亡,那如果是在代码块中申请的动态内存,系统会自动回收么,答案是否定的,如:if ( condition )int* p = new int8;7.1.2 野指针前面提到“指针消亡,并不意味着其指向的内存会被自动释放”,同样“释放动态内存,并不意味着指针会消亡,也不意味着指针的值会改变”,如:int* p=new int8;delete p;指向完delete p后,指针p是不是就自动消亡了呢,错,不论是使用delete/delete还是使用free(),指针p非但不会消亡,其值也保持不变,并不会变为nul
4、l。这时,使用“if (p!=null)”进行处理也无法起到防错作用。为此,指针被free或delete/delete后,一定要置为null,没有置为null的指针常称为“野指针”,释放掉的堆内存会被内存管理器重新分配,野指针指向的内存已经被赋予新的意义。如果使用野指针释放或再次访问这块内存,会给程序带来灾难性的后果。7.1.3 试图修改常量程序中出现的字符串常量和其他全局常量(如全局const常量),是存放在.rodata里面的,.rodata内存页面是不能修改的,试图对常量修改,会引发内存错误,如代码72 。7.1.4 用错sizeof运算符sizeof()可以计算数组的大小(字节数),但
5、对指针来说,sizeof仅仅得到指针变量的字节数。但当数组作为函数的参数进行传递时,数组退化为同类型的指针,用sizeof是无法取得数组的大小的。代码73演示了sizeof的用法:7.1.5 内存越界访问使用指针和数组访问某块内存区域时,编译器并不会对数组下标是否越界、指针是否有效进行检查,如果不注意,很容易造成内存越界访问的错误,内存越界访问有两种:一种是读越界,一种是写越界,常称作缓冲区溢出。读越界,即读了不属于自己的数据,如果所读的内存地址是无效的,程序会立刻崩溃,但如果所读内存地址是有效的,在读的时候不会出问题,但读到的数据是随机的,会产生不可预料的后果。写越界,即往不该写的内存地址空
6、间中写了东西,这往往会给程序带来很多匪夷所思的错误和BUG,有些症状是随机的,时有时无,给问题分析带来很多的困难。一些辅助工具可以帮忙检查内存越界,但从根本上说,在编程时应十分小心,特别是对于外部传入的参数要仔细检查,另外,要做好程序的防错处理。7.1.6 变量的初始化不论是指针变量,还是普通变量,一定要时刻牢记“初始化”,虽然编译器会对有的变量自动初始化为0,但在声明变量时就对它进行初始化,是一个编程的好习惯,还要重视编译器的警告信息,发现有引用未初始化的变量,立即修改过来。7.2 重申:函数参数传递和返回机制前面已经提及,函数的参数传递有值传递、指针传递和引用传递,函数返回也可是返回值、返
7、回指针或返回引用。抛开引用传递,对值和指针,不论是参数传递还是函数返回,理解“副本”的概念十分重要。7.2.1 参数传递时的“副本”先来看一下函数调用的过程,不论是值传递还是指针传递,编译器都要为每个函数制作临时副本,函数体中对参数的修改都是对副本的修改,对传值调用来说,对副本的任何操作不会对传入的参数对象有任何的影响,这很好理解,但对传指针调用来说,情况稍显复杂,很多人对此存在误解。指针参数传递的示例代码见代码74。7.2.2 函数返回时的“副本”函数执行完毕后,函数内部声明的局部变量会自动消亡,对应的内存被释放,由内存管理器收回,但返回值会被放置(复制)到指定位置(可能是CPU寄存器,也可
8、能是某个内存单元),然后上级函从这个位置取得返回值。这个位置,可以看成是函数返回值的“副本”,这解释了为什么可以用“return 局部变量;”来返回一个值,示例见代码76。7.3 函数与指针在前面的章节中已经介绍了函数的传指针参数调用,返回指针,根据数组名和指针的等价性,函数的参数也可以是数组,在这些基本概念的基础上,本节讨论指向函数的指针以及带参主函数的相关内容。7.3.1 指向函数的指针函数是一组代码的封装体,这组代码在内存中占有一片存储空间,该空间的起始地址存放在以函数名为名的单元的,换言之,函数名就是指向函数的常指针,这有点类似于数组名是指向数组内存空间的常指针。(1)函数指针的声明和
9、初始化(2)函数指针使用举例7.3.2 typedef为了避免读者对typedef的用法产生误解,前面一直没有介绍typedef的相关内容,介绍完函数指针,来看看typedef的相关内容。typedef用来给一个已经存在的类型声明一个别名,举一个最简单的例子:typedef int* int_p;/不要忘记分号typedef为int* 引入了一个新的助记符int_p,可以在程序中使用int_p声明指向int型变量的指针,如:int_p pA,pB;上述代码声明了两个int型指针变量pA和pB,应当注意:typedef不同于编译预处理命令define,这种不同主要体现在两个方面:(1)#defi
10、ne是预处理指令,在编译预处理时进行简单的替换,不做正确性检查,不管含义,只是简单的替换,如:#define PI 3.14(2)define结构可以抽象为:define A B7.3.3 通过函数指针将函数作为另一个函数的参数函数指针的一个功用是把函数地址作为参数传送,以提高函数的通用性和灵活性,如代码710 :7.3.4 函数指针数组指向函数的指针还可以组成指针数组,称为函数指针数组。函数指针数组的使用范例见代码:(详细内容请参照本书)7.3.5 返回函数指针的函数和普通指针一样,函数指针也可以作为另一个函数的返回值,如代码712:7.4 函数与数组前面已经讨论过数组和指针的关系,知道数组
11、名实际上是指向数组所占内存单元的常指针,由此可知,和指针一样,数组既可以作函数的参数,也可以作函数的返回值。7.4.1 数组名作函数参数数组名用作函数参数时,用以向程序传递数组所占内存单元的首地址,数组名参数与指针参数的用法几乎完全一致。有数组参数的函数原型的一般形式为:返回类型 函数名(数组名 ,其他参数)习惯上在参数列表中要指明数组元素的个数,在本章前面已经说明,在函数内部使用sizeof(数组名)返回的只是指针大小,而不是数组大小,因此,在参数列表中显式注明数组元素的个数是个好的编程习惯,当然,如果能保证不会出现越界访问的情况,这个参数可以省略。理论上,“数组名”中是空的,没有数字,如果
12、在其中写明元素大小,编译器也不予理会,不会做错误检验。7.4.2 通过指针得到多于1个的回传值理论上说,C+函数最多只能有1个返回值(return返回值),因此,由函数返回数组似乎是不可能的,实际上,使用指针可以使函数得到多于1个回传值。(1)返回指针变量返回指针变量,便可以对指针指向的一片内存区域进行读写,需要注意的是:不可返回指向栈内存的指针,否则会出现“野指针”内存错误。(2)通过指针参数修改多个变量的值。7.5 函数与结构体、共用体及类对象前面已经讲过结构体和共用体的概念,结构体和共用体将数据整合为一个单独的实体。结构体变量、共用体变量以及后面要介绍到的类对象,用法都接近于普通的变量,
13、对结构体变量、共用体变量和类对象来说,函数支持其传值、传指针和传引用调用,同时,函数可返回结构体变量、共用体变量和类对象,也可返回指向这些变量的指针和引用。下面以结构体来讨论使用方式,共用体和类对象与结构体在函数调用和返回的机制上是一致的。7.5.1 3种参数调用同其他变量一样,结构变量也可以作为函数的参数,请看代码。(详细内容请参照本书)7.5.2 3种返回机制函数的返回机制同样有返回结构变量,返回指向结构变量的指针和返回引用3种方式,和参数传递一样,返回结构变量需要复制“结果”,浪费时间和空间,传递指针和引用能有效提高效率,详细的用法与第6章中讲述的普通变量的3种返回机制完全一致。7.6
14、函数编写的建议本节简单讨论下如何写出高效,不易出错的代码,当然,这些建议只是最基本的几条,在网络上或者专门讨论C+编程技巧的教材中,讨论函数编写原则和建议的篇幅是这里的几十倍,这里权当是抛砖引玉,有效与否还靠读者的理解和检验。7.6.1 合理使用const在指针传递或引用传递时,如果参数仅仅是输入用,则应在类型前加const,以防止指针在函数体内被意外修改,若输入参数采用“值传递”方式,函数将自动产生临时变量用于复制该参数,该参数本就不需要保护,不用const修饰。 对于非内部数据类型的输入参数,尤其是占内存字节较多的参数,应该将“值传递”改为“const引用传递”,以提高效率。但对内部数据类
15、型的输入参数而言,不要将“值传递”的方式改为“const引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。C+中,返回值也可用const修饰,这样,在返回引用或指针时,不允许使用如下述代码的形式对返回值改写:函数名(参数表)表达式;7.6.2 检查输入参数的有效性很多函数代码本身并没有太大问题,常常是输入参数出错或出现了没有考虑到的情况,推荐的检查方式是采用assert宏,关于assert宏的详细介绍请参考第20章。此外,还要检查一些全局变量、指针等是否有效。7.6.3 函数返回类型的判断返回值是传值、传指针还是传引用,在函数设计和编写过程中要规划好。这不仅牵扯到函数返回的效率,而且还要保证返回的指针和引用指向的不是栈内存,否则,极容易形成“野指针”。7.7 小结本章讨论了一些和函数相关的相对高阶的内容,以前面几章的内容为基础,首先介绍了C+程序中经常出现的内存错误,这往往是很多初学者忽略的东西,结果让程序到处是漏洞,无从运行。对函数参数传递和返回机制进行了“重申”,理解“副本”的概念,不论是传值还是传指针调用,都存在“复制品”,不同的是传指针仅仅复制指针变量占据的4个字节(某些系统是2个字节),效率相比传值调用要高,尤
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 行政组织理论的数字化转型进程试题及答案
- 数据流量异常监测技术试题及答案
- 畜牧养殖废弃物资源化利用模式创新与推广机制实施效果评估考核试卷
- 三级计算机嵌入式备考注意事项试题及答案
- 行政组织理论中的利益平衡试题及答案
- 数据库查询方式的选择试题及答案
- 网络技术测试与验证的方法论试题及答案
- 计算机三级嵌入式系统实践能力考核试题及答案
- 小区环卫工人管理制度
- 公司对外付款管理制度
- 南宁骏业计算机审计实验正确答案
- 案场考试题库
- 气瓶安全管理小常识
- 京津冀地区耕地和基本农田分析
- 如何构建印刷企业的安全文化
- 肺痿肺痈咳嗽上气病脉证治第七
- 细胞培养实验指导4
- EN779-2012一般通风过滤器——过滤性能测定(中文版)
- 双横臂独立悬架设计
- 华为流程审计方法论共83页文档课件
- 单元式多层住宅设计图
评论
0/150
提交评论