1. 程式人生 > 實用技巧 >C/C++中static的用法全域性變數與區域性變數

C/C++中static的用法全域性變數與區域性變數

1.什麼是static?

  static是C/C++中很常用的修飾符,它被用來控制變數的儲存方式和可見性。

1.1static的引入

  我們知道在函式內部定義的變數,當程式執行到它的定義處時,編譯器為它在棧上分配空間,函式在棧上分配的空間在此函式執行結束時會釋放掉,這樣就產生了一個問題:如果想將函式中此變數的值儲存至下一次呼叫時,如何實現? 最容易想到的方法是定義為全域性的變數,但定義一個全域性變數有許多缺點,最明顯的缺點是破壞了此變數的訪問範圍(使得在此函式中定義的變數,不只受此函式控制)。static關鍵字則可以很好的解決這個問題。

另外,在C++中,需要一個數據物件為整個類而非某個物件服務,同時又力求不破壞類的封裝性,即要求此成員隱藏在類的內部,對外不可見時,可將其定義為靜態資料。

1.2靜態資料的儲存

  全域性(靜態)儲存區:分為DATA段和BSS段。DATA段(全域性初始化區)存放初始化的全域性變數和靜態變數;BSS段(全域性未初始化區)存放未初始化的全域性變數和靜態變數。程式執行結束時自動釋放。其中BBS段在程式執行之前會被系統自動清0,所以未初始化的全域性變數和靜態變數在程式執行之前已經為0。儲存在靜態資料區的變數會在程式剛開始執行時就完成初始化,也是唯一的一次初始化。

在C++中static的內部實現機制:靜態資料成員要在程式一開始執行時就必須存在。因為函式在程式執行中被呼叫,所以靜態資料成員不能在任何函式內分配空間和初始化。
這樣,它的空間分配有三個可能的地方,一是作為類的外部介面的標頭檔案,那裡有類宣告;二是類定義的內部實現,那裡有類的成員函式定義;三是應用程式的main()函式前的全域性資料宣告和定義處。

靜態資料成員要實際地分配空間,故不能在類的宣告中定義(只能宣告資料成員)。類宣告只宣告一個類的“尺寸和規格”,並不進行實際的記憶體分配,所以在類宣告中寫成定義是錯誤的。它也不能在標頭檔案中類宣告的外部定義,因為那會造成在多個使用該類的原始檔中,對其重複定義。
static被引入以告知編譯器,將變數儲存在程式的靜態儲存區而非棧上空間,靜態資料成員按定義出現的先後順序依次初始化,注意靜態成員巢狀時,要保證所巢狀的成員已經初始化了。消除時的順序是初始化的反順序。
優勢:可以節省記憶體,因為它是所有物件所公有的,因此,對多個物件來說,靜態資料成員只儲存一處,供所有物件共用。靜態資料成員的值對每個物件都是一樣,但它的值是可以更新的。只要對靜態資料成員的值更新一次,保證所有物件存取更新後的相同的值,這樣可以提高時間效率。

2.在C/C++中static的作用

2.1總的來說:

(1)生命週期:在修飾變數的時候,static修飾的靜態區域性變數只執行初始化一次,而且延長了區域性變數的生命週期,直到程式執行結束以後才釋放,但不改變作用域。比如修飾函式中存放在棧空間的陣列。如果不想讓這個陣列在函式呼叫結束釋放可以使用static修飾。
(2)可見性:static修飾全域性變數或函式時,這個全域性變數只能在本檔案中訪問,不能在其它檔案中訪問,即便是extern外部宣告也不可以。這個函式也只能在本檔案中呼叫,不能被其他檔案呼叫。
(3)儲存方式:Static修飾的變數存放在全域性資料區的靜態變數區,包括全域性靜態變數和區域性靜態變數,都在全域性資料區分配記憶體。初始化的時候自動初始化為0。
(4)考慮到資料安全性(當程式想要使用全域性變數的時候應該先考慮使用static)。

2.2靜態變數與普通變數

靜態全域性變數有以下特點: (1)靜態變數都在全域性資料區分配記憶體,包括後面將要提到的靜態區域性變數; (2)未經初始化的靜態全域性變數會被程式自動初始化為0(在函式體內宣告的自動變數的值是隨機的,除非它被顯式初始化,而在函式體外被宣告的自動變數也會被初始化為0); (3)靜態全域性變數在宣告它的整個檔案都是可見的,而在檔案之外是不可見的。 優點:靜態全域性變數不能被其它檔案所用;其它檔案中可以定義相同名字的變數,不會發生衝突。

(4)全域性變數和全域性靜態變數的區別

1)全域性變數是不顯式用static修飾的全域性變數,全域性變數預設是有外部連結性的,作用域是整個工程,在一個檔案內定義的全域性變數,在另一個檔案中,通過extern 全域性變數名的宣告,就可以使用全域性變數。 2)全域性靜態變數是顯式用static修飾的全域性變數,作用域是宣告此變數所在的檔案,其他的檔案即使用extern宣告也不能使用。

2.3靜態區域性變數有以下特點:

(1)該變數在全域性資料區分配記憶體; (2)靜態區域性變數在程式執行到該物件的宣告處時被首次初始化,即以後的函式呼叫不再進行初始化; (3)靜態區域性變數一般在宣告處初始化,如果沒有顯式初始化,會被程式自動初始化為0; (4)它始終駐留在全域性資料區,直到程式執行結束。但其作用域為區域性作用域,當定義它的函式或語句塊結束時,其作用域隨之結束。   一般程式把新產生的動態資料存放在堆區,函式內部的自動變數存放在棧區。自動變數一般會隨著函式的退出而釋放空間,靜態資料(即使是函式內部的靜態區域性變數)也存放在全域性資料區。全域性資料區的資料並不會因為函式的退出而釋放空間。 看下面的例子:
 1 //example:
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 int k1 = 1;
 5 int k2;
 6 static int k3 = 2;
 7 static int k4;
 8 int main()
 9 {
10     static int m1 = 2, m2;
11     int i = 1;
12     char*p;
13     char str[10] = "hello";
14     char*q = "hello";
15     p = (char *)malloc(100);
16     free(p);
17     printf("棧區-變數地址    i:%p\n", &i);
18     printf("棧區-變數地址   p:%p\n", &p);
19     printf("棧區-變數地址 str:%p\n", str);
20     printf("棧區-變數地址   q:%p\n", &q);
21     printf("堆區地址-動態申請:%p\n", p);
22     printf("全域性外部有初值 k1:%p\n", &k1);
23     printf("   外部無初值 k2:%p\n", &k2);
24     printf("靜態外部有初值 k3:%p\n", &k3);
25     printf("   外靜無初值 k4:%p\n", &k4);
26     printf("  內靜態有初值 m1:%p\n", &m1);
27     printf("  內靜態無初值 m2:%p\n", &m2);
28     printf("    文字常量地址:%p, %s\n", q, q);
29     printf("      程式區地址:%p\n", &main);
30     return 0;
31 }

3.1特別的,在C++中:

static關鍵字最基本的用法是:

1、被static修飾的變數屬於類變數,可以通過類名.變數名直接引用,而不需要new出一個類來

2、被static修飾的方法屬於類方法,可以通過類名.方法名直接引用,而不需要new出一個類來

被static修飾的變數、被static修飾的方法統一屬於類的靜態資源,是類例項之間共享的,換言之,一處變、處處變。

  在C++中,靜態成員是屬於整個類的而不是某個物件,靜態成員變數只儲存一份供所有物件共用。所以在所有物件中都可以共享它。使用靜態成員變數實現多個物件之間的資料共享不會破壞隱藏的原則,保證了安全性還可以節省記憶體。

靜態成員的定義或宣告要加個關鍵static。靜態成員可以通過雙冒號來使用即<類名>::<靜態成員名>。

3.2靜態類相關

 1 example1:通過類名呼叫靜態成員函式和非靜態成員函式
 2  class Point
 3     {
 4     public:
 5         void init()
 6         {
 7         }
 8         static void output()
 9         {
10         }
11     };
12     void main()
13     {
14         Point::init();
15         Point::output();
16     }

報錯: 'Point::init' : illegal call of non-static member function

結論1:不能通過類名來呼叫類的非靜態成員函式。

//example2:通過類的物件呼叫靜態成員函式和非靜態成員函式
 class Point
    {
    public:
        void init()
        {
        }
        static void output()
        {
        }
    };
void main()
{
    Point pt;
    pt.init();
    pt.output();
}

編譯通過。

結論2:類的物件可以使用靜態成員函式和非靜態成員函式。

 1 //example3:在類的靜態成員函式中使用類的非靜態成員
 2 #include <stdio.h>
 3 class Point
 4 {
 5 public:
 6     void init()
 7     {
 8     }
 9     static void output()
10     {
11         printf("%d\n", m_x);
12     }
13 private:
14     int m_x;
15 };
16 void main()
17 {
18     Point pt;
19     pt.output();
20 }

編譯出錯:error C2597: illegal reference to data member 'Point::m_x' in a static member function

  因為靜態成員函式屬於整個類,在類例項化物件之前就已經分配空間了,而類的非靜態成員必須在類例項化物件後才有記憶體空間,所以這個呼叫就出錯了,就好比沒有宣告一個變數卻提前使用它一樣。

結論3:靜態成員函式中不能引用非靜態成員。

 1 //example4:在類的非靜態成員函式中使用類的靜態成員
 2 class Point
 3 {
 4 public:
 5     void init()
 6     {
 7         output();
 8     }
 9     static void output()
10     {
11     }
12 };
13 void main()
14 {
15     Point pt;  15     Pt.init();
16     pt.output();
17 }

編譯通過。

結論4:類的非靜態成員函式可以呼叫用靜態成員函式,但反之不能。

 1 //example5:使用類的靜態成員變數
 2  #include <stdio.h>
 3     class Point
 4     {
 5     public:
 6         Point()
 7         {
 8             m_nPointCount++;
 9         }
10         ~Point()
11         {
12             m_nPointCount--;
13         }
14         static void output()
15         {
16             printf("%d\n", m_nPointCount);
17         }
18     private:
19         static int m_nPointCount;
20     };
21     void main()
22     {
23         Point pt;
24         pt.output();
25     }

按Ctrl+F7編譯無錯誤,按F7生成EXE程式時報連結錯誤

error LNK2001: unresolved external symbol "private: static int Point::m_nPointCount" (?m_nPointCount@Point@@0HA)

這是因為類的靜態成員變數在使用前必須先初始化。

在main()函式前加上int Point::m_nPointCount = 0;

再編譯連結無錯誤,執行程式將輸出1。

結論5:類的靜態成員變數必須先初始化再使用。

  思考總結:靜態資源屬於類,但是是獨立於類存在的。從J類的載入機制的角度講,靜態資源是類初始化的時候載入的,而非靜態資源是類例項化物件的時候載入的。 類的初始化早於類例項化物件,比如Class.forName(“xxx”)方法,就是初始化了一個類,但是並沒有例項化物件,只是載入這個類的靜態資源罷 了。所以對於靜態資源來說,它是不可能知道一個類中有哪些非靜態資源的;但是對於非靜態資源來說就不一樣了,由於它是例項化物件出來之後產生的,因此屬於類的這些東西它都能認識。所以上面的幾個問題答案就很明確了:

1)靜態方法能不能引用非靜態資源?不能,例項化物件的時候才會產生的東西,對於初始化後就存在的靜態資源來說,根本不認識它。

2)靜態方法裡面能不能引用靜態資源?可以,因為都是類初始化的時候載入的,大家相互都認識。

3)非靜態方法裡面能不能引用靜態資源?可以,非靜態方法就是例項方法,那是例項化物件之後才產生的,那麼屬於類的內容它都認識。

  (static修飾類:這個用得相對比前面的用法少多了,static一般情況下來說是不可以修飾類的, 如果static要修飾一個類,說明這個類是一個靜態內部類(注意static只能修飾一個內部類),也就是匿名內部類。像執行緒池 ThreadPoolExecutor中的四種拒絕機制CallerRunsPolicy、AbortPolicy、DiscardPolicy、 DiscardOldestPolicy就是靜態內部類。靜態內部類相關內容會在寫內部類的時候專門講到。)

3.3總結:

(1)靜態成員函式中不能呼叫非靜態成員。

(2)非靜態成員函式中可以呼叫靜態成員。因為靜態成員屬於類本身,在類的物件產生之前就已經存在了,所以在非靜態成員函式中是可以呼叫靜態成員的。

(3)靜態成員變數使用前必須先初始化(如int MyClass::m_nNumber = 0;),否則會在linker時出錯。

參考:http://blog.csdn.net/morewindows/article/details/6721430

一般總結:在類中,static可以用來修飾靜態資料成員和靜態成員方法
靜態資料成員
(1)靜態資料成員可以實現多個物件之間的資料共享,它是類的所有物件的共享成員,它在記憶體中只佔一份空間,如果改變它的值,則各物件中這個資料成員的值都被改變。
(2)靜態資料成員是在程式開始執行時被分配空間,到程式結束之後才釋放,只要類中指定了靜態資料成員,即使不定義物件,也會為靜態資料成員分配空間。
(3)靜態資料成員可以被初始化,但是隻能在類體外進行初始化,若未對靜態資料成員賦初值,則編譯器會自動為其初始化為0
(4)靜態資料成員既可以通過物件名引用,也可以通過類名引用。

靜態成員函式
(1)靜態成員函式和靜態資料成員一樣,他們都屬於類的靜態成員,而不是物件成員。
(2)非靜態成員函式有this指標,而靜態成員函式沒有this指標。
(3)靜態成員函式主要用來方位靜態資料成員而不能訪問非靜態成員。

From:C/C++中static的用法全域性變數與區域性變數

More:C/C++---static函式,static成員函式,static變數,static成員變數 再來理一理

More:靜態變數與全域性變數的區別