static关键字用法学习笔记(C++)

本文重点探讨 C++ 中的 static 关键字,在此之前先复习一下吧。

static 关键字是 C C++ 中都存在的关键字,它主要有三种使用方式

       (1) 局部静态变量  
  (2)
外部静态变量 / 函数  
  (3)
静态数据成员 / 成员函数   

下面就这三种使用方式及注意事项分别说明    

一、局部静态变量  
    
C/C++ 中,局部变量按照存储形式可分为三种 auto static register

auto 类型 ( 普通 ) 局部变量相比, static 局部变量有三点不同    
       1.
存储空间分配不同  
  auto
类型分配在栈上,属于动态存储类别,占动态存储区空间,函数调用结束后自动释放,而 static 分配在静态存储区,在程序整个运行期间都不释放。两者之间的作用域相同,但生存期不同。    
       2.static
局部变量在所处模块在初次运行时进行初始化工作,且只操作一次。
       3.
对于局部静态变量,如果不赋初值,编译期会自动赋初值 0 或空字符 ,而 auto 类型的初值是不确定的。 ( 对于 C++ 中的 class 对象例外, class 的对象实例如果不初始化,则会自动调用默认构造函数,不管是否是 static 类型 )  
       
特点: static 局部变量具有“记忆性” 与生存期的“全局性”    
       
所谓“记忆性”是指在两次函数调用时,在第二次调用进入时,能保持第一次调用退出时的值。   (和全局变量比起来, static 可以控制变量的可见范围,详见注意事项二
 
示例程序一  
  #include   <iostream>  
  using   namespace   std;  
  void   staticLocalVar()  
  {  
    static   int   a   =   0;   //
运行期时初始化一次

// 下次再调用时,不进行初始化工作  
    cout<<"a="<<a<<endl;  
    ++a;  
  }  
   
  int   main()  
  {  
    staticLocalVar();   //
第一次调用,输出 a=0  
    staticLocalVar();   //
第二次调用记忆了第一次退出时的值,输出 a=1  
    return   0;  
  }  
   
 
应用 :  
     
利用“记忆性”,记录函数调用的次数 示例程序一:利用生存期的“全局性”,改善 ”return   a   pointer   /   reference   to   a   local   object” 的问题。 Local object 的问题在于退出函数,生存期即结束。利用 static 的作用,延长变量的生存期。  
     
注意事项 :  
      1.
“记忆性”,程序运行很重要的一点就是可重复性,而 static 变量的“记忆性”破坏了这种可重复性,造成不同时刻至运行的结果可能不同。  
      2.
“生存期”全局性和唯一性。普通的 local 变量的存储空间分配在 stack 上,因此每次调用函数时,分配的空间都可能不一样,而 static 具有全局唯一性的特点,每次调用时,都指向同一块内存,这就造成一个很重要的问题——不可重入性 !!!  
    
这样在多线程程序设计或递归程序设计中,要特别注意这个问题。

比如 :
 static int id = 0;
 sql = "select * from table where id=" + id;
 
如果这样写的话,在单机测试的时候没有问题,但是在多人同时对数据进行测试的时候,就会有问题了。假如, A 用户访问他的 id 20 ,则 id 的值在内存中为 20 ,而此时 B 用户访问,他的 id 30, id 在内存中的值是 30 A 用户的 id 值则被更改了。如果此时你将这个方法用非静态成员来写,则不会出现这样的情况。因为非静态成员是你声明的时候,实例化的时候才会分配内存。所以 A 用户访问的时候, App 会因为 A 实例化而给 A 用户的请求分配内存。而 B 用户访问的时候也一样会因为 B 用户的访问而分配内存。所以两个用户访问的是不同的内存块,所以不会出现数据覆盖和错乱的现象。   具体应用如下:
    const   char   *   IpToStr(UINT32   IpAddr)  
 
  {  
 
    static   char   strBuff[16];   //   static 局部变量,   用于返回地址有效  
 
    const   unsigned   char   *pChIP   =   (const   unsigned   char   *)&IpAddr;  
 
    sprintf(strBuff,"%u,%u,%u, %u", pChIP[0],pChIP[1],pChIP[2],pChIP[3]);  
 
    return   strBuff;  
 
  }  
     
假设现在有两个线程 A B 运行期间都需要调用 IpToStr() 函数,将 32 位的 IP 地址转换成点分 10 进制的字符串形式。现 A 先获得执行机会,执行 IpToStr() ,传入的参数是 0x0B090A0A ,顺序执行完应该返回的指针存储区内容是 :”10,10,9,11” ,现执行到 ⑥时,失去执行权,调度到 B 线程执行, B 线程传入的参数是 0xA8A8A8C0 ,执行至 ⑦,静态存储区的内容是 192,168,168,168 。当再调度到 A 执行时,从 ⑥ 继续执行,由于 strBuff 的全局唯一性,内容已经被 B 线程冲掉,此时返回的将是 192,168,168,168 字符串,不再是 10,10,9,11 字符串。  
   
 
二、外部静态变量/函数  
      C static 有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。  
     
当我们同时编译多个文件时,所有未加 static 前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是 a.c ,另一个是 main.c

下面是 a.c 的内容

char  a = 'A';  // global variable
void  msg() 
{
    printf("Hello/n"); 
}

 

下面是 main.c 的内容

int  main( void )
{    
    
extern   char  a;     // extern variable must be declared before use
    printf("%c ", a);
    (
void )msg();
    
return  0;
}

你可能会问:为什么在 a.c 中定义的全局变量 a 和函数 msg 能在 main.c 中使用?前面说过,所有未加 static 前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中, a 是全局变量, msg 是函数,并且都没有加 static 前缀,因此对于另外的源文件 main.c 是可见的。

但为了限制全局变量/ 函数的作用域,函数或变量前加static 使得函数成为静态函数。但此处“static” 的含义不是指存储方式,而是指对函数的作用域仅局限于本文件( 所以又称内部函数) 。注意此时,对于外部( 全局) 变量,不论是否有static 限制,它的存储区域都是在静态存储区,生存期都是全局的。此时的static 只是起作用域限制作用,限定作用域在本模块( 文件) 内部。  
    
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

 
三、静态数据成员/成员函数(C++ 特有)  
     C++
重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数 。这是与普通成员函数的最大区别,也是其应用所在,比如在对某一个类的对象进行计数时,计数生成多少个类的实例,就可以用到静态数据成员。在这里面,static 既不是限定作用域的,也不是扩展生存期的作用,而是指示变量/ 函数在此类中的唯一性。这也是“属于一个类而不是属于此类的任何特定对象的变量和函数 的含义。因为它是对整个类来说是唯一的,因此不可能属于某一个实例对象的。( 针对静态数据成员而言,成员函数不管是否是static ,在内存中只有一个副本,普通成员函数调用时,需要传入this 指针,static 成员函数调用时,没有this 指针。)  

  当然说到static, 就避免不了要谈到static 变量的初始化,

比如

class A

{

public:

    A(int val);

private:

    int mVal;

};

 

class B

{

private:

    static A m_A;

};

m_A 怎么初始化?

规则:static 成员一定要在类外初始化。

 这是因为被static 声明的类静态数据成员,其实体远在main() 函数开始之前就已经在全局数据段中诞生了( 见《Inside   The   C++   Object   Modelpage247) !其生命期和类对象是异步的,(而且静态语意说明即使没有类实体的存在, 其静态数据成员的实体也是存的)这个时候对象的生命期还没有开始,如果你要到类中去初始化类静态数据成员, 让静态数据成员的初始化依赖于类的实体, ,那怎么满足前述静态语意呢? 难道类永远不被实例化,我们就永远不能访问到被初始化的静态数据成员吗?

 另外类定义仅仅只是定义而已,没有分配内存空间的,所以不能初始化。 除了使用static   const   int   SIZE   =   10;      enum{SIZE   =   10}; 参看C++ Primer 叙述如下:

Ordinarily,   class   static   members,   like   ordinary   data   members,   cannot   be   initialized   in   the   class   body.   Instead,   static   data   members   are   normally   initialized   when   they   are   defined.

One   exception   to   this   rule   is   that   a   const   static   data   member   of   integral   type   can   be   initialized   within   the   class   body   as   long   as   the   initializer   is   a   constant   expression:

          class   Account   {

           public:

                  static   double   rate()   {   return   interestRate;   }

                  static   void   rate(double);     //   sets   a   new   rate

          private:

                  static   const   int   period   =   30;   //   interest   posted   every   30   days

                  double   daily_tbl[period];   //   ok:   period   is   constant   expression

          };

A   const   static   data   member   of   integral   type   initialized   with   a   constant   value    is   a   constant   expression.   As   such,   it   can   be   used   where   a   constant   expression   is  

required,   such   as   to   specify   the   dimension   for   the   array   member   daily_tbl.

 

  When   a   const   static   data   member    is   initialized   in   the   class   body,   the   data   member   must   still   be   defined   outside   the   class   definition.

When   an   initializer   is   provided   inside   the   class,   the   definition   of   the   member   must   not   specify   an   initial   value:

          //   definition   of   static   member   with   no   initializer;

          //   the   initial   value   is   specified   inside   the   class   definition

          const   int   Account::period;

哦,还有一点需要补充的是static 成员应该在.cpp 文件里定义,而不是放在.h 文件中。一般的c++ 都是把一个.cpp 文件编译成一个.obj 文件, 我们定义的static 成员全局唯一,需要找一个obj 文件放,你放在头文件里面,可以被include 很多次,那到底放在那个obj 里面呢?编译器不知道了吧,所以写在一个.cpp 文件里面,这样才好办,编译器才知道放在.obj 里面,link 的时候也好办,明白了吧。
    
另外, 在设计类的多线程操作时, 由于POSIX 库下的线程函数pthread_create() 要求是全局的, 普通成员函数无法直接做为线程函数, 可以考虑用static 成员函数做线程函数。(总结完毕2008-12-27 2229

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值