Week3 notes
A.面向對象編程,面向對象設計
composition,復合表示has-a
template>
class queue{
…
protected:
Sequencec; //底層容器
Public:
//以下玩完全利用c的操作函數完成
boolempty() const {return c.empty();}
size_typesize() const {return c.size();}
referencefront() {return c.front();}
referenceback() {return c.back();}
//
void push(constvalue_type& x) {c.push_back(x);}
void pop(){c.pop_front();}
};
a擁有b就叫composition,以上是一個特例,a擁有b,a所有的功能都用b來實現,但現實生活中確實有這種東西,deque的意思是兩端都可以進出。所以deque得功能比queue強大。這里做的是改裝一下,比如最后的pop方法,換了一個面貌出現,說不定deque有一百個功能,但現只包含了6個功能,而且名字可能變了,這是一種設計模式,叫做adaptor。變壓器,改造,適配,現在手上有一個東西完全滿足要用的功能,只是可能接口不同,名字不同,所以就改造一下,這里queue就是adaptor.
從內存的角度來理解。
40
Template
Class queue{
Protected:
Dequec;
}
16*2+4+4
template
class deque{
protected:
Itrstart;
Itr finish;
T** map;
Unsigned intmap_size;
}
4*4
template
struct Itr{
T* cur;
T*
T*
T*
}
復合關系下的構造和析構:
構造由內而外,container的構造函數首先調用component的default構造函數,然后才執行自己。
Container::container(…): component(){…};
析構由外而內,container的析構函數首先執行自己,再調用component的析構函數。
Container::~container(…) {…~component()};
這些部分編譯器自己會加上去,形成合理。構造函數是默認的這一個,如果這一個不符合的話,就要我們自己寫,自己調用內部的構造函數,輸入都要自己寫,但是析構函數只有一個所以不需要。
I’?
Delegation委托,composition byreference.ZX
String.hpp
Class StringRep;
Class String{
Public:
String();
String(constchar* s);
String(constString& s);
Stringoperator=(const String& s);
~String();
private:
StringRep*rep; //pimpl
};
#include “String.hpp”
namespace{
class stringRep{
friend class String;
StringRep(constchar* s);
~StringRep();
intcount;
char*rep;
}
};
兩個類之間用指針相連就叫委托,如果有了一個外部的就有一個內部,就叫composition,現在用指針相連,只有在要用到右邊的時候才用到,叫委托,也叫composition by reference,具體的實現都在右邊做,當左邊要用到的時候調用右邊的服務,這種寫法非常有名,pointer to implementation. Handle/body為什么這么有名呢?因為我們如果把類都寫成這樣的話,左手邊都不需要換,字符串如果要怎么變這個指針可以指向不同的實現類,右邊怎么變動都不影響左邊,也就不影響客戶端,這個手法也叫編譯防火墻,左邊都不需要管右邊。
但用指針三個人共享同一個hello要注意abc互相不知道他們引用同一個,當a改變的時候要單獨給你一份改,叫copy on write.
Inheritance繼承,表示is-a
Struct _List_node_base
{
_list_node_base*Mnext;
Listnodebase*_M_prev;
};
template
struct _List_node
:public_List_node_base
{
_Tp_M_data;
}
c++有三種繼承,使用public繼承就是is-a,如果你用public繼承但兩個類的關系不是is-a,將來有可能出錯。
繼承關系下的構造和析構。Derived派生類。從內存的角度來看子類的對象里頭有一個父類的base part,base class的dtor必須是virtual的,否則會出現undefined behavior
構造要由內而外,
derived的構造函數首先調用base的default構造函數,然后才執行自己。
Derivevd::derived(…): base(){…};
析構由外而內
derived的析構函數首先執行自己,然后才調用base的析構函數。
Derived::~derived(…) {…~base()};
繼承要搭配虛函數virtual function
B.虛函數與多態
虛函數與繼承
當我們使用繼承的時候要搭配虛函數,非虛函數non-virtual function是你不希望derived class重新定義(override,復寫)他。
虛函數是你希望derived class重新定義(override)它已有的默認定義。Override一定被用在虛函數被重新定義。
Pure virtual函數是你希望derived class一定要重新定義它,你對他沒有默認定義。和虛函數的區別是你根本沒有定義。
Class Shape{
Public:
Virtualvoid draw() const = 0;//pure virtual
Virtualvoid error(const std::string& msg);//impure virtual
IntobjectID() const;//non-virtual
…
}
class Rectangle: public Shape{…};
class Ellipse: public Shape {…};
CDocument::
OnFileOpen(){
…
Serialize()
…
}
讀的動作是serialize()
我們寫我們自己的子類
class CMyDoc:
publicCDocument{
virtualSeriallize() {…}
}
main(){
CmyDocmyDoc;
myDoc.OnFileOpen();
}
通過子類的對象調用父類的函數。函數的全名是CDocument::OnFileOpen(&myDoc);
23個設計模式之一Template
Method,模板方法,將一些函數延緩寫出來,我先幫你想好了你要寫一個應用程序你要有哪些功能,很多功能都是相同的,我先幫你預設好,具體的部分留到你自己的子類中去重構他。這里的CDocument中的OnFileOpen是應用框架,Application Framework,十多年前MFC Microsoft Foundation classes
繼承和復合關系下的構造和析構
如果子類繼承父類之外又有一個component,那哪個構造函數先被調用呢?
如果父類中有一個component的話又是怎么樣呢?
1父類,component,子類
委托加繼承關系
Office軟件中可以開多個窗口看同一個東西。或者是同一份數據可以用三種不同的形式觀察。要想有這種功能,有兩個CLASS一種是存儲,一種是表現。
Class Subject{
Intm_value;
Vectorm_views;
Public:
Voidattach(Observer* obs){
M_views.push_back(obs);
}
void set_val(intvalue){
m_value = value;
notify();
}
void notify(){
for(int I = 0; i< m_value.size();i++)
m_views[i]->update(this, m_value);
}
};
class Observer{
public:
virtualvoid update(Subject*sub, int value) = 0;
};
左邊是有一個delegation的關系,左邊要有注冊和注銷的功能。Attach實現注冊功能。
C.委托相關設計
委托加繼承
Composite
現在要寫一個file system,有目錄,有文件,目錄里面可以放文件,目錄還可以放到其他目錄里。window system,大窗口里面有小窗口。那應該使用哪些class,應該使用什么將他們聯系起來。
目錄系統。
Primitive,composite可以放左邊的primitive,也可以放自己,如果左邊和右邊寫一個父類,這樣兩邊都是is-a component,所以就可以寫成c:vector,不能放對象,要放指針,右邊這個東西是一個組合物,所以他應該具有一個函數add(e:component*),因為它又可以加左邊的東西,,也可以加右邊的東西,這樣兩個東西都可以接受。發現有很多解法都是要用到兩把武器。這種解法叫做composite是23個設計模式中的一個。
Class primitive: public Component{
Public:
Primitive(intval): Component(val) {}
};
class Component{
intvalue;
public:
Component(intval) {value = val;}
Virtualvoid add(component*) {}
};
class Composite: public component{
vectorc;
public:
composite(intval): component(val) {}
voidadd(component* elem) {
c.puch_back(elem);
}
…
};
prototype
我要創建未來
LandSatImage是一個子類,都有一個靜態的自己,很久以前寫好的框架要能看得到后來寫的東西,-LandSatImage(),負的代表private,#代表Protected,我們故意把構造函數設為私有,那在創建的時候私有的構造函數可以被調用起來嗎?可以,因為是自己人,不是外界在調用,我們就借用這個私有的構造函數,addPrototype(this).而且掛上去在父類中看得到。父類Image中,有addPrototype(p:Image*):void而且所有的子類都應該有一個函數clone():Image*,做的事情是return
new LandSatImage.通過原型這個對象可以調用clone這個函數,如果沒有這個原型的話。
D.復合,繼承關系下的構造和析構
繼承關系下的構造和析構
復合關系下的構造和析構
繼承加上復合關系下的構造和析構
我們要談的是class和class之間的關系,三種關系。當發生繼承時,子類對象里頭有一個父類的成分。豬是一種哺乳動物,豬里頭就應該有哺乳動物的成分,哺乳動物應該有動物的成分。由于有這樣的關系,當我們在探討構造和析構的時候,我們要注意由內而外,析構函數要先執行自己,再調用父類的析構。
繼承和復合關系下的構造和析構,子類的構造函數首先調用父類的default構造函數,然后調用component的default構造函數,最后才執行自己。
析構由外而內,子類的析構函數首先執行自己,然后調用component的析構函數,最后調用base的析構函數。
本次作業要求構造十個隨機矩形和隨機十個圓并根據他們的面積進行刪選,遇到的問題有一開始思考如何將兩個類的對象放在一個數組里,很普通的問題,說明肯定已經有完美的解決方案,多態的使用,即使用基類指針指向子類對象,這時一定要注意要在子類中使用的方法一般都要在基類中使用虛函數或者純虛函數先寫出來,再在下面的子類中進行重載。包含有純虛函數的類叫做虛類,那什么時候我們需要構建一個虛類呢?當類是一個抽象概念的時候,比如這個題目中形狀這個類,我們就要構建它為虛函數,因為沒有一個對象是一個形狀,我們也不會將這個類實例化。具體的做法是shape** a = new shape* [20],這樣就構建了一個指針數組,且數組也使用指針表示,第二個遇到的問題是在刪選出合適的對象后,我一開始想要調用拷貝賦值函數,發現很麻煩,實際上可以直接拷貝指針,省去很多問題。然后不需要建立兩個數組,可以用快慢指針的方法將符合要求的一個個拷貝到當前數組,并將不符合的刪除,注意在刪除的時候要將指針置為空指針,在刪除數組的時候要用delete[]。