> 技术文档 > 函数-变量的作用域和生命周期

函数-变量的作用域和生命周期


变量作用

引入问题

我们在函数设计的过程中,经常要考虑对于参数的设计,换句话说,我们需要考虑函数需要几个参数,需要什么类型的参数,但我们并没有考虑函数是否需要提供参数,如果说函数可以访问到已定义的数据,则就不需要提供函数形参。

那么我们到底要不要提供函数形参,取决于什么?答案就是变量的作用域(如果函数在变量的作用域范围内,则函数可以直接访问数据,无需提供形参)

变量作用域

**概念:**变量的作用范围,也就是说变量在什么范围有效。

变量的分类

根据变量的作用域不同,变量可以分为:

  • 全局变量

    说明:定义在函数之外,也称之为外部变量或者全程变量。

    作用域:从全局变量定义到本源文件结束。

    初始值:整型和浮点型,默认值是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]; } }

    使用全局变量的优缺点

    优点:

    1. 利用全局变量可以实现一个函数对外输出的多个结果数据。
    2. 利用全局变量可以减少函数形参的个数,从而降低内存消耗,以及因为形参传递带来的时间消耗。

    缺点:

    1. 全局变量在程序的整个运行期间,始终占据内存空间,会引起资源消耗。
    2. 过多的全局变量会引起程序的混乱,操作程序结果错误。
    3. 降低程序的通用性,特别是当我们进行函数移植时,不仅仅要移植函数,还要考虑全局变量。
    4. 违反了“高内聚,低耦合”的程序设计原则。

    总结:

    ​ 我们发现弊大于利,建议尽量减少对全局变量的使用,函数之间要产生联系,仅通过实参+形参的方式产生联系。

作用域举例

函数-变量的作用域和生命周期

注意:

如果全局变量和局部变量同名,程序执行的时候,就近原则(区分作用域)

 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关键字的作用
  1. static修饰局部变量,延长其生命周期,但不影响局部变量的作用域。
  2. static修饰全局变量,不影响全局变量的生命周期,会限制全局变量的作用域仅限本文件内使用(私有化);
  3. static修饰函数:此函数就称为内部函数,仅限本文件内调用(私有化)。static int funa(){..}

内部函数和外部函数

  • 内部函数:使用static修饰的函数,称作内部函数,内部函数只能在当前文件中调用。
  • 外部函数:使用extern修饰的函数,称作外部函数,extern是默认的,可以不写(区分编译环境),也就是说本质上我们所写的函数基本上都是外部函数,建议外部函数在被其他文件调用的时候,在其他文件中声明的时候,加上extern关键字,主要是提高代码的可读性。