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 用以生成惟一的標誌。

相關文章
相關標籤/搜索