C++ 第六章(多型性和虛擬函式)上篇
目錄
- 多型性
- 多型性的定義
- 例子
- 虛擬函式
- 虛擬函式的作用
- 什麼情況下使用虛擬函式
- 虛解構函式
- 純虛擬函式和抽象類
什麼是多型性
- 面對不同的物件傳送同一個訊息,不同的物件在接受時會產生不同的行為(方法)。也就是說,每個物件可以用自己的方式去響應共同的的訊息。所謂訊息就是函式的呼叫,不同的行為就是指不同的實現,即執行不同的函式。
舉個例子 學校要開學了 向所有人發了一個鬼故事,明天開學,這時學生做出反應是 快發個朋友圈(假期餘額已不足,然後吐槽學校)。然後乖乖明天去上學,你的父母收到訊息 開心的不能行,老師收到訊息就要準備開學後要講的課。這就是一個訊息,不同的人收到,會做出不同的反應。
靜態多型性:(編譯時)函式的過載 和 運算子的過載。
動態多型性:不在編譯時確定呼叫的是哪個函式,而是在程式執行過程中才確定地確定操作所指標的物件。執行時的多型性。通過虛擬函式實現 virtual
例子演示
#include<iostream> using namespace std; class Point{ public : Point (){ x = 0; y = 0; } Point (double, double); void setPoint(double, double); double getX() const{return x;} double getY() const{return y;} friend ostream& operator << (ostream &, const Point&); friend istream& operator >> (istream &, Point &); protected: double x; double y; }; Point::Point(double a, double b){ x = a; y = b; } void Point::setPoint(double a, double b){ x = a; y = b; } ostream &operator << (ostream & cout, const Point &p){ cout << "( " << p.x << ", " << p.y << " )" << endl; } istream &operator >> (istream & cin, Point &p){ cin >> p.x >> p.y; } int main (){ Point p(3.5, 6.4); cout << "x = " << p.getX() << " , y = " << p.getY() << endl; p.setPoint(8.5, 6.8); cout << "p(new) : " << p << endl; cin >> p; cout << "p(new) : " << p << endl; return 0; }
進行擴充套件
#include<iostream> using namespace std; class Circle : public Point{ public : Circle(double, double, double); void setRadius(double); double getRadius() const; double area() const; friend ostream &operator << (ostream &, const Circle &); friend istream &operator >> (istream &, Circle &); protected: double radius; }; Circle:: Circle(double a, double b, double r):Point(a,b), radius(r){} void Circle::setRadius(double r){ radius = r; } double Circle::getRadius() const{return radius;} double Circle::area() const{ return 3.14 * radius * radius; } istream &operator >> (istream &cin, Circle &c){ cin >> c.x >> c.y >> c.radius; } ostream &operator << (ostream &cout, const Circle &c){ cout << "圓心:" << "( " << c.x << ", " << c.y << " )" << endl; cout << "Radius: " << c.radius << endl; cout << "area : " << c.area() << endl; } int main (){ Circle c(3.3, 5.4, 5); cout << c << endl; c.setRadius(3); c.setPoint(5,5); cout << "new c : " << c << endl; Point &p = c; cout << "p = " << p << endl; return 0; }
有一點就是 const 這是詞如果加到函式後面
double getRadius() const;
函式後面+const 不能修改類內的資料成員, 只能訪問。
再擴充
#include<iostream>
using namespace std;
class Cylinder : public Circle{
public :
Cylinder(double , double, double, double);
void setHeight(double);
double getHeight() const;
double area() const;
double volume() const;
friend ostream &operator << (ostream &, const Cylinder &);
protected:
double height;
};
Cylinder::Cylinder(double a, double b, double r, double h)
: Circle(a, b, r), height(h){}
void Cylinder::setHeight(double h){
height = h;
}
double Cylinder :: area() const
{
return 2 * Circle::area() + 2*3.14 *radius*height;
}
double Cylinder :: volume() const{
return Circle::area() * height;
}
ostream &operator << (ostream & cout, const Cylinder &cy){
cout << "( " << cy.x << ", " << cy.y << " )" << endl;
cout << "radius : " << cy.radius << endl;
cout << "height : " << cy.height << endl;
cout << "area : " << cy.area() << endl;
cout << "volume : " << cy.volume() << endl;
}
int main (){
Cylinder cy(3, 4, 5, 6);
cout << cy << endl;
cy.setHeight(15);
cy.setRadius(7.5);
cy.setPoint(5,5);
cout << cy << endl;
Point &p = cy;
cout << p << endl;
Circle &q = cy;
cout << q << endl;
return 0;
}
Circle 中有area ()函式 Cylinder 中也有area()函式 當我們用 cy.area()時 ,呼叫的是Cylinder的函式,不是Circle中area()他們不是過載函式,因為不在一個類內(分別在基類和派生類中),這種呼叫屬於同名覆蓋。過載函式的引數個數和引數型別必須至少有一個不同,否則系統無法確定呼叫哪個,
再看上面的cout 輸出的 我過載了 3個 cout 這三個傳遞的引數不同從輸出結果可以看出呼叫哪個運算子。
上面的3個例子中 存在的靜態多型性,是由過載運算子引起的。
利用虛擬函式實現動態多型性
(1)虛擬函式的作用
作用:程式中不再是通過不同的物件名去呼叫不同派生層次中的同名函式,而是通過指標分別呼叫這些同名函式。只須在呼叫前臨時給指標變數pt賦以不同的值(指向不同類的物件)即可。
打個比方 你要去某個地方辦事,如果坐公交車,必須事先確定好目的地,然後乘坐能夠達到目的的公交車。如果改為乘計程車,就簡單很多了,不必查行車路線,因為計程車什麼地方都去,只要在上車後臨時告訴司機要到哪裡即可。如果想訪問多個目的地,只要在到達一個目的地後再告訴下個目的地即可。這種事先確定路線的就是靜態多型。而計程車臨時決定行程的相當與動態多型。
虛擬函式的作用是允許再派生類中重新定義與基類同名的函式,並且可以通過基類指標或引用來訪問基類和派生類中的同名函式。
在實際過程中 這些同名函式會建立一個指標表,然後你指標指向哪個類的物件,指標就指向這個類中的函式。
#include<iostream>
using namespace std;
class Student
{
public:
Student(int, string, double);
void display();
protected:
int num;
string name;
double score;
};
class Graduate:public Student
{
public:
Graduate(int, string, double, double);
void display();
double wage;
};
Student::Student(int n, string nam, double s){
num = n;
name =nam;
score = s;
}
Graduate::Graduate(int n, string nam, double s, double w):Student(n, nam, s){
wage = w;
}
void Student::display(){
cout << "num : " << num << endl;
cout << "name : " << name << endl;
cout << "score : " << score << endl;
}
void Graduate::display(){
cout << "num : " << num << endl;
cout << "name : " << name << endl;
cout << "score : " << score << endl;
cout << "wage : " << wage << endl;
}
int main (){
Student stud(999, "LiuMang", 99);
Graduate grad(1001, "Dalao", 100, 20000);
Student *p = &stud;
p->display();
cout << endl;
p = &grad;
p->display();
return 0;
}
輸出結果
在主函式中我們定義了指向基類物件的指標變數pt,並先使pt指向stud,用pt->display()輸出的是基類物件stud的全部的資料成員,然後我們將pt指向grad,再次呼叫pt->display()企圖輸出grad1中的全部的資料成員,但是隻輸出了grad中的基類的成員,說明它並沒有呼叫grad中的display()函式,而只是呼叫了stud中的display().
假如想輸出grad中的全部的資料成員,當然也可以採用這樣的方法:通過物件名用display函式如,grad.display(),或者定一個指向Gradudate類物件的指標變數pt,然後使pt指向grad,再用pt->display();呼叫,當然可以。
但是如果該基類有多個派生類,每個派生類又產生新的派生類,形成了同基類的類族。每個派生類都有同名函式display,在程式中要呼叫同一類組中不同類的同名函式,就要定義多個指向多個各派生類的指標變數。這兩種種方法很不方便。
我們是否可以用一種簡單的方法來呼叫同一類族中不同類的所有的同名函式。
虛擬函式誕生了,他的作用就是可以實現用一個基類指標 就可以呼叫同一類族中所有的同名函式,但是在呼叫前要將指標指向你要用的類的物件。
在Student 的diplay函式前加virtual;
virtual void display(); //將基類中的函式作為虛擬函式
輸出的結果
這時我們就呼叫了grad中的display函式,pt是同一個基類指標可以呼叫同一類族中不同類的虛擬函式,這就是多型性,對同一個訊息,不同的物件有不同響應方式。
簡單的說如果你不宣告為vritual 虛擬函式 你指向基類的指標就只能呼叫派生類中 基類的資料成員和函式,如果你宣告為虛擬函式,你就可以通過指向基類的指標 去呼叫派生類中的成員函式和資料成員,基類指標會自動轉換為派生類指標。
將基類的某個成員函式宣告為虛擬函式後,允許其派生類中對該函式重新定義,賦予它新的功能,並且可以通過指向基類的指標指向同一類族中不同類的物件從而呼叫其中的同名函式,
虛擬函式 的使用方法:
(1)類內宣告需要加 virtual ,類外定義時不需要+。
(2)在派生類中重定義時,此函式的:函式名,函式型別,函式引數個數,和型別必須與基類的虛擬函式相同,根據派生類的需要重新定義函式體。
(3)當一個成員函式被宣告為虛擬函式後,其派生類中的同名函式都自動成為虛擬函式,一次在派生類重新宣告該函式時,可以不+,但是建議+vritual,使程式更加清晰。
(4)定義一個指向基類物件的指標變數,並使它指向同一類族中需要呼叫該函式的物件。
(5)通過該指標變數呼叫此虛擬函式,此時呼叫的就是指標變數指向的物件的同名函式(指向哪個派生類就呼叫哪個)。
2.什麼情況下宣告虛擬函式
(1)只能用virtual宣告類的成員函式,把它作為虛擬函式,而不能將類外的普通函式宣告為虛擬函式。因為虛擬函式的作用就是派生類對基類的函式的重定義。
(2)一個成員函式被宣告為虛擬函式後,在同一類族中的類就不能再定義一個非virtual的但是與該函式具有相同引數(個數和型別)和函式返回值型別的同名函式。
我們從哪些方面考慮將一個成員函式宣告為虛擬函式呢?
(1)看成員函式的類是否為作為基類,並且改函式在派生時作用發生改變,
(2)如過成員函式被繼承後不被修改,那就沒有必要宣告為虛擬函式。
(3)對成員函式的呼叫是通過物件名還是通過類指標或引用去訪問,如果是通過類指標則宣告為虛擬函式。
(4)有時在定義虛擬函式時並不定義其函式體,即函式體是空的,它的作用只是定義了一個虛擬函式名,具體功能留給派生類去新增
使用虛擬函式,系統有一定的空間開銷,當一個類帶有虛擬函式時,編譯系統會為該類構造一個虛擬函式表,他是一個指標陣列,存放每個虛擬函式的入口地址,系統在進行動態關聯時的時間開銷是很少的,因此多型性高效。