重載和多態ios
1. 區別:重載是同名但參數不一樣,經過參數來肯定調用哪一個函數;函數
多態是同名同參數,經過函數的實際類型決定調用哪一個函數。this
2. 多態的實現(如下將具體分析這三點):spa
① 函數重載3d
② 運算符重載指針
③ 虛函數code
多態從實現的角度來說可分爲:編譯時的多態和運行時的多態。對象
前者是在編譯的過程當中肯定了同名操做的具體操做對象(靜態綁定);blog
然後者則是在程序運行過程當中才動態地肯定操做所針對的具體對象(動態綁定)。繼承
這種肯定操做地具體對象地過程就是綁定(就是把一條消息和一個對象的方法相結合的過程)。
3. 重寫:若子類和父類的某個函數具備相同的函數名,形參列表,且父類中的函數被定義爲虛函數,則子類對該函數的實現被稱爲函數的重寫。重寫是常見的多態實現方式。
1、 函數重載
1. 概念:兩個以上的函數,具備相同的函數名,可是形參的個數或者類型不一樣,編譯器根據實參和形參的類型及個數的最佳匹配,自動肯定調用哪個函數,也就是函數的重載。簡單理解,就是「一物多用」。
注:重載函數的參數個數、參數類型或者參數順序三者中必須有一個不一樣。
·例如:
1 int add(int x, int y); 2 float add(float x, float y);//形參類型不一樣 3 4 int add(int x, int y); 5 int add(int x, int y, int z);//形參個數不一樣
2.C++容許功能相近的函數在相同的做用域內聲明幾個相似的同名函數,從而造成重載。編譯程序對實參和形參的類型、個數、順序,來選擇調用哪個函數。重載函數經常使用來處理實現功能相似但數據類型不一樣的問題。
·如下指出幾種錯誤狀況:
int add(int x, int y); int add(int b, int b);//錯誤,編譯器不以形參名來區分函數 int add(int x, int y); void add(int x, int y);//錯誤,編譯器不以返回值來區分函數
1 //當使用具備默認形參值得函數重載形式時,須要注意防止二義性 2 //如下兩個函數原型,在編譯時便沒法區別爲不一樣的重載形式: 3 4 void fun(int length, int width = 2, int height = 33); 5 void fun(int length); 6 7 //也就是說,當以fun(1)來調用函數fun時,編譯器沒法肯定應該執行哪一個重載函數
//不要將不一樣功能的函數定義爲重載函數 int add(int x, int y) { return x + y; } float add(float x, float y) { return x - y; }
·具體實例1(形參類型不一樣):
1 #include<iostream> 2 using namespace std; 3 4 int ADD(int left, int right) 5 { 6 return left + right; 7 } 8 9 double ADD(double left, double right) //與第一個函數相比,形參類型不一樣 10 { 11 return left + right; 12 } 13 14 int main() 15 { 16 17 cout << ADD(1, 2) << endl; 18 cout << ADD(1.3, 2.3) << endl; 19 return 0; 20 }
·具體實例2(形參個數不一樣):
#include<iostream> using namespace std; int ADD(int a, int b) { return a + b; } double ADD(int a, int b,int c) //與第一個函數相比,形參個數不一樣 { return a + b+c; } int main() { cout << ADD(1, 2) << endl; cout << ADD(1, 2,3) << endl; return 0; }
2、 運算符重載
·基本知識
1. 概念:運算符重載是對已有的運算符賦予多重含義,是同一個運算符做用域不一樣類型的數據時致使不一樣的行爲。(運算符重載的實質就是函數重載)
2. 運算符重載是針對新類型數據的實際須要,對原有運算符進行適當的改造。
例如:
·使複數類的對象能夠用「+」運算符實現加法;
·使時鐘類的對象能夠用「++」運算符實現時間增長1秒。
3. C++幾乎能夠重載所有的運算符,並且只可以重載C++中已經有的。不能重載的運算符:「.」 「.*」 「::」 「?:」。
4. 重載以後運算符的優先級和結合性都不會改變。
5. 運算符重載的形式有兩種:
·重載爲類的非靜態成員函數;
·重載爲非成員函數。
·具體講解
1、 重載爲類的非靜態成員函數
1.定義:
1 返回類型 operator 運算符(形參表) 2 { 3 函數體 4 } 5 參數個數=原操做數個數-1(後置++、--除外)
·返回類型:指定了重載運算符的返回值類型,也就是運算結果類型。
·operator:是定義運算符重載的關鍵字。
·運算符:是要重載的運算符名稱,好比要重載加法運算符,這裏就寫「+」。
·形參表:給出重載運算符所須要的參數和類型。
2. 重載爲成員函數,她就能夠自由地訪問本類的數據成員。
3. 雙目運算符重載:
(1)若是要重載B爲類成員函數,使之可以實現表達式oprd1 B oprd2,其中oprd1爲A類對象,則B應被重載爲A類的成員函數,該函數只有一個形參,形參類型是oprd2所屬的類型。
(2)經重載後,表達式oprd1 B oprd2至關於oprd1.operator B(oprd2)。
·實例1(複數加減運算):
1 #include<iostream> 2 using namespace std; 3 4 class Complex { //複數類定義 5 public: //外部接口 6 Complex(double r = 0.0, double i = 0.0) :real(r), imag(i) {}//構造函數 7 Complex operator+(const Complex& c2)const;//運算符+重載成員函數 8 Complex operator-(const Complex& c2)const;//運算符-重載成員函數 9 void display()const; //輸出複數 10 private: 11 double real; //複數實部 12 double imag; //複數虛部 13 }; 14 Complex Complex::operator+(const Complex& c2)const //重載運算符函數實現 15 { 16 return Complex(this->real + c2.real, this->imag + c2.imag);//建立一個臨時無名對象做爲返回值,含義:調用Complex構造函數建立一個臨時對象並返回它。 17 } 18 Complex Complex::operator-(const Complex& c2)const 19 { 20 return Complex(this->real - c2.real, this->imag - c2.imag); 21 } 22 void Complex::display()const 23 { 24 cout << "(" << real << "," << imag << ")" << endl; 25 } 26 int main() { 27 Complex c1(5, 4), c2(2, 10), c3; 28 cout << "c1="; c1.display(); 29 cout << "c2="; c2.display(); 30 c3 = c2 - c1; 31 cout << "c3=c2-c1"; c3.display();//使用重載運算符完成複數減法 32 c3 = c1 + c2; 33 cout << "c3=c1+c2="; c3.display();//使用重載運算符完成複數加法 34 return 0; 35 }
上面的語句:return Complex(this->real + c2.real, this->imag + c2.imag);
能夠改寫爲:Complex Complex::operator+(const Complex& c2)const {
Complex c(this->real + c2.real, this->imag + c2.imag);
return c;
}
4. 前置單目運算符重載:
(1)若是要重載U爲類成員函數,使之可以實現表達式U oprd,其中oprd爲A類對象,則U應被重載爲A類的成員函數,無形參。
(2)經重載後,表達式U oprd至關於oprd.operator U()。
5. 後置單目運算符++和--重載
(1)若是要重載++和--類成員函數,使之可以實現表達式oprd++或者oprd--,其中oprd爲A類對象,則++和--應被重載爲A類的成員函數,且具備一個int類型形參。
(2)經重載後,表達式oprd++至關於oprd.operator ++()。
(3)前置單目運算符重載爲成員函數時沒有形參;後置單目運算符重載爲成員函數時須要有一個int型形參,該int型參數在函數體中並未使用,純粹只是用來區別前置和後置,因此參數表中能夠只給出類型名,沒有參數名。
·實例2(時鐘類):
1 #include<iostream> 2 using namespace std; 3 4 class Clock { 5 public: 6 Clock(int hour = 0, int minute = 0, int second = 0); 7 void showTime()const; 8 Clock& operator++();//前置單目運算符重載 9 Clock operator++(int);//後置單目運算符重載 10 private: 11 int hour, minute, second; 12 }; 13 Clock::Clock(int hour/*=0*/, int minute/*=0*/, int second/*=0*/) 14 { 15 if (0 <= hour && hour < 24 && 0 <= minute && minute < 60 && 0 <= second && second, 60) { 16 this->hour = hour; 17 this->minute = minute; 18 this->second = second; 19 } 20 else 21 cout << "Time error!" << endl; 22 } 23 void Clock::showTime()const { 24 cout << hour << ":" << minute << ":" << second << endl; 25 } 26 Clock& Clock::operator++() {//前置單目運算符重載函數 27 second++; 28 if (second >= 60) { 29 second -= 60; 30 if (minute >= 60) { 31 minute -= 60; 32 hour = (hour + 1) % 24; 33 } 34 } 35 return(*this); 36 } 37 Clock Clock::operator++(int) {//後置單目運算符重載函數 38 Clock old = *this; 39 ++(*this); 40 return old; 41 } 42 int main() { 43 Clock myClock(23, 59, 59); 44 cout << "First time output:"; 45 myClock.showTime(); 46 cout << "Show myClock++:"; 47 (myClock++).showTime(); 48 cout << "Show ++myClock:"; 49 (++myClock).showTime(); 50 return 0; 51 }
2、 重載爲非成員函數
1.定義:
1 返回類型 operator 運算符(形參表) 2 { 3 函數體 4 } 5 參數個數=原操做數個數(後置++、--除外)
2. 函數的形參表明依自左至右次序排列的各操做數。
3. 至少應該有一個自定義類型的參數。
4. 若是在運算符的重載函數中須要對操做某類對象的私有成員,能夠將此函數聲明爲該類的友元函數。
5. 雙目運算符B重載後:
表達式oprd1 B oprd2至關於operator B(oprd1,oprd2)。
6. 前置單目運算符B重載後:
表達式B oprd至關於operator B(oprd)。
7. 後置單目運算符++和--重載後:
表達式B oprd至關於operator B(oprd,0)。
·實例3(複數加減運算):
1 #include<iostream> 2 using namespace std; 3 4 class Complex { 5 public: 6 Complex(double r=0.0,double i=0.0):real(r),imag(i){} 7 friend Complex operator+(const Complex& c1, const Complex& c2);//運算符+重載 8 friend Complex operator-(const Complex& c1, const Complex& c2);//運算符-重載 9 friend ostream& operator<<(ostream& out, const Complex& c);//運算符<<重載 10 private: 11 double real, imag; 12 }; 13 Complex operator+(const Complex& c1, const Complex& c2) { 14 return Complex(c1.real + c2.real, c1.imag + c2.imag); 15 } 16 Complex operator-(const Complex& c1, const Complex& c2) { 17 return Complex(c1.real - c2.real, c1.imag - c2.imag); 18 } 19 ostream& operator<<(ostream& out, const Complex& c) { 20 out << "(" << c.real << "," << c.imag << ")"; 21 return out; 22 } 23 int main() { 24 Complex c1(5, 4), c2(2, 10), c3; 25 cout << "c1=" << c1 << endl; 26 cout << "c2=" << c2 << endl; 27 c3 = c1 - c2; 28 cout << "c3=c1-c2=" << c3 << endl; 29 c3 = c1 + c2; 30 cout << "c3=c1+c2=" << c3 << endl; 31 return 0; 32 }
1.上面將<<(雙目)重載爲非成員函數,並將其聲明爲複數類的友元,它的左操做數是ostream類型的引用,右操做數是Complex類型的引用,這樣在執行cout<<c1時,就會調用用operator<<(cout,c1)。該函數把經過第一個參數傳入的ostream對象以引用形式返回,用以支持下面形式的輸出:
cout<<a<<b;
該輸出調用的是:
operator<<(operator<<(cout,a),b);
2.以非成員函數重載,能夠直接使用5.0+c1或者c1+5.0,由於Complex的構造函數使得實數能夠被隱含轉換爲Complex類型。而以成員函數重載時,左操做數必須具備Complex類型,不能是實數,只有右操做數能夠是實數。
3、虛函數
·基本知識
1. 虛函數的聲明:
virtual 函數類型 函數名(形參表);
2. 虛函數聲明只能出如今類定義的函數原型聲明中,不能在成員函數實現的時候。
3. 虛函數是動態綁定的基礎,必須是非靜態的成員函數。虛函數通過派生以後,在類族中就能夠實現運行過程當中的多態。
4. 若是須要經過基類得指針指向派生類的對象,並訪問某個與基類同名的成員,那麼首先在基類中將這個同名函數說明爲虛函數。這樣,經過基類類型的指針,就可使不一樣派生類的不一樣對象產生不一樣的行爲,從而實現運行過程當中的多態。
5. 運行時的多態須要知足如下三個條件:
·知足賦值兼容規則;
·要聲明虛函數;
·由成員函數來調用或者是經過指針、引用來訪問虛函數。(若是是使用對象名來訪問虛函數,則綁定在編譯過程當中就能夠進行(靜態綁定),而無須在運行過程當中進行)
6. 虛函數通常不聲明爲內聯函數,由於對虛函數的調用須要動態綁定,而對內聯函數的處理是靜態的。但將虛函數聲明爲內聯函數也不會引發錯誤。
7. 構造、靜態函數不能是虛函數,析構函數能夠是虛函數。
·具體講解
·實例4:(書例7-3):
1 #include<iostream> 2 using namespace std; 3 4 class Base1 { 5 public: 6 virtual void display()const;//虛函數 7 /*可在該語句前加virtual,能夠更清楚地提示這是一個虛函數,也可不加*/ 8 }; 9 void Base1::display()const { 10 cout << "Base1::display()" << endl; 11 } 12 class Base2:public Base1 { 13 public: 14 void display()const;//覆蓋基類的虛函數 15 }; 16 void Base2::display()const { 17 cout << "Base2::display()" << endl; 18 } 19 class Derived :public Base2 { 20 public: 21 void display()const;//覆蓋基類的虛函數 22 }; 23 void Derived::display()const { 24 cout << "Derived::display()" << endl; 25 } 26 void fun(Base1* ptr) {//參數爲指向基類對象的指針 27 ptr->display();//「對象指針->成員名」 28 } 29 int main() { 30 Base1 base1; 31 Base2 base2; 32 Derived derived; 33 fun(&base1);//用Base1對象的指針調用fun函數 34 fun(&base2);//用Base2對象的指針調用fun函數 35 fun(&derived);//用Derived對象的指針調用fun函數 36 return 0; 37 }
1.在本程序中,派生類並無顯式給出虛函數聲明virtual,這時系統就會遵循如下規則來判斷派生類的一個函數成員是否是虛函數。若是肯定爲虛函數,這時,派生類的虛函數將覆蓋基類的虛函數。
·該函數是否與基類的虛函數有相同的名稱
·該函數是否與基類的虛函數有相同的參數個數及相同的對應參數類型
·該函數是否與基類的虛函數有相同的返回值或者知足賦值兼容規則的指針、引用型的返回值
2.通常習慣於在派生類的函數中也使用virtual關鍵字,以加強程序的可讀性
3.若是把fun函數中的ptr->display()改成ptr->Base::display(),不管ptr所指向對象的動態類型是什麼,最終被調用的老是Base1類的display()函數。在派生類的函數中,有時就能夠用「基類名::函數名(…)」來調用基類中被覆蓋的函數。
4.在重寫繼承來的虛函數時,若是函數有默認形參值,不要從新定義不一樣的值。雖然虛函數是動態綁定的,但默認形參值是靜態綁定的。也就是說,經過一個指向派生類對象的基
類指針,能夠訪問到派生類的虛函數,但默認形參之卻只能來自基類的定義。
例如:將實例4中的fun函數的參數類型設定爲Base1而非Base1*,那麼3次fun函數的調用中,被執行的都會是Base1::display()。由於基類的對象不能表示派生類的對象
5.只有經過基類的指針或引用調用虛函數時,纔會發生動態綁定。
例如:將實例4中的fun函數的參數類型設定爲Base1而非Base1*,那麼3次fun函數的調用中,被執行的都會是Base1::display()。由於基類的對象不能表示派生類的對象。
1 Derived d; //定義派生類對象 2 Base* ptr = &d;//基類指針ptr能夠指向派生類對象 3 Base& ref = d;//基類引用ref能夠做爲派生類對象的別名 4 Base b = d;//調用Base1的複製構造函數用d構造b,b的類型是Base而非Derived