天天看點

C++ 類的static靜态成員

靜态static

靜态成員的提出是為了解決資料共享的問題。實作共享有許多方法,如:設定全局性的變量或對象是一種方法。但是,全局變量或對象是有局限性的。

在全局變量前,加上關鍵字static該變量就被定義成為了一個靜态全局變量。  該變量隻有在本源檔案中可見,嚴格講應該為定義之處開始到本檔案結束,靜态全局變量不能被其他檔案所用。

通常,在函數體内定義一個變量,每當程式運作到該語句時都會給該局部變量配置設定棧記憶體。但随着程式退出函數體,系統就會回收棧記憶體,局部變量也相應失效。但有時候我們需要在兩次調用之間對變量的值進行儲存。通常的想法是定義一個全局變量來實作。但這樣一來,變量已經不再屬于函數本身了不再受函數的控制,給函數的維護帶來不便。靜态局部變量正好可以解決這個問題。靜态局部變量儲存在全局資料區,而不是儲存在棧中,每次的值儲存到下一次調用,直到下次賦新值。

與函數體内的靜态局部變量相似,在類中使用靜态成員變量可實作多個對象之間的資料共享,又不會破壞隐藏的原則,保證了安全性還可以節省記憶體。定義資料成員為靜态變量,表明此全局資料邏輯上屬于該類。定義成員函數為靜态函數,表明此全局函數邏輯上屬于該類,而且該函數隻對靜态資料、全局資料或者參數進行操作,而不對非靜态資料成員進行操作。

綜上:

靜态全局變量

定義:在全局變量前,加上關鍵字 static 該變量就被定義成為了一個靜态全局變量。

特點:

  A、該變量在全局資料區配置設定記憶體。

  B、初始化:如果不顯式初始化,那麼将被隐式初始化為0。

靜态局部變量

定義:在局部變量前加上static關鍵字時,就定義了靜态局部變量。

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

靜态資料成員

  A、記憶體配置設定:在程式的全局資料區配置設定。

  B、初始化和定義:

    a、靜态資料成員定義時要配置設定空間,是以不能在類聲明中定義。

    b、為了避免在多個使用該類的源檔案中,對其重複定義,所在,不能在類的頭檔案中

    定義。

    c、靜态資料成員因為程式一開始運作就必需存在,是以其初始化的最佳位置在類的内部實作。

  C、特點

    a、對相于 public,protected,private 關鍵字的影響它和普通資料成員一樣,

    b、因為其空間在全局資料區配置設定,屬于所有本類的對象共享,是以,它不屬于特定的類對象,在沒産生類對象時其作用域就可見,即在沒有産生類的執行個體時,我們就可以操作它。

  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 ++;
}      

作者:王陸