函数-变量的作用域和生命周期
变量的作用域
引入问题
我们在函数设计的过程中,经常要考虑对于参数的设计,换句话说,我们需要考虑函数需要几个参数,需要什么类型的参数,但我们并没有考虑函数是否需要提供参数,如果说函数可以访问到已定义的数据,则就不需要提供函数形参。
那么我们到底要不要提供函数形参,取决于什么?答案就是变量的作用域(如果函数在变量的作用域范围内,则函数可以直接访问数据,无需提供形参)
变量作用域
**概念:**变量的作用范围,也就是说变量在什么范围有效。
变量的分类
根据变量的作用域不同,变量可以分为:
-
全局变量
说明:定义在函数之外,也称之为外部变量或者全程变量。
作用域:从全局变量定义到本源文件结束。
初始值:整型和浮点型,默认值是0;字符型,默认值是\0;指针型,默认值NULL
举例:
int num1; // 全局变量,num1能被fun1、fun2、main共同访问void fun1(){}int num2; // 全局变量,num2能被fun2、main共同访问void fun2(){}void main(){}int num3; // 全局变量,不能被任何函数访问
-
局部变量
说明 作用域 初始值 形式参数(形参) 函数作用域 随机值,需要手动赋初值 函数内定义的变量 函数作用域 随机值,需要手动赋初值 复合语句中定义的变量 块作用域 随机值,需要手动赋初值 for循环表达式1定义的变量 块作用域 随机值,需要手动赋初值 举例:
// a,b就是形式参数(局部变量)int add(int a, int b){return a + b;}int add2(int a, int b){// z就是函数内定义的变量(局部变量)int z = a + b;return z;}int list(int arr[], int len){// i就是for循环表达式1的变量(局部变量)for(int i = 0; i < len; i++){// num就是复合语句中定义的变量(局部变量)int num = arr[i];}}
使用全局变量的优缺点:
优点:
- 利用全局变量可以实现一个函数对外输出的多个结果数据。
- 利用全局变量可以减少函数形参的个数,从而降低内存消耗,以及因为形参传递带来的时间消耗。
缺点:
- 全局变量在程序的整个运行期间,始终占据内存空间,会引起资源消耗。
- 过多的全局变量会引起程序的混乱,操作程序结果错误。
- 降低程序的通用性,特别是当我们进行函数移植时,不仅仅要移植函数,还要考虑全局变量。
- 违反了“高内聚,低耦合”的程序设计原则。
总结:
我们发现弊大于利,建议尽量减少对全局变量的使用,函数之间要产生联系,仅通过实参+形参的方式产生联系。
作用域举例
注意:
如果全局变量和局部变量同名,程序执行的时候,就近原则(区分作用域)
int a = 10; // 全局变量 全局作用域int main(){int a = 20; // 局部变量 函数作用域printf("%d\n", a); // 20 就近原则for (int a = 0; a < 5; a++) // 局部变量 块作用域{printf("%d", a); // 0 1 2 3 4 就近原则}printf("%d\n",a); // 20 就近原则}
变量的生命周期
定义
**概念:**变量在程序运行中的存在时间(内存申请到内存释放的时间)
根据变量存在的时间不同,变量可分为静态存储方式和动态存储方式
变量的存储类型
语法:
变量的完整定义格式: [存储类型] 数据类型 变量列表;
存储类型:
-
auto
auto存储类型只能修饰局部变量,被auto修饰的局部变量是存储在动态存储区(栈区和堆区)的。auto也是局部变量默认的存储类型。
int main(){int a;int b;// 以下写法等价于上面写法auto int a;auto int b;int a,b;// 以下写法等价于上面写法auto int a,b;}
-
static
**修饰局部变量:**局部变量会被存储在静态存储区。局部变量的生命周期被延长。但是作用域不发生改变,不推荐
**修饰全局变量:**全局变量的生命周期不变,但是作用域衰减,一般限制全局变量只能在本源文件内访问,其他文件不可访问。
**修饰函数:**被static修饰的函数,只能被当前文件访问,其他引用该文件的文件是无法访问的,有点类似于java中private
-
extern
外部存储类型:只能修饰全局变量,此全局变量可以被其他文件访问,相当于扩展了全局变量的作用域。
extern修饰外部变量,往往是外部变量进行声明,声明该变量是在外部文件中定义的。起到一个标识作用。函数同理。
demo01.c
#include "demo01.h"int fun_a = 10;int fun1(){..}
demo02.c
#include "demo01.h"// 声明访问的外部文件的变量extern int fun_a;// 声明访问的外部文件的函数extern int fun1();int fun2();
-
register
寄存器存储类型:只能修饰局部变量,用register修饰的局部变量会直接存储到CPU的寄存器中,往往将循环变量设置为寄存器存储类型(提高读的效率)
for (register int i = 0; i < 10; i++){...}
static关键字的作用
- static修饰局部变量,延长其生命周期,但不影响局部变量的作用域。
- static修饰全局变量,不影响全局变量的生命周期,会限制全局变量的作用域仅限本文件内使用(私有化);
- static修饰函数:此函数就称为内部函数,仅限本文件内调用(私有化)。
static int funa(){..}
内部函数和外部函数
- 内部函数:使用static修饰的函数,称作内部函数,内部函数只能在当前文件中调用。
- 外部函数:使用extern修饰的函数,称作外部函数,extern是默认的,可以不写(区分编译环境),也就是说本质上我们所写的函数基本上都是外部函数,建议外部函数在被其他文件调用的时候,在其他文件中声明的时候,加上extern关键字,主要是提高代码的可读性。