子類繼承了父類的所有成員函式,但是父類的成員函式不一定適合子類,子類可以過載或者重寫(覆蓋)父類的成員函式。
過載我們在之前的章節中已有介紹,即函式名相同,引數不同。
重寫(覆蓋)的意思是函式名和參數列完全一樣,即函式的原型完全一樣。
比如,void human::introduce()函式的功能是輸出人類的各成員變數的值。但是它並不合適student物件,因為該函式沒有輸出student類增加的年級成員變數grade的值。因此student類可以新增乙個更適合自己的版本,即重寫(覆蓋)該函式。
class student : public human
int get_grade()
int grade;
void introduce(); // 新增introduce成員函式
};void student::introduce()
else
cout << age << "歲" << endl;
if(id.length() == 0)else
// 新增grade成員的輸出
cout << "年級:" << grade << endl;
}
注意,與過載不同,重寫(覆蓋)只能發生在繼承關係中。單獨的乙個類中的兩個成員函式,或者類外部的兩個函式是不能有相同的函式原型的。
class foo
; void member(){}; // 錯誤,乙個類中不能出現原型完全一樣的成員函式
};void fun(int x){}
void fun(int x){} // 錯誤,不能出現原型完全一樣的函式
重寫(覆蓋)的意義在於子類可以增加一些成員函式,這些成員函式與父類的成員函式有相同的介面(函式原型),不同的實現。使它們更適合子類。換句話說即成員函式功能的細化。
重寫(覆蓋)的意義在於子類的」新版本「的成員函式優化了/細化了父類的一些」老版本「成員函式。
注意,父類的」老版本「的被重寫的函式也被子類繼承了,在子類中存在」老版本「和」新版本「的兩種版本。在子類中要使用」老版本「的函式需要使用範圍運算子::。否則,預設是」新版本「的。即重寫和過載一樣,在子類中都會自動隱藏父類的版本的函式。
下面是void student::introduce()另一種正確的寫法。
void student::introduce()
而下面的void student::introduce()的實現會造成void student::introduce()遞迴呼叫。
void student::introduce()
前面說過,繼承反映的是子類與父類之間「是乙個」的關係,比如說物件賦值:
human p;
student s;
s.init("周潤發", true, 30, "123456", 1);
p = s; // 把student類物件s中包含的human部分的各成員的值賦給p的各成員
再比如說,指標操作
human *p_human = null;
student s;
s.init("周潤發", true, 30, "123456", 1);
p_human = &s;
p_human->introduce();
圖
因為,定義p_human時指定它的型別是human *,因此它認為自己指向的是human物件。於是,p_human->introduce()呼叫的是void human::introduce()方法。
不過,等等,這裡我們是不是忽略了什麼。
顯然,p_human實際上指向的是student類物件s,而student重寫了introduce方法,為什麼p_human->introduce()呼叫的不能是」新版本「的introduce呢,它更適合student物件啊。
在這種場景下,要想p_human->introduce()呼叫的是」新版本「的void student::introduce(),那麼introduce必須是虛函式。
如果想在父類中定義乙個成員函式留待子類中進行細化,我們必須在它前面加關鍵字virtual ,以便可以使用指針對指向相應的物件進行操作。
class human
virtual void introduce(); // 虛函式
private:
string name;
int age;
bool is_male;
string id;
};void human::introduce()
else
cout << age << "歲" << endl;
if(id.length() == 0)else
}class student : public human
void introduce();
private:
int grade; // 年級
};void student::introduce()
class soldier : public human
void introduce();
private:
string rank; // 軍銜
};void soldier::introduce()
int main()
父類成員函式是虛函式,那麼通過父類指標來呼叫,呼叫的版本由指標指向的物件來決定。這就體現了多型的含義。即一種介面,多種實現。
大家可以去掉human類中的virtual關鍵字,對比一下程式執行結果。
多型有什麼作用呢?它幫助我們達到「軟體復用」。
那在程式裡,可能編寫了很多處理human類的**,比如下面的函式,
... // human、student、soldier類的定義
void introduce_triple(human *p)
}
會讓human的進行3遍自我介紹。
那這個函式能否處理學生物件呢,即當p指向學生物件,p->introduce();是否可以呼叫學生類的introduce。在introduce函式是虛函式的情況下是可以的。
int main()
舉乙個生活中的例子,tcl出了某款型號的彩電比如「栩栩如生」系列第一代產品,還有附帶的遙控器可以來遙控電視。隨後,後續又推出了該系列第二代、第三代產品。這時,大家可能會想,控制第一代的遙控器也可以控制它們就好了。如果可以,那遙控器就可以復用了。
什麼時候需要將父類的成員函式修飾為虛函式,即加virtual關鍵字呢?就看這個函式是否是適應於子類的,
如果是成員函式適合子類的就不需要加virtual關鍵字。
如果子類可能會細化這個函式,那就需要在父類中加virtual關鍵字。
比如void human::set_age(int age)
和int human::get_age()
這兩個函式是用來設定和讀取年齡的。顯然,human類的子類不需要重寫這個函式。那麼這兩個函式就不需要設定為虛函式。
多型必須通過父類指標呼叫才可以起作用,或者引用也可以,如下面的**
... // human、student、soldier類的定義
void introduce_triple(human &p) // 父類的引用
}int main()
如果不是指標和引用,而是父類型別,那麼最終呼叫的是父類的版本,沒有多型的效果。如下面**:
... // human、student、soldier類的定義
void introduce_triple(human p) // 父類型別本身
}int main()
shape類的例子
虛函式特性是被繼承的,子類重寫虛函式,在類的定義裡可加virtual關鍵字也可不加。當然加上更明顯的說明此函式是虛函式。
...
class student : public human
virtual void introduce(); // 正確,也可以不加virtual關鍵字
private:
int grade; // 年級
};
類定義中指出是某個成員函式是虛函式,如果該函式的實現寫在類定義外邊,不能加virtual關鍵字了。
virtual void student::introduce() // 錯誤,不能在這裡加virtual關鍵字
虛函式與純虛函式
1 基本形式 virtual returntype function 1 虛函式宣告 virtual returntype function 2 0 純虛函式宣告 先講示例吧,再總結結論。2 示例 classanimail 這段 的輸出結果是什麼呢?起初我認為是 animail function 1...
虛函式與純虛函式
參考 虛函式 比如 virtual void function1 virtual關鍵字修飾的 成員函式 就是虛函式。把基類的成員函式 設為virtual,其 派生類的相應的函式也會自動變為虛函式。指向 基類的指標在操作它的 多型類物件時,會根據不同的類物件,呼叫其相應的函式,這個函式就是虛函式。純虛...
虛函式與虛函式表
當類中有虛函式時,類的大小會多4個位元組 多出的這4個位元組是乙個位址,指向一張表,裡面儲存了所有虛函式的位址 虛函式表 class base virtual void function 2 virtual void function 3 class sub public base virtual ...