前言
在分析static关键字作用之前,我们先看下static关键字修饰的变量的内存分配和初始化,示例代码如下:
示例1:
#include "stdafx.h" #include<iostream> using namespace std; static int s_nSum1 = 10; static int s_nSum2 = 11; int g_nTotal; int StaticVar(int nAdd) { static int s_nSum; //静态局部变量 s_nSum += nAdd; g_nTotal = 10; return s_nSum; } int _tmain(int argc, _TCHAR* argv[]) { for (int i = 0; i < 5; i++) { printf("StaticVar(%d)= %d ",i, StaticVar(i)); } return 0; }第一步:在main函数的第一个语句打断点,即for语句。
第二步:按Alt+8 快捷键调出该文件的反汇编窗口,其StaticVar函数内容如下。
int StaticVar(int nAdd) { 011113C0 push ebp 011113C1 mov ebp,esp 011113C3 sub esp,0C0h 011113C9 push ebx 011113CA push esi 011113CB push edi 011113CC lea edi,[ebp-0C0h] 011113D2 mov ecx,30h 011113D7 mov eax,0CCCCCCCCh 011113DC rep stos dword ptr es:[edi] static int s_nSum; //静态局部变量 s_nSum += nAdd; 011113DE mov eax,dword ptr [s_nSum (1117158h)] 011113E3 add eax,dword ptr [nAdd] 011113E6 mov dword ptr [s_nSum (1117158h)],eax g_nTotal = 10; 011113EB mov dword ptr [g_nTotal (111715Ch)],0Ah return s_nSum; 011113F5 mov eax,dword ptr [s_nSum (1117158h)] }可以看出在调用StaticVar函数之前静态局部变量s_nSum和全局变量g_nTotal已经分配了内存空间,其地址分别是0x1117158和0x111715c,而局部变量nAdd并没有看到相关的地址。
第三步:调用VS内存查看窗口,并分别输入上述两个地址,可以发现编译器会自动为它们赋初值0。
结论:
从 以上操作结果可以看出,静态变量(静态局部变量和静态全局变量)和全局变量是在编译的时候就已经赋予内存空间和初始值,而局部变量是在运行的时候才分配内存空间。
另外,需要对静态变量和全局变量做如下说明:
1、静态变量和全局变量都是在全局区(静态区)分配内存空间,在整个程序运行期间都不释放,直到程序结束运行。
2、静态变量和全局变量只在编译的时候完成一次赋初值。
静态变量
静态局部变量
有时候希望函数中的局部变量的值在函数调用结束后不消失而保留原值,即其占用的内存单元不释放,在 下一次该函数调用时,该变量保留上一次函数调用结束时的值。这时候我们应该指定该局部变量为静态局部变量(static local varible),例如:
示例2:
int StaticVar(int nAdd) { //静态局部变量,从生命周期角度看待,内存单元直到程序结束才释放 static int s_nSum; s_nSum += nAdd; //g_nTotal = 10; return s_nSum; } int AutoVar(int nAdd) { //自动局部变量,离开函数后,就释放内存 int nSum = 0; nSum += nAdd; return nSum; } int _tmain(int argc, _TCHAR* argv[]) { for (int i = 0; i < 5; i++) { printf("StaticVar(%d)= %d ",i, StaticVar(i)); } for (int i = 0; i < 5; i++) { printf("AutoVar(%d) = %d ",i, AutoVar(i)); } printf(" "); return 0; }
运行结果:
静态全局变量
有时在程序设计时,希望某些外部变量只限定在本文件引用,而不被其他文件引用,这时可以在定义外部变量时添加一个static声明,在示例1代码中我们定义了全局变量s_nSum2和g_nTotal,我们想在另一个文件中引用这两个变量,我们可以采用extern关键字去声明。例如:
//File1.cpp static int g_nTotal; static int s_nSum2 = 10; int main() { .... } //File2.cpp extern int g_nTotal; extern int s_nSum2; int test() { cout <<"g_nTotal" << g_nTotal <<endl; cout <<"s_nSum2 " << s_nSum2 <<endl;//编译错误,无法解析的外部符号 "int s_nSum2" }但是,从运行结果来看,用static关键字声明的全局变量,在其他文件不能正确的识别。说明被static关键字修饰的变量被限制了可见范围,即只能在本文件被引用。
静态函数
函数从本质来说是全局的,因为一个函数要被另外一个函数调用。但是我们也可以指定函数只能被本文件调用,而不能被其他调用其他文件。因此,可以将函数区分为内部函数(静态函数)和外部函数,内部函数是用关键字static声明,外部函数以extern声明,一般情况下,我们都是省略extern关键字。
静态函数声明示例如下:
static int func(int a, int b)示例3:
//File1.cpp void test() { printf("test in file1.cpp"); } //File2.cpp void test() { printf("test in file2.cpp"); }
我们在两个文件中都定义了test函数,我们点击编译运行,编译器将会报错,提示是“void __cdecl test(void)" (?test@@YAXXZ) 已经在xxx中定义”,因为这两个函数都是全局属性,如果我们将其中一个test函数中添加static关键字,编译将会正常。
结论:
对于具有全局属性的函数而言,static关键字限制了被修饰函数对外的可见性。
在c++中引入了类的概念,我们也可以为类中的数据成员和成员函数添加static关键字,在类中用static关键字修饰的数据成员和成员函数,有了其他的含义,下面一一介绍。
静态数据成员
静态数据成员是一种特殊的数据成员,它以关键字static开头。例如:
class CBox { public: int volume() { return m_nHeight * m_nWidth * m_nLength; } CBox():m_nLength(0), m_nWidth(0){}; CBox(int nLengh, int nWidth):m_nLength(0), m_nWidth(0){}; public: static int m_nNum;//静态数据成员 private: static int m_nHeight;//m_nHeight是私有的静态数据成员 int m_nWidth; int m_nLength; }; //静态数据成员的初始化,同时赋初值10和0.没有这一句程序编译错误 int CBox::m_nHeight = 10; int CBox::m_nNum = 0;
下面从多角度解析静态数据成员。
1.多个对象之间的数据共享。
静态数据成员不属于某个对象,而是属于这个类,静态数据成员在内存中只占用一份,所有的对象都可以引用和修改它。一个对象修改了它,则其他对象的值也都跟随发生变化。
2.内存分配。
静态数据成员在所有对象之外单独开辟空间,不占用类对象的空间。只要类中声明了静态数据成员,则该成员在程序编译的时候就已经分配了内存空间。
3.生命周期
静态数据成员不随对象的建立和分配空间,也不随对象的撤销而释放空间。它是在程序编译时分配空间,程序运行结束才释放。
4.初始化
静态数据成员可以初始化,但只能在类体外进行初始化工作,没有初始化程序编译错误。若初始化时,没有赋初值,编译器自动赋值为0.
格式如下:
数据类型 类名::静态数据成员 = 初值。
注意:不能用参数初始化列表进行静态数据成员的初始化。例如:
CBox():m_nHeight(0),m_nLength(0), m_nWidth(0){};//错误,m_nHeight是静态数据成员 int CBox::m_nHeight = 0;//正确5.静态数据成员的引用
静态数据成员可以通过对象名引用,也可以通过类名来引用,如果该静态成员是私有的,则不能在类外访问。
CBox box; cout << CBox::m_nHeight << endl;//private不能类外访问 cout << CBox::m_nNum << endl; //public 正确 cout << box.m_nHeight << endl; //private不能类外访问 cout << box.m_nNum << endl; //public 正确
静态成员函数
成员函数也可以定义为静态的,就是在函数声明时加上关键字static。例如:
static void Output();下面也从多角度说明静态成员函数。
1.内存分配
静态成员函数是类的一部分,而不是对象的一部分。在类加载的时候,编译器就已经为静态成员函数分配空间。
2.静态成员函数引用
假设该静态成员函数是公用的,则可以在类外通过类名和域运算符“::”加以调用,也可以通过对象名调用该静态成员函数。例如:
CBox box; box.Output1(); //对象名,本质是使用对象的类型(CBox) CBox::Output1();//类名和域作用符3.静态成员函数的作用
静态成员函数的作用是为了能够处理静态成员数据,静态成员函数一般只用来处理静态成员数据,调用非静态数据成员,将会出现错误。例如:
static void Output1() { //正确 引用静态成员函数 cout << m_nNum << endl; } static void Output3() { //错误,对非静态成员“CBox::m_step”的非法引用 //编译期间m_step还未创建,而函数Output3已经分配好内存 cout << m_nNum + m_step << endl; //静态成员函数想正确的调用非静态数据成员,可以使用入参的形式。 }然而,非静态成员函数却可调用静态数据成员。例如:
void Output2() { cout << m_nNum << endl; //正确。 }
4.静态成员函数和非静态成员函数的区别
非静态成员函数有this指针,可以进行默认调用的成员函数,而静态成员函数却没有this指针,无法知道使用哪个对象的数据成员,因此不能进行默认调用。例如:
static void Output3() { //错误,对非静态成员“CBox::m_step”的非法引用 //编译期间m_step还未创建,不能进行默认调用 cout << m_step << endl; } void Output4() { //正确,m_step 等价于this->m_step,属于默认调用 cout << m_step<< endl; }
现在对C/C++中的static关键字做基本总结: