更深入的理解——函数进阶.ppt_第1页
更深入的理解——函数进阶.ppt_第2页
更深入的理解——函数进阶.ppt_第3页
更深入的理解——函数进阶.ppt_第4页
更深入的理解——函数进阶.ppt_第5页
已阅读5页,还剩20页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第18章 更深入的理解函数进阶,第10章中已经讨论了函数的基本知识,让读者对函数有了基本的认识,本章从更深层次帮助读者理解函数。从和函数关系最密切的调用和返回入手,函数的参数传递有传值,传指针2种方式,从类型的角度上看,参数不仅仅可以是系统内建的数据类型,还可以是数组、结构等。此外,递归编程机制,函数的作用域和可见域,变量的生存期、作用域和可见域等都是本章讨论的重点。,18.1 参数传递的副本机制,如果将函数比作剧本,那形参和实参的关系相当于角色和演员的关系,函数的参数传递有传值和传地址两种方式。传值调用时,在函数内对形参的改变都不会影响实参,要想在函数内对实参进行操作,必须采用传地址调用的方式。这是形象化的理解,从本质上说,这是由参数传递的副本机制决定的。 所谓副本机制,是指copy(拷贝)的思想,不论是传值调用还是传址调用,编译器都要为每个参数制作临时副本,或称拷贝,函数体中对参数的修改都是对副本的修改,下面具体分析之。,18.1.1 传值调用的副本,传值调用的情况相对简单,不论传递的参数如何,编译器都为这些参数制作临时副本,函数体中对参数的修改都是针对副本进行的,丝毫不会影响传来的参数,试通过下述一段示例,体会传值调用的副本机制。,18.1.2 传址调用的副本机制,相比传值调用,传址调用似乎要复杂一点,但只要知道,传址调用也是通过副本机制,便能很好地理解传址调用的机理,同样从一个形象的例子入手。,18.2 函数返回值的副本机制,如果要细分,函数返回也可以认为存在传值和传址两种方式。函数返回同样也是根据副本机制来处理的,首先来回顾下函数返回的流程: 当执行到return语句时,return的值被复制到某个内存单元或寄存器中,其地址是由编译器来维护的,程序员无法直接访问该地址,也就是说,在函数执行完毕,相关现场被撤销前,返回的值被复制保存到了某个地方,编译器访问该位置即可知道函数的返回值。该位置即可看成是函数中返回值的副本。 对函数返回取地址是不合法的,即假设存在如下函数: int A(int b,int c); 不允许使用如下形式的语句: ,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已被撤销,不存在了。实际上,在c被撤销前,函数已经为返回值c创建了副本,保存在特定的位置上,赋值操作是由该位置处的副本完成的,形象的示意如所示。,18.2.2 返回指针申请动态内存,下面来看一下如何通过返回指针在函数中动态申请内存,试比较下述与的异同: 代码 通过返回指针传递动态内存GetMemorySunccess2,18.2.3 不要返回指向栈内存的指针,动态申请内存是在堆中完成的,而函数返回不会释放堆内存,但不要忘记,函数返回时,栈内存中的内容会被自动清除,因此,不要返回指向栈内存的指针。 请读者试着分析下述的问题所在:,18.2.4 返回指向只读存储区的指针,如果将中的GetMemory函数修改如下,会怎样? char* GetMemory(void) /*定义函数GetMemory*/ char* p=“Hello,C“; /*栈内存中开辟字符串*/ return p; /*返回局部指针*/ “Hello,C“作为常量字符串,位于程序的只读存储区(.rodata),此时,返回指向只读存储区的指针p并没有问题,但该指针只能用于输出,而不能用于输入改写。,18.3 函数与结构体,结构体可以看成一种数据组织方式,将很多不同类型的相关数据打包,构成一种新的类型,从这种意义上说,结构体变量完全可以当成是一种普通类型的变量来使用。结构体变量作函数参数时,也有传值和传址两种方式,函数返回亦是如此,既可以返回结构体变量,也可以返回指向非局部结构体变量的指针。,18.3.1 结构体变量的传值和传址调用,采用值传递时,在函数内将生成实参的“复制品”,如果参数多是像int,char之类的简单变量,这些变量占用的内存并不多,复制也快。但结构或共用体变量往往由多个成员变量组成,占用内存大,如果复制一份,会造成时间和空间双重浪费。采用值传递不会造成时空浪费,因为不管是多么复杂的结构类型,指针参数只占4个内存字节。,18.3.2 结构体变量的成员作为函数参数,结构体变量的数据成员作函数实参时,结构体变量的数据成员可以当成是普通变量来使用,同样存在传值和传址两种函数调用方式,改写如下:,18.3.3 返回结构体的函数,如果函数的返回值是某个结构体变量,常称该函数为结构体型函数,结构体型函数定义的基本格式为: struct 结构体名 函数名(形参列表) 函数体; 声明一个结构体型函数时也不要遗漏struct,如下: struct 结构体名 函数名(形参列表);,18.3.4 返回结构体指针的函数,结构体指针同样可以作为函数的返回类型,前提是该指针不是指向栈内存的,换句话说,该指针不是指向局部结构体变量的。与返回结构体变量相比,返回结构体指针大大节省了函数返回时创建副本的时空开销,有较高的效率。,18.4 函数与数组,数组是种使用广泛的数据结构,数组名和数组元素都可以作为函数的参数,实现函数间的数据传递和共享。此外,由于数组名和指针的对应关系,在一些需要指针型参数的场合,可以用数组名(即常指针)作函数参数。,18.4.1 数组元素作为函数参数,这和上节讨论的“结构体变量的成员作函数参数”类似,数组元素可以当成是普通的变量(内置类型的变量或自定义的结构变量)来看,也就是说,一个数组元素实质上就是一个同类型的普通变量,只要是可以使用该类型变量的场合,都可以使用数组元素。,18.4.2 数组名作函数参数,数组名既可以作为函数的形参,也可以作为函数的实参,数组名实际上是数组元素的首地址,所以,数组名作函数参数属于传址调用,先来看一段示例:,18.4.3 多维数组名作函数参数,如果数组是二维甚至更高维的,在函数参数列表中的形参数组,除第一维外,其余各维的长度说明必须给出,因为多维数组元素的存放是按连续地址进行的。同时,编译器自动忽略掉第一维方括号中的内容,来看下述示例:,18.4.4 数组名作函数参数时的退化,数组名作函数参数时,仅仅相当于一个指针,函数无法得到数组的大小,如果在函数中需要用到数组的大小,必须显式以参数传递的形式来完成,下面的代码验证了这一说法,请读者先来判断下会输出什么样的结果。,18.5 递归,递归是程序设计中的一种算法,或者说是一种编程机制。一个函数直接调用自己本身或者通过调用其他函数来间接地调用自己,称为递归函数。,18.5.1 递归流程,简单地说,递归就是编写这样一个特殊的函数,该函数体中有一个语句用于调用函数本身,称为递归调用。递归函数实现了自我的嵌套执行,先来看一个递归应用最广泛的例子,计算N的阶乘N!,由数学知识,我们知道:,18.5.2 递归两要素,递归要成功,离不开两点,一是递归递进机制,二是递归终止条件,递进机制应保证每次调用都向调用终止靠近一步,中,f(5)调用f(4) ,f(4)调用f(3),一步步靠近f(1)这一终止条件。这保证了递归调用正常结束,不至于出现无限循环,导致系统内存耗尽而崩溃,通常用参数对调用过程进行判断,以便在合适的时候切断调用链,如中的“if(n=1)”结构。,18.5.3 效率VS可读性,递归调用中,每次函数调用都有一定的堆栈操作,相比循环方式,时空开销交到,因此,递归函数的效率总比功能相同的循环结构略低,任何用递归编写的函数都可用循环代替,以提高效率。 但是,递归带来的好处也是显而易见的:一是程序的可读性比较好,易于修改和维护,二是在不断的函数调用中,函数的规模在减小,在求解阶乘例子中,不断用复杂度为n-1的问题来描述复杂度为n的问题,直到问题可以直接求解为止(参数为1)。 如果用循环语句实现的算法比使用递归复杂得多,有必要考虑复杂度和效率的折衷,此时建议优先采用递归方式来解决问题。,18.6 带参主函数,前面介绍过的main函数都是不带参数的,实际上,main函数也可以带参数。main函数的参数是由谁传来的呢?答案是操作系统,C+规定main函数的参数只能有两个:argc和argv,带参main函数的形式为: int main(int argc, char* argv ) 第一个参数argc必须是整型变量,称作参数计数器,其值是包括命令名在内的参数个数;第二个参数argv必须是指向字符指针数组,存放命令名和参数字符串的地址。 要调用带参主函数必须在操作系统环境下进行,来看一段示例代码:,18.7 小结,本章讨论了有关函数的深层次的内容,先从函数的参数传递和返回值角度,用大量实例帮助读者理解“副本机制”,C语言的函数参数传递有传值和传址两种方式,不论采用哪种方式,函数都会为实参在堆栈中开辟副本,所有的操作都是针对副本进行的,因此,传值调用不会修改实参,但传址调用时,因为指针间接操作的关系,会影响实参所在的内存区域。函数返回同样有值和地址之分,但应注意,不要返回

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论