学习足迹
前言
作用域是在任何一种程序语言中都不可忽视的概念,只有清楚地知道每种变量的作用域,了解它们在什么时候被创建和释放,才能完成一个正确的算法。本文将用简洁的逻辑,介绍C语言中基本的作用域概念和常用的存储类标识,希望能帮助大家理清静态变量、外部变量和局部变量的区别及作用。
参考及引用:笨办法学C
一、基于作用域划分的变量类型
首先描述一下作用域的概念,按字面意思理解,作用域就是限定变量名字的可用性的代码范围,即表示已定义变量的可用区域。
我们可以按照不同的作用域对变量进行划分,将其分为三类:全局变量、文件级变量和函数级变量。下面对它们分别进行介绍。
- 全局变量:它包括两种含义,一种是指作用域可以在整个工程中的变量;第二种是指我们通常说所说的作用域是一个源文件的变量。但是人们对第二种含义又重新命了一个新名字,因此在本文中可以将全局变量按照第一种含义来理解。(一个工程中通常包含多个源文件)
- 文件级变量:指作用域是一个源文件的变量,即前面描述的全局变量的第二个含义。
- 函数级变量:指作用域是一个函数体的变量,通常我们在函数中定义的变量就是函数级变量,也可以称为局部变量。
以上就是我认为的三种变量类型划分,大家可以作为参考~
二、三大存储类标识
在C语言中有几种保留字是用来作为存储类标识的,通过它们可以指定当前定义变量的作用域及生命周期。常用的有三种,分别是extern、static、auto。
这些标识可以在定义一个变量时直接加在变量类型的前面,比如 :extern int a 就表示定义了一个外部整型变量a。
下面我们分别来看这三种标识能为变量确定怎样的作用域和生命周期。
上表源百度百科
1.外部(extern)变量
外部变量有两个主要特点:
- 外部变量可以通过在头文件中定义的形式,被其他源文件使用,当然也可以在当前源文件中使用。简而言之,如果你想的话,它可以在整个工程中被使用;
- 只需要定义一次,在程序结束之前就都不会被释放,变量的值可以随时被改动并且会保持最近一次改动后的结果。
啧,外部变量果然是一枚交际花啊…够外部。
当在一个源文件中定义文件级变量(可以直接认为是在源文件内所有函数外部定义的变量)时,如果没有人为地指定存储类,那么它就会被自动定义成外部变量。
2.静态(static)变量
静态变量具有和外部变量迥然相反的性格,它只想安安静静地呆在自己源文件中。但是它的生命周期和外部变量一样,我们来看它的特点:
- 静态变量只能在当前源文件中或者是它所在的一个函数体中被使用(具体是哪种情况需要依据变量被定义的位置来判断);
- 静态变量也是定义一次就一直在那里的变量,在程序结束之前都不会被释放。
3. auto变量
auto变量其实就是我们熟悉的局部变量了,是在我们通常在函数中定义的变量类型,它的作用域就是它所在的函数,其特点如下:
- auto变量只能在被定义的函数体或者代码块(比如由if、while构建的代码块)之内被使用;
- auto变量的生命周期最短,在函数调用开始时被创立,在函数调用结束时就会被释放。
当在一个函数中定义函数级变量时,若没有手动指定存储类,那么编译器会自动将其看作是auto变量。
这里需要注意的是,在一个函数体或者代码块中被定义的变量,不论是静态变量还是auto变量,其作用域均在这个函数体或是代码块内。比如:
if(count >10){
int count =100;
printf("count in this scope is %d", count);
}
在这个由if构建的代码块中,int之后的count是一个被新创建的局部变量,它只在当前代码块区域中有效。哪怕代码块外部有一个和它重名的变量(即if之后的count),二者也不会相互影响。
这种情况在python中貌似无法实现,不过最好不要使用这样的重名变量,可以但没必要…
前文表格中出现的 register 是一种类型限定符,它表示强制让编译器将这个变量保存在寄存器中,并且也可以无视它,对变量的作用域不会产生特殊作用。目前的编译器更善于处理在哪里存放变量,所以应该只在确定这样会提升性能时使用它。
三、静态变量、外部变量、局部变量的用法
根据作用域和存储类标识不同,我们在程序中通常可以遇到四类变量,分别是文件级静态变量、全局变量(外部变量)、函数级静态变量和函数级局部变量。下面对它们逐个介绍。
1. 文件级静态变量
文件级静态变量就是在某个源文件中所有函数体之外被定义的静态变量。比如这样:
static int THE_AGE =37;
int get_age()
{
return THE_AGE;
}
void set_age(int age)
{
THE_AGE = age;
}
其中 THE_AGE 就是文件级静态变量,其作用域在当前的源文件中,无法被工程中其他源文件引用。并且其生命周期是整个程序的运行阶段,其值可以在程序运行结束前一直保持着。
如果希望其他源文件的函数使用或修改该变量,那就要构造一个函数来返回或者修改这个变量值。上面代码中的 get_age() 和 set_age() 函数就起到了获取静态变量和修改变量的作用。
2. 全局变量(外部变量)
这里的全局变量是在整个工程中均可使用的变量,也就是外部变量。
使用方法
我们可以在一个头文件 ext.h 中声明外部变量,并在同名源文件ext.c中定义该变量;
// 这是头文件中的内容
#ifndef _ext_h
#define _ext_h
// makes THE_SIZE in ext.c available to other .c files
extern int THE_SIZE;
#endif
// 这是同名源文件中的内容
#include<stdio.h>
#include"ext.h"
int THE_SIZE =1000;
之后在主程序所在源文件 test.c 中引用该头文件:
#include"ext.h"
printf("THE_SIZE is: %d", THE_SIZE);
那么在运行主程序时,就可以无需定义而使用变量 THE_SIZE。这是因为在ext.h头文件中已经声明了该变量是外部变量,在程序中遇到该变量值时,可以去其他源文件中寻找。
3. 函数级静态变量
函数级静态变量是指定义在函数体内的静态变量,它的作用域在当前函数中,但是只需定义一次,便可在程序结束运行前不被释放。如:
double update_ratio(double new_ratio)
{
static double ratio =1.0;
double old_ratio = ratio;
ratio = new_ratio;
return old_ratio;
}
上面的函数完成了更新ratio的功能,代码中ratio就是一个函数级静态变量。在多次调用该函数时,它可以实现值的跟踪,相当于对函数传入值的历史记录。
4. 函数级局部变量
函数级局部变量就是通常所说的局部变量,当我们在函数体中不加存储类标识地定义变量时,得到的就是函数级局部变量。不难看出,这也是auto变量,是我们最常使用的一种。
比如前面代码块中的old_ratio就是一个函数级局部变量。
总结
看完本文希望大家可以清楚文件级、函数级以及全局变量的含义;并且能够根据自己的需求,使用extern、static标识来准确定义每一个变量,也可以判断一个程序中每个变量的作用域和生命周期。
本文是个人总结,如有不对的地方欢迎大家指正!