资源预览内容
第1页 / 共7页
第2页 / 共7页
第3页 / 共7页
第4页 / 共7页
第5页 / 共7页
第6页 / 共7页
第7页 / 共7页
亲,该文档总共7页全部预览完了,如果喜欢就下载吧!
资源描述
第第 3 章章 简单函数简单函数4. 全局变量、局部变量和作用域全局变量、局部变量和作用域我们把函数中定义的变量称为局部变量(Local Variable),由于形参相当于函 数中定义的变量,所以形参也是一种局部变量。在这里“局部”有两层含义:1、一个函数中定义的变量不能被另一个函数使用。例如 print_time 中的 hour 和 minute 在 main 函数中没有定义,不能使用,同样 main 函数中的局部变量也 不能被 print_time 函数使用。如果这样定义:void print_time(int hour, int minute)printf(“%d:%dn“, hour, minute);int main(void)int hour = 23, minute = 59;print_time(hour, minute);return 0;main 函数中定义了局部变量 hour,print_time 函数中也有参数 hour,虽然它 们名称相同,但仍然是两个不同的变量,代表不同的存储单元。main 函数的局 部变量 minute 和 print_time 函数的参数 minute 也是如此。2、每次调用函数时局部变量都表示不同的存储空间。局部变量在每次函数调用 时分配存储空间,在每次函数返回时释放存储空间,例如调用 print_time(23, 59)时分配 hour 和 minute 两个变量的存储空间,在里面分别存上 23 和 59,函 数返回时释放它们的存储空间,下次再调用 print_time(12, 20)时又分配 hour 和 minute 的存储空间,在里面分别存上 12 和 20。与局部变量的概念相对的是全局变量(Global Variable),全局变量定义在所 有的函数体之外,它们在程序开始运行时分配存储空间,在程序结束时释放存 储空间,在任何函数中都可以访问全局变量,例如:例例 3.5. 全局变量全局变量#include int hour = 23, minute = 59;void print_time(void)printf(“%d:%d in print_timen“, hour, minute);int main(void)print_time();printf(“%d:%d in mainn“, hour, minute);return 0;正因为全局变量在任何函数中都可以访问,所以在程序运行过程中全局变量被 读写的顺序从源代码中是看不出来的,源代码的书写顺序并不能反映函数的调 用顺序。程序出现了 Bug 往往就是因为在某个不起眼的地方对全局变量的读写 顺序不正确,如果代码规模很大,这种错误是很难找到的。而对局部变量的访 问不仅局限在一个函数内部,而且局限在一次函数调用之中,从函数的源代码 很容易看出访问的先后顺序是怎样的,所以比较容易找到 Bug。因此,虽然全虽然全局变量用起来很方便,但一定要慎用,能用函数传参代替的就不要用全局变量局变量用起来很方便,但一定要慎用,能用函数传参代替的就不要用全局变量。如果全局变量和局部变量重名了会怎么样呢?如果上面的例子改为:例例 3.6. 作用域作用域则第一次调用 print_time 打印的是全局变量的值,第二次直接调用 printf 打 印的则是 main 函数局部变量的值。在 C 语言中每个标识符都有特定的作用域, 全局变量是定义在所有函数体之外的标识符,它的作用域从定义的位置开始直 到源文件结束,而 main 函数局部变量的作用域仅限于 main 函数之中。如上图 所示,设想整个源文件是一张大纸,也就是全局变量的作用域,而 main 函数是 盖在这张大纸上的一张小纸,也就是 main 函数局部变量的作用域。在小纸上用 到标识符 hour 和 minute 时应该参考小纸上的定义,因为大纸(全局变量的作 用域)被盖住了,如果在小纸上用到某个标识符却没有找到它的定义,那么再 去翻看下面的大纸上有没有定义,例如上图中的变量 x。到目前为止我们在初始化一个变量时都是用常量做 Initializer,其实也可以用表 达式做 Initializer,但要注意一点:局部变量可以用类型相符的任意表达式来初局部变量可以用类型相符的任意表达式来初 始化,而全局变量只能用常量表达式(始化,而全局变量只能用常量表达式(Constant Expression)初始化)初始化。例如, 全局变量 pi 这样初始化是合法的:double pi = 3.14 + 0.0016;但这样初始化是不合法的:double pi = acos(-1.0);然而局部变量这样初始化却是可以的。程序开始运行时要用适当的值来初始化 全局变量,所以初始值必须保存在编译生成的可执行文件中,因此初始值在编编 译时译时就要计算出来,然而上面第二种 Initializer 的值必须在程序运行时运行时调用 acos 函数才能得到,所以不能用来初始化全局变量。请注意区分编译时和运行 时这两个概念。为了简化编译器的实现,C 语言从语法上规定全局变量只能用 常量表达式来初始化,因此下面这种全局变量初始化是不合法的:int minute = 360;int hour = minute / 60;虽然在编译时计算出 hour 的初始值是可能的,但是 minute / 60 不是常量表达 式,不符合语法规定,所以编译器不必想办法去算这个初始值。如果全局变量在定义时不初始化则初始值是 0,如果局部变量在定义时不初始 化则初始值是不确定的。所以,局部变量在使用之前一定要先赋值局部变量在使用之前一定要先赋值,如果基于 一个不确定的值做后续计算肯定会引入 Bug。如何证明“局部变量的存储空间在每次函数调用时分配,在函数返回时释放”? 当我们想要确认某些语法规则时,可以查教材,也可以查 C99,但最快捷的办 法就是编个小程序验证一下:例例 3.7. 验证局部变量存储空间的分配和释放验证局部变量存储空间的分配和释放#include void foo(void)int i;printf(“%dn“, i);i = 777;int main(void)foo();foo();return 0;第一次调用 foo 函数,分配变量 i 的存储空间,然后打印 i 的值,由于 i 未初 始化,打印的应该是一个不确定的值,然后把 i 赋值为 777,函数返回,释放 i 的存储空间。第二次调用 foo 函数,分配变量 i 的存储空间,然后打印 i 的 值,由于 i 未初始化,如果打印的又是一个不确定的值,就证明了“局部变量 的存储空间在每次函数调用时分配,在函数返回时释放”。分析完了,我们运 行程序看看是不是像我们分析的这样:134518128777结果出乎意料,第二次调用打印的 i 值正是第一次调用末尾赋给 i 的值 777。 有一种初学者是这样,原本就没有把这条语法规则记牢,或者对自己的记忆力 没信心,看到这个结果就会想:哦那肯定是我记错了,改过来记吧,应该是 “函数中的局部变量具有一直存在的固定的存储空间,每次函数调用时使用它, 返回时也不释放,再次调用函数时它应该还能保持上次的值”。还有一种初学 者是怀疑论者或不可知论者,看到这个结果就会想:教材上明明说“局部变量 的存储空间在每次函数调用时分配,在函数返回时释放”,那一定是教材写错 了,教材也是人写的,是人写的就难免出错,哦,连 C99 也这么写的啊,C99 也是人写的,也难免出错,或者 C99 也许没错,但是反正运行结果就是错了, 计算机这东西真靠不住,太容易受电磁干扰和宇宙射线影响了,我的程序写得 再正确也有可能被干扰得不能正确运行。这是初学者最常见的两种心态。不从客观事实和逻辑推理出发分析问题的真正 原因,而仅凭主观臆断胡乱给问题定性,“说你有罪你就有罪”。先不要胡乱 怀疑,我们再做一次实验,在两次 foo 函数调用之间插一个别的函数调用,结 果就大不相同了:int main(void)foo();printf(“hellon“);foo();return 0;结果是:134518200hello0这一回,第二次调用 foo 打印的 i 值又不是 777 了而是 0,“局部变量的存储 空间在每次函数调用时分配,在函数返回时释放”这个结论似乎对了,但另一 个结论又不对了:全局变量不初始化才是 0 啊,不是说“局部变量不初始化则 初值不确定”吗?关键的一点是,我说“初值不确定”,有没有说这个不确定值不能是 0?有没 有说这个不确定值不能是上次调用赋的值?在这里“不确定”的准确含义是: 每次调用这个函数时局部变量的初值可能不一样,运行环境不同,函数的调用 次序不同,都会影响到局部变量的初值。在运用逻辑推理时一定要注意,不要不要 把必要条件(把必要条件(Necessary Condition)当充分条件()当充分条件(Sufficient Condition), 这一点在 Debug 时尤其重要,看到错误现象不要轻易断定原因是什么,一定要 考虑再三,找出它的真正原因。例如,不要看到第二次调用打印 777 就下结论 “函数中的局部变量具有一直存在的固定的存储空间,每次函数调用时使用它, 返回时也不释放,再次调用函数时它应该还能保持上次的值”,这个结论倒是 能推出 777 这个结果,但反过来由 777 这个结果却不能推出这样的结论。所以 说 777 这个结果是该结论的必要条件,但不是充分条件。也不要看到第二次调 用打印 0 就断定“局部变量未初始化则初值为 0”,0 这个结果是该结论的必 要条件,但也不是充分条件。至于为什么会有这些现象,为什么这个不确定的 值刚好是 777,或者刚好是 0,等学到例 19.1 “研究函数的调用过程”就能解 释这些现象了。从第 2 节 “自定义函数”介绍的语法规则可以看出,非定义的函数声明也可 以写在局部作用域中,例如:int main(void)void print_time(int, int);print_time(23, 59);return 0;这样声明的标识符 print_time 具有局部作域,只在 main 函数中是有效的函数 名,出了 main 函数就不存在 print_time 这个标识符了。写非定义的函数声明时参数可以只写类型而不起名,例如上面代码中的 void print_time(int, int);,只要告诉编译器参数类型是什么,编译器就能为 print_time(23, 59)函数调用生成正确的指令。另外注意,虽然在一个函数体 中可以声明另一个函数,但不能定义另一个函数,C 语言不允许嵌套定义函数5。5 但 gcc 的扩展特性允许嵌套定义函数,本书不做详细讨论。
网站客服QQ:2055934822
金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号