更深入的理解——函数进阶

上传人:豆浆 文档编号:50727062 上传时间:2018-08-10 格式:PPT 页数:25 大小:331.50KB
返回 下载 相关 举报
更深入的理解——函数进阶_第1页
第1页 / 共25页
更深入的理解——函数进阶_第2页
第2页 / 共25页
更深入的理解——函数进阶_第3页
第3页 / 共25页
更深入的理解——函数进阶_第4页
第4页 / 共25页
更深入的理解——函数进阶_第5页
第5页 / 共25页
点击查看更多>>
资源描述

《更深入的理解——函数进阶》由会员分享,可在线阅读,更多相关《更深入的理解——函数进阶(25页珍藏版)》请在金锄头文库上搜索。

1、第18章 更深入的理解函数进阶 第10章中已经讨论了函数的基本知识,让读者对 函数有了基本的认识,本章从更深层次帮助读者 理解函数。从和函数关系最密切的调用和返回入 手,函数的参数传递有传值,传指针2种方式,从 类型的角度上看,参数不仅仅可以是系统内建的 数据类型,还可以是数组、结构等。此外,递归 编程机制,函数的作用域和可见域,变量的生存 期、作用域和可见域等都是本章讨论的重点。18.1 参数传递的副本机制 如果将函数比作剧本,那形参和实参的关系相当 于角色和演员的关系,函数的参数传递有传值和 传地址两种方式。传值调用时,在函数内对形参 的改变都不会影响实参,要想在函数内对实参进 行操作,必

2、须采用传地址调用的方式。这是形象 化的理解,从本质上说,这是由参数传递的副本 机制决定的。 所谓副本机制,是指copy(拷贝)的思想,不论 是传值调用还是传址调用,编译器都要为每个参 数制作临时副本,或称拷贝,函数体中对参数的 修改都是对副本的修改,下面具体分析之。18.1.1 传值调用的副本传值调用的情况相对简单,不论传递的参数如何,编译器都为这 些参数制作临时副本,函数体中对参数的修改都是针对副本进行 的,丝毫不会影响传来的参数,试通过下述一段示例,体会传值 调用的副本机制。18.1.2 传址调用的副本机制 相比传值调用,传址调用似乎要复杂一点,但只 要知道,传址调用也是通过副本机制,便能

3、很好 地理解传址调用的机理,同样从一个形象的例子 入手。18.2 函数返回值的副本机制如果要细分,函数返回也可以认为存在传值和传址两种方 式。函数返回同样也是根据副本机制来处理的,首先来回 顾下函数返回的流程: 当执行到return语句时,return的值被复制到某个内存单 元或寄存器中,其地址是由编译器来维护的,程序员无法 直接访问该地址,也就是说,在函数执行完毕,相关现场 被撤销前,返回的值被复制保存到了某个地方,编译器访 问该位置即可知道函数的返回值。该位置即可看成是函数 中返回值的副本。 对函数返回取地址是不合法的,即假设存在如下函数: int A(int b,int c); 不允许使

4、用如下形式的语句: 18.2.1 return 局部变量为什么合法函数返回的副本机制很好地解释了为什么return一个局部变量是 合法的,来看一段简单的求和函数代码: int sum(int a,int b) /*函数定义*/ int c=a+b;/*局部变量c*/ return c;/*返回*/ int d=sum(1,2);/*函数调用*/ 来看语句“int d=sum(1,2);”,该语句先执行函数sum,sum函数 执行完毕后将结果赋值给int型变量d,如果从字面上理解,是将c 赋值给d,但实际上,在执行赋值操作时,由于函数sum已经执行 完毕返回,函数中的局部变量c已被撤销,不存在了

5、。实际上,在 c被撤销前,函数已经为返回值c创建了副本,保存在特定的位置 上,赋值操作是由该位置处的副本完成的,形象的示意如所示。18.2.2 返回指针申请动态内存下面来看一下如何通过返回指针在函数中动态申请内存,试比较下述与的 异同: 代码 通过返回指针传递动态内存GetMemorySunccess218.2.3 不要返回指向栈内存的指针动态申请内存是在堆中完成的,而函数返回不会释放堆内存,但 不要忘记,函数返回时,栈内存中的内容会被自动清除,因此, 不要返回指向栈内存的指针。 请读者试着分析下述的问题所在:18.2.4 返回指向只读存储区的指针如果将中的GetMemory函数修改如下,会怎

6、样? char* GetMemory(void) /*定义函数GetMemory*/ char* p=“Hello,C“; /*栈内存中开辟字符串*/ return p; /*返回局部指针*/ “Hello,C“作为常量字符串,位于程序的只读存储区( .rodata),此时,返回指向只读存储区的指针p并没有问 题,但该指针只能用于输出,而不能用于输入改写。18.3 函数与结构体 结构体可以看成一种数据组织方式,将很多不同 类型的相关数据打包,构成一种新的类型,从这 种意义上说,结构体变量完全可以当成是一种普 通类型的变量来使用。结构体变量作函数参数时 ,也有传值和传址两种方式,函数返回亦是如此

7、 ,既可以返回结构体变量,也可以返回指向非局 部结构体变量的指针。18.3.1 结构体变量的传值和传址调用 采用值传递时,在函数内将生成实参的“复制品 ”,如果参数多是像int,char之类的简单变量, 这些变量占用的内存并不多,复制也快。但结构 或共用体变量往往由多个成员变量组成,占用内 存大,如果复制一份,会造成时间和空间双重浪 费。采用值传递不会造成时空浪费,因为不管是 多么复杂的结构类型,指针参数只占4个内存字节 。18.3.2 结构体变量的成员作为函数参数 结构体变量的数据成员作函数实参时,结构体变 量的数据成员可以当成是普通变量来使用,同样 存在传值和传址两种函数调用方式,改写如下

8、:18.3.3 返回结构体的函数 如果函数的返回值是某个结构体变量,常称该函 数为结构体型函数,结构体型函数定义的基本格 式为: struct 结构体名 函数名(形参列表) 函数体; 声明一个结构体型函数时也不要遗漏struct,如 下: struct 结构体名 函数名(形参列表);18.3.4 返回结构体指针的函数 结构体指针同样可以作为函数的返回类型,前提 是该指针不是指向栈内存的,换句话说,该指针 不是指向局部结构体变量的。与返回结构体变量 相比,返回结构体指针大大节省了函数返回时创 建副本的时空开销,有较高的效率。18.4 函数与数组 数组是种使用广泛的数据结构,数组名和数组元 素都可

9、以作为函数的参数,实现函数间的数据传 递和共享。此外,由于数组名和指针的对应关系 ,在一些需要指针型参数的场合,可以用数组名 (即常指针)作函数参数。18.4.1 数组元素作为函数参数 这和上节讨论的“结构体变量的成员作函数参数 ”类似,数组元素可以当成是普通的变量(内置 类型的变量或自定义的结构变量)来看,也就是 说,一个数组元素实质上就是一个同类型的普通 变量,只要是可以使用该类型变量的场合,都可 以使用数组元素。18.4.2 数组名作函数参数数组名既可以作为函数的形参,也可以作为函数的实参,数组名实际上是 数组元素的首地址,所以,数组名作函数参数属于传址调用,先来看一段 示例:18.4.

10、3 多维数组名作函数参数如果数组是二维甚至更高维的,在函数参数列表中的形参数组, 除第一维外,其余各维的长度说明必须给出,因为多维数组元素 的存放是按连续地址进行的。同时,编译器自动忽略掉第一维方 括号中的内容,来看下述示例:18.4.4 数组名作函数参数时的退化数组名作函数参数时,仅仅相当于一个指针,函数无法得 到数组的大小,如果在函数中需要用到数组的大小,必须 显式以参数传递的形式来完成,下面的代码验证了这一说 法,请读者先来判断下会输出什么样的结果。18.5 递归 递归是程序设计中的一种算法,或者说是一种编 程机制。一个函数直接调用自己本身或者通过调 用其他函数来间接地调用自己,称为递归

11、函数。18.5.1 递归流程 简单地说,递归就是编写这样一个特殊的函数, 该函数体中有一个语句用于调用函数本身,称为 递归调用。递归函数实现了自我的嵌套执行,先 来看一个递归应用最广泛的例子,计算N的阶乘N !,由数学知识,我们知道:18.5.2 递归两要素 递归要成功,离不开两点,一是递归递进机制, 二是递归终止条件,递进机制应保证每次调用都 向调用终止靠近一步,中,f(5)调用f(4) ,f(4) 调用f(3),一步步靠近f(1)这一终止条件 。这保证了递归调用正常结束,不至于出现无限 循环,导致系统内存耗尽而崩溃,通常用参数对 调用过程进行判断,以便在合适的时候切断调用 链,如中的“if

12、(n=1)”结构。18.5.3 效率VS可读性 递归调用中,每次函数调用都有一定的堆栈操作 ,相比循环方式,时空开销交到,因此,递归函 数的效率总比功能相同的循环结构略低,任何用 递归编写的函数都可用循环代替,以提高效率。 但是,递归带来的好处也是显而易见的:一是程 序的可读性比较好,易于修改和维护,二是在不 断的函数调用中,函数的规模在减小,在求解阶 乘例子中,不断用复杂度为n-1的问题来描述复杂 度为n的问题,直到问题可以直接求解为止(参数 为1)。 如果用循环语句实现的算法比使用递归复杂得多 ,有必要考虑复杂度和效率的折衷,此时建议优 先采用递归方式来解决问题。18.6 带参主函数前面介

13、绍过的main函数都是不带参数的,实际上,main函数也可以带参数。main函数 的参数是由谁传来的呢?答案是操作系统,C+规定main函数的参数只能有两个: argc和argv,带参main函数的形式为: int main(int argc, char* argv ) 第一个参数argc必须是整型变量,称作参数计数器,其值是包括命令名在内的参数个 数;第二个参数argv必须是指向字符指针数组,存放命令名和参数字符串的地址。 要调用带参主函数必须在操作系统环境下进行,来看一段示例代码:18.7 小结本章讨论了有关函数的深层次的内容,先从函数的参数传 递和返回值角度,用大量实例帮助读者理解“副本

14、机制” ,C语言的函数参数传递有传值和传址两种方式,不论采用 哪种方式,函数都会为实参在堆栈中开辟副本,所有的操 作都是针对副本进行的,因此,传值调用不会修改实参, 但传址调用时,因为指针间接操作的关系,会影响实参所 在的内存区域。函数返回同样有值和地址之分,但应注意 ,不要返回指向局部变量,即栈内存的指针。函数是C语言 的核心所在,只有全面掌握了函数的机制,才能读懂高质 量代码,写出自己的高质量代码。 作为两种有效组织数据的手段,结构和数组在函数中有着 广泛的应用,结构体变量本质上可以当成是普通变量来使 用,而用数组名作参数传递给函数时,其退化成一个指针 ,数组的大小等需要另外显式传递给函数。 最后说明了一种常用的编程机制递归,使用递归应注意 终止条件的书写,防止出现无限调用循环,造成程序崩溃 。

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 行业资料 > 其它行业文档

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号