您的位置:首頁>正文

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修飾的靜態區域變數只執行初始化一次, 而且延長了區域變數的生命週期,

直到程式運行結束以後才釋放。

(2)static修飾全域變數的時候, 這個全域變數只能在本檔中訪問, 不能在其它檔中訪問, 即便是extern外部聲明也不可以。

(3)static修飾一個函數, 則這個函數的只能在本檔中調用, 不能被其他檔調用。 Static修飾的變數存放在全域資料區的靜態變數區, 包括全域靜態變數和局部靜態變數, 都在全域資料區分配記憶體。 初始化的時候自動初始化為0。

(4)不想被釋放的時候, 可以使用static修飾。 比如修飾函數中存放在棧空間的陣列。 如果不想讓這個陣列在函式呼叫結束釋放可以使用static修飾。

(5)考慮到資料安全性(當程式想要使用全域變數的時候應該先考慮使用static)。

2.2靜態變數與普通變數

靜態全域變數有以下特點:

(1)靜態變數都在全域資料區分配記憶體,

包括後面將要提到的靜態區域變數;

(2)未經初始化的靜態全域變數會被程式自動初始化為0(在函數體內聲明的自動變數的值是隨機的, 除非它被顯式初始化, 而在函數體外被聲明的自動變數也會被初始化為0);

(3)靜態全域變數在聲明它的整個檔都是可見的, 而在檔之外是不可見的。

優點:靜態全域變數不能被其它檔所用;其它檔中可以定義相同名字的變數, 不會發生衝突。

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

1)全域變數是不顯式用static修飾的全域變數, 全域變數預設是有外部連結性的, 作用域是整個工程, 在一個檔內定義的全域變數, 在另一個檔中, 通過extern 全域變數名的聲明, 就可以使用全域變數。

2)全域靜態變數是顯式用static修飾的全域變數,作用域是聲明此變數所在的檔,其他的檔即使用extern聲明也不能使用。

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

(1)該變數在全域資料區分配記憶體;

(2)靜態區域變數在程式執行到該物件的聲明處時被首次初始化,即以後的函式呼叫不再進行初始化;

(3)靜態區域變數一般在聲明處初始化,如果沒有顯式初始化,會被程式自動初始化為0;

(4)它始終駐留在全域資料區,直到程式運行結束。但其作用域為局部作用域,當定義它的函數或語句塊結束時,其作用域隨之結束。

一般程式把新產生的動態資料存放在堆區,函數內部的自動變數存放在棧區。自動變數一般會隨著函數的退出而釋放空間,靜態資料(即使是函數內部的靜態區域變數)也存放在全域資料區。全域資料區的資料並不會因為函數的退出而釋放空間。

看下面的例子:

1 //example: 2 #include 3 #include 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 ", &i); 18 printf("棧區-變數位址 p:%p ", &p); 19 printf("棧區-變數位址 str:%p ", str); 20 printf("棧區-變數位址 q:%p ", &q); 21 printf("堆區位址-動態申請:%p ", p); 22 printf("全域外部有初值 k1:%p ", &k1); 23 printf(" 外部無初值 k2:%p ", &k2); 24 printf("靜態外部有初值 k3:%p ", &k3); 25 printf(" 外靜無初值 k4:%p ", &k4); 26 printf(" 內靜態有初值 m1:%p ", &m1); 27 printf(" 內靜態無初值 m2:%p ", &m2); 28 printf(" 文字常量位址:%p, %s ", q, q); 29 printf(" 程式區位址:%p ", &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:不能通過類名來調用類的非靜態成員函數。

1 //example2:通過類的物件調用靜態成員函數和非靜態成員函數 2 class Point 3 { 4 public: 5 void init 6 { 7 } 8 static void output 9 { 10 } 11 }; 12 void main 13 { 14 Point pt; 15 pt.init; 16 pt.output; 17 }

編譯通過。

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

1 //example3:在類的靜態成員函數中使用類的非靜態成員 2 #include 3 class Point 4 { 5 public: 6 void init 7 { 8 } 9 static void output 10 { 11 printf("%d ", 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 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 ", 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時出錯。

一般總結:在類中,static可以用來修飾靜態資料成員和靜態成員方法

靜態資料成員

(1)靜態資料成員可以實現多個物件之間的資料共用,它是類的所有物件的共用成員,它在記憶體中只占一份空間,如果改變它的值,則各物件中這個資料成員的值都被改變。

(2)靜態資料成員是在程式開始運行時被分配空間,到程式結束之後才釋放,只要類中指定了靜態資料成員,即使不定義物件,也會為靜態資料成員分配空間。

(3)靜態資料成員可以被初始化,但是只能在類體外進行初始化,若未對靜態資料成員賦初值,則編譯器會自動為其初始化為0

(4)靜態資料成員既可以通過物件名引用,也可以通過類名引用。

靜態成員函數

(1)靜態成員函數和靜態資料成員一樣,他們都屬於類的靜態成員,而不是物件成員。

(2)非靜態成員函數有this指標,而靜態成員函數沒有this指標。

(3)靜態成員函數主要用來方位靜態資料成員而不能訪問非靜態成員。

2)全域靜態變數是顯式用static修飾的全域變數,作用域是聲明此變數所在的檔,其他的檔即使用extern聲明也不能使用。

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

(1)該變數在全域資料區分配記憶體;

(2)靜態區域變數在程式執行到該物件的聲明處時被首次初始化,即以後的函式呼叫不再進行初始化;

(3)靜態區域變數一般在聲明處初始化,如果沒有顯式初始化,會被程式自動初始化為0;

(4)它始終駐留在全域資料區,直到程式運行結束。但其作用域為局部作用域,當定義它的函數或語句塊結束時,其作用域隨之結束。

一般程式把新產生的動態資料存放在堆區,函數內部的自動變數存放在棧區。自動變數一般會隨著函數的退出而釋放空間,靜態資料(即使是函數內部的靜態區域變數)也存放在全域資料區。全域資料區的資料並不會因為函數的退出而釋放空間。

看下面的例子:

1 //example: 2 #include 3 #include 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 ", &i); 18 printf("棧區-變數位址 p:%p ", &p); 19 printf("棧區-變數位址 str:%p ", str); 20 printf("棧區-變數位址 q:%p ", &q); 21 printf("堆區位址-動態申請:%p ", p); 22 printf("全域外部有初值 k1:%p ", &k1); 23 printf(" 外部無初值 k2:%p ", &k2); 24 printf("靜態外部有初值 k3:%p ", &k3); 25 printf(" 外靜無初值 k4:%p ", &k4); 26 printf(" 內靜態有初值 m1:%p ", &m1); 27 printf(" 內靜態無初值 m2:%p ", &m2); 28 printf(" 文字常量位址:%p, %s ", q, q); 29 printf(" 程式區位址:%p ", &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:不能通過類名來調用類的非靜態成員函數。

1 //example2:通過類的物件調用靜態成員函數和非靜態成員函數 2 class Point 3 { 4 public: 5 void init 6 { 7 } 8 static void output 9 { 10 } 11 }; 12 void main 13 { 14 Point pt; 15 pt.init; 16 pt.output; 17 }

編譯通過。

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

1 //example3:在類的靜態成員函數中使用類的非靜態成員 2 #include 3 class Point 4 { 5 public: 6 void init 7 { 8 } 9 static void output 10 { 11 printf("%d ", 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 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 ", 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時出錯。

一般總結:在類中,static可以用來修飾靜態資料成員和靜態成員方法

靜態資料成員

(1)靜態資料成員可以實現多個物件之間的資料共用,它是類的所有物件的共用成員,它在記憶體中只占一份空間,如果改變它的值,則各物件中這個資料成員的值都被改變。

(2)靜態資料成員是在程式開始運行時被分配空間,到程式結束之後才釋放,只要類中指定了靜態資料成員,即使不定義物件,也會為靜態資料成員分配空間。

(3)靜態資料成員可以被初始化,但是只能在類體外進行初始化,若未對靜態資料成員賦初值,則編譯器會自動為其初始化為0

(4)靜態資料成員既可以通過物件名引用,也可以通過類名引用。

靜態成員函數

(1)靜態成員函數和靜態資料成員一樣,他們都屬於類的靜態成員,而不是物件成員。

(2)非靜態成員函數有this指標,而靜態成員函數沒有this指標。

(3)靜態成員函數主要用來方位靜態資料成員而不能訪問非靜態成員。

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示