當前位置:
首頁 > 知識 > 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修飾的靜態局部變數只執行初始化一次,而且延長了局部變數的生命周期,直到程序運行結束以後才釋放。

(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 <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
", &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 }

C/C++中static的用法全局變數與局部變數

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 }

C/C++中static的用法全局變數與局部變數

報錯: "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 <stdio.h>
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 <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
", 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)靜態成員函數主要用來方位靜態數據成員而不能訪問非靜態成員。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 達人科技 的精彩文章:

使用Spring boot + jQuery上傳文件(kotlin)
一步一步學Vue(四)
一種基於kafka+storm實現的日誌記錄方法(二)
4.一次性密碼 && 身份認證三要素
誰將新樽辭舊月,今月曾經照古人

TAG:達人科技 |

您可能感興趣

VBScript 變數
「Python」Chapter1 變數和簡單數據類型
static 成員變數、static 成員函數、類/對象的大小
Python和Scala的定義變數
linux-shell編程中awk變數的使用
非靜態內部類中 static/final 成員變數相關的一道趣題
python基礎之變數類型number(math模塊)
Python數據類型、運算符、變數
Perl 變數
Flipkart-Walmart交易:軟銀最終會有變數嗎
全新斯巴魯Levorg問世有變數 EyeSight成拖累主因
Shell 變數
The Daily Beast:美朝峰會出現變數,特朗普怪罪中國
「學習筆記」Python dir()函數和 __doc__ 變數的使用
Nginx 日誌和變數
關於如何使用webpack命令行傳入變數,並通過process.env來調用
TensorFlow中常量與變數的基本操作演示
【外媒報中國】The Daily Beast:美朝峰會出現變數,特朗普怪罪中國
微軟摺疊屏專利現身,Surface Phone或有變數
你真的需要了解一下CSS變數 var()的用法