注意:本文中代碼均使用 Qt 開發編譯環境
面向對象的多態性可以分為四類:重載多態、強制多態、包含多態和參數多態,前兩種統稱為專用多態,而后兩種也稱為通用多態。
多態的實現###
多態從實現的角度可以劃分為:編譯時多態和運行時的多態。
確定操作的具體對象的過程就是綁定(binding,也叫聯編)。綁定是指計算機程序自身彼此關聯的過程,也就是把一個標識符名和一個存儲地址聯系在一起的過程;就是把一條消息和一個對象的方法相結合的過程。按照綁定進行的階段不同,可分為:靜態綁定和動態綁定,這兩種綁定過程中分別對應著多態的兩種實現方式:
1.綁定工作在編譯連接階段完成的情況稱為靜態綁定。也叫早期綁定或前綁定。
2.綁定工作在程序運行階段完成的情況稱為動態綁定。也叫晚期綁定或后綁定。
運算符重載###
C++ 中預定義的運算符的操作對象只能是基本數據類型。但實際上,對于許多用戶自定義類型(例如類),也需要類似的運算操作。這時就必須在C++中重新定義這些運算符,賦予已有運算符新的功能,使它能夠用于特定類型執行特定的操作。運算符重載的實質是函數重載,它提供了 C++ 的可擴展性,也是 C++ 最吸引人的特性之一。
運算符重載是通過創建運算符函數實現的,運算符函數定義了重載的運算符將要進行的操作。運算符函數的定義與其他函數的定義類似,惟一的區別是運算符函數的函數名是由關鍵字 operator 和其后要重載的運算符符號構成的。
運算符函數定義的一般格式如下:
<返回類型說明符> operator <運算符符號> (<參數表>)
{
<函數體>
}
運算符重載時要遵循以下規則:
(1) 除了類屬關系運算符 "." 、成員指針運算符 ".*" 、作用域運算符 "::" 、sizeof 運算符和三目運算符 "?:" 以外,C++ 中的所有運算符都可以重載。
(2) 重載運算符限制在 C++ 語言中已有的運算符范圍內的允許重載的運算符之中,不能創建新的運算符。
(3) 運算符重載實質上是函數重載,因此編譯程序對運算符重載的選擇,遵循函數重載的選擇原則。
(4) 重載之后的運算符不能改變運算符的優先級和結合性,也不能改變運算符操作數的個數及語法結構。
(5) 運算符重載不能改變該運算符用于內部類型對象的含義。它只能和用戶自定義類型的對象一起使用,或者用于用戶自定義類型的對象和內部類型的對象混合使用時。
(6) 運算符重載是針對新類型數據的實際需要對原有運算符進行的適當的改造,重載的功能應當與原有功能相類似,避免沒有目的地使用重載運算符。
示例1:
#include <QCoreApplication>
#include <QDebug>
class complex{
public:
complex(double r=0.0, double i=0.0) : real(r), imag(i) {}
complex operator +(complex c2);
complex operator -(complex c2);
QString display() const;
private:
double real;
double imag;
};
complex complex::operator + (complex c2){
return complex(real + c2.real, imag + c2.imag);
}
complex complex::operator - (complex c2){
return complex(real - c2.real, imag - c2.imag);
}
QString complex::display() const {
return "(" + QString::number(real) + ", " + QString::number(imag) + ")";
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
complex c1(5, 4), c2(2, 10), c3;
qDebug() << "c1 = " << c1.display();
qDebug() << "c2 = " << c2.display();
c3 = c1 - c2;
qDebug() << "c3 = c1 - c2 = " << c3.display();
c3 = c1 + c2;
qDebug() << "c3 = c1 + c2 = " << c3.display();
return a.exec();
}
運行結果:
運算符函數重載一般有兩種形式:重載為類的成員函數和重載為類的非成員函數。非成員函數通常是友元。(可以把一個運算符作為一個非成員、非友元函數重載。但是,這樣的運算符函數訪問類的私有和保護成員時,必須使用類的公有接口中提供的設置數據和讀取數據的函數,調用這些函數時會降低性能。可以內聯這些函數以提高性能。)
成員函數運算符###
運算符重載為類的成員函數的一般格式為:
<函數類型> operator <運算符> ( <參數表> ) {
<函數體>
}
當運算符重載為類的成員函數時,函數的參數個數比原來的操作數要少一個(后置單目運算符除外),這是因為成員函數用this指針隱式地訪問了類的一個對象,它充當了運算符函數最左邊的操作數。因此:
(1) 雙目運算符重載為類的成員函數時,函數只顯式說明一個參數,該形參是運算符的右操作數。
(2) 前置單目運算符重載為類的成員函數時,不需要顯式說明參數,即函數沒有形參。
(3) 后置單目運算符重載為類的成員函數時,函數要帶有一個整型形參。
調用成員函數運算符的格式如下:
<對象名>.operator <運算符>(<參數>)
它等價于
<對象名><運算符><參數>
例如:a+b等價于a.operator+(b)。一般情況下,我們采用運算符的習慣表達方式。
示例2:
#include <QCoreApplication>
#include <QDebug>
class Clock {
public:
explicit Clock();
Clock(int NewH=0,int NewM=0,int NewS=0);
QString ShowTime() const;
Clock& operator ++(); //前置單目運算符重載
Clock operator ++(int); //后置單目運算符重載
private:
int Hour, Minute, Second;
};
Clock::Clock() : Hour(0), Minute(0), Second(0) {
}
Clock::Clock(int NewH, int NewM, int NewS) {
Hour = qMax(0, qMin(NewH, 23));
Minute = qMax(0, qMin(NewM, 59));
Second = qMax(0, qMin(NewS, 59));
}
QString Clock::ShowTime() const {
return QString::number(Hour) + ":"
+ QString::number(Minute) + ":"
+ QString::number(Second);
}
Clock& Clock::operator ++() {
if (++Second>=60) {
Second = Second - 60;
if (++Minute >= 60) {
Minute = Minute - 60;
Hour = (++Hour) % 24;
}
}
return *this;
}
Clock Clock::operator ++(int) {
Clock old = *this;
++(*this);
return old;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Clock myClock(23,59,59);
qDebug() << "myClock is: " << myClock.ShowTime();
qDebug() << "myClock++:" << (myClock++).ShowTime();
qDebug() << "myClock is: " << myClock.ShowTime();
qDebug() << "++myClock:" << (++myClock).ShowTime();
qDebug() << "++myClock:" << (++myClock).ShowTime();
return a.exec();
}
運行結果:
友元函數運算符###
運算符重載為類的友元函數的一般格式為:
friend <函數類型> operator <運算符> ( <參數表> ) {
<函數體>
}
當運算符重載為類的友元函數時,由于沒有隱含的this指針,因此操作數的個數沒有變化,所有的操作數都必須通過函數的形參進行傳遞,函數的參數與操作數自左至右一一對應。
調用友元函數運算符的格式如下:
operator <運算符>(<參數1>,<參數2>)
它等價于
<參數1><運算符><參數2>
例如:a+b等價于operator+(a,b)。
友元示例:
#include <QCoreApplication>
#include <QDebug>
class complex {
public:
complex(double r=0.0,double i=0.0) : real(r), imag(i) {
}
friend complex operator + (complex c1, complex c2);
friend complex operator - (complex c1, complex c2);
QString display() const;
private:
double real;
double imag;
};
complex operator + (complex c1,complex c2) {
return complex(c1.real + c2.real, c1.imag + c2.imag);
}
complex operator - (complex c1,complex c2) {
return complex(c1.real - c2.real, c1.imag - c2.imag);
}
QString complex::display() const {
return "(" + QString::number(real) + ","
+ QString::number(imag) +")";
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
complex c1(5,4), c2(2,10), c3;
qDebug() << "c1 =" << c1.display();
qDebug() << "c2 =" << c2.display();
c3 = c1 - c2;
qDebug() << "c3 = c1-c2 = " << c3.display();
c3 = c1 + c2;
qDebug() << "c3 = c1+c2 = " << c3.display();
return a.exec();
}
運行結果:
兩種重載形式的比較
在多數情況下,將運算符重載為類的成員函數和類的友元函數都是可以的。但成員函數運算符與友元函數運算符也具有各自的一些特點:
(1) 一般情況下,單目運算符最好重載為類的成員函數;雙目運算符則最好重載為類的友元函數。
(2) 以下一些雙目運算符不能重載為類的友元函數:=、()、[]、->。
(3) 類型轉換函數只能定義為一個類的成員函數而不能定義為類的友元函數。
(4) 若一個運算符的操作需要修改對象的狀態,選擇重載為成員函數較好。
(5) 若運算符所需的操作數(尤其是第一個操作數)希望有隱式類型轉換,則只能選用友元函數。
(6) 當運算符函數是一個成員函數時,最左邊的操作數(或者只有最左邊的操作數)必須是運算符類的一 個類對象(或者是對該類對象的引用)。如果左邊的操作數必須是一個不同類的對象,或者是一個內部 類型的對象,該運算符函數必須作為一個友元函數來實現。
(7) 當需要重載運算符具有可交換性時,選擇重載為友元函數。