变量的时空限制

变量作用域(空间限制)

变量作用域描述了此变量名在文件(翻译单元)的多大范围可见。变量的作用域与程序块(block)有关。

例如,在函数中定义的变量可以在该函数中使用,但不能在其他函数中使用;而在文件中的函数定义之前定义的变量则可在所有函数中使用。


局部变量和全局变量

我们可简单将定义的变量分为两类: 局部变量和全局变量

  • 局部变量(Local Variable)

    • 在程序块内(函数、复合语句)中定义的变量

    • main()函数中定义的变量也是局部变量(main函数也是一个函数,只不过对于运行环境来说特殊一些)

  • 全局变量(Global Variable )

    • 在所有函数之外定义的变量

    • 作用范围:从定义位置到文件结束

    • 作用:方便函数间的数据传递

    • 如在作用范围外的函数要使用此变量,用关键词extern在函数内说明此全局变量。(比如要在另一个文件中引用此变量)

//file 作用域示例.c

void Function();
int x = 1; int y = 2;					//全局变量
extern int z = 3;						//可被外部文件链接的全局变量

int main()
{   int i=0;							//作用域为main()的局部变量
    cout <<x << “, ”<<y<<endl;
    Function();
    return 0;
}

void  Function()
 {   int a = 0; 						//作用域为Function()的局部变量
     cout <<x << “, ”<<y<<endl;
 }

规则

变量作用域遵循以下几个规则:

  • 在程序块(block)中说明的变量是局部的,仅能在本块中和内部的块中存取。

    如下程序中的i作用域只在for循环内(for是一个程序块而非函数),当跳出for循环后,i变量就无法被外部的main()读取到。

    int main()
     {   int sum = 0, m;									//		sum,m作用域
         for (int i=0; i<5; i++)			// 	  i作用域					|
         {									//		|					  |
             cout<< "input a nmuber:";		//		|					  |
             cin>>m;						//		|					  |
             sum = sum + m;					//    	|				      |
         }									//	  i作用域					|
         cout << "sum = "<< sum << endl;					//			  |
         return 0;											//		sum, m作用域
    }
    
  • 当内部块与外部块有同名变量时,在内部块中屏蔽外部块的同名变量。(简单来说,就近原则,看最近的定义)

    • 当局部变量与全局变量同名时,会按照就近原则选取变量,如main()函数中没有定义x,y变量,则选取全局定义的x,y变量值。而Function()函数中重新定义了局部变量x,y,因此在Function()函数中就直接用定义局部变量的值。可见下程序运行结果。

    void Function();
    int x = 1; int y = 2;
    
    int main()
    {
        cout <<"x="<<x<<"  y=" <<y<<endl;
        Function();
        return 0;
    }
    
    void  Function()
    {   int x=2, y=1 ;
         cout <<"x="<<x<<"  y=" <<y<<endl;
    }
    
    //output 
    //x=1 y=2
    //x=2 y=1
    
    • 如果目前的程序块中有局部变量与程序的全局变量重名,那么如何调用全局变量?使用作用域限定符::

    // C++ program to show that we can access a global
    // variable using scope resolution operator :: when
    // there is a local variable with same name
    #include<iostream>
    using namespace std;
    
    // Global x
    int x = 0;
    
    int main()
    {
        // Local x
        int x = 10;
        cout << "Value of global x is " << ::x;
        cout<< "\nValue of local x is " << x;
        return 0;
    }
    
    • 当实际参数与形式参数同名时,可见下程序运行结果。形参值改变不影响与其同名的实参值

    void Function(int x,  int y) ;
    
    int main()
    {   
        int x = 1; int y = 2;
        cout <<"x="<<x<<"  y=" <<y<<endl;
        Function(x,y);
        return 0;
    }
    
    void Function(int x,  int y)
     {
         x=2; y=1 ;
         cout <<"x="<<x<<"  y=" <<y<<endl;
     }
    
    //output 
    //x=1 y=2
    //x=2 y=1
    
    • 注意形式参数不可以和局部变量同名。也就是在函数内不能定义与形式变量同名的变量。

  • 在一个函数中,我们不能存取主调函数(caller)的变量,即使知道该变量的名字。

    比如函数A调用了函数B,那在函数B中也无法调用函数A中定义的变量(即使知道该变量的名字)。

以上总结一下:

  • 在程序块(block)中说明的变量是局部的,仅能在本块中和内部的块中存取。

  • 当内部块与外部块有同名变量时,在内部块中屏蔽外部块的同名变量。

  • 在一个函数中,我们不能存取主调函数(caller)的变量,即使知道该变量的名字。


全局变量使用须知

  • 一般使用的情况

    • 当多个函数必须共享同一个变量时

    • 当少数几个函数必须共享大量数据时;

  • 全局变量破坏了模块化,建议尽量少使用!

  • 当全局变量和局部变量同名时,在局部变量的作用范围中全局变量被屏蔽。如果要使用全局变量,可以使用作用域运算符::

  • 全局变量的使用将在模块化设计中详细介绍。

变量生存时限(时间限制)

变量的生存时限主要涉及到static关键字的使用。也就是静态(static)变量

我们知道一般在调用函数时,被调用函数内部的变量都是重新初始化的,这保证了每一次调用函数都能得到相同的结果,使函数变得通用(这也是我们定义函数的意义)。这样被调用函数中的变量生存时间就很短,在每次被调用时初始化,调用结束时被回收。

如下程序中的count变量,在main()函数每次调用demo()时重新定义初始化,并在调用结束后被回收。这样的过程总共持续了5次。因此我们可以认为在程序执行过程中出现了五个count变量(或许称为count1 count2 count3 count4 count5)。它们的行为都是相同的。

#include <iostream>
#include <string>
using namespace std;

void demo()
{
	int count = 0;
	cout << count << " ";
	count++;
}

int main()
{
	for (int i=0; i<5; i++) 
		demo();
	return 0;
}

//output
//0 0 0 0 0

但有时我们需要函数中的变量在整个运行程序中都持续存在,比如要记录调用了demo()函数的次数,此时我们就要延长count变量的生存时间,不让其在demo()被调用完后就被回收。

static关键字就起到这个作用,在修饰变量的时候,static修饰的静态局部变量只执行初始化一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放,且不改变作用域。如下程序

// C++ program to demonstrate
// the use of static Static
// variables in a Function
#include <iostream>
#include <string>
using namespace std;

void demo()
{
	// static variable
	static int count = 0;
	cout << count << " ";
	// value is updated and
	// will be carried to next
	// function calls
	count++;
}

int main()
{
	for (int i=0; i<5; i++) 
		demo();
	return 0;
}

//output
//0 1 2 3 4

静态变量使用须知

  1. 未被程序员初始化的静态变量都由系统初始化为0

  2. 局部静态变量的初值是编译时赋的。当运行时重复调用此函数时,不重复赋初值。

  3. 虽然局部静态变量在函数调用结束后仍然存在,但其他函数不能引用它

  4. 局部的静态变量在程序执行结束时消亡

  5. static还可以用在函数定义或说明中。该函数只能被用于本源文件中,其他源文件不能调用此函数(达到了隐藏的作用)

C++内存布局(Memory Layout)

static变量只是C++存储类别中的一种,因为存储类别的不同,定义的变量将会有不同的特性,如static variable的主要特性就在前介绍了。

存储类别

  • 在C++语言中 ,每个变量有两个属性:

    • 数据类型:变量所存储的数据类型

    • 存储类别:变量的存储位置和期限

  • 标准的变量定义: 存储类别    数据类型    变量名static  int  count

  • 存储类别主要有四类

    • 自动变量 auto

    • 静态变量 static

    • 寄存器变量 register

    • 外部变量 extern


内存布局

之所以称为存储类别,是因为在一个C++语言生成的可执行文件在内存中有不同的存储位置。

典型的可执行文件可分为两部分

  • 代码段(.text),由编译后的机器指令组成,为只读

  • 数据段:

    • 初始化的数据段(initialized data segment):也称.data 段,需要与内存进行数据交互

    • 未初始化的数据段(uninitialized data segment):也称.bss( block stated by symbol )段,此段的变量只有名称和大小,没有值(故无需内存数据交互)

    • 栈(.stack):存储动态数据

    • 堆(.heap):动态内存布局(由C++中的new)


下面介绍各个不同存储类别的变量在内存不居中的位置及其特性

  • 自动变量 auto

    在函数内或块内定义的变量缺省时是auto。如auto  int i等价于 int i; 也就是说我们平时定义的变量都是自动变量,其有以下特性

    • 当函数被调用时,系统为自动变量分配空间(栈中以桢的形式保存)

    • 当退出时,系统释放分配给自动变量的值

    • 当再次进入该块时,系统重新分配空间

    简单来说,就是auto变量存储在栈(.stack)中,每次被调用时被分配到栈中,调用结束就出栈。

    *注意C++11后auto关键字被用于自动推断变量类型,自动变量无需再用auto*说明

  • 静态变量 static

    静态变量就是在整个程序运行期间都存在的变量,当程序开始运行时就加载到内存中,程序完全退出时一起释放内存。

    • 存储位置:.bss.data区别在于其是否初始化。

    • 静态的局部变量:离开函数后,值仍保留

    • 静态的全局变量:作用域为整个文件,就算有extern也无法跨文件访问,会触发linkage error(也就是作用域为此文件,其他文件无法引用)

  • 寄存变量 register

    代替自动变量或形参,可以提高变量的访问速度(因为处理器访问寄存器的速度是最快的)。

    • 存储位置:寄存器

    • 如无合适的寄存器可用,则编译器把它设为自动变量。

    • 只有局部自动变量才能定义为寄存器变量,全局变量和静态局部变量不能定义。

  • 外部变量 extern

    声明一个不在本模块作用范围内的全局变量。主要用于多文件编译程序,目前大家都只用一个文件运行可能不需要考虑这个类型的变量,但在较大工程中的代码规范必然会用到extern变量。

    • 在某函数中引用了一个声明在本函数后的全局变量时,需要在函数内用extern声明此全局变量

    • 当一个程序有多个源文件组成时,用extern可引用另一文件中的全局变量。