Skip to content

作用域

C语⾔变量的作用域分为:

  • 代码块作用域(代码块是 {} 之间的一段代码)
  • 函数作用域
  • 文件作用域

局部变量

局部变量也叫auto⾃动变量(auto可写可不写),一般情况下代码块{}内部定义的变量都是⾃动变量,它有如下特点:

  • 在一个函数内定义,只在函数范围内有效
  • 在复合语句中定义,只在复合语句中有效
  • 如果没有赋初值,内容为随机
c
#include <stdio.h>
#include <stdlib.h>

void test() {
    //auto写不写是一样的
    //auto只能出现在{}内部
    auto int b = 10;
}

int main(void) {
    //b = 100; //err, 在main作用域中没有b
    if (1) {
        //在复合语句中定义,只在复合语句中有效
        int a = 10;
        printf("a = %d\n", a);
    }
    //a = 10; //err离开if()的复合语句,a已经不存在

    return 0;
}

程序输出:

shell
a = 10

静态局部变量

  • static局部变量的作用域也是在定义的函数内有效
  • static局部变量的生命周期和程序运行周期一样,同时staitc局部变量的值
  • static局部变量若未赋以初值,则由系统⾃动赋值:数值型变量⾃动赋初值0,字符型变量赋空字符
c
#include <stdio.h>
#include <stdlib.h>

void fun1() {
    int i = 0;
    i++;
    printf("i = %d\n", i);
}

void fun2() {
    //静态局部变量,没有赋值,系统赋值为0,而且只会初始化一次
    static int a;
    a++;
    printf("a = %d\n", a);
}

int main(void) {
    fun1();
    fun1();
    fun2();
    fun2();

    return 0;
}

程序输出:

shell
i = 1
i = 1
a = 1
a = 2

全局变量

  • 在函数外定义,可被本文件及其它文件中的函数所共用,若其它文件中的函数调用此变量,须用extern声明
  • 全局变量的生命周期和程序运行周期一样
  • 不同文件的全局变量不可重名

静态全局变量

  • 在函数外定义,作用范围被限制在所定义的文件中
  • 不同文件静态全局变量可以重名,但作用域不冲突
  • static全局变量的生命周期和程序运行周期一样,同时staitc全局变量的值只初始化一次

全局变量声明——extern

extern int a; 声明一个变量,这个全局变量在别的文件中已经定义了,这里只是声明,而不是定义。

extern是C、C++语⾔中表明函数和全局变量作用范围的关键字。,由于函数的声明如“extern int method();”与函数定义“ int method(){} ”可以很清晰的区分开来,为了简便起见,可以把extern关键字省略。

,仅仅是一个变量的声明,其并不是定义,因为不会分配内存空间。一个变量可以声明多次,但如果该变量定义多次,会有链接错误。如果只声明变量而不进行定义,那么声明该变量的意义在于告诉编译器该变量的类型和名称,以便在其他文件中引用该变量。这样可以让编译器知道该变量是在其他文件中定义的,从而避免出现链接错误。

extern修饰变量的声明

举例来说,如果文件 a.c 需要引用 b.c 中变量 int v; ,就可以在 a.c 中声明 extern int v; ,然后就可以引用变量v。能够被其他模块以extern修饰符引用到的变量通常是全局变量。还有很重要的一点是, extern int v; 可以放在 a.c 中的任何地⽅,比如你可以在 a.c 中的函数fun定义的开头处声明 extern int v; ,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都⼼存顾虑。好像extern声明只能用于文件作用域似的。

extern修饰函数声明

从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。如果文件 a.c 需要引用 b.c 中的函数,比如在 b.c 中原型是 int fun(int mu); ,那么就可以在 a.c 中声明 extern int fun(int mu); ,然后就能使用fun来做任何事情。就像变量的声明一样, extern int fun(int mu); 可以放在 a.c 中任何地⽅,而不一定非要放在 a.c 的文件作用域的范围中。对其他模块中函数的引用,最常用的⽅法是包含这些函数声明的头文件。

使用extern和包含头文件来引用函数有什么区别

extern的引用⽅式比包含头文件要简洁得多!extern的使用⽅法是直接了当的,想引用哪个函数就用extern声明哪个函数。这⼤概是KISS原则的一种体现吧!这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。

指示C或者C++函数的调用规范

比如在C++中调用C库函数,就需要在C++程序中用 extern “C” 声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

extern应用实例

实例一

用C语⾔编写程序的时候,我们经常会遇到这样一种情况:希望在头文件中定义一个全局变量,然后包含到两个不同的c文件中,希望这个全局变量能在两个文件中共用。

举例说明:项目文件夹project下有main.c、common.和common.h三个文件,其中common.文件分别#include在main.c和common.c文件中。现在希望声明一个字符型变量key,在 main.c和common.c中公用。

有人想,既然是想两个文件都用,那就在common.h中声明一个 unsigned char key ,然后由于包含关系,在main.c和common.c中都是可见的,所以就能共用了。

这种想法其实是很多初学者都会想到的,想起来确实有道理,但是实际写出来,我们发现编译的时候编译器提⽰出错,一般提⽰⼤概都类似于: Error: L6200E: Symbol key multiply defined (by common.o and main.o). 也就是说编译器认为我们重复定义了key这个变量。这是因为#include命令就是原封不同的把头文件中的内容搬到#include的位置,所以相当于main.c和common.c中都执行了一次 unsigned char key ,而C语⾔中全局变量是项目内(或者叫工程内)可见的,这样就造成了一个项目中两个变量key,编译器就认为是重复定义。

正确的解决办法:使用extern关键字来声明变量为外部变量。具体说就是在其中一个c文件中定义一个全局变量key,然后在另一个要使用key这个变量的c文件中使用extern关键字声明一次,说明这个变量为外部变量,是在其他的c文件中定义的全局变量。请注意我这里的用词:定义和声明。例如在main.c文件中定义变量key,在common.c文件中声明key变量为外部变量,这样这两个文件中就能共享这个变量key了。 代码如下(只写跟我们所说问题有关的部分):

(1)main.c文件

c
unsigned char key;

(2)common.c文件

c
#include "common.h" 
extern unsigned char key;
实例二

情景:在一个工程里⾯有两个文件a.c and b.c,其中它们的内容如下:

a.c

c
#include <stdio.h> 
int i = 3; 
int p(void) {
    printf("%d\n", i);
    return 0; 
}

b.c

c
#include <stdlib.h>

extern int p(void);
extern int i;

int main() {
    p();

    return 0;
}

在b.c里⾯调用a.c里⾯定义的变量和函数,最后在Dev c++里⾯运行,b.c的输出结果为:3。

全局函数和静态函数

在C语⾔中函数默认都是全局的,使用关键字static可以将函数声明为静态,函数定义为static 就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在其他 文件中声明这个函数都没用。

对于不同文件中的staitc函数名字可以相同。

注意

  • 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不⼲扰。
  • 同一源文件中,允许全局变量和局部变量同名,在局部变量的作用域内,全局变量不起作用。
  • 所有的函数默认都是全局的,意味着所有的函数都不能重名,但如果是staitc函数,那么作用域是文件级的,所以不同的文件static函数名是可以相同的。

总结

类型作用域生命周期
auto变量一对{}内当前函数
static局部变量一对{}内整个程序运行期
extern变量整个程序整个程序运行期
static全局变量当前文件整个程序运行期
extern函数整个程序整个程序运行期
static函数当前文件整个程序运行期
register变量一对{}内当前函数
全局变量整个程序整个程序运行期