關鍵字static總結
http://dev.csdn.net/article/27/27663.shtm static關鍵字如果不被引入C++語言,那就違反了C++設計中對低級語言設計支持規(guī)則中的”沒有無故與C語言不兼容規(guī)則”,,原因很簡單,C語言中存在static并發(fā)發(fā)揮著它良好的作用,,所以C++同樣引入static應該是理所當然的,,而實際C++的做法是不僅引入static,而且對它在面向對象程序設計中進行擴充(導入靜態(tài)數據成員和靜態(tài)函數成員的概念),這就使static的概念得到了擴展,,對于其中較新的靜態(tài)成員變量(對象)和靜態(tài)函數成員則應該重點更理解,,下面分別從static的作用,在C++中的分類,,以及一些比較典型的應用三個方面進行總結一下 一:靜態(tài)(static)的作用 首先,,一個很明顯的作用就是解決了全局部名字空間的問題,大家都知道全局變量是一處定義多處修改的,,這當然是它的優(yōu)勢但,,也確實解決了不少問題,但這對面向對象程序思想是相抵觸的,,它破壞的良好的封裝性,,污染了程序的名稱空間,,而對于這些缺點的,正是static能夠解決的問題,我們可以在函數,,結構,,類中定義靜態(tài)對象,當將變量定義為static時,,編譯器就會將變量的存儲在程序的靜態(tài)存儲區(qū)(數據段)域而非普通的函數分配的??臻g上。 其次,,static會將其修修飾的變量對象的可見性限制在本編譯單元內(也就是后所以的具有文件作用域),,使它成為一個內部連接。這與普通全局變量加入”extern”可以在多個文件中使用是相對應的,。 二:靜態(tài)(static)的分類 目前在C++語言有五種靜態(tài)對象的類別,,分別為靜態(tài)局部變量(對象),靜態(tài)全局變量(對象),,靜態(tài)函數(靜態(tài)全局函數),,靜態(tài)成員變量(對象),靜態(tài)成員函數,。 靜態(tài)局部變量,,靜態(tài)全局變量,以及靜態(tài)函數的存在是對C語言兼容的結果,,靜態(tài)員成員變量是在C++引入類之后相對應存在的概念,,靜態(tài)成員函數則是為保持類的封裝的前提下對靜態(tài)成員變量進行更好方法而引入的一相概念,所以說靜態(tài)成員函數就目前來講是所以靜態(tài)對象(包括C++所有的靜態(tài)變量和靜態(tài)成員函數)中最晚一個引入C++語言的,。 1. 靜態(tài)局部變量(對象) 靜態(tài)局部變量(對象)通也是指在函數中定義的靜態(tài)變量(對象),,當函數第一次被調用時,程序會為它在程序的數據段分配存儲空間,,并對它進行初始,,當函數調用完成退出時, 這個變量(對象)會保存此次函數調用后的狀態(tài), 但不會被其它函數使用, 當再次調用該函數時, 并不為它分配空間和初始化,而是直接命使用上次調用的結果,??纯聪旅婧唵蔚某绦颍?/span> //////////////////////////////////////////////////////////////////////////////////////// #include <iostream> #include <cstdlib> using namespace std; void foo(bool i) { static int a=0; int c=0; if(i){ static int b=0; ++b; cout <<"b="<< b <<endl; } ++a; ++c; cout << "a="<< a <<endl; cout << "c="<< c <<endl; } int main() { foo(false); //變量,a,c初始化化并加1,,所以a=1,c=1,此是b并沒有初始化 //和分配空間,函數調用完全c的空間被回收,,a由于是靜態(tài)變量, //所以并沒有回收. cout << "-----------------" <<endl; foo(true); //變量a在上次調用的狀態(tài)上加1,所以a=2,c重新初始分配空間并加1,, //所以c=1,變量b初始化并分配空間后加1,,所以b=1. cout << "-----------------" <<endl; foo(false);//變量a在上次調用的狀態(tài)上加1,所以a=3,c重新初始分配空間并加1, //所以c=1,變量b保自己的狀態(tài),,所以b=1. cout << "-----------------" <<endl; foo(true);//變量a在上次調用的狀態(tài)上加1,所以a=4,c重新初始分配空間并加1,, //所以c=1,變量b在上次調用的狀態(tài)上加1,所以b=2. system("PAUSE"); return 0; } ////////////////////////////////////////////////////////////////// 從程序的運行結可以看出局部靜態(tài)變量只在第一次遇到是進空間的分配和構造,,函數調用完成并不立即回收,,而等程序運行的結束.正因為它只存在一共享空間地址(對象多線程來講),所以在多線程序程序中為保正確的讀取而需要對其進行鎖定,。 2. 靜態(tài)全局變量(對象) 靜態(tài)全局變量(對象)是指全局變量的基礎上加上static修飾符,,它同時具有文件作用域和靜態(tài)生存期兩種特性。具體來講就是指只在定義它的源文件中可見而在其它源文件中不可見的變量(文件作用域),。它與全程變量的區(qū)別是: 全程變量可以再說明為外部變量(extern), 也就是指跨文件作用域,,被其它源文件使用, 而靜態(tài)全程變量卻不能再被說明為外部的, 即只能被所在的源文件使用。 3. 靜態(tài)函數(靜態(tài)全局函數) 靜態(tài)函數一般來說相對較少用到普通函數若未加static修飾,,具有跨文件作用域,,若加static修飾,與靜態(tài)全局變量相類似也具有文件作用域,,一般函數本身的一個目標就是實現(xiàn)共用(不管是函數定義文件還是被作為include對象的文件),,若在函數前加上static主使得函數只能在本文件中使用,其它文件不能使用,,我們當然不想這樣做啦J,,所以,在一般的代碼中,,很少函數靜態(tài)函數(靜態(tài)全局函數)不存在,。 4. 靜態(tài)成員變量(對象) 當在程序中這義靜態(tài)變量(對象)時,那么對于這個類的所以對象都將只分配一個共享的存儲單,,簡單的說,,靜態(tài)成員變量屬于類范圍,而不是具體的某個對象,,相當于類范圍內的全局變量(對象),,當然也可以說是屬于自身類型的所有對象,它的值是可以更新的,。只要對靜態(tài)成員變的值更新一次,,所有對象存取者將是更新后的相同的值(多線程應該自行控制),這樣可以提高時間效率,。 靜態(tài)成員變量(對象)以為類為名稱空間,,為了更好的實現(xiàn)信息隱藏,可以將它設置為private,然后通過函數進行存?。ê竺鎽弥袑敿氈v到),,程序對類靜態(tài)數據成員的訪問會被轉換為類內部的惟一extern實體的直接操作(至于如何被轉換,不同的編譯器將會有不同的算法,要詳細了解請參閱《Inside C++ Object Model》),正因為這樣,,所以在類的內部可以直接定義自身類型的static成員,,而不會造成類定義的無限遞歸,而非靜態(tài)的自身類型則不行,,只能是自身類型的指針或引用,,其實這跟類定義結束是到大括號后的分號為止是有關的,在此之前將無法確定類的大小,,而自身類型的指針或引用的大小則是確定(一個指針的空間),。如下面代碼: class A{ //…… private: //…… static A a1;//ok,因為a1為靜態(tài)成員對象。 A a2;//error此時不能確定A類型的大小,。 A* a3;//ok 此時能確定A*類型的大小,,一個指抽A類型的指針的大小 A& a4;//ok 此時能確定A&類型的大小,一個指抽A類型的指針的大小 }; 與全局變量一樣,,靜態(tài)成員變量也只能提供一次這義,,類聲明只聲明一個類的“尺寸和規(guī)格”,并不進行實際的內存分配,,所以它的實初始化將不能被放在頭文件中,,而應該放入相就是類成員函數的實現(xiàn)文件中,也不能在頭文件中類聲明的外部定義,,因為那會造成在多個使用該類的源文件中,,對其重復定義。對于const類型的靜態(tài)有序類型可以直接初始化,,但其定義還要在類定義之外: //頭文件 class print{ private: static const int count = 10;//直接初始化 string info[count]; }; const int print::count;//可以在頭文件,,也可以在實現(xiàn)文件,因為不會為常分配空間 5. 靜態(tài)成員函數 靜態(tài)成員函數的引入實際是為了更好存取靜態(tài)成員變量(對象),,靜態(tài)成員變量(對象)并不局限于某個具體的對象,,而普通成員函數則不為具體的某個對象服務的,那么當用普通成員函數進行靜態(tài)成員變量(對象)存取就會造成將靜態(tài)成員變量約束為具體的某個對象(原因是因為普通成員函數約束于具體的某個對象),使用靜態(tài)成員函數將會很好的解決這些問題,。所以靜態(tài)成員函數具有靜態(tài)成員變量(對象)相類似的特點,,不管何種形式(一般包括具體對象,具體對象的指針或引用,::運算符,,靜態(tài)成員函數指針)的靜態(tài)員函數的調用將會被轉換成非成員函數(普通函數)的調用,。 靜態(tài)成員函數最大的特點就是沒有this指針,從而導至下面一些特點: 1.不能直接存取類中的非態(tài)成員,,只能直接存取靜態(tài)成員變量(對象),。 從語言層次來說普通成員函數能夠存取非靜態(tài)成員靠是this指針,靜態(tài)成員函數沒有this指針,,所以無法實現(xiàn),。 2.不能為const,volatile或virtual,。 靜態(tài)成員函數本向就不能存取非靜態(tài)成員,所以const對它來說不有意義,。virtual說明在一個類繼承體系中不同類可能不多個不同的實現(xiàn)實體,,而靜態(tài)成員函數則只有一個,所以static對virtual并不適合(個人理解,,僅供參考,,也有可能是錯誤碼的噢J) 3.不需要約束于類對象才能被調用,。 靜態(tài)成員函數本身就是為了解決此問題而被提出的J. 4.可以作為回調函數(CallBack),。 靜態(tài)成員函數擺脫的this指針的約束而成為普通的函數。 三:靜態(tài)(static)幾個典型的應用: 1. 代替menu的作用(menu hank) 大家都知道如果程序要在,,要在類定義的內部直接初始化變量并使用對于以夜的編譯器只能使用menu來達到這種效果,,代碼像這樣: class print { menu{count = 10 };//借助枚舉來得到一個初始值 string info[count]; }; 用MFC編寫過程序代碼的都應該知道這個功能,它就是通過menu來接初始化類中的IDD然后進行使用,,看一面一段MFC產生的代碼 /////////////////////////////////////////////////////////////////////// class CACETESTDlg : public CDialog { public: //……其它成員函數和構造函數的定義 // 對話框數據 enum { IDD = IDD_ACETEST_DIALOG }; //使用menu直接初始化IDD,IDD_ACETEST_DIALOG是對話框的ID值 //……其他成員函數的定義,。 }; //實現(xiàn)文件 //…… CACETESTDlg::CACETESTDlg(CWnd* pParent /*=NULL*/) : CDialog(CACETESTDlg::IDD, pParent)//注:這里直接使用IDD初始化基類。 { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } //…… //////////////////////////////////////////////////////////////////////////// 上面這種直接初始并使用的功能是借助menu來實現(xiàn)的,。但這有個不好之處就是使menu在這里喪失了本來的作用(menu本身的功能沒有得到體現(xiàn))但有了static之后可以在一個類中使用static常量來達到同樣的效果,,因為一個類的靜態(tài)常量被當作一個編譯時的常量,而又因為靜態(tài)具有文件作用域的內部連接,所以不會產生重復定義的沖突,。其代碼示例在介紹靜態(tài)成員變量(對象)時已經給出,。 不知道大家有不有注意,menu只能使用整型,,但類內部靜態(tài)常量可以是任何類型,,甚至抱用戶自定義類型。 2. 單件模式 相大家都知道單件模式吧,,如果不熟悉可以參考相產模式的書籍,,單件模式保證一個類僅有一個實例存在,具體就是通過靜態(tài)成員變量和靜態(tài)成員函數以及類的訪問層次相結合而實現(xiàn)的,具體代碼如下: ////////////////////////////////////////////////////////////////////////// class Singleton{ public: static Singleton* Instance();//注:靜態(tài)成員函數返回類的實例 virtual ~Singleton(){}; protected: Singleton(){}; private: static Singleton* _instance;//注:靜態(tài)成員變量指向類的實例 }; //實現(xiàn)文件: Singleton* Singleton::_instance = 0;//靜態(tài)成員變量的初始化 Singleton* Singleton::Instance() //返回指向類實例的靜態(tài)指針 { if(_instance == 0) _instance = new Singleton; return _instance; } ///////////////////////////////////////////////////////////////////// 靜態(tài)成員函數加靜態(tài)成員變量便可實現(xiàn)單件模式,,其中靜態(tài)成員函數控制返回類的實例,,因為靜態(tài)成員函數不能為虛函數,因此子類就不能多態(tài)的對它進行重定義,,這就保證了只能過此接口對類進行使用,。用靜態(tài)成員變量指向那個唯一被實例化的對象,當然最實始被初始化為0,,然后使用惰性初始化時行實例的建立,, 如果將這些換成全局靜態(tài)對象和全局靜態(tài)函數,相信會有不少問題存在J. 3. 消除全局數據 大家都知道,,大家都知道,,引入全局數據確實在適當的時候能解決一些棘手的問題,但全局數據是跨文件作用域,使得變量的正確初始在有些情況下非常難控制(后面第4點將會講到),,全局變量一旦建立,,要消除它是相當難,隨著而來便是”名字空間污染”問題,,不僅如此,,全局變量將變量(對象)和算法代碼綁定在一起,使得復用又相當困難,。 對于全局變量所存在的這些問題,,可以使用封裝加靜態(tài)訪問的方法使問題得到優(yōu)化 首先將全局變量放入一個類或結構中 然后將它們私有化并添加靜態(tài)成員函數訪問接口 例如有下面的全局變量 bool g_flag; int g_size; const char* in_modulename; 對于這些全局變量,運用上面的方法介紹的方法進行改進,,可以產生下面的代碼: class Global { private: static bool s_flag_; static int s_size_; static const char* s_in_modulename_; private: Global();//私有的構造函數,,使類不能實例化 //....其它相關的函數 public: //存入相關值 static void setFlag(bool flag){ s_flag_ = flag; } static void setSize(int size) { s_size_ = size; } static void setInmodulename(const char* in_modulename){ s_in_modulename_ = in_modulename; } //取得相關值 static int getFlag() { return s_flag_; } static int getSize() { return s_size_; } static const char* getInmodulename() { return s_in_modulename_; } }; 相信這種重構方法通過引入類的封裝和靜態(tài)成員函數的訪問來解決全局的問題應該是一個比較好的方法。 4. 解決初始相互依賴問題 如果你讓對象A必須在對象B之前初始化,,同時又讓A的初始化依賴于B已經被初始化,同是A,B又在不同的編譯單元,,這是我們就無法控制正確的初始化順序,解決辦法還是有的(注,,以下直入引用《think in C++》的一段例子,,由于是總結,拷貝一段沒問題吧J), 這時我們可用由Jerry Schwarz在創(chuàng)建i o s t r e a m庫(因為c i n , c o u t和c e r r的定義是在不同的文件中)時提供的一種技術,。這一技術要求在庫頭文件中加上一個額外的類,。這個類負責庫中靜態(tài)對象的動態(tài)初始化。下面是一個簡單的例子: //DEPEND.H – Static initialization technique #ifndef DEPEND_H_ #define DEPEND_H_ #include <iostream.h> extern int x;//Delarations,not definitions extern int y; class initializer{ static int init_count; public: initializer(){ cout <<"initializer()"<<endl; if(init_count++ == 0) { cout <<"performing initialization"<<endl; x = 100; y = 100; } } ~initializer(){ cout<< "~initializer()"<<endl; if(--init_count == 0){ cout << "performing cleanup" <<endl; } } }; //The following creates one object in each //file where DEPEND.H is included ,but that //object is only visible within that file: static initializer init; #endif //DEPEND_H_ x,、y的聲明只是表明這些對象的存在,,并沒有為它們分配存儲空間。然而initializer init 的定義為每個包含此頭文件的文件分配那些對象的空間,,因為名字是static的(這里控制可見性而不是指定存儲類型,,因為缺省時是在文件范圍內)它只在本編譯單元可見,所以連接器不會報告一個多重定義錯誤,。 下面是一個包含x,、y和init_count定義的文件: //:DEPDEFS.CPP--Definition #include “depend.h” //Static initialization will force //all these values to zero: int x; int y; int initializer::init_count; (當然,一個文件的init靜態(tài)實例也放在這個文件中)假設庫的使用者產生了兩個其他的文件: //:DEPEND.CPP—Static initialization #include “depend.h” 和 //:DEPEND2.CPP –Static initialization #include “depend.h” int main() { cout << “inside main()” <<endl; cout << “leaving main()” <<endl; return 0; } 現(xiàn)在哪個編譯單元先初始化都沒有關系,。當第一次包含DEPEND.H的編譯單元被初始化時,,init_count為零,這時初始化就已經完成了(這是由于內部類型的全局變量在動態(tài)初始化之前都被設置為零),。對其余的編譯單元,,初始化會跳過去。清除按相反的順序,,且~initializer()可確保它只發(fā)生一次,。這個例子用內部類型作為全局靜態(tài)對象,,這種方法也可以用于類,但其對象必須用initializer動態(tài)初始化,。一種方法就是創(chuàng)建一個沒有構造函數和析構函數的類,,但用不同的名字的成員函數來初始化和清除這個類。當然更常用的做法是在initializer()函數中,,設定指向對象的指針,,并在堆中動態(tài)創(chuàng)建它們。
注:由于是總結,,以后自己查閱方便,,最后一點是直接取自《think in C++》是的一段,我覺得這個例子舉的非常好,!所以已直接拿過來了 :) |
|