C++第九節:多態、虛函數、抽象類

多態、虛函數、抽象類

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();  //父類沒有這個函數,不能通過一個指向子類對象的父類引用,調用子類自己擴展的成員函數
}

發佈了31 篇原創文章 · 獲贊 3 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章