Static使用

一、什么是static?

    static 是C++中很经常使用的修饰符,它被用来控制变量的存储方式和可见性ios

    其他控制变量存储方式的关键字为auto、register、extern。
程序员

二、为何要引入static?

    函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,你们知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 若是想将函数中此变量的值保存至下一次调用时,如何实现? 数组

    最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不只仅受此函数控制),所以引入static静态变量。安全

三、何时用static?

    须要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见。多线程

四、static的内部机制

    静态数据成员要在程序一开始运行时就必须存在。由于函数在程序运行中被调用,因此静态数据成员不能在任何函数内分配空间和初始化函数

    这样,它的空间分配有三个可能的地方:this

    (1)做为类的外部接口的头文件,那里有类声明;spa

    (2)类定义的内部实现,那里有类的成员函数定义;线程

    (3)应用程序的main()函数前的全局数据声明和定义处。指针

    静态数据成员要实际地分配空间,故不能在类的声明中定义(只能声明数据成员)。类声明只声明一个类的“尺寸和规格”,并不进行实际的内存分配,因此在类声明中写成定义是错误的。它也不能在头文件中类声明的外部定义,由于那会形成在多个使用该类的源文件中,对其重复定义。

    static被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间,静态数据成员按定义出现的前后顺序依次初始化,注意静态成员嵌套时,要保证所嵌套的成员已经初始化了。消除时的顺序是初始化的反顺序。

五、static的优点

    能够节省内存,由于它是全部对象所公有的,所以,对多个对象来讲,静态数据成员只存储一处,供全部对象共用。静态数据成员的值对每一个对象都是同样,但它的值是能够更新的。只要对静态数据成员的值更新一次,保证全部对象存取更新后的相同的值,这样能够提升时间效率。

六、static的做用

(1)隐藏

     当咱们同时编译多个文件时,全部未加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 Hello

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

    若是加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性能够在不一样的文件中定义同名函数和同名变量,而没必要担忧命名冲突

    Static能够用做函数和变量的前缀,对于函数来说,static的做用仅限于隐藏,而对于变量,static还有两个做用:内容持久、默认初始化为0。

(2)保持变量内容的持久

    注意:static是保持变量内容的持久(一种存储方式),而不是保持变量内容不变,const才是保持不变,即定义常量。 

    存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是惟一的一次初始化。共有两种变量存储在静态存储区:全局变量和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)默认初始化为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。

拓展:

一、static全局变量与普通的全局变量有什么区别 ?

    全局变量(外部变量)的说明以前再冠以static 就构成了静态的全局变量。全局变量自己就是静态存储方式, 静态全局变量固然也是静态存储方式。 这二者在存储方式上并没有不一样。

    这二者的区别在于非静态全局变量的做用域是整个源程序, 当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其做用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。因为静态全局变量的做用域局限于一个源文件内,只能为该源文件内的函数公用,所以能够避免在其它源文件中引发错误。 

    static全局变量只初使化一次,防止在其余文件单元中被引用。  

二、 static局部变量和普通局部变量有什么区别 ?

    把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的做用域,限制了它的使用范围。  

    static局部变量只被初始化一次,下一次依据上一次结果值。   

三、static函数与普通函数有什么区别?

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

    static函数在内存中只有一份,普通函数在每一个被调用中维持一份拷贝

七、静态数据成员

    在类中,静态成员能够实现多个对象之间的数据共享,而且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。所以,静态成员是类的全部对象中共享的成员,而不是某个对象的成员。

    使用静态数据成员能够节省内存,由于它是全部对象所公有的,所以,对多个对象来讲,静态数据成员只存储一处,供全部对象共用。静态数据成员的值对每一个对象都是同样,但它的值是能够更新的。只要对静态数据成员的值更新一次,保证全部对象存取更新后的相同的值,这样能够提升时间效率。

    静态数据成员的使用方法和注意事项以下:

    一、静态数据成员在定义或说明时前面加关键字static。

    二、静态成员初始化与通常数据成员初始化不一样。静态数据成员初始化的格式以下:

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

    这代表:

            (1) 、初始化在类体外进行,而前面不加static,以避免与通常静态变量或对象相混淆。

            (2)、 初始化时不加该成员的访问权限控制符private,public等。

            (3) 、初始化时使用做用域运算符来标明它所属类,所以静态数据成员是类的成员,而不是对象的成员。

    三、静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。

    四、引用静态数据成员时,采用以下格式:

    <类名>::<静态成员名>

    若是静态数据成员的访问权限容许的话(即public的成员),可在程序中,按上述格式来引用静态数据成员。

八、 静态成员函数

    静态成员函数和静态数据成员同样,它们都属于类的静态成员,它们都不是对象成员。所以,对静态成员的引用不须要用对象名

    在静态成员函数的实现中不能直接引用类中说明的非静态成员,能够引用类中说明的静态成员。若是静态成员函数中要引用非静态成员时,可经过对象来引用。

    下面看一个例子:

#include <iostream.h>
class Point
{
    public:
    void output()
    {}
    static void init()
    {} 
};
void main( void )
{
    Point pt;
    pt.init();
    pt.output(); 
}

    这样编译是不会有任何错误的。

    下面这样看:

#include <iostream.h>
class Point
{
    public:
    void output()
    {}
    static void init()
    {} 
};
void main( void )
{
    Point::output();
}

    这样编译会处错,错误信息:illegal call of non-static member function,为何?

    由于在没有实例化一个类的具体对象时,类是没有被分配内存空间的

    好的再看看下面的例子:

#include <iostream.h>
class Point
{
    public:
    void output()
    {}
    static void init()
    {} 
};
void main( void )
{
    Point::init();
}

    这时编译就不会有错误,由于在类的定义时,它静态数据和成员函数就有了它的内存区,它不属于类的任何一个具体对象

    好的再看看下面的例子:

#include <iostream.h>
class Point
{
    public:
    void output()
    {}
    static void init()
    { 
       x = 0;
       y = 0;
    }
    private:
    int x;
    int y;
};
void main( void )
{
    Point::init();
}

    编译出错:

illegal reference to data member ''Point::x'' in a static member function
illegal reference to data member ''Point::y'' in a static member function

    在一个静态成员函数里错误的引用了数据成员,

    仍是那个问题,静态成员(函数),不属于任何一个具体的对象,那么在类的具体对象声明以前就已经有了内存区,而如今非静态数据成员尚未分配内存空间,那么这里调用就错误了,就好像没有声明一个变量却提早使用它同样。

    也就是说在静态成员函数中不能引用非静态的成员变量。

    好的再看看下面的例子:

#include <iostream.h>
class Point
{
    public:
    void output()
    {
       x = 0;
       y = 0;
       init();  
    }
    static void init()
    {}
    private:
    int x;
    int y;
};
void main( void )
{
    Point::init();
}

    好的,这样就不会有任何错误。这最终仍是一个内存模型的问题, 任何变量在内存中有了本身的空间后,在其余地方才能被调用,不然就会出错。

    好的再看看下面的例子:

#include <iostream.h>
class Point
{
    public:
    void output()
    {}
    static void init()
    { 
       x = 0;
       y = 0;
    }
    private:
    static int x;
    static int y;
};
void main( void )
{
    Point::init();
}

    编译:

Linking...
test.obj : error LNK2001: unresolved external symbol "private: static int Point::y" 
test.obj : error LNK2001: unresolved external symbol "private: static int Point::x" 
Debug/Test.exe : fatal error LNK1120: 2 unresolved externals

    执行 link.exe 时出错.

    能够看到编译没有错误,链接错误,这又是为何呢?

    这是由于静态的成员变量要进行初始化,能够这样:

#include <iostream.h>
class Point
{
    public:
    void output()
    {}
    static void init()
    { 
       x = 0;
       y = 0;
    }
    private:
    static int x;
    static int y;
};
int Point::x = 0;
int Point::y = 0;
void main( void )
{
    Point::init();
}

    在静态成员数据变量初始化以后就不会出现编译错误了。

    再看看下面的代码:

#include <iostream.h>
class Point
{
    public:
    void output()
    {}
    static void init()
    { 
       x = 0;
       y = 0;
    }
    private:
    static int x;
    static int y;
};
void main( void )
{
}

    编译没有错误,为何?

    即便他们没有初始化,由于咱们没有访问x,y,因此编译不会出错。  

    C++会区分两种类型的成员函数:静态成员函数和非静态成员函数。这二者之间的一个重大区别是,静态成员函数不接受隐含的this自变量。因此,它就没法访问本身类的非静态成员。

    在某些条件下,好比说在使用诸如pthread(它不支持类)此类的多线程库时,就必须使用静态的成员函数,由于其地址同C语言函数的地址兼容。这种铜限制就迫使程序员要利用各类解决办法才可以从静态成员函数访问到非静态数据成员。

    第一个解决办法是声明类的全部数据成员都是静态的。运用这种方式的话,静态的成员函数就可以直接地访问它们,例如:

class Singleton
{
    public:
       static Singleton * instance();
    private:
       static Singleton * p;
       static Lock lock;
};

Singleton *Singleton::p = NULL;
Singleton * Singleton::instance()
{
    lock.getlock(); // fine, lock is static
    if (!p)
       p=new Singleton;
    lock.unlock();
    return p;
}

    这种解决方法不适用于须要使用非静态数据成员的类。

    访问非静态数据成员

    将参照传递给须要考量的对象可以让静态的成员函数访问到对象的非静态数据:

class A
{
    public:
       static void func(A & obj);
       intgetval() const; //non-static member function
    private:
    intval;
};

    静态成员函数func()会使用参照obj来访问非静态成员val。

voidA::func(A & obj)
{
   int n = obj.getval();
}

    将一个参照或者指针做为静态成员函数的自变量传递,就是在模仿自动传递非静态成员函数里this自变量这一行为。

九、注意事项

    (1)、类的静态成员函数是属于整个类而非类的对象,因此它没有this指针,这就致使了它仅能访问类的静态数据和静态成员函数

    (2)、不能将静态成员函数定义为虚函数

    (3)、因为静态成员声明于类中,操做于其外,因此对其取地址操做,就多少有些特殊,变量地址是指向其数据类型的指针 ,函数地址类型是一个“nonmember函数指针”。

    (4)、因为静态成员函数没有this指针,因此就差很少等同于nonmember函数,结果就产生了一个意想不到的好处:成为一个callback函数,使得咱们得以将C++和C-based X Window系统结合,同时也成功的应用于线程函数身上。

    (5)、static并无增长程序的时空开销,相反她还缩短了子类对父类静态成员的访问时间,节省了子类的内存空间。

    (6)、静态数据成员在<定义或说明>时前面加关键字static。

    (7)、静态数据成员是静态存储的,因此必须对它进行初始化。

    (8)、静态成员初始化与通常数据成员初始化不一样:

            a、初始化在类体外进行,而前面不加static,以避免与通常静态变量或对象相混淆;

            b、初始化时不加该成员的访问权限控制符private,public等; 

            c、初始化时使用做用域运算符来标明它所属类; 因此咱们得出静态数据成员初始化的格式:

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

    (9)、为了防止父类的影响,能够在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。

    这里有一点须要注意:咱们说静态成员为父类和子类共享,但咱们有重复定义了静态成员,这会不会引发错误呢?不会,咱们的编译器采用了一种绝妙的手法:name-mangling 用以生成惟一的标志。

相关文章
相关标签/搜索