1、变量的定义

  变量定义:在程序运行过程中其值可以更改的

格式:<存储类型>  <数据类型>   <变量名>  =  <初始值>;

2、变量的作用域

  变量的作用域定义:程序中可以访问一个指示符的一个或多个区域,即变量出现的有效区域,决定了程序的那些部分通过变量名来访问变量。

一个变量根据其作用域的范围可以分为函数原型作用域、局部变量和全局变量。

  2.1、函数原型参数作用域

    函数原型中的参数的作用域始于‘(’,终于‘)’。

  2.2、局部变量

  (1)、在函数内部定义的变量称为局部变量。

  (2)、局部变量仅能被定义该变量的函数使用,其他函数是用不了的。局部变量仅存在于该变量的执行代码块中,即局部变量在进入模块时生成(压入栈堆),在退出模块时消亡(弹出堆栈),定义局部变量的最常见的代码块是函数。

  (3)、当全局变量和局部变量同名时,在局部范围内全局变量被屏蔽,不再起作用,或者说变量的使用遵守就近原则,如果在当前作用域中存在同名变量,就不会向更大的作用域中去寻找变量。

  (4)、可以在不同的函数中使用相同的变量名,他们表示不同的数据,分配不同的内存,互不干扰,也不会发生混淆。

  (5)、局部变量的定义和声明可以不加区分。

  (6)、在{}内定义的变量只在{}内起作用。

  2.3、全局变量

源文件,可被源文件中的任何一个函数使用。

全局变量的定义和全局变量的声明并不是一回事,全局变量定义必须在所有函数之外,且只能定义一次,一般定义形式如下:

  全局变量的定义格式: 

<数据类型>  <变量名,变量名…..>

  全局变量的声明出现在要使用该变量的各个函数内。

  全局变量的声明格式:

<extern>  <数据类型>   <变量名,变量名…….>

  (3)、全局变量在定义时就已分配了内存单元,并且可做初始赋值。全局变量声明时就不能再赋初值了,因为全局变量声明只是表示在函数内要使用某外部变量。

  (4)、外部变量可以加强函数模块之间的数据联系,但是有使得函数要依赖这些变量,因而使得函数的独立性降低。

  (5)、全局变量的内存分配是在编译过程中完成的,它在程序的全部执行过程中都要占据存储空间,而不是仅在需要时才开辟存储空间。

  (6)、在同一源文件中,允许全局变量和局部变量同名。在局部变量的作用域内,全局变量不起作用。因此,若想在某函数中使用全局变量,就不能再定义一个同名的局部变量。

  (7)、全局变量的作用域可以通过关键字extern扩展到整个源文件或其他源文件。

3、C语言存储类型

  我们知道,变量是有数据类型的,用以说明它占用多大的内存空间,可以进行什么样的操作。

除了数据类型,变量还有一个属性,称为“存储类别”。 在进程的地址空间中,常量区、全局数据区和栈区可以用来存放变量的值。

常量区和全局数据区的内存在程序启动时就已经由操作系统分配好,占用的空间固定,程序运行期间不再改变,程序运行结束后才由操作系统释放;它可以存放全局变量、静态变量、一般常量和字符串常量。

栈区的内存在程序运行期间由系统根据需要来分配(使用到变量才分配内存;如果定义了变量但没有执行到该代码,也不会分配内存),占用的空间实时改变,使用完毕后立即释放,不必等到程序运行结束;它可以存放局部变量、函数参数等。

auto(自动的)、static(静态的)、register(寄存器的)、extern(外部的)。

  知道了变量的存储类别,就可以知道变量的生存期。通俗地讲,生存期指的是在程序运行过程中,变量从创建到销毁的一段时间,生存期的长短取决于变量的存储类别,也就是它所在的内存区域。

  3.1、概述

  3.1.1、静态存储和动态存储

静态存储和动态存储两种。

静态存储变量通常是在程序编译时就分配一定的存储空间并一直保持不变,直至整个程序结束,全局变量的存储方式即属于此类存储方式。

动态存储变量是在程序执行过程中使用它时才分配存储单元,使用完毕立即释放,典型的例子就是函数的形参,在函数定义时并不给形参分配存储单元,只是在函数调用时才予以分配,函数调用完立即释放,如果一个函数被多次调用,则反复地分配、释放形参变量的存储单元。

  静态存储变量是一直存在的,而动态存储变量是时而存在,时而消失。这种由于变量存储方式不同而产生的特性称为变量的生存期,生存期表示了变量存在的时间。

  生存期和作用域(全局变量、局部变量)是从时间和空间这两个不同的角度来描述变量的特性,这两者既有联系又有区别。

  变量的存储模型由作用域、链接点及存储期三大属性来描述。其中,存储期描述的是变量在内存中的生存时间。

  C语言中变量的链接点分为外部链接、内部链接、空连接三种:

  A、外部链接:外部链接的变量可以在多个文件中使用;

  B、内部链接:内部链接的变量只能在一个文件中使用;

  C、空链接:由定义变量的代码块作用域所私有;

  3.1.2、存储模型

  (1)自动(auto)(作用域为代码块、空链接、动态存储期)

auto为存储类说明符,可以说明一个变量为自动变量,该类具有代码块的作用域、空链接和动态存储期。若没有使用auto修饰,也属于自动存储类型。

  如果变量值没有初始化,他的值是不确定的。

  自动变量的特点:

  A、 自动变量的作用域仅限于定义该变量的模块内。在函数中定义的自动变量,只在该函数内有效。在复合语句中定义的自动变量,只在该复合语句中有效。

  B、 自动变量属于动态存储方式,只有在定义该变量的函数被调用时才给它分配存储单元,开始它的生存期。函数调用结束,释放存储单元,结束生存期。因此函数调用结束之后,自动变量的值不能保留。在复合语句中定义的自动变量,在退出复合语句后也不能再使用,否则将引起错误。

  C、 由于自动变量的作用域和生存期都局限于定义它的模块内(函数或复合语句内),因此不同的模块中允许使用相同的变量名,不会发生混淆。即使在函数内定义的自动变量也可以与该函数内部的复合语句中定义的自动变量同名,但读者应尽量避免使用这种方式。

  静态变量是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量,例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由static加以定义后才能成为静态外部变量,或称静态全局变量。对于自动变量,它属于动态存储方式。但是也可以用static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式。由此看来,一个变量可由static进行再说明,并改变其原有的存  储方式。对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。

  (2)静态、空链接

  静态变量的类型说明符:static。静态变量必须用static修饰。在一个代码块内使用存储类修饰符static声明的局部变量属于静态空链接存储类。该类型具有静态存储时期、代码块作用域和空链接。

  静态变量的存储空间是在编译完成后就分配了,并且在程序运行的全部过程中都不会撤销。这里要区别的是,属于静态存储方式的变量不一定就是静态变量。例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由static加以定义后才能称为静态外部变量,或称静态全局变量。

  文件作用域变量前面加关键字static就是内部链接,表明该变量为定义该变量的文件私有。

  静态变量可以分为静态局部变量和静态全局变量。

  静态局部变量属于静态存储方式,它具有以下特点:

  A、 静态局部变量在函数内定义,它的生存期为整个程序执行期间,但是其作用域任然与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后,尽管该变量还继续存在,但不能使用它。

  B、 可以对构造类静态局部变量赋初值,例如数组。若未赋初值,则由系统自动初始化为0.

  C、 基本数据类型的静态局部变量若在说明时未赋初值,则系统自动赋予0.而对自动变量不赋初值,其值是不确定的。根据静态变量的特点,可以看出他是一种生存期为整个程序运行期的变量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,他又可以继续使用,并且保留了上次被调用后的值。

  因此,当多次调用一个函数且要求在调用之前保留某些变量的值时,可以考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用静态局部变量为宜。

  (3)静态、内部链接

  全局变量在关键字之前在冠以static就构成了静态的局部变量,属于静态、内部链接存储类。与静态、外部链接存储类不同的是,具有内部链接,使得仅能被与它在同一个文件的函数使用。这样的变量也是仅在编译时初始化一次。如未明确初始化,它的字节被设定为0.

  这两种的区别在于非静态全局变量的作用域是整个源程序,但当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的;而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用。

  由于静态全局变量的作用域局限于一个源文件内,只能被该源文件内的函数使用,因此可以避免在其它源文件中引起错误。

  (4)静态、外部链接

  未使用static修饰的全局变量和函数属于静态、外部链接存储类。具有静态存储时期、文件作用域和外部链接。仍在编译时初始化一次。如未明确初始化,它的字节也被设定为0。

  在使用外部变量的函数中使用extern关键字来再次声明。如果是在其它文件中定义的,则必须使用extern声明引用外部变量。

  (5)寄存器

  在一个代码块内(或在一个函数头部作为参量)使用修饰符register声明的变量属于寄存器存储类型。register修饰符暗示编译程序相应的变量将被频繁使用,如果可能的话,应将其保留在CPU的寄存器中,从而加快其存取速度。该类与自动存储类相似,具有自动存储期、代码块作用域和空链接。如果没有被初始化,它的值也是不确定的。

  使用register修饰符有几点限制:

  A、 register变量必须是能被CPU寄存器所接受的类型,这通常意味着register变量必须是一个单个的值,并且其长度应小于或等于整形的长度。这与处理器的类型有关。

  B、 声明为register仅仅是一个请求,而非命令,因此变量仍然可能是普通的自动变量,没有放在寄存器中。

  C、 由于变量有可能存储在寄存器中,因此不能用取地址运算符“&”获取register变量的地址。如果有这样的写法,编译会报错。

  D、 只有局部变量和形参可以作为register变量,全局变量不行

  E、  实际上有些系统并不把register变量存放在寄存器中,而优化的编译系统则可以自动识别使用频繁的变量而把他们放在寄存器中

  3.2、auto, extern, register, static 四种存储类型

  c语言中的存储类型有auto, extern, register, static 这四种,存储类型说明了该变量要在进程的哪一个段中分配内存空间,可以为变量分配内存存储空间的有数据区、BBS区、栈区、堆区。下面来一一举例看一下这几个存储类型:

  3.2.1、auto存储类型

<auto>  <数据类型>  <变量名>

 auto 是自动或默认的意思,很少用到,因为所有的变量默认就是 auto 的。也就是说,定义变量时加不加 auto 都一样,所以一般把它省略,不必多次一举。例如“int n = 10;”与“auto int n = 10;”的效果完全一样。

  auto只能用来标识局部变量的存储类型,对于局部变量,auto是默认的存储类型,不需要显示的指定。因此,auto标识的变量存储在栈区中。示例如下:

#include <stdio.h>  
int main(void)  
{  
    auto int i=1; //显示指定变量的存储类型  
    int j=2;  
  
    printf("i=%d\tj=%d\n",i,j);  
  
    return 0;  
}

  3.2.2、static存储类型

  (1)static全局变量

  我们知道,全局变量和函数的作用域默认是整个程序,也就是所有的源文件,这给程序的模块化开发带来了很大方便,让我们能够在模块 A 中调用模块 B 中定义的变量和函数,而不用把所有的代码都集中到一个模块。

  但这有时候也会引发命名冲突的问题,例如在 a.c 中定义了一个变量 n,在 b.c 中又定义了一次,链接时就会发生重复定义错误,原因很简单,变量只能定义一次。

  如果两个文件都是我们自己编写的或者其中一个是,遇到这样的情况还比较好处理,修改变量的名字即可;如果两个文件都是其他程序员编写的,或者是第三方的库,修改起来就颇费精力了。

实际开发中,我们通常将不需要被其他模块调用的全局变量或函数用 static 关键字来修饰,static 能够将全局变量和函数的作用域限制在当前文件中,在其他文件中无效。

  使用 static 修饰的变量或函数的作用域仅限于当前模块,对其他模块隐藏,利用这一特性可以在不同的文件中定义同名的变量或函数,而不必担心命名冲突。

  (2)static局部变量

  static 除了可以修饰全局变量,还可以修饰局部变量,被 static 修饰的变量统称为静态变量(Static Variable)。

不管是全局变量还是局部变量,只要被 static 修饰,都会存储在全局数据区(全局变量本来就存储在全局数据区,即使不加 static)。

 全局数据区的数据在程序启动时就被初始化,一直到程序运行结束才会被操作系统回收内存;对于函数中的静态局部变量,即使函数调用结束,内存也不会销毁。

  注意:全局数据区的变量只能被初始化(定义)一次,以后只能改变它的值,不能再被初始化,即使有这样的语句,也无效。

  (3)总结

static 声明的变量称为静态变量,不管它是全局的还是局部的,都存储在静态数据区(全局变量本来就存储在静态数据区,即使不加 static)。其生命周期为整个程序,如果是静态局部变量,其作用域为一对{}内,如果是静态全局变量,其作用域为当前文件。

静态数据区的数据在程序启动时就会初始化,直到程序运行结束;对于代码块中的静态局部变量,即使代码块执行结束,也不会销毁。

  注意:静态变量如果没有被初始化,则自动初始化为0。静态数据区的变量只能初始化(定义)一次,以后只能改变它的值,不能再被初始化,即使有这样的语句,也无效。

  所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。

  static 变量主要有两个作用:

  1)隐藏

    程序有多个模块时,将全局变量或函数的作用范围限制在当前模块,对其他模块隐藏。

  2)保持局部变量内容的持久化

    将局部变量存储到全局数据区,使它不会随着函数调用结束而被销毁。

  3.2.3、extern存储类型

  我们知道,C语言代码是由上到下依次执行的,不管是变量还是函数,原则上都要先定义再使用,否则就会报错。但在实际开发中,经常会在函数或变量定义之前就使用它们,这个时候就需要提前声明。

 所谓声明(Declaration),就是告诉编译器我要使用这个变量或函数,你现在没有找到它的定义不要紧,请不要报错,稍后我会把定义补上。

  例如,我们知道使用 printf()、puts()、scanf()、getchar() 等函数要引入 stdio.h 这个头文件,很多初学者认为 stdio.h 中包含了函数定义(也就是函数体),只要有了头文件程序就能运行。其实不然,头文件中包含的都是函数声明,而不是函数定义,函数定义都在系统库中,只有头文件没有系统库在链接时就会报错,程序根本不能运行。

  (1) 函数的声明

我们在声明函数时并没有使用 extern 关键字,这是因为,函数的定义有函数体,函数的声明没有函数体,编译器很容易区分定义和声明,所以对于函数声明来说,有没有 extern 都是一样的。

  总结起来,函数声明有四种形式:

//不使用 extern
datatype function( datatype1 name1, datatype2 name2, ... );
datatype function( datatype1, datatype2, ... );
//使用 extern
extern datatype function( datatype1 name1, datatype2 name2, ... );
extern datatype function( datatype1, datatype2, ... );

  (2)变量的声明

变量和函数不同,编译器只能根据 extern 来区分,有 extern 才是声明,没有 extern 就是定义。

  变量的定义有两种形式,你可以在定义的同时初始化,也可以不初始化:

datatype name = value;
datatype name;

  而变量的声明只有一种形式,就是使用 extern 关键字:

extern datatype name;

  另外,变量也可以在声明的同时初始化,格式为:

extern datatype name = value;

  这种似是而非的方式是不被推荐的,有的编译器也会给出警告,我们不再深入讨论,也建议各位读者把定义和声明分开,尽量不要这样写。

  extern 是“外部”的意思,很多教材讲到,extern 用来声明一个外部(其他文件中)的变量或函数,也就是说,变量或函数的定义在其他文件中。

  extern 是用来声明的,不管具体的定义是在当前文件内部还是外部,都是正确的。

  extern用来声明在当前文件中引用当前项目中的其它文件中定义的全局变量。如果全局变量未被初始化,那么将被存在BBS区中,且在编译时,自动将其值赋值为0,如果已经被初始化,那么就被存在数据区中。全局变量,不管是否被初始化,其生命周期都是整个程序运行过程中,为了节省内存空间,在当前文件中使用extern来声明其它文件中定义的全局变量时,就不会再为其分配内存空间。

变量声明必须加extern,函数声明可以不加extern,这是因为,函数的定义有函数体,函数的声明没有函数体,编译器很容易区分定义和声明,所以对于函数声明来说,有没有 extern 都是一样的。

  变量和函数不同,编译器只能根据 extern 来区分,有 extern 才是声明,没有 extern 就是定义。

  变量声明必须加extern,函数声明在有的编译器里面可以不加extern,这是因为,函数的定义有函数体,函数的声明没有函数体,编译器很容易区分定义和声明,所以对于函数声明来说,有没有 extern 都是一样的。

  但是有些编译器以及在一些大型项目里,使用时一般的会将函数的定义放在源文件中不加extern,而将函数的声明放在头文件中,并且显示的声明成extern类型,使用此函数的源文件包含此头文件即可。

示例如下:

/*1.c*/
#include <stdio.h>  
int i=5; //定义全局变量,并初始化  
void test(void)  
{  
    printf("in subfunction i=%d\n",i);  
}
/*2.c*/
#include <stdio.h>  
extern i; //声明引用全局变量i  
int main(void)  
{  
    printf("in main i=%d\n",i);  
    test();  
    return 0;  
}

  3.2.4、 register存储类型

  声明为register的变量在由内存调入到CPU寄存器后,则常驻在CPU的寄存器中,因此访问register变量将在很大程度上提高效率,因为省去了变量由内存调入到寄存器过程中的好几个指令周期。

  一般情况下,变量的值是存储在内存中的,CPU 每次使用数据都要从内存中读取。如果有一些变量使用非常频繁,从内存中读取就会消耗很多时间,例如 for 循环中的增量控制:

int i;
for(i=0; i<1000; i++)
{
// Some Code
}

  执行这段代码,CPU 为了获得 i,会读取 1000 次内存。

  为了解决这个问题,可以将使用频繁的变量放在CPU的通用寄存器中,这样使用该变量时就不必访问内存,直接从寄存器中读取,大大提高程序的运行效率。

  不过寄存器的数量是有限的,通常是把使用最频繁的变量定义为 register 的。

  来看一个计算 π 的近似值的例子,求解的一个近似公式如下:

变量的作用域 python 变量的作用域有哪些_变量的作用域 python

  为了提高精度,循环的次数越多越好,可以将循环的增量控制定义为寄存器变量,如下所示:

#include <stdio.h>
#include <conio.h>
int main()
{
    register int i = 0; // 寄存器变量
    double sign = 1.0, res = 0, ad = 1.0;
    for(i=1; i<=100000000; i++)
    {
      res += ad;
      sign=-sign;
      ad=sign/(2*i+1);
  }
    res *= 4;
    printf("pi is %f", res);
  getch();
  return 0;
}

  运行结果:

pi is 3.141593

  关于寄存器变量有以下事项需要注意:

  (1)为寄存器变量分配寄存器是动态完成的,因此,只有局部变量和形式参数才能定义为寄存器变量。

  (2)局部静态变量不能定义为寄存器变量,因为一个变量只能声明为一种存储类别。

  (3 寄存器的长度一般和机器的字长一致,只有较短的类型如 int、char、short 等才适合定义为寄存器变量,诸如 double 等较大的类型,不推荐将其定义为寄存器类型。

  (4)CPU的寄存器数目有限,即使定义了寄存器变量,编译器可能并不真正为其分配寄存器,而是将其当做普通的auto变量来对待,为其分配栈内存。当然,有些优秀的编译器,能自动识别使用频繁的变量,如循环控制变量等,在有可用的寄存器时,即使没有使用 register 关键字,也自动为其分配寄存器,无须由程序员来指定。

  (5)、字符串常量

  字符串常量存储在数据区中,其生存期为整个程序运行时间,但作用域为当前文件,示例如下:

#include <stdio.h>  
  
char *a="hello";  
  
void test()  
{  
    char *c="hello";  
      
    if(a==c)  
        printf("yes,a==c\n");  
    else  
        printf("no,a!=c\n");  
}
int main()  
{  
    char *b="hello";  
    char *d="hello2";  

    if(a==b)  
        printf("yes,a==b\n");  
    else  
        printf("no,a!=b\n");      
    test();   
    if(a==d)  
        printf("yes,a==d\n");  
    else  
        printf("no,a!=d\n");  
    return 0;  
}
$ gcc -o test test.c  
$ ./test   
yes,a==b  
yes,a==c  
no,a!=d

  (6)、总结表

类型

作用域

生存域

存储位置

auto变量

一对{ } 内

当前函数

变量默认存储类型,存储在栈区

extern函数

整个程序

编译时分配,直至程序结束

函数默认存储类型,代码段

extern变量

整个程序

编译时分配,直至程序结束

初始化在data段,未初始化在BSS段

static函数

当前文件

编译时分配,直至程序结束

代码段

static全局变量

当前文件

编译时分配,直至程序结束

初始化在data段,未初始化在BSS段

static局部变量

一对{ }内

编译时分配,直至程序结束

初始化在data段,未初始化在BSS段

register变量

一对{ }内

当前函数

运行时存储在CPU寄存器中

字符串常量

当前程序

编译时分配,直至程序结束

数据段