多態、虛函數、抽象類
1、虛函數
1.1 當父類指針或引用指向子類對象,而子類中又覆蓋了父類的函數,希望用父類指針或引用調用到正確版本的成員函數,需要把該成員函數聲明爲虛函數
1.2 調用虛函數時,到底調用哪個版本,是根據調用該函數的對象本身的類型,而不是指向那個對象的指針或引用的類型,即對象的內存空間爲誰(父類or子類)開闢就調用誰的成員方法或成員變量
1.3 虛函數目的:父類指針或引用,不管指向父類還是子類,在調用覆蓋函數時,可以反映真實情況;有了虛函數,無需向下轉型,就可以正確的用父類的指針或引用調用到子類的函數
1.4 如果函數的參數是傳值方式,形參是父類對象,實參是子類對象,則在函數內部,用形參調用的成員函數,依然是父類版本。因爲傳值只是用子類對象給父類對象賦值,父類對象不是指向子類的引用或指針
1.5 如果一個虛函數被其他成員函數調用,子類的版本也會被正確調用
1.6 如果一個類有子類,則這個父類的析構函數必須是虛函數,即虛析構。如果不是虛析構,則當(用delete)刪除一個指向子類對象的父類指針時,將調用父類版本的析構函數,子類只釋放了來自於父類的那部分成員變量,而沒有釋放子類擴展的成員變量,造成內存泄漏。
1.7 如果子類的成員函數是虛函數,則子類覆蓋後,不寫virtual也是虛函數。
1.8 虛函數被調用的時候,到底調用哪個版本,在編譯的時候無法確定,只有在執行時才能確定,稱爲動態綁定。之前的函數調用,是在編譯時就可以確定調用哪個版本的函數
1.9 動態綁定使得程序可以照顧到未來增加的代碼,比如創建一個新的子類,並在子類中覆蓋了父類的虛函數,用之前的父類指針依然可以正確的調用到新子類中的函數,而無需對舊有代碼進行修改。
2、抽象基類、純虛函數
2.1 純虛函數,沒有函數體,不需要實現,在子類中實現純虛函數的具體功能
2.2 擁有純虛函數的類,稱爲抽象類,抽象類提供了不同種類對象的一個通用接口。
2.3 不能創建抽象類的對象,因爲抽象類裏面的純虛函數沒有實現。
2.4 抽象類只能作爲基類使用,即抽象基類,如果想創建子類對象,必須實現抽象基類中的所有純虛函數,否則,子類依然是抽象類
2.5 不能單獨調用抽象類的構造函數,僅可以用於子類構造函數的初始化列表裏,用於初始化子類中繼承自父類的成員變量
2.6 抽象類不是必須有析構函數,一旦有,必須是虛析構
2.7 不能以傳值的方式,向一個函數傳遞抽象基類的參數。如果函數形參是抽象類,實參是子類,就相當於用子類對象,創建了一個臨時的抽象基類對象,後者是不允許的,所以必須以傳引用或指針的方式來傳參
3、多態:用父類的指針或引用指向子類的對象,在函數調用時可以調用到正確版本的函數
3.1 用一個父類的指針指向一個子類對象
3.2 用一個父類的指針當函數的形參,用這個指針可以接受到任何它的子類對象也包括他自己
3.3 在複合類,儘量飲用高層次的類(父類的指針)當做類的成員變量,這樣就可以通過它創建出它所對應的任何子類對象包括他自己
3.4 在容器中,可以聲明一個父類指針的容器,這時可以往容器中添加它所對應的任何子類對象包括他自己
A、虛函數
Base.h
#ifndef __C__No806Class__Base__
#define __C__No806Class__Base__
#include <iostream>
using namespace std;
class Base
{
//當父類指針或父類引用指向子類對象,而子類中又覆蓋了父類的函數,希望用父類指針或父類引用,調用到正確版本的成員函數,需要把該成員函數聲明爲虛函數
//調用虛函數時,到底調用哪個版本,是根據調用該函數的對象本身的類型,而不是指向那個對象的指針或引用的類型
//對象的內存空間是爲誰(子類或父類)開闢的就調用誰的成員方法或成員變量
public:
virtual void func();
};
#endif /* defined(__C__No806Class__Base__) */
Base.cpp
#include "Base.h"
void Base::func()
{
cout << "Base func" << endl;
}
Derived.h
#ifndef __C__No806Class__Derived__
#define __C__No806Class__Derived__
#include <iostream>
#include "Base.h"
class Derived : public Base
{
public:
void func();
};
#endif /* defined(__C__No806Class__Derived__) */
Derived.cpp
#include "Derived.h"
void Derived::func()
{
cout << "Derived func" << endl;
}
main.cpp
#include <iostream>
#include "Derived.h"
void foo(Base &b) //如果不是引用,則調用父類
{
b.func(); //子類??形參b是d的引用
}
int main()
{
Derived d;
Base b;
Base *p = &d; //父類指針或引用指向子類對象
Base &br = d;
b = d; //根據調用該函數的對象本身的類型,而不是指向那個對象的指針或引用的類型
b.func(); //b,父類
d.func(); //d,子類
p -> func(); //d,子類
foo(d); //如果函數的參數是傳值方式,形參是父類對象,實參是子類對象,則在函數內部,用形參調用的成員函數依然是父類版本,因爲傳值只是用子類對象給父類對象賦值,父類對象不是指向子類的引用或指針
br.func(); //d,子類
return 0;
}
B、虛析構
Thing.h
#ifndef __C__No806Class__Thing__
#define __C__No806Class__Thing__
#include <iostream>
using namespace std;
class Thing
{
public:
virtual void what_am_i();
virtual ~Thing();
};
#endif /* defined(__C__No806Class__Thing__) */
Thing.cpp
#include "Thing.h"
void Thing::what_am_i()
{
cout << "I'm Thing" << endl;
}
Thing::~Thing()
{
cout << "析構Thing" << endl;
}
Animal.h
#ifndef __C__No806Class__Animal__
#define __C__No806Class__Animal__
#include <iostream>
#include "Thing.h"
class Animal : public Thing
{
public:
//如果父類的成員函數是虛函數,則子類覆蓋後,不寫virtual,也是虛函數
void what_am_i();
~Animal();
};
#endif /* defined(__C__No806Class__Animal__) */
Animal.cpp
#include "Animal.h"
void Animal::what_am_i()
{
cout << "I'm Animal" << endl;
}
Animal::~Animal()
{
cout << "析構Animal" << endl;
}
mian.cpp
#include "Animal.h"
int main()
{
Thing t;
Animal x;
Thing *array[2];
array[0] = &t;
array[1] = &x;
//虛函數:父類指針或引用,不管指向父類或子類,在調用覆蓋函數時,可以反映真實情況
//有了虛函數,無需向下轉型,就可以正確的用父類的指針或引用,調用到子類的函數
//如果一個虛函數被其他成員函數調用,子類的版本也會被正確調用
//如果一個類有子類,則這個父類的析構函數必須是虛函數,即虛析構
//如果父類的析構不是虛析構,則當刪除一個指向子類對象的父類指針時,將調用父類版本的析構函數,子類只釋放了來自於父類的那部分成員變量,而沒有釋放子類擴展的成員變量,造成內存泄漏
for (int i = 0; i < 2; i++)
array[i] -> what_am_i();
return 0;
}
#include "Animal.h"
int main ()
{
Thing * t = new Thing();
Animal * x = new Animal();
Thing * array[2];
array[0] = t;
array[1] = x;
for (int i = 0; i < 2; i++)
array[i] -> what_am_i();
//delete t;
//delete x;
delete array[0];
delete array[1];
return 0;
}
//I'm Thing I'm Animal 析構Thing 析構Thing (內存泄漏)
//I'm Thing I'm Animal 析構Thing 析構Animal 析構Thing (虛析構)
C、純虛函數Point.h
#include <iostream>
using namespace std;
class Point
{
private:
double x;
double y;
public:
Point (double i, double j);
void print () const;
};
#endif /* defined(__C__No806Class__Point__) */
Point.cpp
#include "Point.h"
Point::Point (double i, double j)
{
x = i;
y = j;
}
void Point::print () const
{
cout << "(" << x << "," << y << ")";
}
Figure.h
#ifndef __C__No806Class__Figure__
#define __C__No806Class__Figure__
#include <iostream>
#include "Point.h"
class Figure
{
private:
Point center; //組合關係
public:
Figure (double i = 0, double j = 0);
Point & location();
void move(Point p);
virtual void draw() = 0; //純虛函數,沒有函數體,不需要實現,在子類中實現純虛函數的具體功能
virtual void rotate(double d) = 0; //對於父類來說,無法確定如何繪製,函數也就無法實現
//抽象基類無法創建對象,因爲抽象類裏的純虛函數沒有實現
//抽象類提供了不同種類對象的一個通用接口
//抽象類不是必須有析構函數,一旦有,必須是虛析構
};
#endif /* defined(__C__No806Class__Figure__) */
Figure.cpp
#include "Figure.h"
Figure::Figure (double i, double j) : center(i, j) {}
Point & Figure::location()
{
return center;
}
void Figure::move(Point p)
{
center = p;
draw(); //移動圖形後重新繪製
}
Circle.h
#ifndef __C__No806Class__Circle__
#define __C__No806Class__Circle__
#include <iostream>
#include "Figure.h"
class Circle : public Figure
{
private:
double radius;
public:
Circle (double i = 0, double j = 0, double r = 0);
void draw();
void rotate(double d);
};
#endif /* defined(__C__No806Class__Circle__) */
Circle.cpp
#include "Circle.h"
Circle::Circle (double i, double j, double r) : Figure(i, j) //不能單獨調用抽象類的構造函數,僅可用於子類構造函數的初始化列表裏,用於初始化子類中繼承自父類的成員變量
{
radius = r;
}
void Circle::draw() //在子類實現繼承自父類的純虛函數,如果不實現,則子類包含純虛函數而依然是抽象類
{
cout << "Center:";
location().print();
cout << " and r = " << radius << endl;
}
void Circle::rotate(double d)
{
cout << "No effect" << endl;
}
Square.h
#ifndef __C__No806Class__Square__
#define __C__No806Class__Square__
#include <iostream>
#include "Figure.h"
class Square : public Figure
{
private:
double side;
double angle;
public:
Square (double i = 0, double j = 0, double d = 0, double a = 0);
void draw();
void rotate(double a);
void vertices();
};
#endif /* defined(__C__No806Class__Square__) */
Square.cpp
#include "Square.h"
Square::Square (double i, double j, double d, double a) : Figure(i, j)
{
side = d;
angle = a;
}
void Square::draw()
{
cout << "Center:";
location().print();
cout << ", side = " << side << ", angle = " << angle << endl;
}
void Square::rotate(double a)
{
angle += a;
cout << "angle = " << angle << endl;
}
void Square::vertices()
{
cout << "VERTICES" << endl;
}
main.cpp
#include "Circle.h"
#include "Square.h"
int main()
{
Circle c(1, 2, 3);
Square s(4, 5, 6);
Figure *f = &c;
Figure &g = s;
f -> draw(); //純虛函數
f -> move(Point (2, 2)); //在普通成員函數內,調用虛函數
g.draw();
g.rotate(1);
g.move(Point(1, 1)); //父類普通成員函數中,調用純虛函數
s.vertices();
//g.vertices(); //父類沒有這個函數,不能通過一個指向子類對象的父類引用,調用子類自己擴展的成員函數
}