1. 什么是static?
static 是 C/C++ 中很常用的修飾符,它被用來(lái)控制變量的存儲(chǔ)方式和可見(jiàn)性。
1.1 static 的引入
我們知道在函數(shù)內(nèi)部定義的變量,當(dāng)程序執(zhí)行到它的定義處時(shí),編譯器為它在棧上分配空間,函數(shù)在棧上分配的空間在此函數(shù)執(zhí)行結(jié)束時(shí)會(huì)釋放掉,這樣就產(chǎn)生了一個(gè)問(wèn)題: 如果想將函數(shù)中此變量的值保存至下一次調(diào)用時(shí),如何實(shí)現(xiàn)? 最容易想到的方法是定義為全局的變量,但定義一個(gè)全局變量有許多缺點(diǎn),最明顯的缺點(diǎn)是破壞了此變量的訪問(wèn)范圍(使得在此函數(shù)中定義的變量,不僅僅只受此函數(shù)控制)。static 關(guān)鍵字則可以很好的解決這個(gè)問(wèn)題。
另外,在 C++ 中,需要一個(gè)數(shù)據(jù)對(duì)象為整個(gè)類(lèi)而非某個(gè)對(duì)象服務(wù),同時(shí)又力求不破壞類(lèi)的封裝性,即要求此成員隱藏在類(lèi)的內(nèi)部,對(duì)外不可見(jiàn)時(shí),可將其定義為靜態(tài)數(shù)據(jù)。
1.2 靜態(tài)數(shù)據(jù)的存儲(chǔ)
全局(靜態(tài))存儲(chǔ)區(qū):分為 DATA 段和 BSS 段。DATA 段(全局初始化區(qū))存放初始化的全局變量和靜態(tài)變量;BSS 段(全局未初始化區(qū))存放未初始化的全局變量和靜態(tài)變量。程序運(yùn)行結(jié)束時(shí)自動(dòng)釋放。其中BBS段在程序執(zhí)行之前會(huì)被系統(tǒng)自動(dòng)清0,所以未初始化的全局變量和靜態(tài)變量在程序執(zhí)行之前已經(jīng)為0。存儲(chǔ)在靜態(tài)數(shù)據(jù)區(qū)的變量會(huì)在程序剛開(kāi)始運(yùn)行時(shí)就完成初始化,也是唯一的一次初始化。
在 C++ 中 static 的內(nèi)部實(shí)現(xiàn)機(jī)制:靜態(tài)數(shù)據(jù)成員要在程序一開(kāi)始運(yùn)行時(shí)就必須存在。因?yàn)楹瘮?shù)在程序運(yùn)行中被調(diào)用,所以靜態(tài)數(shù)據(jù)成員不能在任何函數(shù)內(nèi)分配空間和初始化。
這樣,它的空間分配有三個(gè)可能的地方,一是作為類(lèi)的外部接口的頭文件,那里有類(lèi)聲明;二是類(lèi)定義的內(nèi)部實(shí)現(xiàn),那里有類(lèi)的成員函數(shù)定義;三是應(yīng)用程序的 main() 函數(shù)前的全局?jǐn)?shù)據(jù)聲明和定義處。
靜態(tài)數(shù)據(jù)成員要實(shí)際地分配空間,故不能在類(lèi)的聲明中定義(只能聲明數(shù)據(jù)成員)。類(lèi)聲明只聲明一個(gè)類(lèi)的”尺寸和規(guī)格”,并不進(jìn)行實(shí)際的內(nèi)存分配,所以在類(lèi)聲明中寫(xiě)成定義是錯(cuò)誤的。它也不能在頭文件中類(lèi)聲明的外部定義,因?yàn)槟菚?huì)造成在多個(gè)使用該類(lèi)的源文件中,對(duì)其重復(fù)定義。
static 被引入以告知編譯器,將變量存儲(chǔ)在程序的靜態(tài)存儲(chǔ)區(qū)而非棧上空間,靜態(tài)數(shù)據(jù)成員按定義出現(xiàn)的先后順序依次初始化,注意靜態(tài)成員嵌套時(shí),要保證所嵌套的成員已經(jīng)初始化了。消除時(shí)的順序是初始化的反順序。
優(yōu)勢(shì):可以節(jié)省內(nèi)存,因?yàn)樗撬袑?duì)象所公有的,因此,對(duì)多個(gè)對(duì)象來(lái)說(shuō),靜態(tài)數(shù)據(jù)成員只存儲(chǔ)一處,供所有對(duì)象共用。靜態(tài)數(shù)據(jù)成員的值對(duì)每個(gè)對(duì)象都是一樣,但它的值是可以更新的。只要對(duì)靜態(tài)數(shù)據(jù)成員的值更新一次,保證所有對(duì)象存取更新后的相同的值,這樣可以提高時(shí)間效率。
2. 在 C/C++ 中static的作用
2.1 總的來(lái)說(shuō)
- (1)在修飾變量的時(shí)候,static 修飾的靜態(tài)局部變量只執(zhí)行初始化一次,而且延長(zhǎng)了局部變量的生命周期,直到程序運(yùn)行結(jié)束以后才釋放。
- (2)static 修飾全局變量的時(shí)候,這個(gè)全局變量只能在本文件中訪問(wèn),不能在其它文件中訪問(wèn),即便是 extern 外部聲明也不可以。
- (3)static 修飾一個(gè)函數(shù),則這個(gè)函數(shù)的只能在本文件中調(diào)用,不能被其他文件調(diào)用。static 修飾的變量存放在全局?jǐn)?shù)據(jù)區(qū)的靜態(tài)變量區(qū),包括全局靜態(tài)變量和局部靜態(tài)變量,都在全局?jǐn)?shù)據(jù)區(qū)分配內(nèi)存。初始化的時(shí)候自動(dòng)初始化為 0。
- (4)不想被釋放的時(shí)候,可以使用static修飾。比如修飾函數(shù)中存放在??臻g的數(shù)組。如果不想讓這個(gè)數(shù)組在函數(shù)調(diào)用結(jié)束釋放可以使用 static 修飾。
- (5)考慮到數(shù)據(jù)安全性(當(dāng)程序想要使用全局變量的時(shí)候應(yīng)該先考慮使用 static)。
2.2 靜態(tài)變量與普通變量
靜態(tài)全局變量有以下特點(diǎn):
- (1)靜態(tài)變量都在全局?jǐn)?shù)據(jù)區(qū)分配內(nèi)存,包括后面將要提到的靜態(tài)局部變量;
- (2)未經(jīng)初始化的靜態(tài)全局變量會(huì)被程序自動(dòng)初始化為0(在函數(shù)體內(nèi)聲明的自動(dòng)變量的值是隨機(jī)的,除非它被顯式初始化,而在函數(shù)體外被聲明的自動(dòng)變量也會(huì)被初始化為 0);
- (3)靜態(tài)全局變量在聲明它的整個(gè)文件都是可見(jiàn)的,而在文件之外是不可見(jiàn)的。
優(yōu)點(diǎn):靜態(tài)全局變量不能被其它文件所用;其它文件中可以定義相同名字的變量,不會(huì)發(fā)生沖突。
(1)全局變量和全局靜態(tài)變量的區(qū)別
- 1)全局變量是不顯式用 static 修飾的全局變量,全局變量默認(rèn)是有外部鏈接性的,作用域是整個(gè)工程,在一個(gè)文件內(nèi)定義的全局變量,在另一個(gè)文件中,通過(guò) extern 全局變量名的聲明,就可以使用全局變量。
- 2)全局靜態(tài)變量是顯式用 static 修飾的全局變量,作用域是聲明此變量所在的文件,其他的文件即使用 extern 聲明也不能使用。
2.3 靜態(tài)局部變量有以下特點(diǎn):
- (1)該變量在全局?jǐn)?shù)據(jù)區(qū)分配內(nèi)存;
- (2)靜態(tài)局部變量在程序執(zhí)行到該對(duì)象的聲明處時(shí)被首次初始化,即以后的函數(shù)調(diào)用不再進(jìn)行初始化;
- (3)靜態(tài)局部變量一般在聲明處初始化,如果沒(méi)有顯式初始化,會(huì)被程序自動(dòng)初始化為 0;
- (4)它始終駐留在全局?jǐn)?shù)據(jù)區(qū),直到程序運(yùn)行結(jié)束。但其作用域?yàn)榫植孔饔糜?,?dāng)定義它的函數(shù)或語(yǔ)句塊結(jié)束時(shí),其作用域隨之結(jié)束。
一般程序把新產(chǎn)生的動(dòng)態(tài)數(shù)據(jù)存放在堆區(qū),函數(shù)內(nèi)部的自動(dòng)變量存放在棧區(qū)。自動(dòng)變量一般會(huì)隨著函數(shù)的退出而釋放空間,靜態(tài)數(shù)據(jù)(即使是函數(shù)內(nèi)部的靜態(tài)局部變量)也存放在全局?jǐn)?shù)據(jù)區(qū)。全局?jǐn)?shù)據(jù)區(qū)的數(shù)據(jù)并不會(huì)因?yàn)楹瘮?shù)的退出而釋放空間。
看下面的例子:
實(shí)例
輸出結(jié)果如下:
3. static 用法
3.1 在 C++ 中
static 關(guān)鍵字最基本的用法是:
- 1、被 static 修飾的變量屬于類(lèi)變量,可以通過(guò)類(lèi)名.變量名直接引用,而不需要 new 出一個(gè)類(lèi)來(lái)
- 2、被 static 修飾的方法屬于類(lèi)方法,可以通過(guò)類(lèi)名.方法名直接引用,而不需要 new 出一個(gè)類(lèi)來(lái)
被 static 修飾的變量、被 static 修飾的方法統(tǒng)一屬于類(lèi)的靜態(tài)資源,是類(lèi)實(shí)例之間共享的,換言之,一處變、處處變。
在 C++ 中,靜態(tài)成員是屬于整個(gè)類(lèi)的而不是某個(gè)對(duì)象,靜態(tài)成員變量只存儲(chǔ)一份供所有對(duì)象共用。所以在所有對(duì)象中都可以共享它。使用靜態(tài)成員變量實(shí)現(xiàn)多個(gè)對(duì)象之間的數(shù)據(jù)共享不會(huì)破壞隱藏的原則,保證了安全性還可以節(jié)省內(nèi)存。
靜態(tài)成員的定義或聲明要加個(gè)關(guān)鍵 static。靜態(tài)成員可以通過(guò)雙冒號(hào)來(lái)使用即 <類(lèi)名>::<靜態(tài)成員名>。
3.2 靜態(tài)類(lèi)相關(guān)
通過(guò)類(lèi)名調(diào)用靜態(tài)成員函數(shù)和非靜態(tài)成員函數(shù):
報(bào)錯(cuò):
'Point::init' : illegal call of non-static member function
結(jié)論 1:不能通過(guò)類(lèi)名來(lái)調(diào)用類(lèi)的非靜態(tài)成員函數(shù)。
通過(guò)類(lèi)的對(duì)象調(diào)用靜態(tài)成員函數(shù)和非靜態(tài)成員函數(shù)。
編譯通過(guò)。
結(jié)論 2:類(lèi)的對(duì)象可以使用靜態(tài)成員函數(shù)和非靜態(tài)成員函數(shù)。
在類(lèi)的靜態(tài)成員函數(shù)中使用類(lèi)的非靜態(tài)成員。
編譯出錯(cuò):
error C2597: illegal reference to data member 'Point::m_x' in a static member function
因?yàn)殪o態(tài)成員函數(shù)屬于整個(gè)類(lèi),在類(lèi)實(shí)例化對(duì)象之前就已經(jīng)分配空間了,而類(lèi)的非靜態(tài)成員必須在類(lèi)實(shí)例化對(duì)象后才有內(nèi)存空間,所以這個(gè)調(diào)用就出錯(cuò)了,就好比沒(méi)有聲明一個(gè)變量卻提前使用它一樣。
結(jié)論3:靜態(tài)成員函數(shù)中不能引用非靜態(tài)成員。
在類(lèi)的非靜態(tài)成員函數(shù)中使用類(lèi)的靜態(tài)成員。
編譯通過(guò)。
結(jié)論 4:類(lèi)的非靜態(tài)成員函數(shù)可以調(diào)用用靜態(tài)成員函數(shù),但反之不能。
使用類(lèi)的靜態(tài)成員變量。
按 Ctrl+F7 編譯無(wú)錯(cuò)誤,按 F7 生成 EXE 程序時(shí)報(bào)鏈接錯(cuò)誤。
error LNK2001: unresolved external symbol "private: static int Point::m_nPointCount" (?m_nPointCount@Point@@0HA)
這是因?yàn)轭?lèi)的靜態(tài)成員變量在使用前必須先初始化。
在 main() 函數(shù)前加上 int Point::m_nPointCount = 0; 再編譯鏈接無(wú)錯(cuò)誤,運(yùn)行程序?qū)⑤敵?1。
結(jié)論 5:類(lèi)的靜態(tài)成員變量必須先初始化再使用。
思考總結(jié):靜態(tài)資源屬于類(lèi),但是是獨(dú)立于類(lèi)存在的。從 J 類(lèi)的加載機(jī)制的角度講,靜態(tài)資源是類(lèi)初始化的時(shí)候加載的,而非靜態(tài)資源是類(lèi)實(shí)例化對(duì)象的時(shí)候加載的。 類(lèi)的初始化早于類(lèi)實(shí)例化對(duì)象,比如 Class.forName(“xxx”) 方法,就是初始化了一個(gè)類(lèi),但是并沒(méi)有實(shí)例化對(duì)象,只是加載這個(gè)類(lèi)的靜態(tài)資源罷 了。所以對(duì)于靜態(tài)資源來(lái)說(shuō),它是不可能知道一個(gè)類(lèi)中有哪些非靜態(tài)資源的;但是對(duì)于非靜態(tài)資源來(lái)說(shuō)就不一樣了,由于它是實(shí)例化對(duì)象出來(lái)之后產(chǎn)生的,因此屬于類(lèi)的這些東西它都能認(rèn)識(shí)。所以上面的幾個(gè)問(wèn)題答案就很明確了:
- 1)靜態(tài)方法能不能引用非靜態(tài)資源?不能,實(shí)例化對(duì)象的時(shí)候才會(huì)產(chǎn)生的東西,對(duì)于初始化后就存在的靜態(tài)資源來(lái)說(shuō),根本不認(rèn)識(shí)它。
- 2)靜態(tài)方法里面能不能引用靜態(tài)資源?可以,因?yàn)槎际穷?lèi)初始化的時(shí)候加載的,大家相互都認(rèn)識(shí)。
- 3)非靜態(tài)方法里面能不能引用靜態(tài)資源?可以,非靜態(tài)方法就是實(shí)例方法,那是實(shí)例化對(duì)象之后才產(chǎn)生的,那么屬于類(lèi)的內(nèi)容它都認(rèn)識(shí)。
(static 修飾類(lèi):這個(gè)用得相對(duì)比前面的用法少多了,static 一般情況下來(lái)說(shuō)是不可以修飾類(lèi)的, 如果 static 要修飾一個(gè)類(lèi),說(shuō)明這個(gè)類(lèi)是一個(gè)靜態(tài)內(nèi)部類(lèi)(注意 static 只能修飾一個(gè)內(nèi)部類(lèi)),也就是匿名內(nèi)部類(lèi)。像線(xiàn)程池 ThreadPoolExecutor 中的四種拒絕機(jī)制 CallerRunsPolicy、AbortPolicy、DiscardPolicy、 DiscardOldestPolicy 就是靜態(tài)內(nèi)部類(lèi)。靜態(tài)內(nèi)部類(lèi)相關(guān)內(nèi)容會(huì)在寫(xiě)內(nèi)部類(lèi)的時(shí)候?qū)iT(mén)講到。)
3.3 總結(jié):
- (1)靜態(tài)成員函數(shù)中不能調(diào)用非靜態(tài)成員。
- (2)非靜態(tài)成員函數(shù)中可以調(diào)用靜態(tài)成員。因?yàn)殪o態(tài)成員屬于類(lèi)本身,在類(lèi)的對(duì)象產(chǎn)生之前就已經(jīng)存在了,所以在非靜態(tài)成員函數(shù)中是可以調(diào)用靜態(tài)成員的。
- (3)靜態(tài)成員變量使用前必須先初始化(如 int MyClass::m_nNumber = 0;),否則會(huì)在 linker 時(shí)出錯(cuò)。
一般總結(jié):在類(lèi)中,static 可以用來(lái)修飾靜態(tài)數(shù)據(jù)成員和靜態(tài)成員方法。
靜態(tài)數(shù)據(jù)成員
- (1)靜態(tài)數(shù)據(jù)成員可以實(shí)現(xiàn)多個(gè)對(duì)象之間的數(shù)據(jù)共享,它是類(lèi)的所有對(duì)象的共享成員,它在內(nèi)存中只占一份空間,如果改變它的值,則各對(duì)象中這個(gè)數(shù)據(jù)成員的值都被改變。
- (2)靜態(tài)數(shù)據(jù)成員是在程序開(kāi)始運(yùn)行時(shí)被分配空間,到程序結(jié)束之后才釋放,只要類(lèi)中指定了靜態(tài)數(shù)據(jù)成員,即使不定義對(duì)象,也會(huì)為靜態(tài)數(shù)據(jù)成員分配空間。
- (3)靜態(tài)數(shù)據(jù)成員可以被初始化,但是只能在類(lèi)體外進(jìn)行初始化,若未對(duì)靜態(tài)數(shù)據(jù)成員賦初值,則編譯器會(huì)自動(dòng)為其初始化為 0。
- (4)靜態(tài)數(shù)據(jù)成員既可以通過(guò)對(duì)象名引用,也可以通過(guò)類(lèi)名引用。
靜態(tài)成員函數(shù)
- (1)靜態(tài)成員函數(shù)和靜態(tài)數(shù)據(jù)成員一樣,他們都屬于類(lèi)的靜態(tài)成員,而不是對(duì)象成員。
- (2)非靜態(tài)成員函數(shù)有 this 指針,而靜態(tài)成員函數(shù)沒(méi)有 this 指針。
- (3)靜態(tài)成員函數(shù)主要用來(lái)方位靜態(tài)數(shù)據(jù)成員而不能訪問(wèn)非靜態(tài)成員。
再給一個(gè)利用類(lèi)的靜態(tài)成員變量和函數(shù)的例子以加深理解,這個(gè)例子建立一個(gè)學(xué)生類(lèi),每個(gè)學(xué)生類(lèi)的對(duì)象將組成一個(gè)雙向鏈表,用一個(gè)靜態(tài)成員變量記錄這個(gè)雙向鏈表的表頭,一個(gè)靜態(tài)成員函數(shù)輸出這個(gè)雙向鏈表。
實(shí)例
程序?qū)⑤敵觯?/p>
原文地址:https://www.cnblogs.com/33debug/p/7223869.html