Collection & list都是抽象類,分別聲明不同的函數。
此題要求完成對以上類的實現。<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMiBpZD0="知識點補充">知識點補充:
虛繼承 是面向對象編程中的一種技術,是指一個指定的基類,在繼承體系結構中,將其成員數據實例共享給也從這個基類型直接或間接派生的其它類。
舉例來說:假如類A和類B各自從類X派生(非虛繼承且假設類X包含一些數據成員),且類C同時多繼承自類A和B,那麼C的對象就會擁有兩套X的實例數據(可分別獨立訪問,一般要用適當的消歧義限定符)。但是如果類A與B各自虛繼承了類X,那麼C的對象就只包含一套類X的實例數據。對於這一概念典型實現的編程語言是C++。
這一特性在多重繼承應用中非常有用,可以使得虛基類對於由它直接或間接派生的類來說,擁有一個共同的基類對象實例。避免由於帶有歧義的組合而產生的問題(如“菱形繼承問題”)。其原理是,間接派生類(C)穿透了其父類(上面例子中的A與B),實質上直接繼承了虛基類X。
(摘自Wikipedia)
class Animal {
public:
virtual void eat();
};
// Two classes virtually inheriting Animal:
class Mammal : public virtual Animal {
public:
virtual void breathe();
};
class WingedAnimal : public virtual Animal {
public:
virtual void flap();
};
// A bat is still a winged mammal
class Bat : public Mammal, public WingedAnimal {
};
Bat::WingedAnimal中的Animal部分現在和Bat::Mammal中的Animal部分是相同的了,這也就是說Bat現在有且只有一個共享的Animal部分,所以對於Bat::eat()的調用就不再有歧義了。另外,直接將Bat實例分派給Animal實例的過程也不會產生歧義了,因為現在只存在一種可以轉換為Animal的Bat實體了。
因為Mammal實例的起始地址和其Animal部分的內存偏移量直到程序運行分配內存時才會明確,所以虛繼承應用給Mammal和WingedAnimal創建了虛表(vtable)指針(“vpointer”)。因此“Bat”包含vpointer, Mammal, vpointer, WingedAnimal, Bat, Animal。這裡共有兩個虛表指針,其中最派生類的對象地址所指向的虛表指針,指向了最派生類的虛表;另一個虛表指針指向了WingedAnimal的類的虛表。Animal虛繼承而來。在上面的例子裡,一個分配給Mammal,另一個分配給WingedAnimal。因此每個對象占用的內存增加了兩個指針的大小,但卻解決了Animal的歧義問題。所有Bat類的對象都包含這兩個虛指針,但是每一個對象都包含唯一的Animal對象。假設一個類Squirrel聲明繼承了Mammal,那麼Squirrel中的Mammal對象的虛指針和Bat中的Mammal對象的虛指針是不同的,盡管他們占用的內存空間大小是相同的。這是因為在內存中Mammal到Animal的距離是相同的。虛表不同而實際上占用的空間相同。
純虛函數或純虛方法是一個需要被非抽象派生類執行的虛函數. 包含純虛方法的類被稱作抽象類; 抽象類不能被直接調用, 一個抽象基類的一個子類只有在所有的純虛函數在該類(或其父類)內給出實現時, 才能直接調用. 純虛方法通常只有聲明(簽名)而沒有定義(實現).
(From Wikipedia)
在許多情況下,在基類中不能對虛函數給出有意義的實現,而把它聲明為純虛函數,它的實現留給該基類的派生類去做。這就是純虛函數的作用。
純虛函數可以讓類先具有一個操作名稱,而沒有操作內容,讓派生類在繼承時再去具體地給出定義。凡是含有純虛函數的類叫做抽象類。這種類不能聲明對象,只是作為基類為派生類服務。除非在派生類中完全實現基類中所有的的純虛函數,否則,派生類也變成了抽象類,不能實例化對象。
一個類維護一個虛函數相關的表–vtable(__vfptr指向它),函數聲明前面包含關鍵字“virtual”的函數,就會創建一個指向該函數的指針(函數指針)被存入vtable中。虛函數表的作用是用來實現多態,但同時也帶來了執行效率和額外內存空間的增加。
我們先來看看一下代碼:
#include
using namespace std;
class A
{
public:
A()
{
cout <<"A..."<
輸出:
A…
B…
~A…
派生類的析構函數未被調用,為什麼呢?
派生類繼承自基類,那麼基類就只會存在於派生類中,直到派生類調用析構函數後。
假定:基類的析構函數調用比派生類要早,會造成的一種情況就是類成員不存在了,而類本身卻還在,但是類存在的情況下,類成員應該還存在。所以這就矛盾了,所以派生類的析構函數會先被調用,基類的析構函數再被調用。
#include
using namespace std;
class A
{
public:
A()
{
cout <<"A..."<
A…
B…
~B…
~A…
總結:如果某個類不包含虛函數,那一般是表示它將不作為一個基類來使用。當一個類不准備作為基類使用時,就不要定義虛析構函數了,因為它會增加一個虛函數表,使得對象的體積翻倍,還有可能降低其可移值性。
所以基本的一條是:無故的聲明虛析構函數和永遠不去聲明一樣是錯誤的。
當且僅當類裡包含至少一個虛函數的時候,才去聲明虛析構函數。
抽象類是准備被用做基類的,基類必須要有一個虛析構函數,純虛函數會產生抽象類,所以在想要成為抽象類的類裡聲明一個純虛析構函數。
定義一個函數為虛函數,不代表該函數未被實現,只是為了來實現多態。
定義一個函數為純虛函數,才表示函數未被實現 ,定義它是為了實現一個接口,起一個規范作用。繼承抽象類的派生類要實現這個函數…
意外情況
鏈接錯誤
在第一次寫完代碼時,Xcode給出了這麼個錯誤:linker command failed with exit code 1 (use -v to see invocation)
翻閱了很多資料之後發現,原來是有一個函數重名了,而且他們同時都被include到main,如此就會出現鏈接錯誤。
棧溢出:
棧溢出是由於C語言系列沒有內置檢查機制來確保復制到緩沖區的數據不得大於緩沖區的大小,因此當這個數據足夠大的時候,將會溢出緩沖區的范圍。
棧溢出就是緩沖區溢出的一種。 由於緩沖區溢出而使得有用的存儲單元被改寫,往往會引發不可預料的後果。程序在運行過程中,為了臨時存取數據的需要,一般都要分配一些內存空間,通常稱這些空間為緩沖區。如果向緩沖區中寫入超過其本身長度的數據,以致於緩沖區無法容納,就會造成緩沖區以外的存儲單元被改寫,這種現象就稱為緩沖區溢出。緩沖區?長度一般與用戶自己定義的緩沖變量的類型有關。
由於緩沖區溢出而使得有用的存儲單元被改寫,往往會引發不可預料的後果。向這些單元寫入任意的數據,一般只會導致程序崩潰之類的事故,對這種情況我們也至多說這個程序有bug。但如果向這些單元寫入的是精心准備好的數據,就可能使得程序流程被劫持,致使不希望的代碼被執行,落入攻擊者的掌控之中,這就不僅僅是bug,而是漏洞(exploit)了。
當數據太大而代碼用了太多的棧內存時,就會runtime error。雖然在自己的IDE上並不會出錯。
以上就是整道題中出現的各種知識點和問題。總的來說,此題還是比較難的。需要對代碼的效率把握的很好,適時重構代碼。