C++ 類的static靜態成員
靜態static
靜態成員的提出是為了解決數據共享的問題。實現共享有許多方法,如:設置全局性的變量或對象是一種方法。但是,全局變量或對象是有局限性的。
在全局變量前,加上關鍵字static該變量就被定義成為了一個靜態全局變量。 該變量只有在本源文件中可見,嚴格講應該為定義之處開始到本文件結束,靜態全局變量不能被其他文件所用。
通常,在函數體內定義一個變量,每當程序運行到該語句時都會給該局部變量分配棧內存。但隨著程序退出函數體,系統就會回收棧內存,局部變量也相應失效。但有時候我們需要在兩次調用之間對變量的值進行保存。通常的想法是定義一個全局變量來實現。但這樣一來,變量已經不再屬於函數本身了不再受函數的控制,給函數的維護帶來不便。靜態局部變量正好可以解決這個問題。靜態局部變量保存在全局數據區,而不是保存在棧中,每次的值保存到下一次調用,直到下次賦新值。
與函數體內的靜態局部變量相似,在類中使用靜態成員變量可實現多個對象之間的數據共享,又不會破壞隱藏的原則,保證了安全性還可以節省內存。定義數據成員為靜態變量,表明此全局數據邏輯上屬於該類。定義成員函數為靜態函數,表明此全局函數邏輯上屬於該類,而且該函數只對靜態數據、全局數據或者參數進行操作,而不對非靜態數據成員進行操作。
綜上:
靜態全局變量
定義:在全局變量前,加上關鍵字 static 該變量就被定義成為了一個靜態全局變量。
特點:
A、該變量在全局數據區分配內存。
B、初始化:如果不顯式初始化,那麽將被隱式初始化為0。
靜態局部變量
定義:在局部變量前加上static關鍵字時,就定義了靜態局部變量。
特點:
A、該變量在全局數據區分配內存。
B、初始化:如果不顯式初始化,那麽將被隱式初始化為0。
C、它始終駐留在全局數據區,直到程序運行結束。但其作用域為局部作用域,當定義它的函數或語句塊結束時,其作用域隨之結束。
靜態數據成員
特點:
A、內存分配:在程序的全局數據區分配。
B、初始化和定義:
a、靜態數據成員定義時要分配空間,所以不能在類聲明中定義。
b、為了避免在多個使用該類的源文件中,對其重復定義,所在,不能在類的頭文件中
定義。
c、靜態數據成員因為程序一開始運行就必需存在,所以其初始化的最佳位置在類的內部實現。
C、特點
a、對相於 public,protected,private 關鍵字的影響它和普通數據成員一樣,
D、訪問形式
a、 類對象名.靜態數據成員名
E、靜態數據成員,主要用在類的所有實例都擁有的屬性上。比如,對於一個存款類,帳號相對於每個實例都是不同的,但每個實例的利息是相同的。所以,應該把利息設為存款類的靜態數據成員。這有兩個好處,第一,不管定義多少個存款類對象,利息數據成員都共享分配在全局區的內存,所以節省存貯空間。第二,一旦利息需要改變時,只要改變一次,則所有存款類對象的利息全改變過來了,因為它們實際上是共用一個東西。
靜態成員函數
特點:
A、靜態成員函數與類相聯系,不與類的對象相聯系。
B、靜態成員函數不能訪問非靜態數據成員。原因很簡單,非靜態數據成員屬於特定的類實例。
作用:
主要用於對靜態數據成員的操作。
調用形式:
類對象名.靜態成員函數名()
一、靜態數據成員
在類中,靜態成員可以實現多個對象之間的數據共享,並且使用靜態數據成員還不會破壞隱藏的原則,保證了安全性。
靜態數據成員在定義或說明時前面加關鍵字static,如:
class A { int n; static int s; };
sizeof 運算符不會計算靜態成員變量,sizeof(CMyclass)等於4。使用靜態數據成員可以節省內存,因為它是所有對象所公有的,因此,對多個對象來說,靜態數據成員只存儲一處,供所有對象共用。
靜態數據成員是靜態存儲的,它是靜態生存期,必須對它進行初始化。靜態成員初始化與一般數據成員初始化不同,類數據成員在類內部聲明,但是靜態成員必須在類的外面初始化,靜態數據成員初始化的格式如下:
<數據類型><類名>::<靜態數據成員名>=<值>
如果一個類中說明了靜態數據成員,只有在這個類的第一個對象被創建時被初始化,自第二個對象起均不作初始化。對A類中靜態數據成員s進行初始化:
int A::s = 0;
初始化在類體外進行,而前面不加static,以免與一般靜態變量或對象相混淆。
static int A::s = 0; // error C2720: “A::s”: 成員上的“static ”存儲類說明符非法
初始化時不加該成員的訪問權限控制符private,public等。初始化時使用作用域運算符來標明它所屬類,因此,靜態數據成員是類的成員,而不是對象的成員。
引用靜態數據成員時,采用如下格式:
<類名>::<靜態成員名>
類為靜態數據成員只分配了一塊存儲空間。如果一個數據是一個類的所有實例都需要的,而這個數據值的變化對於這個類的所有實例又始終應當是統一的,就應該把這個數據定義為靜態數據成員。
#include <iostream> using namespace std; class A { public: A(int i) { s += i; } int n; static int s; }; int A::s = 0;//類外初始化 int main( ) { A a1(5); A a2(3); cout<<"s = "<<A::s<<endl; return 0; }
程序執行結果:
s = 8
靜態數據成員被類的所有對象所共享,包括該類派生類的對象。即派生類對象與基類對象共享基類的靜態數據成員。
#include <iostream> using namespace std; class B { public : static int i; }; int B::i = 0; class D:public B { }; int main() { B b; D d; b.i++; cout<<"base class static data number i is "<<b.i<<endl; d.i++; cout<<"derived class static data number i is "<<d.i<<endl; return 0; }
程序執行結果:
base class static data number i is 1
derived class static data number i is 2
二、靜態成員函數
除靜態數據成員以外,一個類還可以有靜態成員函數。靜態函數僅可以訪問靜態成員,或是靜態成員函數或是靜態數據成員。
靜態成員函數和靜態數據成員一樣,它們都屬於類的靜態成員,它們都不是對象成員。因此,對靜態成員的引用不需要用對象名。
class A { public: void setX(int i) { x = i; }; static int getN( ) { setX(10); // error C2352: "A::setX": 非靜態成員函數的非法調用 y = 100; //error C2597: 對非靜態成員"A::y"的非法引用 return s; //OK } void func() { getN(); //OK } private: int x; int y; static int s; };
因為靜態成員函數屬於整個類,在類實例化之前就已經分配空間了,而類的非靜態成員必須在類的實例化對象之前才能有內存空間,所以類的靜態成員訪問非靜態成員就會出錯,就好像沒有聲明一個變量卻提前使用它一樣。但類的非靜態成員函數卻可以調用靜態成員函數。
class A { public: static int i; static void func( ){} }; int A::i = 0; int main( ) { A c1; c1.func( ); // 通過類對象訪問靜態成員函數 A::func( ); // 通過類名訪問靜態成員函數 int x = c1.i; //通過類對象訪問靜態數據成員 int y = A::i; //通過類名訪問靜態數據成員 }
從上例從中可看出,調用靜態成員函數使用如下格式:
<類名>::<靜態成員函數名>(<參數表>);
另外,還可通過類的對象來訪問靜態數據成員和靜態成員函數。
在說明前面加了static關鍵字的靜態成員變量為所有對象共享。如果是public的話,那麽靜態成員在沒有對象生成的時候也能直接訪問。靜態成員函數沒有this指針,所以它不需要實例就能運行。
#include <iostream> using namespace std; class A { public: static int i; static void func() { cout<<"i = "<<i<<endl; } }; int A::i = 0; int main( ) { A::func( ); // 通過類名訪問靜態成員函數 return 0; }
程序執行結果:
i=0;
同靜態數據成員一樣,靜態成員函數是被類的所有對象所共享,包括該類派生類的對象。
#include <iostream> using namespace std; class B { public: static func() { i++; } static int i; }; int B::i = 0; class D:public B { }; int main() { B b; D d; b.func(); cout<<"base class static data number i is "<<b.i<<endl; d.func(); cout<<"derived class static data number i is "<<d.i<<endl; return 0; }
和非靜態成員函數一樣,靜態成員函數可以在派生類中被重定義,派生類會隱藏基類同名的函數。但靜態成員函數不能為virtual函數,這是因為virtual函數由編譯器提供了this指針,而static是沒有this指針的。
三、靜態數據成員和靜態成員函數例子
#include <iostream> using namespace std; class Apple { private : int nWeight; static int nTotalWeight; static int nTotalNumber; public: Apple( int w) ; ~Apple( ) ; static void print( ); }; Apple::Apple( int w) { nWeight = w; nTotalWeight += w; nTotalNumber ++; } Apple::~Apple( ) { nTotalWeight -= nWeight; nTotalNumber --; } void Apple::print() { cout<<"TotalWeight = "<<nTotalWeight<<" TotalNumber = "<<nTotalNumber<<endl; } int Apple::nTotalWeight = 0; int Apple::nTotalNumber = 0; int main() { Apple a1(6), a2(1); Apple::print( ); return 0; }
程序執行結果:
TotalWeight = 7 TotalNumber = 2
將上例中的print( )函數改為:
void Apple:: print( ) { cout <<"Weight = "<<nWeight<<" TotalWeight = "<<nTotalWeight<< " TotalNumber = "<< nTotalNumber<<endl; }
則:
Apple a;
a.print( ); // 解釋得通
Apple::print( ); // 解釋不通,nWeight到底是屬於那個對象的?
上面Apple類的不足之處:在使用Apple類的過程中,有時會調用復制構造函數生成臨時的隱藏的Apple對象(作為參數時,作為返回值時)那麽臨時對象在消亡時會調用析構函數,減少nTotalNumber 和 nTotalWeight的值,可是這些臨時對象在生成時卻沒有增加 nTotalNumber 和 nTotalWeight的值。例如main函數改為:
int main() { Apple a1(6), a2(1); { Apple a3(a2); } Apple::print( ); return 0; }
此時,程序執行結果為:
TotalWeight = 6 TotalNumber = 1
a3對象是個局部對象,它是通過a2來初始化的,因此會調用復制構造函數,離開作用域時會調用析構函數使TotalWeight和TotalNumber都減少,不該出現的情況發生了:“蘋果被多吃了”。因此,要為Apple類寫一個復制構造函數:
Apple::Apple( Apple & a ) { nWeight = a.nWeight; nTotalWeight += a.nWeight ; nTotalNumber ++; }
C++ 類的static靜態成員