一、多態
1、定義:相同的對象收到不同的消息或者不同的對象收到相同的消息時,產生的不同的動作。
2、靜多態(早綁定):在編譯之前就知道要用哪個函數
3、動多態(晚綁定):是利用虛函數實現了運行時的多態,也就是說在系統編譯的時候並不知道程序將要調用哪一個函數,只有在運行到這裏的時候才能確定接下來會跳轉到哪一個函數的棧幀。
動多態的前提:以封裝和繼承爲基礎,至少兩個類(父、子)
面向對象三大特徵:封裝、繼承、多態
二、代碼演示
要求:
1、Shape類,成員函數:calcArea(),構造、析構
2、Rect類,成員函數:calcArea(),構造、析構, 數據成員:m_dWidth,m_dHeight
3、Circle類,成員函數:calcArea(),構造、析構,數據成員:m_dR
Shape.h
#ifndef SHAPE_H
#define SHAPE_H
#include<iostream>
#include<string>
using namespace std;
class Shape
{
public:
Shape();
~Shape();
double calcArea();
};
#endif
Shape.cpp
#include"Shape.h"
Shape::Shape()
{
cout<<"Shape()"<<endl;
}
Shape::~Shape()
{
cout<<"~Shape()"<<endl;
}
double Shape::calcArea()
{
cout<<"Shape::calcArea()"<<endl;
return 0;
}
Rect.h
#include"Shape.h"
class Rect:public Shape
{
public:
Rect(double width,double height);
~Rect();
double calcArea();
protected:
double m_dWidth;
double m_dHeight;
};
Rect.cpp
#include"Rect.h"
Rect::Rect(double width,double height)
{
cout<<"Rect()"<<endl;
m_dHeight = height;
m_dWidth = width;
}
Rect::~Rect()
{
cout<<"~Rect()"<<endl;
}
double Rect::calcArea()
{
cout<<"Rect::calcArea"<<endl;
return m_dHeight*m_dWidth;
}
Circle.h
#include"Shape.h"
#include"Coordinate.h"
class Circle:public Shape
{
public:
Circle(double r);
~Circle();
double calcArea();
protected:
double m_dR;
};
Circle.cpp
#include"Circle.h"
Circle::Circle(double r)
{
cout<<"Circle()"<<endl;
m_dR = r;
}
Circle::~Circle()
{
cout<<"~Circle()"<<endl;
}
double Circle::calcArea()
{
cout<<"Circle::calcArea()"<<endl;
return 3,14*m_dR*m_dR;
}
main.cpp
#include"Circle.h"
#include"Rect.h"
#include<stdlib.h>
int main()
{
Shape *shape1 = new Rect(3,6);
Shape *shape2 = new Circle(5);
shape1->calcArea();//調用的是父類的calcArea()
shape2->calcArea();
delete shape1;
shape1 = NULL;
delete shape2;
shape2 = NULL;//都只執行了父類的析構函數
return 0;
}
運行結果:
用父類的指針指向在堆內申請的子類的對象,Rect類和Circle類以及父類都含有calcArea()函數,用實例化出的子類的對象去調用calcArea(),結果打印出的是父類的calcArea()函數,無法調用子類的calcArea()函數;在銷燬指針時,也只調用了父類的析構函數,而沒有調用子類的析構函數。
如何解決?--->在父類的析構函數和calcArea()函數前分別加上virtual,其他函數不做修改(子類中的析構函數和calcArea()函數前不加virtual也可以,virtual會被繼承下去)
虛函數的特性可以被繼承,當子類中定義的函數與父類中虛函數的聲明相同時,該函數也是虛函數
Shape.h
#ifndef SHAPE_H
#define SHAPE_H
#include<iostream>
#include<string>
using namespace std;
class Shape
{
public:
Shape();
virtual ~Shape();
virtual double calcArea();
};
#endif
運行結果:
加上virtual關鍵字之後,實例化出的子類的對象可以正常調用自己的calcArea()函數。
總結:
1、virtual 可以修飾某一個類的普通的成員函數也可以修飾某一個類的析構函數
2、virtual在函數中的使用限制
普通函數不能是虛函數,會導致編譯錯誤
內聯函數inline不能是虛函數,會正常編譯 但是inline會被忽略掉 。例如inline virtual int eat(),會忽略掉inline關鍵字,使之成爲虛函數 。
靜態成員函數不能是虛函數,會導致編譯錯誤
構造函數不能是虛函數,會導致編譯錯誤
3、虛析構函數是爲了避免使用父類指針釋放子類對象時造成的內存泄漏
4、覆蓋與隱藏區別
覆蓋指的是子類覆蓋父類函數(被覆蓋),特徵是:
(1)分別位於子類和父類中
(2)函數名字與參數都相同
(3)父類的函數是虛函數(virtual)
隱藏指的是子類隱藏了父類的函數(還存在),具有以下特徵:
(1)子類的函數與父類的名稱相同,但是參數不同,父類函數被隱藏
(2)子類函數與父類函數的名稱相同,參數也相同,但是父類函數沒有virtual,父類函數被隱藏
5、基類中定義了虛函數,在派生類中該函數始終保持虛函數的特性。
6、最好將基類的析構函數聲明爲虛函數。(因爲如果派生類不從堆上申請內存,那麼即便是沒有虛析構函數,也是無所謂的 ;但最好還是加上,因爲我們並不知道未來子類的構造函數是否會從堆上申請內存)