(转)C/C++中static关键字
下面的转自
http://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777441.html
静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为0,使用时可以改变其值。
静态变量或静态函数只有本文件内的代码才能访问它,它的名字在其它文件中不可见。
用法1:函数内部声明的static变量,可作为对象间的一种通信机制
如果一局部变量被声明为static,那么将只有唯一的一个静态分配的对象,它被用于在该函数的所有调用中表示这个变量。这个对象将只在执行线程第一次到达它的定义时初始化。
用法2:局部静态对象
对于局部静态对象,构造函数是在控制线程第一次通过该对象的定义时调用。在程序结束时,局部静态对象的析构函数将按照他们被构造的相反顺序逐一调用,没有规定确切时间。
用法3:静态成员和静态成员函数
如果一个变量是类的一部分,但却不是该类的各个对象的一部分,它就被成为是一个static静态成员。一个static成员只有唯一的一份副本,而不像常规的非static成员那样在每个对象里各有一份副本。同理,一个需要访问类成员,而不需要针对特定对象去调用的函数,也被称为一个static成员函数。
类的静态成员函数只能访问类的静态成员(变量或函数)。
进一步详细解释如下:
1.先来介绍它的第一条也是最重要的一条:隐藏
当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是a.c,另一个是main.c. 下面是a.c的内容:
1 char a = 'A'; // global variable 2 void msg() { printf("Hello\n"); }
下面是main.c的内容:
1 int main(void) 2 { 3 extern char a; // extern variable must be declared before use 4 printf("%c ", a); 5 (void)msg(); // 也需要extern一下 6 return 0; 7 }
程序的运行结果是:
A Hello
你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
2. static的第二个作用是保持变量内容的持久
存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见,但我还是举一个例子。
1 #include <stdio.h> 2 int fun(void){ 3 static int count = 10; // 事实上此赋值语句从来没有执行过 4 return count--; 5 } 6 int count = 1; 7 int main(void) { 8 printf("global\t\tlocal static\n"); 9 for (; count <= 10; ++count) 10 printf("%d\t\t%d\n", count, fun()); 11 return 0; 12 }
程序的运行结果是:
global local static 1 10 2 9 3 8 4 7 5 6 6 5 7 4 8 3 9 2 10 1
3. static的第三个作用是默认初始化为0.其实全局变量也具备这一属性,因为全局变量也存储在静态数据区
在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加‘\0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是‘\0’。不妨做个小实验验证一下。
1 int a; 2 int _tmain(int argc, _TCHAR* argv[]) 3 { 4 int i; 5 static char str1[10]; 6 char str2[10]; 7 printf("integer: %d; string: (begin)%s(end)\n", a, str1); 8 printf("integer: %d; string: (begin)%s(end)\n", a, str2); 9 return 0; 10 }
程序的运行结果如下
最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0.
4. 用static声明的函数和变量小结
static 声明的变量在C语言中有两方面的特征:
1)、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。
2)、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。
Tips:
A.若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
D.如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的函数)
E.函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。
函数前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。
扩展分析:
全局变量、静态全局变量、静态局部变量和局部变量的区别
变量可以分为:全局变量、静态全局变量、静态局部变量和局部变量。
(1) 按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。
(2) 按作用域分,全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。
把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期(函数退出后消亡变成在整个程序运行期间均存在)。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围(其他文件也可用变成只能在当前文件中可用)。
(1) static 函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件
(2) static全局变量与普通的全局变量有什么区别:static全局变量只初始化一次,防止在其他文件单元中被引用;
(3) static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
(4) static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝.
(5) 全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。
下面的转自:
http://blog.csdn.net/hackbuteer1/article/details/7487694
C++的static有两种用法:面向过程程序设计中的static和面向对象程序设计中的static。前者应用于普通变量和函数,不涉及类;后者主要说明static在类中的作用。
一、面向过程设计中的static
1、静态全局变量
在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。我们先举一个静态全局变量的例子,如下:
1 #include<iostream> 2 using namespace std; 3 4 static int n; //定义静态全局变量 5 6 void fn() 7 { 8 n++; 9 cout<<n<<endl; 10 } 11 12 int main(void) 13 { 14 n = 20; 15 cout<<n<<endl; 16 fn(); 17 return 0; 18 }
静态全局变量有以下特点:
- 该变量在全局数据区分配内存;
- 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);
- 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;
静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下图:
代码区 |
全局数据区(全局变量,静态数据-包括函数内部的静态局部变量) |
堆区(new) |
栈区(函数内部的自动变量) |
一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。细心的读者可能会发现,Example 1中的代码中将
static int n; //定义静态全局变量
改为
int n; //定义全局变量
程序照样正常运行。
的确,定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:
静态全局变量不能被其它文件所用;
其它文件中可以定义相同名字的变量,不会发生冲突;
您可以将上述示例代码改为如下:
1 //File1 2 #include<iostream> 3 using namespace std; 4 5 void fn(); 6 static int n; //定义静态全局变量 7 8 int main(void) 9 { 10 n = 20; 11 cout<<n<<endl; 12 fn(); 13 return 0; 14 } 15 16 //File2 17 #include<iostream> 18 using namespace std; 19 20 extern int n; 21 22 void fn() 23 { 24 n++; 25 cout<<n<<endl; 26 }
编译并运行这个程序,您就会发现上述代码可以分别通过编译,但运行时出现错误。试着将
static int n; //定义静态全局变量
改为
int n; //定义全局变量
再次编译运行程序,细心体会全局变量和静态全局变量的区别。
2、静态局部变量
在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。
我们先举一个静态局部变量的例子,如下:
1 #include<iostream> 2 using namespace std; 3 4 void fn(); 5 6 int main(void) 7 { 8 fn(); 9 fn(); 10 fn(); 11 return 0; 12 } 13 14 void fn() 15 { 16 static int n = 10; 17 cout<<n<<endl; 18 n++; 19 }
通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。
但有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。
静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
静态局部变量有以下特点:
(1)该变量在全局数据区分配内存;
(2)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
(3)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
(4)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;
3、静态函数
在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
静态函数的例子:
1 #include<iostream> 2 using namespace std; 3 4 static void fn(); //声明静态函数 5 6 int main(void) 7 { 8 fn(); 9 return 0; 10 } 11 12 void fn() //定义静态函数 13 { 14 int n = 10; 15 cout<<n<<endl; 16 }
定义静态函数的好处:
静态函数不能被其它文件所用;
其它文件中可以定义相同名字的函数,不会发生冲突;
二、面向对象的static关键字(类中的static关键字)
1、静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。先举一个静态数据成员的例子。
1 #include<iostream> 2 using namespace std; 3 4 class Myclass 5 { 6 private: 7 int a , b , c; 8 static int sum; //声明静态数据成员 9 public: 10 Myclass(int a , int b , int c); 11 void GetSum(); 12 }; 13 14 int Myclass::sum = 0; //定义并初始化静态数据成员 15 // 如果在构造函数中初始化,则提示如下error:error C2655: “Myclass::sum”: 当前范围内的定义或重新声明非法 16 17 Myclass::Myclass(int a , int b , int c) 18 { 19 // sum = 0;可以添加此行代码,则第二、三次调用后sum = 15; 20 this->a = a; 21 this->b = b; 22 this->c = c; 23 sum += a+b+c; 24 } 25 void Myclass::GetSum() 26 { 27 cout<<"sum="<<sum<<endl; 28 } 29 30 int main(void) 31 { 32 Myclass M(1 , 2 , 3); // sum=6 33 M.GetSum(); 34 Myclass N(4 , 5 , 6); 35 N.GetSum(); // sum=21 36 M.GetSum(); // sum=21 37 return 0; 38 }
可以看出,静态数据成员有以下特点:
- 对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
- 静态数据成员存储在全局数据区。静态数据成员定义时要分配空间,所以不能在类声明中定义。在Example 5中,语句int Myclass::Sum=0;是定义静态数据成员;
- 静态数据成员和普通数据成员一样遵从public,protected,private访问规则;
- 因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;
- 静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为:
<数据类型><类名>::<静态数据成员名>=<值> - 类的静态数据成员有两种访问形式:
<类对象名>.<静态数据成员名>或<类类型名>::<静态数据成员名>
如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员; - 静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了;
- 同全局变量相比,使用静态数据成员有两个优势:
1 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
2 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;
2、静态成员函数
与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。下面举个静态成员函数的例子。
1 #include<iostream> 2 using namespace std; 3 class Myclass 4 { 5 private: 6 int a , b , c; 7 static int sum; //声明静态数据成员 8 public: 9 Myclass(int a , int b , int c); 10 static void GetSum(); //声明静态成员函数 11 }; 12 13 int Myclass::sum = 0; //定义并初始化静态数据成员 14 15 Myclass::Myclass(int a , int b , int c) 16 { 17 this->a = a; 18 this->b = b; 19 this->c = c; 20 sum += a+b+c; //非静态成员函数可以访问静态数据成员 21 } 22 void Myclass::GetSum() //静态成员函数的实现 23 { 24 //cout<<a<<endl; //错误代码,a是非静态数据成员 25 cout<<"sum="<<sum<<endl; 26 } 27 28 int main(void) 29 { 30 Myclass M(1 , 2 , 3); // sum=6 31 M.GetSum(); 32 Myclass N(4 , 5 , 6); 33 N.GetSum(); // sum=21 34 Myclass::GetSum(); // sum=21 35 // GetSum不加static则提示error:error C2352 : “Myclass::GetSum” : 非静态成员函数的非法调用 36 return 0; 37 }
关于静态成员函数,可以总结为以下几点:
- 出现在类体外的函数定义不能指定关键字static;
- 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
- 非静态成员函数可以任意地访问静态成员函数和静态数据成员;
- 静态成员函数不能访问非静态成员函数和非静态数据成员;
- 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
- 调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:
<类名>::<静态成员函数名>(<参数表>)
调用类的静态成员函数。
posted on 2015-03-24 14:37 darkknightzh 阅读(247) 评论(0) 编辑 收藏 举报