`
444878909
  • 浏览: 637901 次
文章分类
社区版块
存档分类
最新评论

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的内容:

char a = 'A'; // global variable

void msg() { printf("Hello\n"); }


下面是mian.c的内容:

int main(void) { 

extern char a; // extern variable must be declared before use

printf("%c ", a);

(void)msg();

return 0; }


程序的运行结果是:

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还是用来隐藏的。虽然这种用法不常见,但我还是举一个例子。

#include <stdio.h>

int fun(void){

static int count = 10; // 事实上此赋值语句从来没有执行过

return count--;

}

int count = 1;

int main(void) {

printf("global\t\tlocal static\n");

for(; count <= 10; ++count)

printf("%d\t\t%d\n", count, fun());

return 0; }

程序的运行结果是:

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’。不妨做个小实验验证一下。

#include <stdio.h>

int a;

int main(void){

int i;

static char str[10];

printf("integer: %d; string: (begin)%s(end)", a, str);

return 0;

}

程序的运行结果如下integer: 0; string: (begin)(end)

最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0.

4. 用static声明的函数和变量小结

static 声明的变量在C语言中有两方面的特征:

1)、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。

2)、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。

Tips:
A.若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
D.如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的的函数)
E.函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。

函数前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

扩展分析:

术语static有着不寻常的历史.起初,在C中引入关键字static是为了表示退出一个块后仍然存在的局部变量。随后,static在C中有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。为了避免引入新的关键字,所以仍使用static关键字来表示这第二种含义。最后,C++重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数(与Java中此关键字的含义相同)。

全局变量、静态全局变量、静态局部变量和局部变量的区别

变量可以分为:全局变量、静态全局变量、静态局部变量和局部变量。

(1) 按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。

(2) 按作用域分, 全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。

全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。

从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。

(1) static 函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件

(2) static全局变量与普通的全局变量有什么区别:static全局变量只初始化一次,防止在其他文件单元中被引用;

(3) static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
(4) static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝.
(5) 全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。

5. C++的static

C++的static有两种用法:面向过程程序设计的static和面向对象程序设计中的static。前者应用于普通变量和函数,不涉及类;后者主要说明static在类中的作用。

(1)、面向过程设计中的static

1)、静态全局变量

在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。我们先举一个静态全局变量的例子,如下:

//Example 1

#include <iostream.h>

void fn();

static int n; //定义静态全局变量

void main()

{

n=20;

cout<<n<<endl;

fn();

}

void fn()

{

n++;

cout<<n<<endl;

}

静态全局变量有以下特点:

i ) 该变量在全局数据区分配内存;
ii ) 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);
iii ) 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;

静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下图:

代码区

全局数据区

堆区

栈区

  一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。细心的读者可能会发现,Example 1中的代码中将

static int n; //定义静态全局变量

改为

int n; //定义全局变量

程序照样正常运行。的确,定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处:

1) 静态全局变量不能被其它文件所用;

2) 其它文件中可以定义相同名字的变量,不会发生冲突;

您可以将上述示例代码改为如下:

//Example 2

//File1

#include <iostream.h>

void fn();

static int n; //定义静态全局变量

void main()

{

n=20;

cout<<n<<endl;

fn();

}

//File2

#include <iostream.h>

extern int n;

void fn()

{

n++;

cout<<n<<endl;

}

编译并运行Example 2,您就会发现上述代码可以分别通过编译,但运行时出现错误。试着将

static int n; //定义静态全局变量

改为

int n; //定义全局变量

再次编译运行程序,细心体会全局变量和静态全局变量的区别。

2)、静态局部变量

在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。 我们先举一个静态局部变量的例子,如下:

//Example 3

#include <iostream.h>

void fn();

void main()

{

fn();

fn();

fn();

}

void fn()

{

static n=10;

cout<<n<<endl;

n++;

}

  通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。但是有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。  静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下次函数调用时。

以下内容转自:http://blog.csdn.net/lwbeyond/article/details/6184035

(2)面向对象(C++)中的static关键字

1)、在类数据成员声明之前加上static关键字,该数据成员成为类内的静态数据成员

#include <iostream.h>
class Myclass
{
public:
 Myclass(int a,int b,int c);
 void GetSum();

private:
 int a,b,c;
 static int Sum; //声明静态数据成员
};

int Myclass::Sum=0; //定义并初始化静态数据成员
//static int Myclass::Sum = 0; //注意加static, 是错误的

Myclass::Myclass(int a,int b,int c)
{
 this->a=a;
 this->b=b;
 this->c=c;
 Sum+=a+b+c;
}

void Myclass::GetSum()
{
 cout<<"Sum="<<Sum<<endl;
}

void main()
{
 Myclass M(1,2,3);
 M.GetSum(); // cout 6
 
 Myclass N(4,5,6);
 N.GetSum(); // cout 21
 M.GetSum(); // cout 21
}


可以看出,static数据成员有以下特点

(1).于非static数据成员,每个类对象都有自己的拷贝。而static数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。


(2). 静态数据成员存储在全局数据区。静态数据成员定义时才分配空间,所以不能在类声明中定义。

在上例中,语句 int Myclass::Sum = 0; 是定义静态数据成员;


(3). 静态数据成员和普通数据成员一样遵从public, protected, private 访问规则;除了定义,定义不要管访问规则。


(4). 因为静态数据成员在全局数据区分配内存,属于本类的所有对象共享,所以,它不属于特定的类对象,在没有产生类对象时其作用域就可见,即在没有产生类的实例时,我们就可以操作它;


(5). 静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为:

//<数据类型><类名>::<静态数据成员名>=<值>

int Myclass::Sum=0;


(6). 类的静态数据成员有两种访问形式:

//<类对象名>.<静态数据成员名>
M.Sum = 0

//<类类型名>::<静态数据成员名>
Myclass::Sum = 0 

但是上面这个例子是不行的,因为他是private的变量,如果静态数据成员的访问权限允许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员 ;


(7). 静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了;


(8). 同全局变量相比,使用静态数据成员有两个优势:
a. 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
b. 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;

2、static 成员函数

static 成员函数,它为类的全部对象服务而不是为某一个类的具体对象服务。普通的成员函数一般都隐含了一个this指针,但静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的no-static数据成员,也无法访问no-static成员函数,它只能调用其余的静态成员函数

#include <iostream.h>
class Myclass
{
public:
 Myclass(int a,int b,int c);
 static void GetSum(); /声明静态成员函数

private:
 int a,b,c;
 static int Sum; //声明静态数据成员
};

int Myclass::Sum = 0;//定义并初始化静态数据成员

Myclass::Myclass(int a,int b,int c)
{
 this->a=a;
 this->b=b;
 this->c=c;
 Sum+=a+b+c; //非静态成员函数可以访问静态数据成员
}

//static void Myclass::GetSum(){...} //加上static是错误的
void Myclass::GetSum() //静态成员函数的实现
{
 //cout<<a<<endl; //错误代码,a是非静态数据成员
 cout<<"Sum="<<Sum<<endl; //静态函数是能访问静态数据成员
}
void main()
{
 Myclass M(1,2,3);
 M.GetSum();
 
 Myclass N(4,5,6);
 N.GetSum();
 
 Myclass::GetSum();
}

关于静态成员函数,可以总结为以下几点:

(1). 在类体外,静态函数的定义前不能加关键字static


(2). static成员之间可以相互访问,包括static成员函数访问static数据成员和访问static成员函数;


(3). 非静态成员函数可以任意地访问静态成员函数和静态数据成员;


(4). 静态成员函数不能访问非静态成员函数和非静态数据成员,只能访问静态的


(5). 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;


(6). 调用静态成员函数,可以用成员访问操作符(.)和(->)或直接用类名调用静态成员函数

M.GetSum();
Myclass::GetSum();调用类的静态成员函数。

但是,一样要遵从 public,protected,private 访问规则



分享到:
评论

相关推荐

    C++ static关键字的原理及用法详解

    在C++中,static关键字是一个多功能且重要的修饰符,它可以用于变量、函数、类成员以及类本身。本文详细介绍了static关键字在C++中的作用和原理,并通过充分的代码示例进行了说明,帮助读者深入理解并正确使用static...

    C/C++static详解

    详细的描述C/C语言的关键字static的作用,及使用时应该注意的事项!

    浅谈C/C++中的static与extern关键字的使用详解

    本篇文章是对C/C++中的static与extern关键字的使用进行了详细的分析介绍,需要的朋友参考下

    实例详解C/C++中extern关键字

    则告诉编译器在编译fun这个函数名时按着C的规则去翻译相应的函数名而不是C++的,C++的规则在翻译这个函数名时会把fun这个名字变得面目全非,可能是fun@aBc_int_int#%$也可能是别的,这要看编译器的”脾气”了(不同的...

    C++中关键字的探讨

    C++的有些关键字的详解,收集一下很有帮助的

    C++面试基础之static关键字详解

    static是 c++ 的关键字,顾名思义是表示静态的含义。它在 c++ 中既可以修饰变量也可以修饰函数。那当我们使用 static 时,编译器究竟做了哪些事情呢? 早先面试中被问到 static 关键字,感觉既熟悉又陌生。熟悉是都...

    详解c++中的 static 关键字及作用

    主要介绍了c++中的 static 关键字,在我们日常使用过程中,static通常有两个作用,具体内容在文中给大家详细介绍,需要的朋友可以参考下

    详解C++中static的用法

    C 语言的 static 关键字有三种(具体来说是两种)用途: 1. 静态局部变量:用于函数体内部修饰变量,这种变量的生存期长于该函数。 int foo(){ static int i = 1; // note:1 //int i = 1; // note:2 i += 1; ...

    详解c++ 静态成员变量

    C++的static关键字可修饰类成员变量/方法,表示变量/方法不从属于特定对象,而是属于类的。仔细琢磨静态成员变量,会发现其与C++的方式既相容也矛盾,具有特殊性。 先说相容的一面。·C/C++·有声明和定义的说法:...

    使用@符号让C#中的保留字做变量名的方法详解

    在c和c++中,我们知道没办法起一个变量名叫int,因为这是C/C++保留的关键字,起这么一个变量名没办法区分到底是int类型还是int变量。事实上在C#中也有这么一条规矩,就是不能起一个变量名是保留的关键字。如下图: ...

    C++100天经典实战系(含大量案例源码及通俗讲解教程,effective_c++、STL、综合能力全面提升)

    class、struct、static、sizeof、inline那些事、func_pointer、extern C、explicit(显式)关键字那些事、decltype那些事、const介绍及案例、C++面向对象特性 2、10日c++实战狂练(习题和源码) 3、C++11新特性详解 4...

    C++中this指针用法详解及实例

    C++中this指针用法详解及实例 概要: 本文简单介绍this指针的基本概念,并通过一个实际例子介绍this指针用于防止变量命名冲突和用于类中层叠式调用的两个用法。 this指针概览 C++中,每个类 对应了一个对象,每个...

    C++思维导图Xmind文件和.png文件(持续更新)

    初始化列表、匿名对象、static成员、类的隐式类型转换和explicit关键字、内部类思维导图xmind文件和.png文件 内存管理(new/delete使用详解) 模板——初识 STL——string类 STL——vector STL适配器——stack && ...

    详解C++编程中断言static_assert的使用

    C++ 语言支持可帮助您调试应用程序的三个错误处理机制:#error 指令、static_assert 关键字和 assert (CRT) 宏。所有的三种机制都会发出错误消息,其中两个还会测试软件断言。软件断言指定在程序的某个特定点应满足...

    Linux多线程服务端编程:使用muduo C++网络库

    4.4.2exit(3) 在C++ 中不是线程安全的.. . . . . . . . . . . . . . . . 94 4.5善用__thread 关键字.. . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 4.6多线程与IO . . . . . . . . . . . . . . . . ....

    自己写的dll的简介

    1. 如果你只用C语言,那么必然以C文件创建DLL(自动编出C符号名),考虑到潜在的C++用户(此类用户多以静态调用方式使用DLL,因而需要看到其函数声明),我们还需要使用EXTERN_C关键字(详见上面的讨论)。...

Global site tag (gtag.js) - Google Analytics