最近在Leecode上见到很多关于静态变量,常量,全局变量等相关的题,于是结合一些Leecode例题和《C++编程思想》整理了这篇博客,详细解释了下它们的区别和用法。
1. 全局变量
全局变量是在所有函数体的外部定义的,程序的所有部分(其他文件的代码)都可以使用。全局变量不受作用域的影响。全局变量的生命周期一直到程序的结束,全局变量是静态存储方式。如果在一个文件中使用extern关键字来声明另一个文件中存在的全局变量,那么这个文件可以使用这个变量。通常都是在一个.CPP文件的开始声明这个变量,在其他需要用到这个变量的地方其头文件也就是.h文件中extern type vari;
2. 局部变量
局部变量出现在一个作用域内,它们是局限于一个函数的。局部变量经常被称为自动变量,因为它们在进入作用域时,自动生成,离开作用域时自动消失。关键字auto可以显示的说明这个问题,但局部变量默认为auto,所以没必要声明为auto。通常函数里定义的变量,函数的参数都局部变量。所以一般需要在某个函数里,返回数组的地址或者指针之类,这时候必须定义成static,或者动态申请内存,这样函数结束返回时才不会释放掉那块内存空间。
寄存器变量也是一种局部变量。register来修饰,就是告诉编译器尽可能快的访问这个变量,通过存放在寄存器中来实现快速访问。
3. 静态变量
静态变量的修饰关键字是static,static可以作用于变量以及函数。由static修饰的,可分为静态局部变量,静态全局变量,静态函数。静态变量的生命期和程序生命期是一样的,在程序结束之后操作系统会回收空间。 静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量。 例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 static加以定义后才能成为静态外部变量,或称静态全局变量。
!C++中可以在类的数据成员或成员函数之前加上static,这样定义的数据成员或成员函数就被类所拥有,而不再属于类的对象。如下:
<span style="font-size:14px;">class A
{
char c;
int a;
static int b;
}a ;
//X86机器下,sizeof(a)=8 (字节对齐是4的倍数,静态成员变量不算)</span>
<1>静态局部变量
在局部变量前面加上static后,就定义了静态局部变量,静态局部变量属于静态存储方式,静态局部变量只会被初始化一次,下次使用依据上一次保存的值。它具有以下特点:
(1) 静态局部变量在函数内定义 ,它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它。(作用域不变,生存周期变了)
(2) 允许对构造类静态局部量赋初值 ,例如数组,若未赋以初值,则由系统自动赋以0值。(而非静态局部变量未赋初始值,系统会给个随机值)
(3) 对基本类型的静态局部变量,若在说明时未赋以初值,则系统自动赋予0值。而对自动变量没有赋初值,则其值是随机值。
静态局部变量是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了上次被调用后留下的值。 因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用局部静态变量为宜。
<span style="font-size:14px;">//静态局部变量
int Fun(){
static int n = 0;
++n;
return n;
}
for(int i = 0; i < 5; i++){
cout<<Fun()<<" ";
}
/*
输出结果是
1 2 3 4 5 //这里只初始化一次,每次调用都是依据上一次保存的值。</span>
<2> 静态全局变量
全局变量(外部变量)的说明之前再加static 就构成了静态全局变量。static全局变量只初使化一次,防止在其他文件单元中被引用。全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。 这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用, 因此可以避免在其它源文件中引起错误。
综上:把局部变量改变为静态局部变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态全局变量是改变了它的作用域, 限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。
//测试静态全局变量
<span style="font-size:14px;">
staticint value;voidfun(){value= 0;++value;}for(inti = 0; i < 5; i++){fun();cout<<value<<"";}/*输出1 1 1 1 1 。静态全局变量可以被多次赋值为0.</span>
<3> static 函数
Statci函数和普通函数作用域不同,它的作用只在定义它的本文件中。如果只在当前源文件中调用的函数,应说明为内部函数(static)。下面说下内部函数和外部函数:
内部函数和外部函数:当一个源程序由多个源文件组成时,C语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。
(1) 内部函数(又称静态函数)
如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:
static 函数类型 函数名(函数参数表) {}
内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
(2) 外部函数
外部函数的定义:在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数:
[extern] 函数类型 函数名(函数参数表) {}
调用外部函数时,需要对其进行说明:
[extern] 函数类型 函数名(参数类型表)[,函数名2(参数类型表2)……];
static函数和普通函数的区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份复制品。在类中,静态成员函数不需要类的对象来调用,直接类调用:
classA
{
static voidtest() {}
};
A::test();
静态成员函数只能访问静态成员变量,静态成员变量必须单独初始化。静态成员函数可定义为inline函数。
<4> 类的static 成员和函数
(1)当将类的某个数据成员声明为static时,该静态数据成员只能被定义一次,而且要被同类的所有对象共享。各个对象都拥有类中每一个普通数据成员的副本,但静态数据成员只有一个实例存在,与定义了多少类对象无关。静态方法就是与该类相关的,是类的一种行为,而不是与该类的实例对象相关。
(2) 静态数据成员不能在类中初始化,也不能在类的构造函数中初始化该成员,因为静态数据成员为类的各个对象共享,否则每次创建一个类的对象则静态数据成员都要被重新初始化。
(3) 静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的。你在一个对象里给它赋值,其他对象里的该成员也会发生变化。为了避免混乱,所以不可在类体内进行赋值。
(4) 静态成员的值对所有的对象是一样的。静态成员可以被初始化,但只能在类体外进行初始化。
(5) 它在对象中不占用存储空间,这个属性为整个类所共有,不属于任何一个具体对象
(4)
(5) 它在对象中不占用存储空间,这个属性为整个类所共有,不属于任何一个具体对象
(6)静态函数成员必须通过对象名来访问非静态数据成员。另外,静态成员函数在类外实现时候无须加static关键字,否则是错误的。
(7)静态成员函数可以直接访问该类的静态数据和函数成员,而访问非静态数据成员必须通过参数传递的方式得到一个对象名,然后通过对象名来访问。
static关键字至少下列几个作用:
(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
4. 外部变量
在全局变量那已经提了external关键字,它就是告诉编译器存在一个变量或函数,即使在当前的编译的文件中没有看到它,这个变量或函数可能在另一个文件中或者在当前文件的后面定义。
!!!如果一个变量i,它已经被定义为static,限定了作用域,这时你再用extern定义它是全局的,编译器就会报错。
5. 常量
在旧版C中,通常建立一个常量,必须使用预处理器:
#define PI 3.14159
而C++用const来修饰常量,并加进了标准C中。一旦定义了常量,就不能再去修改它的值。如果初始化没给常量赋值,那它就是一个随机数,并且以后也不能给它赋值。C和C++中const的使用还是不同的,虽然const标记着“不会被改变”(见下题)。
<span style="font-size:14px;">在c++中,
const int i=0 ;
int* j = (int *)&i;
*j =1;
Printf(“ %d ,%d”,i,*j);
输出是多少?
A 0,1
B 1,1
C 1,0
D 0,0</span>
答案是A,C++中i被定义为常量,在编译阶段就将常量i相当于宏定义,确定了它的初始值,C++在后面输出i时只是将i用初始值来做替换,和宏定义一样,而没有去读i的内存的值,所以输出i为0,而j是访问i的内存里的值,这里其实已经被改为1 了!而C中是运行时,const才确定值,将会输出它的值(被改变的),上题在C中运行结果就是1,1
大家可能对于const 修饰指针有点迷惑,下面几个:
<span style="font-size:14px;">int b=1;
int c=2;
const int* a1=&b;
int const* a2=&b;
int * const a3=&b;
const int* const a4=&b;
a1=&c; //这三个都正确
a2=&c;
*a3=3;</span>
a1和a2一样,const都是修饰指针所指向的变量,即指针指向是常量,所以不能修改指针指向的内容,但可以修改指针的本身。
a3中const修饰的是指针本身,即指针本身是常量。所以不能修改指针本身,但可以修改指针指向的内容。
a4中const int* const类型。即不能修改指针本身也不能修改指向的内容。
const关键字至少以下作用:
(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的 成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
小结:从存储空间分配来说,全局变量、静态全局变量、静态局部变量都是在静态存储区(全局数据区)中分配空间的,而局部变量是在栈上分配空间的。常量存储在常量区。全局/静态存储区,是在程序编译时就已经分配好的,在整个运行期间都存在。而栈区系统自动释放(静态分配内存),预编译阶段就确定好了大小,栈区存放函数的参数值、局部变量的值。而new,malloc是在堆区申请内存,然后手动通过delete,free释放掉。不过要注意区分,new/delete是运算符,会调用构造和析构函数,而malloc/free是库函数,不会调用构造和析构。
关于数据段:
BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。
代码段:代码段(code segment/text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。
堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。