VC杂记
多態性的類型
程序設計的多態性有兩種形式:編譯時的多態性和運行時的多態性。
多態性的實現與聯編有關。將一個函數的調用與其相應的函數體代碼相鏈接的過程,稱為函數聯編。C++中有兩種類型的聯編:靜態聯編和動態聯編。
編譯時的多態性是通過靜態聯編來實現的。靜態聯編是指在調用同名函數(即重載函數)時,編譯器將根據調用時所使用的實際參數個數、類型的不同確定應該調用哪一個函數的實現,它是在程序編譯階段就確定下來的多態性。靜態聯編通過使用重載(overload)機制來獲得,重載機制包括函數重載和運算符重載。
運行時的多態性是通過動態聯編來實現的。動態聯編是在程序的運行階段根據當時的情況,確定應該調用哪個同名函數的實現。動態聯編主要通過繼承與虛函數兩者結合來實現的。
靜態聯編與動態聯編各有優缺點。靜態聯編的代碼鏈接工作是在編譯時完成的,所以運行時不需要額外時間來做這些工作,因此靜態聯編的代碼效率較高;而動態聯編將函數調用的鏈接時間后移到代碼執行的時候,這必然使函數調用時間增加,效率降低,但大大增強了語言的靈活性。
多態的前提條件
???????? 必須存在一個繼承體系結構。
???????? 繼承體系結構中的一些類必須具有同名的virtual成員函數(virtual是關鍵字)。
???????? 至少有一個基類類型的指針或基類類型的引用,這個指針或引用可用來對virtual成員函數進行調用。
注意:
基類類型的指針可以指向任何基類對象或派生類對象,反過來派生類類型的指針是不可以的基類的對象的。
函數重載
如果兩個以上的函數取相同的函數名稱,但是形參個數或者類型不同時,編譯器會根據實參和形參的類型和個數選擇最匹配的函數,自動確定調用哪個函數,這就是函數的重載。
注意?①函數重栽時,函數聲明必須能夠互相區別,即函數的參數個數或參數類型必須有所不同,如果只有函數的返回值不同,則不能區分重栽函數;②函數功能表面相類似,本質上有區別的函數,最好不要重載;③不同參數傳遞方式無法區別重載函數。例如,int add(int a, int b)和int add(int &a, int &b)這兩個函數不能作為重載函數,否則編譯時會出錯。
操作符重載
下列操作符不能被重載:
???????? 成員選擇符(.)、成員對象選擇符(.*)、域解析操作符(::)和條件操作符(?:)
賦值操作符(=)不能被派生類所繼承。
???????? 下標操作符[]、賦值操作符=、函數調用操作符()和指針操作符->必須以類的成員函數的形式進行重載。
賦值操作符的重載:
???????? 拷貝構造函數和賦值操作符都是用來拷貝一個類的對象給另一個同類型的對象。拷貝構造函數將一個對象拷貝到另一個新的對象;賦值操作符將一個對象拷貝到另一個已經存在的對象。如果類的設計者沒有提供拷貝構造函數,也沒有重載賦值操作符,編譯器將會為這個類提供一個拷貝構造函數和一個賦值操作符。需要注意的問題就是和拷貝構造函數同樣的問題,編譯器提供的運作機制的特點以及引起的問題。
重載不能改變操作符的優先級和語法。
???????? 如果一個內建操作符是一元的,那么所有對它的重載仍是一元的。如果一個內建操作符是二元的,那么所有對它的重載仍是二元的。
虛函數
基類的在派生類中仍然是虛函數,無論其前面是否有關鍵字virtual,編譯器都將視它們為虛函數。虛函數和普通成員函數一樣,在派生類中虛成員函數也可以從基類繼承。構造函數不能是虛成員函數,但析構函數可以是虛成員函數,而且一般都把一個類的析構函數設計成虛成員函數。只有非靜態成員函數才可以是虛函數;只有對象成員函數才可以是虛函數。
注意:
???????? 基類中被聲明了為虛函數的成員函數,在被繼承以后的派生類當中即使不被顯式的聲明為虛函數,它也自動地被認為是虛函數。
???????? 虛函數在類聲明之外定義,關鍵字virtual僅在函數聲明時需要,不需在函數定義中使用virtual關鍵字。
???????? C++僅允許將成員函數定義為虛函數,頂層函數不能為虛函數
在基類中只聲明虛函數而不給出具體的定義,將它的具體定義放在各派生類中,這種虛函數稱為純虛函數(pure virtual function)。通過該基類指針或引用就可以調用所有派生類的虛函數,基類只是用于繼承,僅作為一個接口,具體功能在派生類中實現。聲明了純虛函數的類,稱為抽象類(abstract class)。
純虛函數的聲明形式如下:
virtual? 函數類型?函數原型(參數表)=0;
將虛函數的原型設置為0,該虛函數即為純虛函數。
抽象類的定義
只能用于別的類的基類,而本身不能直接創建對象的類稱為抽象基類。帶有純虛函數的基類是抽象類。它的主要作用是通過抽象類為一個類族建立一個公共的接口,使其能夠更有效地發揮多態性。抽象類聲明了一族派生類共同操作的通用語義,而接口的完整實現——純虛函數的實現體,要由派生類自己給出。
純虛函數的例子:
#include <iostream.h>?
class shape
{
protected:
double x,y;
public:
void set (double I, double j) {x=I; y=j;}
virtual void area()=0;//聲明純虛函數
}; //抽象類shape定義結束
class triangle: public shape
{
public:
void area()
{
cout <<"Triangle S=1/2*"<<x<<"*"<<y<<"="<<0.5*x*y<<""n";
} // 派生類重新定義虛函數
};
class rectangle: public shape
{
public:
void area()
{
cout <<"Rectangle S=" <<x<<"*"<<y<<"="<<x*y<<""n";
}//派生類重新定義虛函數
}; //派生類rectangle定義結束
void main()
{
shape *p;
triangle t;
rectangle r;
p=&t;
p->set(5.1,10);
p->area();
p=&r;
p->set(5.1,10);
p->area();
}
采用虛函數的優點有:
①可以使程序簡單易懂;
②使程序模塊間的獨立性加強,增加了程序的易維護性;
③提高了程序中“信息隱藏”的等級。類的封裝本身是私有成員的隱藏,而在基類和派生類之間虛函數的設置,實際上是以虛函數作為對外接口,它隱藏的是在各個派生類中虛函數內容各異的不同實現;
④提高了“軟件重用”的等級。虛函數是一類簇對外提供的接口,它實質上是一種接口重用。
通過基類指針來訪問
一般情況下,指向一種類型對象的指針不允許指向另一種類型的對象。然而在具有層次關系的類結構中,指向基類對象的指針可以指向該基類的公有派生類對象(反向則不成立)。通過調用基類指針所指向的虛函數(指針指向哪一個派生類就調用哪一個派生類的虛函數),最終實現運行時的多態性。
純虛函數的定義
在基類中只聲明虛函數而不給出具體的定義,將它的具體定義放在各派生類中,這種虛函數稱為純虛函數(pure virtual function)。通過該基類指針或引用就可以調用所有派生類的虛函數,基類只是用于繼承,僅作為一個接口,具體功能在派生類中實現。聲明了純虛函數的類,稱為抽象類(abstract class)。
純虛函數的聲明形式如下:
virtual? 函數類型?函數原型(參數表)=0;
將虛函數的原型設置為0,該虛函數即為純虛函數。
繼承
類型兼容規則:
一個公有派生類的對象在使用上可以被當作基類的對象,反之則禁止。具體表現在:
派生類的對象可以被賦值給基類對象。
派生類的對象可以初始化基類的引用。
指向基類的指針也可以指向派生類。
通過基類對象名、指針只能使用從基類繼承的成員。
繼承機制下的構造函數:
??? 當創建一個派生類對象時,基類的構造函數被自動調用,用來對派生類對象中的基類部分進行初始化,并完成其他一些相關事務。如果派生類定義了自己的構造函數,則由該構造函數負責對象中派生類添加部分的初始化工作。
繼承機制下的析構函數:
在類的層次結構當中,構造函數按基類到派生類的次序執行,析構函數則按照派生類到基類的次序執行,因此,析構函數的執行次序和構造函數的執行次序是相反的。
基類成員的訪問控制:
表3.1?繼承成員訪問控制規則
| 繼承訪問控制 | 基類成員訪問控制 | 在派生類中的訪問控制 |
| public | public | public |
| protected | protected | |
| private | 不可訪問 | |
| protected | public | protected |
| protected | protected | |
| private | 不可訪問 | |
| private | public | private |
| protected | private | |
| private | 不可訪問 |
靜態數據成員及成員函數
class Task
{
public:
?????? // …
private:
?????? static int?sm_nCount;
?????? // …
};
int Task:: sm_nCount = 1000;
?? 特點:
??? 1. 屬于一個類所共有;
2. static數據成員在類聲明的內部聲明,它必須在任何程序塊之外被定義;
3. static數據成員不會影響該類及其對象的sizeof。
class Task
{
public:
?????? static int getCount() const { return sm_nCount; }
?????? // …
private:
?????? static int?sm_nCount;
?????? // …
};
???????? 靜態成員函數只能訪問其他的static成員,包括數據成員和成員函數,不能訪問非static數據成員和非static成員函數。
內聯函數聲明與使用
???? 聲明時使用關鍵字 inline。
???? 編譯時在調用處用函數體進行替換,節省了參數傳遞、控制轉移等開銷。
???? 注意:
–?內聯函數體內不能有循環語句和switch語句。
–?內聯函數的聲明必須出現在內聯函數第一次被調用之前。
–?對內聯函數不能進行異常接口聲明。
拷貝構造函數
拷貝構造函數是一種特殊的構造函數,其形參為本類的對象引用。
class 類名
{?
public :
?????? 類名(形參); //構造函數
?????? 類名(類名 &對象名);//拷貝構造函數
?????????? ...
};
類名:: 類名(類名 &對象名)//拷貝構造函數的實現
{???
函數體???
}
當用類的一個對象去初始化該類的另一個對象時系統自動調用拷貝構造函數實現拷貝賦值。
int main()
{?
?? Point A(1,2);
?? Point B(A);????? //拷貝構造函數被調用
?? cout << B.GetX() << endl;
}
若函數的形參為類對象,調用函數時,實參賦值給形參,系統自動調用拷貝構造函數。例如:
void fun1(Point p)
{??
??? cout<<p.GetX()<<endl;
}
int main()
{??
??? Point A(1,2);
??? fun1(A);??????? //調用拷貝構造函數
}?? ??
當函數的返回值是類對象時,系統自動調用拷貝構造函數。例如:
Point fun2()
{???
???? Point A(1,2);
???? return A;?????? //調用拷貝構造函數
}
int main()
{
???? Point B;
???? B = fun2();
}
拷貝構造函數小結
???????? 特點:
???? ???1. 拷貝構造函數創建一個新的對象,它是一個已有對象的拷貝;
?????????????????? 2. 拷貝構造函數可以有多個參數;
?????????????????? 3. 如果類的設計者沒有提供拷貝構造函數,編譯器會自動生成一個。
何時需要定義自己的拷貝構造函數:通常,如果一個類包含指向動態存儲空間指針類型的數據成員,則就應為這個類設計拷貝構造函數。轉載于:https://www.cnblogs.com/Wolf-PL/archive/2007/12/26/1016011.html
總結
- 上一篇: 用“已知”的办法解决“未知”的办法---
- 下一篇: C++:函数参数不确定时用cstdarg