[題目地址](https://github.com/GeekBand/GeekBand-CPP-1501-Homework/blob/master/%E6%9E%81%E5%AE%A2%E7%8F%ADC%2B%2B%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E9%AB%98%E7%BA%A7%E7%BC%96%E7%A8%8B%E6%B5%8B%E8%AF%95%E9%A2%98(8%E6%9C%888%E6%97%A5).pdf)
首先我們要明確下規(guī)范寥假,寫函數(shù)內容的時候,最好使用this指針
this->width = width;
this->height = height;
而非:
width = width;
height = height;
為什么要這樣寫葬毫?這樣寫的好處是什么呢夭坪?
我們應該知道當局部變量名與全局變量同名時,全局變量會被覆蓋,我們自己寫構造函數(shù)的時候很可能會選擇用不同名的變量名來進行賦值操作藐守,像這樣的:
width_d = width;
height_d = height;
但最好是寫成這樣的:
this->width = width;
this->height = height;
這里的this非常明確地指出了左邊的width是當前類的成員,而不至于令別人看的時候摸不著頭腦蹂风,不清楚這里的width到底哪里的東西卢厂。
##從簡單的構造函數(shù)談起
剛拿到題的時候我是呵呵笑的,腦袋中已經有了初始模型惠啄,直到我下筆寫的時候……
說來慚愧慎恒,我寫第一個構造函數(shù)的就卡住了,磨磨蹭蹭十來分鐘過去了還是想不出怎么調用Point類里的數(shù)據(jù)成員(x, y)比較好撵渡。
原題中Rectangle類定義了一個指向Point的指針leftUp融柬。我們可以利用這個指針來對(x, y)進行構造賦值。
起初我是這樣寫的:
x = leftUp->x;
y = leftUp->y;
……編譯器啪啪啪打臉趋距,簡直不忍直視粒氧,大家?guī)兔Ψ治鱿逻@樣賦值錯在哪里?
左邊的x节腐,y到底是誰外盯?代表的是Point的成員還是另外創(chuàng)建的數(shù)據(jù)?leftUp之前只是定義了下翼雀,這個指針并未在堆中被創(chuàng)建饱苟,leftUp還沒有被實例化哪里來的成員(x,y)?
很多時候就是這樣的狼渊,感覺自己挺懂的箱熬,下筆寫出來的往編譯器來一丟立馬報一堆錯。
你說我沒有實例化囤锉,那么我來搞一個
this->leftUp = Point* ptr;
this->leftUp->x = ptr->x;
this->leftUp->y = ptr->y;
這樣乍看之下似乎無錯坦弟,但大多數(shù)負責任的編譯器仍會報錯,這是為什么呢官地?
這里涉及到設計C++類酿傍、函數(shù)要考慮到的一些東西。當我們編寫一個函數(shù)的時候通常會默認我們調用的外部的數(shù)據(jù)是安全的驱入,是可用的赤炒,同時為了保證自己的魯棒性我們要防止外部改動導致本函數(shù)的失效或者更為惡劣的程序崩潰。所以我們在對指針指向的類成員變量賦值時最好是這樣做:
this->leftUp = new Point(x, y);
直接在堆中new一個亏较,同時調用Point的默認構造函數(shù)傳進(x, y)值莺褒;
##你真的會拷貝構造嗎?
老師講完拷貝構造的時候我問了老刁雪情,你拷貝類Shape里的no了嗎遵岩?我們開始都沒注意到這個值,不過細心的網友應該記著Rectangle是繼承Shape而來的。所以他默認的數(shù)據(jù)里還是有no這個成員的尘执,你怎么能拋棄他呢舍哄?但是我們要怎么給他拷貝復制呢?
我們使用Shape(other)誊锭,直接來調用父類Shape的默認構造函數(shù)表悬。為啥可以這樣寫呢?直接調用父類不會出錯嗎丧靡?
不會的蟆沫,如果你認真看了侯捷老師的視頻,應該已經知道子温治、父類之間會為友元饭庞。
完整的代碼如下
```C++
inline
Rectangle::Rectangle(const Rectangle& other)
: Shape(other), width(other.width), height(other.height)
{
if(other.leftUp != NULL)
{
this->leftUp = new Point(*other.leftUp);
}
else
{
this->leftUp = NULL;
}
}
```
有的人可能會將函數(shù)名后緊跟的初始化操作寫成這樣的順序:
width(other.width), height(other.height), Shape(other)
這時候李老師問了一個問題:你認為拷貝構造時的順序是怎樣的?是按照你寫的初始化的順序嗎罐盔?
幾乎所有人都回答“是”但绕。
然而事實是有點坑爹的,構造函數(shù)開頭的
inline
Rectangle::Rectangle(const Rectangle& other)
: Shape(other), width(other.width), height(other.height)
并不是按照你寫的順序來的惶看,而是按照編譯器定義的優(yōu)先級來的,先是拷貝構造父類的數(shù)據(jù)六孵,然后是原類里對數(shù)據(jù)定義的順序纬黎,所以你在開頭考慮寫成什么順序并沒有什么亂用,無論你寫成什么順序劫窒,他內部都已經有約定好的順序了本今,但是為了代碼閱讀方便,讓人一看就知道拷貝構造的順序主巍,你這里只要按照他內部約定好的順序來寫冠息,先父類,后定義順序孕索,權當做個順序說明便好了逛艰。
最后我們要面對的是leftUp這個指針成員,很多人可能會直接這樣寫:
this->leftUp = new Point(*other.leftUp);
如果你拷貝的other里的leftUp是個空指針呢搞旭?我們還在堆里創(chuàng)建他干嘛散怖?
所以這里要加個if判斷。
if(other.leftUp != NULL)
{
this->leftUp = new Point(*other.leftUp);
}
else
{
this->leftUp = NULL;
}
##賦值操作符-你不造的那些事兒
只有構造函數(shù)可以這樣
Rectangle::Rectangle(const Rectangle& other)
: Shape(other), width(other.width), height(other.height)
{
···
}
在花括號之前這樣直接初始化肄渗,賦值操作符是不可以的镇眷,這是構造函數(shù)才擁有的特例。
所以我們還是老老實實用this指針吧翎嫡。不過我相信很多人會忘了在開頭寫這句判斷:
if(this == &other)
{
return *this;
}
如果他賦值操作的就是他本身欠动,我們不加判斷的直接進行操作,在處理leftUp的時候惑申,
this->leftUp = new Point(*other.leftUp);
已有的other.leftUp被再次指向了一個新的Point對象具伍,但你原來的的other.left并沒有被銷毀翅雏,原來的數(shù)據(jù)不再被記錄在案,換句話說你搞丟他了沿猜,這樣便出現(xiàn)了內存泄露枚荣。
不過不用驚訝,李老師說“大部分程序員的c++程序里必然會出現(xiàn)內存泄露的問題”啼肩,所以要想成為那少數(shù)的“大砰献保”,就從現(xiàn)在開始養(yǎng)成良好的習慣祈坠,學習畫你的數(shù)據(jù)內存模型害碾,搞清楚每一個數(shù)據(jù)的動向、聯(lián)系赦拘。確保你寫的程序萬無一失慌随。
接下來我們要思考父類Shape要怎么寫呢?
說實話躺同,我對處理繼承的父類的東西是一竅不通的阁猜,連上面那個構造函數(shù)Shape(other)都是看的別人的,看到這個直接歇菜了蹋艺。我開始寫了個這樣的
Shape(other.no);
連我自己都不知道這是什么鬼剃袍,一提筆便暴漏出很多問題來。我們幾個C++都不曉得該怎么處理捎谨,然后有人從網上找了個答案民效。寫成了醬紫:
Shape::operator=(other);
李老師后來過來看到我們這樣寫還以為我們是懂的,說這是對父類操作符重載的標準寫法涛救。
這里我們把“operator=”看做一個整體畏邢,即Shape的成員函數(shù),然后我們直接傳入?yún)?shù)other检吆,這樣便調用了shape的默認構造函數(shù)舒萎,對no進行的賦值操作,這樣做的好處是我們完全不必管Shape內部是如何實現(xiàn)的咧栗,以及是否發(fā)生改動逆甜,我們只管做我們的賦值操作就OK了。
今天李老師講的時候致板,提到了過了很多次這個思想交煞,你這個函數(shù)定義的什么功能就只做什么事情,不要直接"left->x = x"斟或,搞得你很懂外面那個類是怎么實現(xiàn)的一樣素征。很多時候我們在進行團隊合作的時候,尤其是大公司,你調用的東西一很可能不是你寫的御毅,你不知道你用的那個數(shù)據(jù)何時回發(fā)生改動根欧,所以你要保證你的通用性,魯棒性端蛆。盡量用這種寫法凤粗,否則后患無窮。
this->leftUp = new Point(x, y);
this->leftUp = new Point(*other.leftUp);
Shape::operator = (other);
賦值操作的最后我們仍要談到喜歡逗你玩的leftUp今豆。
if(other.leftUp != NULL)
{
if(leftUp != NULL ) {
*leftUp = *other.leftUp;
}
else
{
leftUp = new Point(*other.leftUp);
}
}
else
{
delete leftUp;
this->leftUp = NULL;
}
首先我們要判斷other.leftUp是否為空嫌拣,如果不為空我們就準備進行賦值操作,繼續(xù)判斷當前類成員leftUp是否為空呆躲,若為空就new一個直接在堆中初始化异逐,否則直接改變leftUp指向的內容(原來指向的會被析構函數(shù)釋放掉)。最后插掂,若要賦值的other.leftUp為空灰瞻,我們就先delete當前類中的leftUp,然后將他指向NULL辅甥。
```c++
inline
Rectangle& Rectangle::operator=(const Rectangle& other)
{
if(this == &other)
{
return *this;
}
Shape::operator = (other);
this->width = other.width;
this->height = other.height;
if(other.leftUp != NULL)
{
if(leftUp != NULL ) {
*leftUp = *other.leftUp;//直接在堆上創(chuàng)建對象進行賦值操作
}
else
{
leftUp = new Point(*other.leftUp);
}
}
else
{
delete leftUp;
this->leftUp = NULL;
}
return *this;
}
```
##析構函數(shù)
```c++
inline
Rectangle::~Rectangle()
{
delete[] leftUp;
}
```
##總結
- 測試能反映出很多問題酝润,往往你并不能將你心里所想完美無誤的實現(xiàn)出來。
- 畫內存模型圖璃弄,李老師在以前的先下課提到過袍祖,大部分時候你感覺你的程序沒問題,編譯器也沒報錯谢揪,但是仔細一分析,其實錯誤百出捐凭。有些問題只有到達了一定量級才會被你發(fā)現(xiàn)拨扶,但是畫內存模型分析圖可以避免這種尷尬的事情。
- 眼高手低要不得茁肠,看幾十遍視頻也不一定有親自實現(xiàn)一遍程序體會的深刻患民。
- 其實感覺很有問題沒寫出來,C++的東西深究起來不得了垦梆,很多事情要考慮清楚才能寫出完美無誤的代碼匹颤,而這一直是我追求的目標,我先反省下自己托猩。
- 今天聽老師單獨給我們分析印蓖,談到一些問題的時候要自己思考沒空做筆記,今天寫的這些都是記在腦子里的京腥,估計會有些遺漏赦肃,還望跟我一起接受指導的同學們批評指出。