(14)'c++:COMPLETE REFERENCE' 第一部分 第二章(表达式) 第六节

来源:互联网 发布:头文字d配乐 知乎 编辑:程序博客网 时间:2024/05/16 12:49

存储类别限制符

      c语言支持四种存储类别限制符:extern、static、register、auto。

      限制符告诉编译器如何存储其后的变量。通常格式如下:

      storage_specifier type var_name;

      需要注意的是,存储类别限制符必须书写在任何其它修饰符之前。

      注意:c++中比在c语言的基础上又增加了一个新的限制符mutable,我们将在第二部分描述。

extern

      在讲述extern之前,首先我们引入c/c++链接(linkage)的概念。c语言和c++定义了三种类型的链接:external、internal、none。通常,函数和全局变量都有外部链接。也就是说它们对于组成整个程序的所有文件都是可见的。被static限制符所限定的全局变量有内部链接,它只能在声明它的文件中可见。而本地变量都没有链接,它们仅仅在声明它们的代码块中有效。

      使用extern限制符限定的变量,应在程序的其它文件中被声明并且具有外部链接(external linkage)。首先在让我们来区分一下声明和定义(declaration and defination)的概念。declaration将会声明一个对象的名称和类型,而defination将会导致为该对象分配存储空间。所以,一个对象可以被多次声明,但是它只能被定义一次。大部分情况下,变量的声明和定义是重合的。使用extern限制符,就可以预先声明一个变量而不去定义它。因此,当你需要访问一个在程序其它部分中定义的变量的时候,你可以使用extern限制符。下面看一个例子:

#include <stdio.h>

int main( void )
{
    extern int first, last;  /* use global vars */
    printf( "%d%d", first, last );
    return 0;
}

/* global defination of first and last*/

int first = 10, last = 0;

      程序能正常运行,输出10和20。extern限制符告诉编译器,first和last变量将在程序的其它部分被定义。在本程序中,两个变量的定义在程序的底部。因为有了extern限制符做出的声明,尽管程序在first和last变量被定义之前就访问了这两个变量,该程序仍然能够正确的被编译运行。本程序中将first和last用extern声明为外部变量仅仅因为main()函数在这两个变量被定义之前就使用的它们。有个过程我们必须知道,当编译器发现某个代码块使用了在该块中未经声明的变量,编译器就会在之前所有声明的全局变量中寻找该名称的变量并使用。这时如果代码块中要使用在随后的程序中才声明的变量,那么就需要使用extern限制符。

      我们说过可以使用extern来声明一个变量但是并不定义它。然而,如果在使用extern声明变量的同时,又对变量赋值,那该声明同时也是对该变量的定义。这点很重要,必须记得一个变量可以多次声明但是仅能定义一次。extern的另外一个重要作用体现在多文件程序中的。c++允许一个程序被分解到多个文件中,分别编译这些文件再把它们链接在一起。这种情况下,每一个文件都需要能访问到程序的全局变量,于是extern就被派上用场。通常把所有全局变量都集中到一个文件中声明和定义,然后在其它用到它们的文件中用extern再次声明。如下:

File One

int x,y;
char ch;
int main( void )
{
    /* ... */
}

void func1( void )
{
    x = 123;
}

File Two

extern int x,y;
extern char ch;
void func2( void )
{
    x = y/10;
}

void func3( void )
{
    y = 10;
}

      这是一个在不同的文件中使用全局变量的例子。

静态变量(static variables)

      静态变量是一种在其所属的函数或者文件中能够持久保留的变量。和全局变量不同,它们在函数体外或者文件之外是不可见的。但是它们在每一次被调用之后始终保留,不会被清除。这个特性在书写可供他人使用的通用函数或者函数库的时候非常有用。static用在全局变量和本地变量上会有不同的效果。

静态本地变量(static local variable)

      当把static修饰符用在本地变量上时,编译器会为变量分配静态的存储空间,这和给全局变量分配空间的方式相同。静态本地变量与全局变量关键的区别是它们的作用区域(有效区域)不同,静态本地变量仅在声明它们的代码块中可见。简单的说,静态本地变量就是一种在函数多次调用之间能保留自己的值的本地变量。

      静态本地变量的一个重要用途就是在创建有独立功能的子程序的时候使用,因为有些类型的子程序需要在每次调用之间保存一些数据。

      如果不能使用static静态变量,那么就只能使用全局变量,就不能够避免全局变量可能产生的副作用了。下面有一个数字序列产生器的例子,它的作用是产生一系列的数字,后一个数字是基于前一个数字按照某一规则产生的。在这个例子中,我们可以采用全局变量来保存每一次产生的数字。然而如果这样,每一次在程序中需要产生数字序列的时候,都需要声明一个全局变量,并且还需要确保它没有和其它的全局变量产成冲突。所以,换一种更合理的办法,那就是把用来保存每次产生的数字的那个变量声明为静态本地变量,也就是使用static。

int series( void )
{
    static int series_num;

    series_num = series_num + 23;
    return series_num;
}

      在上面的例子中,每次函数调用之后,变量suries_num始终被保留,这和一般的本地变量有所不同。这样每一次调用函数就可以产生一个基于上一次函数调用产生的数字的新数字。这里避免了使用全局变量。

      可以对静态本地变量赋值,但是这个赋值操作仅会进行一次,赋值会在程序开始运行的时候只进行一次,而不同于普通本地变量的方式:在变量所属的代码块每次开始执行时进行。我们来看下面的例子:

int series( void )
{
    static int series_num = 100;

    series_num = series_num + 23;
    return series_num;
}

      在这个例子中,函数产生的数字序列每一次都会从一个固定的数字123开始。这样的函数也可能会适合在一些程序中使用,但是还有很多应用中都需要用户能自己输入数字序列的起点。一种方法是将保存数字的变量声明为全局变量,然后允许用户对它赋值。然而,不使用全局变量正是使用static本地变量的目标。这里引出static的第二种用法。

静态全局变量(static global variable)

      在全局变量前加static修饰符能指示编译器该全局变量的有效范围仅为它所被声明的文件。也就是说,尽管这个变量是全局变量,但是它对于程序的其它文件中的子程序是不可见的,这样就避免了可能的副作用。在个别情况下,静态本地变量不适合使用的时候,你可以创建一个小文件,这个文件仅仅包含静态全局变量和需要用到它的函数。然后单独编译整个文件,这样也就避免了全局变量产生其它副作用。

      我们把前面的例子重新编写,来演示静态全局变量是如何发挥作用的。新的例子中,可以利用series_start()函数来初始化数字序列的起点。

/* This must all be in one file - preferably by itself. */

static int series_num;
void series_start( int seed );
int series( void );

int series( void )
{
    series_num = series_num +23;
    return series_num;
}

/* initialize series_num */
void series_start( int seed )
{
    series_num = seed;
}

      这个新的程序的功能的叙述省略。

      让我们回顾一下:本地变量仅仅在声明它的代码块中可见;而静态全局变量只在它所在的文件中可见。如果把上面的两个函数都加入函数库,那么可以在其它地方使用这两个函数,但是并不能访问series_num变量,他对于程序的其它代码是不可见的。甚至你可以在程序的其它文件中声明一个同名称的变量,当然,只能是在其它文件中。本质上,static修饰符使得全局变量可以仅仅为需要用到它们的函数所用,而避免了副作用的产生。

      静态变量使得我们可以将程序的一个部分相对于其它部分隐藏起来。这可是个很大的优点,如果你需要编写庞大而复杂的程序。

      注意:在c++中,前述的static的用法仍然支持,但是不鼓励这样做。它已经被更优化的名字空间所取代,我们将在本书第二部分讲述名字空间。

寄存器变量(register variables)

      存储限制符register原本仅仅适用于int、char和指针变量。然而,现在register的定义被扩展了,它适用于所有的变量。

      最初,register限制符用来指示编译器将变量存储在cpu的寄存器中,而不是和其它变量一样存储在内存中。这样就可以在访问这种变量的时候有更高的效率,因为无需读写内存来获取或者改变它们的值。今天,register存储限制符的定义被扩展了。标准C仅仅指出对于register型的变量需要能最高效的访问。(c++标准则指出,register限制符用来暗示被它修饰的对象将在程序中使用非常频繁。)事实上,int和char类型的变量在register修饰下会被存储在寄存器中,而有些被register修饰符限定的变量,例如数组,显然不能够存储在寄存器中。不过,它们也会在编译的过程中被优先处理。根据各种c++编译器和它们的操作环境不同,寄存器变量对象可能有多种被处理的方式。实际上,编译器甚至可以忽略register修饰符,不过,通常不会这样。

      register存储限制符只能用在局部变量(本地变量)或者形式参数前,不能用于全局变量。下面还是来看一个例子,这个示例中用到了register修饰符。该函数用来计算整数的M^e。

int int_pwr( register int m, register int e )
{
    register int temp;

    temp = 1;
    for( ; e; e--)  temp = temp*m;
    return temp;
}

      这个例子中,m、e、temp三个变量都被声明为register变量,因为它们都会进入loop循环,也就是会被频繁的使用。实际上,register类型的变量都会在访问速度上被优化,以便于它们更好的适应loop等循环操作。register变量在被频繁使用的时候才能发挥最大的作用,明白这点很重要,因为你可以定义任意数量的register类型变量,但是并不是所有的这种变量的访问速度都会被优化。

      在任意代码块中,获得速度优化处理的寄存器变量的数目取决于程序运行的环境和具体的c++实现。不必担心定义了过量的寄存器变量,因为编译器会自动在寄存器变量的数目达到上限的时候把寄存器变量转化为非寄存器变量。(这点确保了代码在不同的处理器之间有广泛的移植性。)通常,至少有两个int或者char类型的寄存器变量可以被存储在cpu的寄存器中。如果需要知道是否有其它类型的优化选择,可以参考编译器的文档。

      在C语言中,不允许通过使用 & 操作符来获得寄存器变量的地址。(& 操作符会在后面讨论。)这样做的意义在于,寄存器变量会被优化存储在寄存器中,而寄存器是不能用地址定位的。在c++中,没有这种限制,不过,如果在c++中定位了一个寄存器变量的地址将使它不能被彻底的优化。尽管寄存器变量的概念已经比传统的概念扩展了,但在实际应用中,还是仅仅int和char类型的变量能获得最好的效果。因此,最好不要指望register应用于其它数据类型的变量时也能有实质性的速度优化。 

原创粉丝点击