[C++]類的其它特性

丫就是熊個貓貓發表於2016-12-11

類的其它特性

友元函式

類中私有和保護的成員在類外不能被訪問。

友元函式是一種定義在類外部的普通函式,其特點是能夠訪問類中私有成員和保護成員,即類的訪問許可權的限制對其不起作用。

友元函式需要在類體內進行說明,在前面加上關鍵字friend。

一般格式為:

friend  <type> FuncName(<args>);

friend(關鍵字)   float(返回值型別)  Volume(A &a)(函式引數);

友元函式不是成員函式,用法也與普通的函式完全一致,只不過它能訪問類中所有的資料。友元函式破壞了類的封裝性和隱蔽性,使得非成員函式可以訪問類的私有成員。

一個類的友元可以自由地用該類中的所有成員。

有關友元函式的使用,說明如下:

友元函式不是類的成員函式

友元函式近似於普通的函式,它不帶有this指標,因此必須將物件名或物件的引用作為友元函式的引數,這樣才能訪問到物件的成員。

友元函式與一般函式的不同點在於:

1.友元函式必須在類的定義中說明,其函式體可在類內定義,也可在類外定義;

2.它可以訪問該類中的所有成員(公有的、私有的和保護的),而一般函式只能訪問類中的公有成員。

 

友元函式不受類中訪問許可權關鍵字的限制,可以把它放在類的私有部分,放在類的公有部分或放在類的保護部分,其作用都是一樣的。換言之,在類中對友元函式指定訪問許可權是不起作用的。

友元函式的作用域與一般函式的作用域相同。

謹慎使用友元函式

通常使用友元函式來取物件中的資料成員值,而不修改物件中的成員值,則肯定是安全的。

大多數情況是友元函式是某個類的成員函式,即A類中的某個成員函式是B類中的友元函式,這個成員函式可以直接訪問B類中的私有資料。這就實現了類與類之間的溝通。

class A{

...

void fun( B &);  既是類A的成員函式

};

class B{

...

friend void fun( B &);   又是類B的友元函式

};

注意:一個類的成員函式作為另一個類的友元函式時,應先定義友元函式所在的類。

class   B  ;//先定義類A,則首先對類B作引用性說明

class   A{

   ......//類A的成員定義

   public:

   void  fun( B & );//函式的原型說明

    };

class  B{......

    friend  void  A::fun( B & );//定義友元函式

};

    void  A::fun ( B  &b)   //函式的完整定義

{

     ......//函式體的定義

類A中的成員函式fun()是類B的友元函式。即在fun()中可以直接引用類B的私有成員。

 

友元類

class A{

    .....

    friend class B;         類B是類A的友元

}

class B{

    .....

 }

類B可以自由使用類A中的成員

對於類B而言,類A是透明的

類B必須通過類A的物件使用類A的成員

不管是按哪一種方式派生,基類的私有成員在派生類中都是不可見的。

如果在一個派生類中要訪問基類中的私有成員,可以將這個派生類宣告為基類的友元。

 

虛擬函式

多型性是物件導向的程式設計的關鍵技術。

多型性:呼叫同一個函式名,可以根據需要但實現不同的功能。

 

執行時的多型性是指在程式執行之前,根據函式名和引數無法確定應該呼叫哪一個函式,必須在程式的執行過程中,根據具體的執行情況來動態地確定

可以將一個派生類物件的地址賦給基類的指標變數。

 

若要訪問派生類中相同名字的函式,必須將基類中的同名函式定義為虛擬函式,這樣,將不同的派生類物件的地址賦給基類的指標變數後,就可以動態地根據這種賦值語句呼叫不同類中的函式。

虛擬函式的定義和使用

   可以在程式執行時通過呼叫相同的函式名而實現不同功能的函式稱為虛擬函式。定義格式為:

virtual  <type>  FuncName(<ArgList>);

一旦把基類的成員函式定義為虛擬函式,由基類所派生出來的所有派生類中,該函式均保持虛擬函式的特性。

在派生類中重新定義基類中的虛擬函式時,可以不用關鍵字virtual來修飾這個成員函式 。

虛擬函式是用關鍵字virtual修飾的某基類中的protected或public成員函式。它可以在派生類中重新定義,以形成不同版本。只有在程式的執行過程中,依據指標具體指向哪個類物件,或依據引用哪個類物件,才能確定啟用哪一個版本,實現動態聚束。

 

關於虛擬函式,說明以下幾點:

1、當在基類中把成員函式定義為虛擬函式後,在其派生類中定義的虛擬函式必須與基類中的虛擬函式同名,引數的型別、順序、引數的個數必須一一對應,函式的返回的型別也相同。若函式名相同,但引數的個數不同或者引數的型別不同時,則屬於函式的過載,而不是虛擬函式。若函式名不同,顯然這是不同的成員函式。

2、實現這種動態的多型性時,必須使用基類型別的指標變數,並使該指標指向不同的派生類物件,並通過呼叫指標所指向的虛擬函式才能實現動態的多型性。

3、虛擬函式必須是類的一個成員函式,不能是友元函式,也不能是靜態的成員函式。

4、在派生類中沒有重新定義虛擬函式時,與一般的成員函式一樣,當呼叫這種派生類物件的虛擬函式時,則呼叫其基類中的虛擬函式。

5、可把解構函式定義為虛擬函式,但是,不能將建構函式定義為虛擬函式。

6、虛擬函式與一般的成員函式相比較,呼叫時的執行速度要慢一些。為了實現多型性,在每一個派生類中均要儲存相應虛擬函式的入口地址表,函式的呼叫機制也是間接實現的。因此,除了要編寫一些通用的程式,並一定要使用虛擬函式才能完成其功能要求外,通常不必使用虛擬函式。

7、一個函式如果被定義成虛擬函式,則不管經歷多少次派生,仍將保持其虛特性,以實現“一個介面,多個形態”。

 

虛擬函式的訪問

用基指標訪問與用物件名訪問

用基指標訪問虛擬函式時,指向其實際派生類物件重新定義的函式。實現動態聚束。

通過一個物件名訪問時,只能靜態聚束。即由編譯器在編譯的時候決定呼叫哪個函式。

 

純虛擬函式

在基類中不對虛擬函式給出有意義的實現,它只是在派生類中有具體的意義。這時基類中的虛擬函式只是一個入口,具體的目的地由不同的派生類中的物件決定。這個虛擬函式稱為純虛擬函式。

class    <基類名>

{ virtual <型別><函式名>(<參數列>)=0;

......

};

1、在定義純虛擬函式時,不能定義虛擬函式的實現部分。

2、把函式名賦於0,本質上是將指向函式體的指標值賦為初值0。與定義空函式不一樣,空函式的函式體為空,即呼叫該函式時,不執行任何動作。在沒有重新定義這種純虛擬函式之前,是不能呼叫這種函式的。

3、把至少包含一個純虛擬函式的類,稱為抽象類。這種類只能作為派生類的基類,不能用來說明這種類的物件。

其理由是明顯的:因為虛擬函式沒有實現部分,所以不能產生物件。但可以定義指向抽象類的指標,即指向這種基類的指標。當用這種基類指標指向其派生類的物件時,必須在派生類中過載純虛擬函式,否則會產生程式的執行錯誤。

4、在以抽象類作為基類的派生類中必須有純虛擬函式的實現部分,即必須有過載純虛擬函式的函式體。否則,這樣的派生類也是不能產生物件的。

綜上所述,可把純虛擬函式歸結為:抽象類的唯一用途是為派生類提供基類,純虛擬函式的作用是作為派生類中的成員函式的基礎,並實現動態多型性。

 

虛基類

多基派生中的多條路徑具有公共基類時,在這條路徑的匯合處就會因對公共基類產生多個拷貝而產生同名函式呼叫的二義性。

解決這個問題的辦法就是把公共基類定義為虛基類,使由它派生的多條路徑的匯聚處只產生一個拷貝。

class Base{ };

class A : public Base{ };

class B:  public  Base{ };

class C: public A, public  B{ };

類C中繼承了兩個類Base,即有兩個類Base的實現部分,在呼叫時產生了二義性。

由虛基類派生出的物件初始化時,直接呼叫虛基類的建構函式。因此,若將一個類定義為虛基類,則一定有正確的建構函式可供所有派生類呼叫。

用虛基類進行多重派生時,若虛基類沒有預設的建構函式,則在每一個派生類的建構函式中都必須有對虛基類建構函式的呼叫 (且首先呼叫)。

 

靜態成員

通常,每當說明一個物件時,把該類中的有關成員資料拷貝到該物件中,即同一類的不同物件,其成員資料之間是互相獨立的。

當我們將類的某一個資料成員的儲存型別指定為靜態型別時,則由該類所產生的所有物件,其靜態成員均共享一個儲存空間,這個空間是在編譯的時候分配的。換言之,在說明物件時,並不為靜態型別的成員分配空間。

在類定義中,用關鍵字static修飾的資料成員稱為靜態資料成員。

class A{

    int x,y;  static  int z;

    public:  

     void Setxy(int a, int b)

    {  x=a;   y=b;}

};

A   a1,  a2;

有關靜態資料成員的使用,說明以下幾點:

1、類的靜態資料成員是靜態分配儲存空間的,而其它成員是動態分配儲存空間的(全域性變數除外)。當類中沒有定義靜態資料成員時,在程式執行期間遇到說明類的物件時,才為物件的所有成員依次分配儲存空間,這種儲存空間的分配是動態的;而當類中定義了靜態資料成員時,在編譯時,就要為類的靜態資料成員分配儲存空間。

2、必須在檔案作用域中,對靜態資料成員作一次且只能作一次定義性說明。因為靜態資料成員在定義性說明時已分配了儲存空間,所以通過靜態資料成員名前加上類名和作用域運算子,可直接引用靜態資料成員。在C++中,靜態變數預設的初值為0,所以靜態資料成員總有唯一的初值。當然,在對靜態資料成員作定義性的說明時,也可以指定一個初值。

3、靜態資料成員具有全域性變數和區域性變數的一些特性。靜態資料成員與全域性變數一樣都是靜態分配儲存空間的,但全域性變數在程式中的任何位置都可以訪問它,而靜態資料成員受到訪問許可權的約束。必須是public許可權時,才可能在類外進行訪問。

4、為了保持靜態資料成員取值的一致性,通常在建構函式中不給靜態資料成員置初值,而是在對靜態資料成員的定義性說明時指定初值。

 

靜態成員函式

可以將類的成員函式定義為靜態的成員函式。即使用關鍵字static來修飾成員函式 。

class A

{    float x, y;

public :

     A( ){  }

     static   void sum(void)  { ..... }

};

對靜態成員函式的用法說明以下幾點:

1、與靜態資料成員一樣,在類外的程式程式碼中,通過類名加上作用域操作符,可直接呼叫靜態成員函式。

2、靜態成員函式只能直接使用本類的靜態資料成員或靜態成員函式,但不能直接使用非靜態的資料成員 (可以引用使用)。這是因為靜態成員函式可被其它程式程式碼直接呼叫,所以,它不包含物件地址的this指標。

3、靜態成員函式的實現部分在類定義之外定義時,其前面不能加修飾詞static。這是由於關鍵字static不是資料型別的組成部分,因此,在類外定義靜態成員函式的實現部分時,不能使用這個關鍵字

4、不能把靜態成員函式定義為虛擬函式。靜態成員函式也是在編譯時分配儲存空間,所以在程式的執行過程中不能提供多型性。

5、可將靜態成員函式定義為內聯的(inline),其定義方法與非靜態成員函式完全相同。

 

const 、volatile物件和成員函式

用const修飾的物件,只能訪問該類中用const修飾的成員函式,而其它的成員函式是不能訪問的。用volatile修飾的物件,只能訪問該類中用volatile修飾的成員函式,不能訪問其它的成員函式。

當希望成員函式只能引用成員資料的值,而不允許成員函式修改資料成員的值時,可用關鍵詞const修飾成員函式。一旦在用const修飾的成員函式中出現修改成員資料的值時,將導致編譯錯誤。  

 

const和volatile成員函式

在成員函式的前面加上關鍵字const,則表示這函式返回一個常量,其值不可改變。

const成員函式則是指將const放在參數列之後,函式體之前,其一般格式為:

<type>  FuncName(<args>)  const ;

其語義是指明這函式的this指標所指向的物件是一個常量,即規定了const成員函式不能修改物件的資料成員,在函式體內只能呼叫const成員函式,不能呼叫其它的成員函式。

 

用volatile修飾一個成員函式時,其一般格式為:

<type>  FuncName(<args>)   volatile;

其語義是指明成員函式具有一個易變的this指標,呼叫這個函式時,編譯程式把屬於此類的所有的資料成員都看作是易變的變數,編譯器不要對這函式作優化工作。

 

由於關鍵字const和volatile是屬於資料型別的組成部分,因此,若在類定義之外定義const成員函式或volatile成員函式時,則必須用這二個關鍵字修飾,否則編譯器認為是過載函式,而不是定義const成員函式或volatile成員函式。

 

指向類成員的指標

在C++中可以定義一種特殊的指標,它指向類中的成員函式或類中的資料成員。並可通過這樣的指標來使用類中的資料成員或呼叫類中的成員函式。

指向類中資料成員的指標變數

定義一個指向類中資料成員的指標變數的一般格式為:

<type>  ClassName:: *PointName;

其中type是指標PointName所指向資料的型別,它必須是類ClassName中某一資料成員的型別

 

1、指向類中資料成員的指標變數不是類中的成員,這種指標變數應在類外定義。

2、與指向類中資料成員的指標變數同型別的任一資料成員,可將其地址賦給這種指標變數,賦值的一般格式為:

PointName = &ClassName::member;

這種賦值,是取該成員相對於該類的所在物件中的偏移量,即相對地址(距離開始位置的位元組數)

    如:mptr = &S::y;  

 表示將資料成員y的相對起始地址賦給指標變數mptr。

3、用這種指標訪問資料成員時,必須指明是使用那一個物件的資料成員。當與物件結合使用時,其用法為:

ObjectName.* PointName

4、由於這種指標變數並不是類的成員,所以使用它只能訪問物件的公有資料成員。若要訪問物件的私有資料成員,必須通過成員函式來實現。

 

指向類中成員函式的指標變數

定義一個指向類中成員函式的指標變數的一般格式為:

<type>  (ClassName:: *PointName)(<ArgsList>);

其中PointName是指向類中成員函式的指標變數;ClassName是已定義的類名;type是通過函式指標PointName呼叫類中的成員函式時所返回值的資料型別,它必須與類ClassName中某一成員函式的返回值的型別相一致;<ArgsList>是函式的形式參數列。

在使用這種指向成員函式的指標前,應先對其賦值

PointName= ClassName::FuncName;

同樣地,只是將指定成員函式的相對地址賦給指向成員函式的指標。

在呼叫時,用(物件名.指標)( )的形式。

比較 :int max( int a,int b)

{return (a>b?a:b);}

若有:int  (*f)( int, int ); f=max;

則呼叫時    (*f)(x,y);

所以:(s1.*mptr1)();         (s1.*mptr2)(100);

或:(ps->*mptr1)();(ps-*mptr2)(100);

 

對指向成員函式的指標變數的使用方法說明以下幾點:

1、指向類中成員函式的指標變數不是類中的成員,這種指標變數應在類外定義。

2、不能將任一成員函式的地址賦給指向成員函式的指標變數,只有成員函式的引數個數、引數型別、引數的順序和函式的型別均與這種指標變數相同時,才能將成員函式的指標賦給這種變數。

3、使用這種指標變數來呼叫成員函式時,必須指明呼叫那一個物件的成員函式,這種指標變數是不能單獨使用的。用物件名引用。

4、由於這種指標變數不是類的成員,所以用它只能呼叫公有的成員函式。若要訪問類中的私有成員函式,必須通過類中的其它的公有成員函式。

5、當一個成員函式的指標指向一個虛擬函式,且通過指向物件的基類指標或物件的引用來訪問該成員函式指標時,同樣地產生執行時的多型性。

6、當用這種指標指向靜態的成員函式時,可直接使用類名而不要列舉物件名。這是由靜態成員函式的特性所確定的。

 

相關文章