Effective c++(筆記)之繼承關係與物件導向設計
1.公有繼承(public inheritance) 意味著"是一種"(isa)的關係
解析:一定要深刻理解這句話的含義,不要認為這大家都知道,本來我也這樣認為,當我看完這章後,就不這樣認為了。
公有繼承可以這樣理解,如果令class D以public 的形式繼承了class B ,那麼可以這樣認為,每一個型別為D的物件同時也可以認為是型別為B的物件,但反過來是不成立的,物件D是更特殊化更具體的的概念,而B是更一般化的概念,每一件事情只要能夠施行於基類物件身上,就一定可以應用於派生類物件上(注意:在此處指的是每件事情,也就是基類的每個成員函式)。感覺這點非常容易混淆,同樣舉例子來說明一下,
企鵝是一種鳥,鳥可以飛,然後我們用c++的繼承關係描述如下:
//鳥類
class Bird{
public:
virtual void fly();
};
//企鵝類,注意是共有繼承
class Penguin : public Bird{
};
按照上面這個繼承來看,企鵝也可以fly(),不管fly()是虛擬函式還是非虛擬函式,公有繼承後企鵝可以飛,但是這不符合常理,但是企鵝是一種鳥,但是這絕對不是public inheritance的關係,因為,它不滿足凡是在基類上可以施行的行為都可以在派生類上施行,所以,可以看出來與我們的直覺不同,當確定public inheritance時一定要看是不是isa的關係,看是否滿足規則。
很明顯,上面我們認為企鵝isa鳥直接用public inheritance是不正確的,可以進行下面的改正
//鳥類
class Bird{
//沒有fly函式
};
//會飛的鳥類
class FlyingBird : public Bird{
public:
virtual void fly();
};
//不會飛的鳥類
class NonFlyingBird : public Bird{
//沒有fly函式
};
//企鵝類,共有繼承NonFlyingBird
class Penguin : public NonFlyingBird{
//沒有fly函式
};
上面的描述可能更能準確描述我們的思想,由上面的例子看出來,不要總是"自己認為"和"自己直覺",有時候是錯誤的。
因為我也覺得public inheritance 等於 isa 的關係這點不是很好理解,所以再舉個例子加深印象
正方形是(isa)矩形,這是我們公認的,但是用c++繼承方式描述我們認為的如下:
class Rectangle{
public:
//設定矩形的高度和寬度
virtual void setHeight(int Height);
virtual void setWidth(int Width);
//傳回當前矩形物件的高度和寬度
virtual int height() const;
virtual int width() const;
//使矩形的面積變大,改變寬度
void makeBigger(Rectangle &r);
};
void Rectangle::makeBigger(Rectangle &r)
{
int oldHeigth = r.height();
//改變矩形物件的寬度
r.setWidth(r.width() + 10);
assert(r.height() == oldHeigth);
}
class Square : public Rectangle{
};
按照上面的描述。makeBigger函式中只改變了矩形的寬度並沒有改變高度,但是這個行為如果施行於正方形時,便不成立,所以將正方形的類public inheritance於矩形類是不合理的,不恰當的。
在c++中應該確定瞭解這些類之間的相互關係,後面會說到has-a和 is- implemented in terms of 將這些關係搞清楚,並應該知道如何在c++中塑造他們。
結論:
public inheritance 公有繼承意味著isa的關係,凡是在基類B上施行的行為都可以在派生類D上施行。
2.怎麼去區分介面繼承和實現繼承
同問:純虛擬函式,(非純)虛擬函式,非虛擬函式他們分別給派生類繼承了什麼?
答:
宣告一個純虛擬函式的目的是為了讓派生類只繼承其介面
純虛擬函式是可以有函式體的,大家都知道宣告為純虛擬函式的類是抽象類,抽象類不能實體化,抽象類最大的作用就是給其他類來繼承,而抽象類中的純虛擬函式則在派生類中必須重新定義實現,當然並不是說抽象類中的純虛擬函式不能有函式體,也可以有,這個函式體常常放置函式的預設行為,也就是當抽象類的大多數派生類在實現這個純虛擬函式時都需要這個行為,可以直接在函式體內顯示呼叫該純虛擬函式,看下面例子就知道了我在說什麼了
class Airplane{
public:
virtual void fly(const Ariport &destination) = 0;
};
void Airplane::fly(const Ariport &destination)
{
//default opreation
}
class ModelA : public Airplane{
public:
virtual void fly(const Ariport &destination)
{
Airplane::fly(destination);
}
};
class ModelB : public Airplane{
public:
virtual void fly(const Ariport &destination)
{
Airplane::fly(destination);
}
};
宣告(非純)虛擬函式的目的就是為了讓派生類繼承介面和預設行為
這是什麼意思類,上述的純虛擬函式可以沒有函式體,如果有函式體的話常常放置預設行為,但在派生類中必須顯式呼叫基類的這個函式,這樣達到程式碼的複用,而虛擬函式常常就會有函式體,當派生類繼承基類後,如果派生類想改寫這個虛擬函式,可以在派生類中自己重寫該函式,因為虛擬函式是動態繫結的(執行的時候指標或者引用指向的物件),根據動態繫結的物件來決定執行哪個物件的虛擬函式,如果動態繫結的是派生類物件,而此派生類物件中沒有改寫此虛擬函式,也就是找到該函式的名字,此時就向基類中查詢,一直向上查詢,直到找到這個虛擬函式然後執行,由上可見虛擬函式不僅為想改寫此函式的派生類提供了幾口而且還為那些不想改寫此虛擬函式的派生類提供了預設行為。
宣告非虛擬函式的目的就是使派生類繼承介面和實現
在類中宣告那些非虛擬函式的成員函式說明這個函式不給派生類改寫,也就是不變性大於變異性,下一個問題會詳細的講述,當派生類繼承基類後,那些非虛擬函式的成員函式不用在派生類中重寫,派生類會繼承了這個函式的介面和實現,同時也說明了派生類和基類在這個行為上是共有的,是不變的,比如說基類和派生類都需要同樣的計算方法來返回一個ID號,而這個返回ID號的成員函式就不用定義虛擬函式。
3.千萬不要重寫從基類繼承下來的非虛擬函式
答:從概念上來講,當基類定義了這個非虛擬函式成員函式時也就表明了,凡是屬於該基類也就是isa基類的派生類在這個行為上是一致的,因為isa的關係上面已經說明了,那麼這個非虛成員函式在派生類中就不需要重寫定義並且改寫,大家的行為都是一樣,並且基類給你提供了,何必再去花費功夫去重寫定義呢?
再來說說如果在派生類中重寫了基類中的非虛成員函式,會造成什麼後果,這就會造成模稜兩可的後果,並且在派生類中如果定義了重名的非虛成員函式將會覆蓋掉基類中的這個函式,非虛成員函式的呼叫是靜態繫結的,根據宣告時的物件來執行,如果此時定義了派生類物件呼叫了這個非虛成員函式,我們本來的目的是呼叫它繼承基類的,結果你重寫了這個函式便會覆蓋掉基類中的,所以會造成奇怪的結果。
其實如果你在派生類中重寫了非虛成員函式後,也會違反我開始所說的public inheritance 意味著isa的這個規則,大家可以想想對麼?
結論-----無論什麼情況下都不要重新定義一個繼承而來的非虛擬函式,非虛擬函式的不變性凌駕於變異性之上
4.在派生類中不要改變從基類繼承的帶有預設引數值的虛擬函式的這些預設引數值
答:感覺我寫的好拗口,這麼來說吧,當基類中有虛擬函式時,我們常常在派生類中來重寫這個虛擬函式,如果這個虛擬函式帶有預設引數值時,我們儘量不要在派生類中改變這些預設引數值。
還是舉個例子來說明,這樣更具體些哈!
enum ShapeColor{RED , GREEN , BLUE};
class Shape{
public:
virtual void draw(ShapeColor color = RED) const = 0;
};
class Rectangle : public Shape{
public:
virtual void draw(ShapeColor color = GREEN);
};
由上面程式碼可見,在派生類中必須重寫純虛擬函式,並且可以看到派生類中重寫這個虛擬函式的預設引數值從RED 改為了GREEN
這有什麼影響呢?
Shape *pr = new Rectangle;
pr->draw(BLUE);
pr->draw(); // 呼叫的是Rectangle(RED)而不是Rectangle(GREEN)
我們知道虛擬函式是動態繫結的,是根據動態的型別決定,而這些預設引數卻是靜態繫結的,根據靜態物件的型別來決定的。
靜態型別:程式在宣告時所採用的型別
動態型別:程式目前所代表的或者所參考的型別
當我們執行pr->draw(),靜態型別是基類,而基類的預設引數是RED,而pr指標指向的型別是派生類,派生類中的預設引數是GREEN,這就會導致這個函式有基類和派生類共同完成的,這樣效果很不好,往往給讀者覺得很怪的現象,所以我們強烈建議不要在派生類中去修改虛擬函式的預設引數值。
5.儘量不要在繼承體系下進行向下轉型動作(即由基類指標轉向派生類指標)
答,有人可能會說,我們什麼時候才能有向下轉型的動作,或者說如果某天我遇到這個動作我該怎麼解決,什麼解決辦法最好?
下面就以例子來開始解決上面的問題
//銀行賬戶類
class BankAccount{
public:
//存款
virtual void makeDeposit(double amount) = 0;
//提款
virtual void makeWithdrawal(double amount) = 0;
//餘額
virtual void balance() const = 0;
};
//儲金賬戶類
class SavingAccount{
public:
void creditInterest();
};
//存放基類BankAccount*指標
list<BankAccount*> allAccounts;
for(list<BankAccount*>::iterator p=allAccounts.begin(); p != allAccounts.end(); ++p)
{
//將BankAccount*轉換成SavingAccount*
static_cast<SavingAccount*>(*p)->creditInterest();
}
首先說明程式碼中,為什麼要存放基類的指標而不存放基類的物件,因為,在容器中存放的都是物件的副本,存放指標可以避免出現很多物件的的副本,所以存放指向物件的指標而不是物件本身,這個也是常用的做法。
當我們呼叫派生類中的方法時,此時就涉及到了向下轉型,將基類指標轉換為派生類的指標,這種轉換就稱為向下轉型。
如果這樣幹是一種解決方法的話,假如此時銀行又增加了支票業務,那麼在for迴圈中就會出現ifelse的分支每個分支都要進行static_cast,這樣做在維護是真的是不恰當。
請看下面的一種方法----抽象出一個抽象類的方法
//銀行賬戶類
class BankAccount{
public:
//存款
virtual void makeDeposit(double amount) = 0;
//提款
virtual void makeWithdrawal(double amount) = 0;
//餘額
virtual void balance() const = 0;
};
//抽象出一個抽象類,用來代表會生利息的賬戶
class InterestBearingAccount : public BankAccount{
public:
virtual void creditInterest() = 0;
};
//儲金賬戶類
class SavingAccount : public InterestBearingAccount{
virtual void creditInterest();
}
class CheckAccount : public InterestBearingAccount{
virtual void creditInterest();
};
//存放基類BankAccount*指標
list<BankAccount*> allAccounts;
for(list<BankAccount*>::iterator p=allAccounts.begin(); p != allAccounts.end(); ++p)
{
//將BankAccount*轉換成SavingAccount*
static_cast<InterestBearingAccount*>(*p)->creditInterest();
}
這是增加支票賬戶後,抽象出了類,但相對於前者只需要一次的向下轉型即可,向下轉型的動作取消的最佳辦法就是以虛擬函式代替,上述還可以將creditInterest()函式加到基類BankAccount中,這樣就完全避免了向下轉型的動作
//銀行賬戶類
class BankAccount{
public:
//存款
virtual void makeDeposit(double amount) = 0;
//提款
virtual void makeWithdrawal(double amount) = 0;
//餘額
virtual void balance() const = 0;
virtual void creditInterest() = 0;
};
//儲金賬戶類
class SavingAccount : public BankAccount{
virtual void creditInterest();
}
class CheckAccount : public BankAccount{
virtual void creditInterest();
};
//存放基類BankAccount*指標
std::list<BankAccount*> allAccounts;
for(std::list<BankAccount*>::iterator p=allAccounts.begin(); p != allAccounts.end(); ++p)
{
//此時完全不用向下轉型
(*p)->creditInterest();
}
另外,除了虛擬函式的方法解決向下轉型的問題,還有種safe downcasting -- 安全向下的轉型動作也不得不掌握。
這種雖然也是需要向下轉型,但是起碼是安全的哈!用 dynamic_cast運算子。
for(std::list<BankAccount*>::iterator p=allAccounts.begin(); p != allAccounts.end(); ++p)
{
if(SavingAccount *psa = dynamic_cast<SavingAccount*>(*p))
{
psa->creditInterest();
}else if(CheckAccount *pca = dynamic_cast<CheckAccount*>(*p))
{
pca->creditInterest();
}else{
error("......");
}
}
結論--可以記住,儘量在自己的程式中不要進行向下轉型,當沒有辦法時,可以首選虛擬函式的方法,看能否避免向下轉型,當不能避免時應該使用dynamic_cast安全的向下轉型的方法。
6.什麼是繼承,什麼是模板,這兩者的區別是什麼?給定一個實際的例子,我該怎麼去選擇呢?
答:開始看到繼承和模板時,感覺就是容易混淆,可能是以前看c++的時候沒有仔細的去思考這個問題,但是看了書之後,才發現,它們之間是多麼容易區分哈!當某個例子列出了很多行為,當你感覺到這些行為與型別沒有關係時此時就可能是模板了,當感覺到不同型別的物件有不同的行為可能要重寫等很明顯就是繼承的關係了哈!
比如:讓你設計一個棧類,實現棧的初始化、入棧、出棧、判斷棧是否為空等行為,並沒有告訴你該棧中要存放什麼型別的元素,其實無論存放什麼型別,都可以輕鬆的寫出這些行為,此時很明顯就應該是模板,寫出一個棧的模板類應該就ok(個人猜測STL原始碼分析中肯定有這個原始碼,可以水貨的作者還有看),下面是棧的簡單實現的程式碼
template<typename T>
class stack{
public:
stack();
~stack();
void push(const T &object);
T pop();
bool empty() const;
private:
struct StackNode{
T data;
StackNode *next;
StackNode(const T &newData , StackNode *nextNode):data(newData) , next(nextNode){}
};
StackNode *top;
//拒絕編譯器產生的成員函式
//拷貝建構函式
stack(const stack &rhs);
//賦值操作符
stack& operator=(const stack &rhs);
};
template<typename T>
stack<T>::stack(): top(0){}
template<typename T>
stack<T>::~stack()
{
while(top)
{
StackNode *toDie = top;
top = top->next;
delete toDie;
}
}
template<typename T>
void stack<T>::push(const T &object)
{
top = new StackNode(object , top);
}
template<typename T>
T stack<T>::pop()
{
StackNode *topofstack = top;
top=top->next;
T data = topofstack->data;
delete topofstack;
return data;
}
template<typename T>
bool stack<T>::empty() const
{
return top == 0;
}
上面的程式碼對於stack而言是不完整的,還有很多沒實現,但以足夠表達出我的用意,我們並不知道stack中放置什麼型別,便能寫出以上的程式碼,說明棧stack這些行為與其中放置什麼型別是沒有關係的,所以,當行為與型別無關時,我們應該選擇模板。
請看下面一個例子:
現在準備設計一個貓類,用來表現貓的一些行為,比如,貓吃,貓睡等行為,但是不同種類的貓吃的行為是不同的,睡的行為也不同,那麼此時我們應該怎麼設計這個貓類呢?
因為,不同種類的貓吃睡的行為不同,但是每個貓都具有吃和睡的這個行為,只不過這個行為不同而已,說到這很顯然,應該使用繼承inheritance,將吃和睡這個貓都具有的行為但不同種類貓這個行為不同的設計為虛擬函式,這樣便可以在子類中進行重寫,應該很明朗了,見下面的程式碼
class Cat{
public:
virtual ~Cat();
virtual void eat() = 0;
virtual void sleep() = 0;
};
class Cat_One : public Cat{
virtual void eat();
virtual void sleep();
};
class Cat_Two : public Cat{
virtual void eat();
virtual void sleep();
};
結論---template 應該用來產生一群classes , 其中物件的型別不會影響到class的函式行為。
---------inheritance 應該用於一群classes身上,其中物件型別會影響到class的函式行為。
7.什麼是(has-a)的關係?什麼是(is-implemented-in-terms-of)的關係?這兩種關係與我們前面說的(isa)的區別又是什麼呢?
答:has-a 和 is-implemented-in-terms-of的關係是通過layering來實現的,也就是說某個class中的資料成員中含有layered class 。
感覺說的還是模模糊糊,直接上一段程式碼就知道什麼意思了
class Address;
class PhoneNumber;
class Person{
public:
private:
string name;
Address address;
PhoneNumber voiceNumber;
PhoneNumber faxNumber;
};
Person這個類中的資料成員有Address類和PhoneNumber類所構成的成員。這就是layering技術,我們前面說過,公有繼承public inheritance 就是意味著是一種isa的關係,對比的,layering技術意味著有一種has-a和根據某物實現is-implemented-in-terms-of。
可能又有人疑問了,那麼通過layering技術的這種關係與前面的isa的關係的區別又在哪裡呢?
這絕對是這個好問題,因為這就涉及到,當我們在起初物件導向的設計時,我們是應該選擇public inheritance 還是選擇layering技術來實在,這對後期的設計起到很大的影響。
比如,我們要設計set集合,當然首先想到的是STL中的模板set,但是模板中的set中的元素必須是有序的,也就是當你放到set集合中的型別可以比較,可以進行排序,這在很多情況下是很難實現的,比如我們想在set集合總放置我們自己定義的顏色物件,而顏色物件沒辦法比較衡量,此時我們就要設計自己的set集合,現在問題就來了,我們是要通過什麼方法來設計自己的set集合呢?
可能又有人說首先選擇STL中的模板,在模板上進行改進,我覺得這是個好方法,因為達到了程式碼的複用,起碼這些程式碼不用我們自己寫了,選擇模板中的list,接下來,可能很多人都會不多想的選擇去繼承這個list(這是大家的通病,一般怎麼去設計,總是想不想就去public inheritance,好像c++類中只有公有繼承似的)我們要考慮是否可以進行公有繼承。
公有繼承滿足對於任何行為凡是在基類B上為真的,在派生類D上也為真
如果我們進行public inheritance的話,如下所示
template<typename T> class set : public list<T>{
};
我們就拿插入行為來看,當我們向基類list中插入兩次相同的元素時,那麼list容器中便會有兩個這樣的元素,但是我們要設計的set集合中是不允許有相同元素產生的,我們只是不想插入set容器中排序而已,所以就拿插入這個行為而言就不成立,所以,公有繼承public inheritance 是不成立的。
所以只能通過layering技術來實習了,如下所示
template<typename T> class set{
public:
bool member(const T &item) const;
void insert(const T &item);
void remove(const T &item);
int cardinality() const;
private:
list<T> rep;
};
template<typename T>
bool set<T>::member(const T &item) const
{
if(find(rep.begin() , rep.end() , item) != rep.end())
return true;
return false;
}
template <typename T>
void set<T>::insert(const T &item)
{
if(!member(item))
rep.push_back(item);
}
template<typename T>
void set<T>::remove(const T &item)
{
list<T>::iterator iter = find(rep.begin() , rep.end() , item);
if(iter != rep.end())
rep.erase(iter);
}
template<typename T>
int set<T>::cardinality() const
{
return rep.size();
}
可見用layering技術來模擬set和list的關係更加合適,所以,在是用public inheritance 還是 layering 技術時,先要搞清楚你要實現的這些類之間到底是什麼關係,然後再去寫程式碼。
8. 什麼是私有繼承private inheritance?什麼是protected inheritance保護繼承? 私有繼承有什麼用?
答:首先說保護繼承protected inheritance , 這個。。。。幾乎不用,我在這裡寫了,只是為了提醒大家,在c++程式碼中這個沒有人知道是幹什麼的,也有可能我見識短。下面我們就私有繼承privateinheritance來展開問題的討論哈!
如果classes之間的繼承關係是private,編譯器通常不會自動將派生類物件轉換為基類物件,而公有繼承可以自動轉化
然後,私有繼承而來的所有資料成員,在派生類中都會變成私有屬性,縱然它們在基類中原本是protected或者public屬性。
除了上述兩點,應該也沒有什麼了, 但是此時可能會有人跳出來說,私有繼承private inheritance有什麼用呢?
Effective c++為我們展示了它的用途,但是個人感覺一般情況下好像也用不到。
還是拿先前棧stack的例子,前面我們寫了個stack template,用來產生一些類,分別放置不同的物件,但是當你經常使用時就會發現缺點,對於放置不同物件的stack它們成員函式的程式碼是完全分開的,不是複用的。這樣的話將會導致問題,當我們大量使用stack template 後,目的碼的量會增加,會導致程式程式碼膨脹的現象。
那麼怎麼去解決這種程式碼膨脹的現象呢?
書上提到了使用泛型指標的方法,使我們壓入棧和彈出棧的不再是物件,而是指向物件的指標,這樣只需要一份拷貝程式碼的成本,即可來處理堆疊 ,如下所示
template<typename T>
class GenericStack{
public:
GenericStack();
~GenericStack();
void push(void *object);
void* pop();
bool empty() const;
private:
struct StackNode{
T data;
StackNode *next;
StackNode(const T &newData , StackNode *nextNode):data(newData) , next(nextNode){}
};
StackNode *top;
//拒絕編譯器產生的成員函式
//拷貝建構函式
stack(const stack &rhs);
//賦值操作符
stack& operator=(const stack &rhs);
};
仔細看上面的程式碼,我們其實只是將T 換成了void*其餘的根本沒變,但是這種情況容易產生型別誤用的操作,比如,我們將double型別的物件用push操作壓入了int物件型別的棧,因為push中存放的是void*,它沒有區分型別的功能,只要是指標都可以,所以我們還要對其進行改進,使其不能訪問到GenericStack中的函式,如下所示
template<typename T>
class GenericStack{
protected:
GenericStack();
~GenericStack();
void push(void *object);
void* pop();
bool empty() const;
private:
struct StackNode{
T data;
StackNode *next;
StackNode(const T &newData , StackNode *nextNode):data(newData) , next(nextNode){}
};
StackNode *top;
//拒絕編譯器產生的成員函式
//拷貝建構函式
stack(const stack &rhs);
//賦值操作符
stack& operator=(const stack &rhs);
};
其實很容易,只是將public改成了protected,這樣的話便安全了,直接訪問不到類中的成員函式,然後我們用stack私有繼承這個類
template<typename T>
class stack : private GenericStack{
public:
void push(T *objectPtr){GenericStack::push(objectPtr);}
T* pop() {return dynamic_cast<T*>(GenericStack::pop());}
bool empty() const{return GenericStack::empty()}
};
這個例子還是要好好體會,話說現在我也不是很理解,可能自己水平還沒達到。
上述的程式碼達到了安全性和效率上的雙贏,在此也表達了根據某物實現的另一種方式---private inheritance 私有繼承的方式。
9. 多繼承 multiple inheritance , MI
答:多繼承是,class,繼承於多個類,但是它的複雜性不言而喻
多繼承的形式如下所示:
class B_1{
};
class B_2{
};
class D : public B_1 , public B_2{
};
複雜性在於,當所繼承的多個基類中都含有同名的成員函式時,那麼此時就會造成模稜兩可的情況,可能當呼叫基類中同名的函式時,必須顯式的指出你要呼叫哪個基類中的函式。
另外,多繼承可用於下面這個例子
有個Person的抽象類,現在要產生一個具體的物件類MyPerson,此時還有一個與資料庫相關的類PersonInfo類,PersonInfo類的設計目地是為了以各種形式進行資料庫欄位的列印,在每個欄位的前後,以特殊字串作為標記。設計的具象類MyPerson類與這兩個類之阿金的關係又是什麼呢?
MyPerson類與Person類之間關係很明確,Person類是個抽象類,MyPerson與Person之間是isa的關係故用public inheritance 公有繼承,重要的是MyPerson與PersonInfo的關係是什麼,它們之間的關係是根據某物實現,且MyPerson類中還要重寫PersonInfo類的虛擬函式,根據某物實現有兩種方式,通過layering技術或者私有繼承,此處因為還要在Person類中重寫虛擬函式,所以應該使用私有繼承private inheritance 的方式實現。
class Person{
public:
virtual ~Person();
virtual string name() const = 0;
virtual string birthDate() const = 0;
virtual string address() const = 0;
virtual string nationality() const = 0;
};
class DataBaseID;
class PersonInfo{
public:
PersonInfo(DataBaseID pid);
virtual ~PersonInfo();
virtual const char* theName() const;
virtual const char* theBirthDate() const;
virtual const char* theAddress() const;
virtual const char* theNationality() const;
virtual const char* valueDelimOpen() const;
virtual const char* valueDelimClose() const;
};
class MyPerson : public Person , private PersonInfo{
public:
MyPerson(DataBaseID pid) : PersonInfo(pid){}
//重新定義私有繼承下來的虛擬函式
virtual const char* valueDelimOpen() const{
return " ";
}
virtual const char* valueDelimClose() const {
return " ";
}
//實現公有繼承Person基類繼承下來的介面
string name() const{
return PersonInfo::theName();
}
string birthDate const{
return PersonInfo::theBirthDate();
}
string address() const{
return PersonInfo::theAddress();
}
string nationality() const{
return PersonInfo::theNationality();
}
};
上面的程式碼便是集合了多繼承multiple inheritance , 公有繼承public inheritance ,私有繼承private inheritance,其實個人覺得還是蠻複雜的,應該把這些知識運用到日常的c++專案開發中,這樣我覺得才能對c++有更深的理解哈!
相關文章
- 物件導向之繼承物件繼承
- 《Effective C++》第三版-6. 繼承與物件導向設計(Inheritance and Object-Oriented Design)C++繼承物件Object
- 物件導向之_繼承概念物件繼承
- Javascript物件導向與繼承JavaScript物件繼承
- JS物件導向程式設計(四):繼承JS物件程式設計繼承
- java-物件導向程式設計--繼承Java物件程式設計繼承
- Python - 物件導向程式設計 - 三大特性之繼承Python物件程式設計繼承
- 21. 物件導向之繼承物件繼承
- 物件導向--繼承物件繼承
- 物件導向:繼承物件繼承
- 物件導向-繼承物件繼承
- #JAVA#物件導向(繼承中成員方法的關係)Java物件繼承
- ~~核心程式設計(五):物件導向——多繼承~~程式設計物件繼承
- 《JavaScript物件導向精要》之五:繼承JavaScript物件繼承
- 物件導向 -- 三大特性之繼承物件繼承
- java學習——物件導向之繼承Java物件繼承
- Golang物件導向_繼承Golang物件繼承
- java物件導向繼承Java物件繼承
- 前端筆記之JavaScript物件導向(二)內建建構函式&相關方法|屬性|運算子&繼承&物件導向前端筆記JavaScript物件函式繼承
- 理解Js中物件導向程式設計的繼承JS物件程式設計繼承
- 【JavaScript筆記 · 基礎篇(十)】物件導向程式設計之三:繼承機制JavaScript筆記物件程式設計繼承
- Kotlin 物件導向程式設計 (OOP) 基礎:類、物件與繼承詳解Kotlin物件程式設計OOP繼承
- Golang物件導向程式設計之繼承&虛基類【組合&介面】Golang物件程式設計繼承
- Java物件導向03——三大特性之繼承Java物件繼承
- Java中物件導向三大特性之繼承Java物件繼承
- Javascript實現物件導向繼承JavaScript物件繼承
- 【物件導向依賴關係概念總結】物件導向程式設計的五種依賴關係物件程式設計
- [筆記]物件導向的程式設計筆記物件程式設計
- c++中的繼承關係C++繼承
- 5. JPA物件繼承關係物件繼承
- Go 筆記之物件導向Go筆記物件
- 說清楚javascript物件導向、原型、繼承JavaScript物件原型繼承
- JavaScript物件導向 ~ 原型和繼承(1)JavaScript物件原型繼承
- JavaScript物件導向那些東西-繼承JavaScript物件繼承
- JAVA物件導向高階一:繼承Java物件繼承
- 5-Java物件導向-繼承(下)Java物件繼承
- JavaScript物件導向—繼承的實現JavaScript物件繼承
- Cris 的 Scala 筆記整理(八):物件導向中級-繼承和多型筆記物件繼承多型
- 物件導向程式設計C++物件程式設計C++