C++ Static关键字的用法说明

C++中Static关键字的应用

由于C++是一种源自于C的语言,所以我们需要先分析Static在C语言中的应用。在C语言中, 使用Static关键字的作用是:

  • 用于函数内部修饰变量,即函数内的静态变量。这种变量的生存期长于该函数,使得函数具有一定的“状态”。使用静态变量的函数一般是不可重入的,也不是线程安全的,比如strtok(3)。

  • 用在文件级别(函数体之外),修饰变量或函数,表示该变量或函数只在本文件可见,其他文件看不到也访问不到该变量或函数。专业的说法叫“具有internal linkage”(简言之:不暴露给别的translation unit)。

在C++中引出的C语言没有的类,所以我们就需要分析在C++的类里面使用Static关键字的作用与方式。

  • 用于修饰类的数据成员,即所谓“静态成员”。这种数据成员的生存期大于class的对象(实例/instance)。静态数据成员是每个class有一份,普通数据成员是每个instance 有一份。

  • 用于修饰class的成员函数,即所谓“静态成员函数”。这种成员函数只能访问静态成员和其他静态程员函数,不能访问非静态成员和非静态成员函数。

C++ 类中的static数据成员的初始化和特点

class CTypeInit{
public:
	CTypeInit( int c):m_c(c),m_ra(c){ }
private:
	int m_a;				//通过初始化列表初始化,或者构造函数初始化
	/*引用*/
	int &m_ra;				//只能通过初始化列表初始化
	/*静态变量*/
	static int m_b;			
	/*常量*/
	const  int m_c;			

	/*静态整型常量*/
	static const int m_d;	
	/*静态非整型常量*/
	static const double m_e;
};

//静态成员变量,必须在类外初始化,且要去掉static关键字
int CTypeInit::m_b = 6;
const int CTypeInit::m_d = 6;
const double CTypeInit::m_e = 3.1415926;

int main()
{
	CTypeInit obT(2);
	return 0;
}

分为下面几种情况

静态变量

static int m_b;

static成员变量需要在类定义体外进行初始化与定义,因为static数据成员独立该类的任意对象存在,它是与类关联的对象,不与类对象关联。例如:上述程序中的c变量的初始化。

  • 只能在类外初始化
  • 不能通过初始化列表初始化,
  • 不能在类内进行初始化,
  • 不能在构造函数中初始化,

常量

const int m_c;

  • 只能通过初始化列表初始化
  • 不能在类内进行初始化
  • 不能在构造函数中初始化
  • 不能在类外初始化

引用变量

int &m_ra;

只能通过初始化列表初始化且必须用变量初始化,不能在类的定义外(内)初始化,不能通过构造函数初始化。

静态整型常量

static const int m_d;

  • (整型)能否在类中初始化,取决于编译器
  • 能在在类外初始化,不能带static

静态非整型常量

static const double m_e;

  • (double型)能否在类中初始化,取决于编译器
  • 能在在类外初始化,不能带static

静态整型常量和静态非整形常量在类定义内部初始化时,在VC6.0中都不能编译通过,而在GCC中都可以编译通过,在不同编译器下有不同的结果,但前三个是确定的。当然,如果不习惯类内初始化,可以将静态常量和静态变量的初始化统一起来,将静态常量和静态变量的初始化全部都移动类定义之外初始化(推荐使用这种方式)。另外,如果编译器不支持类内初始化,而此时类在编译期又恰恰需要定义的成员常量的值,身出如此左右为难的境地,我们应该考虑使用enum!因为enum本质也是一个整型常量。


小结

类的静态成员属于类作用域,但不属于类对象,它的生命周期和普通的全局静态变量一样,程序运行时进行分配内存和初始化,程序结束时则被释放。所以不能在类的构造函数中进行初始化。

优点

  • static成员的名字是在类的作用域中,因此可以避免与其它类成员或全局对象名字冲突
  • 可以实施封装,static成员可以是私有的,而全局对象不可以
  • 阅读程序容易看出static成员与某个类相关联,这种可见性可以清晰地反映程序员的意图

C++ 类里面的Static成员函数

#include <iostream>

using namespace std;

class test2
{
public:
    test2(int num) : y(num){}
    ~test2(){}

    static void testStaticFun()
    {
      cout << "y = " << y << endl; //Error:静态成员函数不能访问非静态成员
    }

    void testFun()
    {
        cout << "x = " << x << endl; 
    }
private:
    static int x;//静态成员变量的引用性说明
    int y;
};

int test2::x = 10;//静态成员变量的定义性说明

int main(void)
{
    test2 t(100);
    t.testFun();

    return 0;
}

小结 -成员函数特点

  • 因为static成员函数没有this指针,所以静态成员函数不可以访问非静态成员。
  • 非静态成员函数可以访问静态成员。
  • 静态数据成员与类的大小无关,因为静态成员只是作用在类的范围而已。

补充:C语言中Static关键字

C语言程序可以看成由一系列外部对象构成,这些外部对象可能是变量或函数。而内部变量是指定义在函数内部的函数参数及变量。外部变量定义在函数之外,因此可以在许多函数中使用。由于C语言不允许在一个函数中定义其它函数,因此函数本身只能是“外部的”。
由于C语言代码是以文件为单位来组织的,在一个源程序所有源文件中,一个外部变量或函数只能在某个文件中定义一次,而其它文件可以通过extern声明来访问它(定义外部变量或函数的源文件中也可以包含对该外部变量的extern声明)。
而static则可以限定变量或函数为静态存储。如果用static限定外部变量与函数,则可以将该对象的作用域限定为被编译源文件的剩余部分。通过static限定外部对象,可以达到隐藏外部对象的目的。因而,static限定的变量或函数不会和同一程序中其它文件中同名的相冲突。如果用static限定内部变量,则该变量从程序一开始就拥有内存,不会随其所在函数的调用和退出而分配和消失。

C语言中使用静态函数的好处

静态函数会被自动分配在一个一直使用的存储区,直到退出应用程序实例,避免了调用函数时压栈出栈,速度快很多。

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

c语言中static的语义 1.static变量:

  • 局部
    静态局部变量在函数内定义,生存期为整个源程序,但作用域与自动变量相同,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它。 对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。

  • 全局
    全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。但是他们的作用域,非静态全局 变量的作用域是整个源程序(多个源文件可以共同使用); 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。

2.static函数(也叫内部函数)

  • 只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用。区别于一般的非静态函数(外部函数)
  • static在c里面可以用来修饰变量,也可以用来修饰函数。
  • 先看用来修饰变量的时候。变量在c里面可分为存在全局数据区、栈和堆里。其实我们平时所说的堆栈是栈而不包含对,不要弄混。
int a ;  
main()  
{  
	int b ;  
	int c* = (int *)malloc(sizeof(int));  
}  

a是全局变量,b是栈变量,c是堆变量。

  • static对全局变量的修饰,可以认为是限制了只能是本文件引用此变量。有的程序是由好多.c文件构成。彼此可以互相引用变量,但加入static修饰之后,只能被本文件中函数引用此变量。

  • static对栈变量的修饰,可以认为栈变量的生命周期延长到程序执行结束时。一般来说,栈变量的生命周期由OS管理,在退栈的过程中,栈变量的生命也就结束了。但加入static修饰之后,变量已经不在存储在栈中,而是和全局变量一起存储。同时,离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用, 而且保存了前次被调用后留下的值。

  • static对函数的修饰与对全局变量的修饰相似,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用。

小结

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

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

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

例子

  • [正确] 若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;

  • [正确] 若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;

  • [正确] 设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;

  • [错误] 静态全局变量过大,可那会导致堆栈溢出。

unsigned int sum_int( unsigned int base )  
{  
  unsigned int index;  
  static unsigned int sum = 0; // 注意,是static类型的。  
  for (index = 1; index <= base; index++)  
  {  
   sum += index;  
  }  
  return sum;  
 }

所谓的函数是可重入的(也可以说是可预测的),即:只要输入数据相同就应产生相同的输出。

这个函数之所以是不可预测的,就是因为函数中使用了static变量,因为static变量的特征,这样的函数被称为:带“内部存储器”功能的的函数。因此如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量,这种函数中的static变量,使用原则是,能不用尽量不用。

将上面的函数修改为可重入的函数很简单,只要将声明sum变量中的static关键字去掉,变量sum即变为一个auto 类型的变量,函数即变为一个可重入的函数。

当然,有些时候,在函数中是必须要使用static变量的,比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。

全局变量以及全局变量与静态变量的关系: 顾名思义,全局变量是指能够在全局引用的变量,相对于局部变量的概念,也叫外部变量;同静态变量一样,全局变量位于静态数据区,全局变量一处定义,多处引用,用关键字“extern”引用“外部”的变量。 全局变量也可以是静态的,在前面有过说明,静态全局变量的意义就是不让“外部”引用,是单个源文件里的全局变量,即是编译阶段的全局变量,而不是连接阶段的全局变量。

Code

#include <iostream>

using namespace std;

class TestStatic
{
public:
    TestStatic() : y(1), r(y), d(3),private_y(4){++c;} //对于常量型成员变量和引用型成员变量,必须通过参数化列表的方式进行初始化。
    ~TestStatic(){}

    int y;          //普通变量成员
    int &r;         //引用成员变量
    const int d;    //常量成员变量
    static int c;   //静态成员变量
    static const int x;  //静态常量整型成员变量

    static void testStaticFunErr() {
        std::cout << std::endl;
    }

    static void testStaticFun() {
        std::cout << "Call Static Function: " <<  xx << std::endl;
    }
private:
    int private_y;
    static const int xx;        //静态常量整型成员变量声明
    static const double z;  //静态常量非整型成员变量声明
    static const float zz;    //静态常量非整型成员变量
};

const int TestStatic::xx = 4; //静态常量整型成员变量定义
const double TestStatic::z = 5.1; ////静态常量非整型成员变量定义
int TestStatic::c = 2;
// int TestStatic::x;
const int TestStatic::x = 5;


int main(void)
{
    TestStatic a, b, c;
    std::cout << "Class Static variable x:" << TestStatic::x << std::endl;    
    std::cout << "Class Static variable c:" << TestStatic::c << std::endl; 

    return 0;
}
Terry Tang
Terry Tang
Software Development Engineer

My research interests include distributed robotics, mobile computing and programmable matter.

comments powered by Disqus

Related