多態性與虛函數
一、多態性
派生類對象可以替代基類對象為基類的引用初始化或賦值。
函數的多態性其實就是對函數不同形式的聲明的一種靈活應用。比如說,我們同名不同參數的函數就是對函數的一種多態性表現;同名同參就是函數的覆蓋;如果我們用不同類型的參數和個數來聲明不同或相同的函數,那麼程序會根據我們調用實參的個數和類型進行匹配調用之前聲明的函數模型,進行運算求值。
二、虛函數
在類的繼承層次結構中,在不同的層次中可以出現同名同參(類型、個數)都相同的函數。在子類中調用父類的成員方法,可以使用子類對象調用時使用父類的作用域實現。
虛函數的作用是允許在派生類中重新定義與基類同名的函數,並且可以通過基類指針或引用來訪問基類和派生類中的同名函數。
舉一個實例來說明使用虛函數與不使用虛函數的區別,基類和派生類中都有同名函數。
不使用虛函數:
[cpp]
//
// Student.h
// Programs
//
// Created by bo yang on 4/17/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#ifndef Programs_Student_h
#define Programs_Student_h
using namespace std;
class Student
{
public:
Student(int ,string,float);
void display();
protected:
int num;
string name;
float score;
};
#endif
[cpp]
//
// Student.cpp
// Programs
//
// Created by bo yang on 4/17/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#include <iostream>
#include <string>
#include "Student.h"
using namespace std;
//定義構造函數
Student::Student(int n,string nam,float s)
{
num=n;
name=nam;
score=s;
}
void Student::display()
{
cout<<"num:"<<num<<"\n name:"<<name<<"\n score:"<<score<<"\n"<<endl;
}
[cpp]
//
// Graduate.h
// Programs
//
// Created by bo yang on 4/17/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#ifndef Programs_Graduate_h
#define Programs_Graduate_h
#include "Student.h"
#include <string.h>
using namespace std;
class Graduate:public Student
{
public:
Graduate(int ,string ,float,float);
void display();
private:
float pay;
};
#endif
[cpp]
//
// Graduate.cpp
// Programs
//
// Created by bo yang on 4/17/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#include <iostream>
#include "Graduate.h"
#include "Student.h"
using namespace std;
void Graduate::display()
{
cout<<"num:"<<num<<"\nname:"<<name<<"\nscore:"<<score<<"\npay="<<pay<<endl;
}
Graduate::Graduate(int n,string nam,float s,float p):Student(n,nam,s),pay(p)
{
}
[cpp]
//
// main.cpp
// Student&Graduate
//
// Created by bo yang on 4/17/12.
// Copyright (c) 2012 __MyCompanyName__. All rights reserved.
//
#include <iostream>
#include "Student.h"
#include "Graduate.h"
using namespace std;
int main(int argc, const char * argv[])
{
Student s1(1000,"David",100);
Graduate g1(2000,"Jarry",50,20);
Student *p=&s1;
p->display();
p=&g1;
p->display();
return 0;
}
說明:定義一個Student類,讓Graduate類繼承Student類後,兩個類中共同定義了一個display()方法,因為是在基類和派生類中分別定義的同名同參類,所以沒有沖突,但是在調用的時候就出現了問題。我們在主函數中定義一個指針p指向父類對象的首地址,然後用指針p調用display()函數,沒問題,我們可以打印出我們想要的數據。然後用指針p指向派生類Graduate的對象首地址,再次調用display()方法,你會發現Graduate中繼承父類的成員對象沒有被重寫,還是父類中成員值,Graduate自己的成員打印了出來是沒有問題的,在此說明通過一個指針指向不同的類分別調用不同層次中的同名同參類是不行的。這就需要用到虛函數來解決此問題了。
我們在上述程序中作一點修改:在Student類中聲明display()時,加一個關鍵字virtual即
virtual void display();
這樣就把Student類display()聲明成一個虛函數。這樣就可以實現剛才想要的效果了。
說明:基類指針本來是用來指向基類對象的,如果用它指向派生類對象,則進行指針類型轉換,將派生類對象的指針先轉換為基類的指針,所以基類的指針指向的是派生類對象中的基類部分。
程序修改前,是無法通過基類指針去調用派生類對象中的成員函數的。虛函數突破這一限制,在派生類的基類部分中,派生類的虛函數取代了基類原來的虛函數,因此在使基類指針指向派生類對象後,調用虛函數時就調用了派生類的虛函數。只有用virtual聲明了虛函數後才具有以上作用。如果不聲明為虛函數,企圖通過基類指針調用派生類的非虛函數是不行的。
把基類的某個成員函數聲明為虛函數後,允許在其派生類中對該函數重新定義,賦予它新的功能,並且可以通過指向基類的指針指向同一類族中不同類的對象,從而調用其中的同名函數。由虛函數實現的動態多態性就是:同一類族中不同類的對象,對同一函數調用作出不同的響應。
虛函數的使用方法:
1、 在基類用virtual聲明成員函數為虛函數。這樣就可以在派生類中重新定義此函數,為它賦予新的功能,方便使用。
2、 在派生類中重新定義此函數,要求函數名、函數類型、函數參數個數和類型全部與基類的虛函數相同,並根據派生類的需要重新定義函數體。
3、 在主函數中定義一個基類指針指向基類對象,引用基類函數;指向派生類對象,引用派生類函數。
C++規定:一個成員函數被聲明為虛函數時,派生類中的同名函數都自動成為虛函數。所以派生類中同名函數加不加virtual都是可以的,習慣上添加,程序易懂。
注意:如果在派生類中沒有對基類的虛函數重新定義,則派生類簡單地繼承其直接基類的虛函數。
通過虛函數與指向基類對象的指針變量的配合使用,就能方便地調用同一類族中不同類的同名函數,只要先用基類指針指向即可。如果指針不斷地指向同一類族中的不同類的對象,就能不斷調用不同類的對象中的同名函數。
如果在基類中定義的非虛函數在派生類中被重新定義,如果用基類指針調用該成員函數,系統會調用對象中基類部分的成員函數;如果用派生類的指針調用該成員函數,則系統對調用派生類對象中的成員函數,這不是多態,是不同類型的指針,沒有用到虛函數
摘自 安諾的專欄