变量和函数的作用域

一、什么是作用域

就是变量和函数起作用的范围,只要在这个范围内,你就可以访问该变量和函数

作用域实际上有三种:

  • (1)局部变量的代码块作用域
  • (2)函数和全局变量的本文件作用域
  • (3)跨文件作用域 —— 链接域,与链接有关

前两种我们在本小节介绍,第三种我们放到“链接域”小节介绍


二、局部变量的代码模块作用域

1. 什么是代码块

其实有关代码块,我们在讲生命周期时就提到过

那什么是代码块呢?

简单理解就是{}括起来的就是代码块,不要把代码块等价为函数,因为ifforwhile等同样有{}这个东西。

2. 代码块作用域 的 范围

从定义处到代码块结束!

例子:

int main(void)
{
    int a; //a的代码块作用域:从定义位置开始到main函数的}。

    {
        int b; //b的代码块作用域:定义位置开始到内部}。
        ...
    }

    ...
}

int b 所在的内部{}实际上才是真正的代码块,只不过在广义上我们将所有带{}的都理解为代码块,
当然结构体类型定义除外,虽然结构体类型的定义有{},但不是代码块

struct student
{   //这个不是代码块
    int num;
    ...
};

3. 形参的作用域

形参的作用域也是代码块作用域,不过有些特殊的地方需要说明下

我们直接举例介绍。

int fun(int n, int buf[][n]) 
{
    ...
}

int main(void)
{
    int buf[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}};
    fun(4, buf);
}
  • n的作用域:定义位置开始,到参数列表末尾,再到fun}
  • buf[][n]的作用域:定义位置开始,到参数列表末尾,再到fun}

由于n的作用域覆盖了buf,所以才能在buf中使用n,如果反过来的话fun(int buf[][n],int n)
编译时会提示buf中的n无法识别,因为buf不在n的作用域内


函数和全局变量的本文件作用域

三、本文件作用域的有效范围

从定义位置开始到文件结束!

文件:a.c

int main(void)         //main的本文件作用域:从定义位置到文件末尾
{
    fun(g_var1, g_var2);
    return 0;
}

int g_var1 = 100;     //g_var的本文件作用域:从定义位置到文件末尾
int g_var2;

int fun(int a)        //fun的本文件作用域:从定义位置到文件末尾
{
    int var;
    return var +a;
}

四、通过声明改变本文件作用域

在上面的例子中,如果我们想在main函数中使用g_var1g_var2fun的话怎么办?

1. 方法1

将这些函数和变量移动到main的前面去

2. 方法2

main前面进行声明,通过声明将作用域提前

a.c

int g_var1;
int g_var2;

int fun(int a);

int main(void)
{
    fun(g_var1, g_var2);
}

int fun(int a)
{
    int var;
    return var +a;
}

3. 定义与声明的关系

定义与声明的关系,其实就是第一章中介绍的“强弱符号关系”

我们知道,定义与声明的符号名是相同的,编译时同名符号必须进行统一,然后合并为一个,
在第一章中,我们详细介绍过强弱符号的统一规则,我们这里再回顾下

1)谁是强符号,谁是弱符号
· 函数定义:强符号
· 函数声明:弱符号

· 初始化了的全局变量:强符号,我们常将这种称为定义
· 未初始化的全局变量:弱符号,我们常将这种称为声明

2)强弱符号的统一规则

(a)不能允许重复出现同名的强符号,但是允许重复出现有同名的弱符号

以前学C时老师总是讲,声明可以重复,原因就来自于这里

(b)有一个强符号,其它都是弱符号的话,只保留强符号,其它弱符号消失

同名符号的作用域以最前面的那个符号为准,正是因为这点原因,放在最前面的声明才能提前作用域。
所以声明虽然是弱符号,遇到强符号时消失了,但是它能够提前作用域。

(c)全都是弱符号的话,只留一个即可,其它全部消失

同一个.c中的强弱符号统一,是在第二阶段编译时由编译器来完成的,
而不同.c中的强弱符号统一,则是由第4阶段“链接”来完成的,
这一点我们在第1章详细介绍过,后面介绍“链接域”时,还会再次讲到

4. 声明全局变量的特殊例子

int g_var1;

int main(void)
{
    extern int g_var1; //声明
    g_var1 = 100;
}

int fun(int a)
{
    int var;
    return var +a;
}


int g_var1 = 100;

main函数中extern int g_var1这种的声明方式表示,
g_var1只在main函数内有效,对后面的fun无效

extern可以省略吗?

不能省,省了g_var1就变成main的自动局部变量了,extern表示这个全局变量来自于函数外部。
extern有好些用法,不同用法的含义不一样,在这里先了解下这种用法

假如你只想在main中使用g_var1,不想让g_var1的作用域覆盖到fun函数,就可以使用这种方法,
不过这种声明方式用的确实不多,但是在有些源码中可能会看见,这里需要了解下

同样的,函数也可以进行类似的声明。

int main(void)
{
    extern int fun(int a); //fun的声明。
    fun(g_var1, g_var2);
}

int fun(int a)
{
    int var;
    return var +a;
}

5. 局部变量有声明吗?

局部变量没有声明一说,以下做法时错误的。

int main(void)
{
    int a; //声明:错误用法
    a = a + 1;
    int a = 100;
}

对于局部变量来说,变量符号只能有一个,不允许同名符号重复出现

6. 同一个.c中,变量同名的问题

1)全局变量

  • (a)如果同名变量都是强符号,这会导致变量重复定义,编译时会报错。
  • (b)如果同名的是弱符号,它只会改变符号的作用域,除此外没有影响

2)局部变量

不允许存在同名符号