又是一個陽光明媚的下午,九月的太陽照在人身上暖洋洋的。小P整理完《數(shù)字信號處理》的課件、筆記和作業(yè),長長的伸了一個懶腰。“看來我還要復(fù)習(xí)復(fù)習(xí)《信
號與系統(tǒng)》。”他自言自語的說道。扭頭看看老C,好像剛剛和《數(shù)理統(tǒng)計》斗爭完畢。于是他把轉(zhuǎn)椅轉(zhuǎn)向老C,“呵呵,剛才在做作業(yè)嗎?”
“嗯,我剛剛和正態(tài)分布纏斗了一番,最后終于將它打倒在地……”老C開始吹噓。
“嘻嘻,”小P竊笑,心想自己不過20分鐘搞定的事情這個家伙居然用了快一個小時,看來還是有些老啊,“我說,上次我們的代碼已經(jīng)到2.0版本了,你說下來要使用C++了,我們就開始這個活動如何?”
“……”老C看看表,離吃飯還有快2個小時,于是說道,“你今天不去踢球嗎?好,那么我們就來進行C++的部分。”于是他湊到小P電腦旁邊,又把白板從角落中拉了出來。“你這幾天看了我們寫出的代碼了嗎?”
“嗯,花了一些時間看了看,還試著自己重新實現(xiàn)了一下,但是我自己在編寫程序的時候發(fā)現(xiàn)一些問題……”小P應(yīng)道。
“哦?說來聽聽?”老C追問。
“首先是初始化,如果我們采用function(DATA*)的形式來編程,必不可少需要類似于initXXX(DATA*)的函數(shù)來初始化數(shù)據(jù)的狀態(tài),
但是我經(jīng)常忘記在程序的開頭調(diào)用initXXX()函數(shù)來初始化數(shù)據(jù)的狀態(tài),很容易造成數(shù)據(jù)敗壞。”小P眨眨眼,“還有內(nèi)存清理的問題,比如在使用
linked
list的相關(guān)函數(shù)時,如果我忘記在程序最后調(diào)用ListClear()函數(shù),那么就很容易出現(xiàn)內(nèi)存泄漏,就算我調(diào)用了ListClear()函數(shù),如果
在下次使用這個linked list時,忘記調(diào)用了ListInitList()函數(shù),也會造成數(shù)據(jù)敗壞而沒有任何提示……”
“是啊是啊,這個是經(jīng)常遇到的問題,還有嗎?”老C追問。
“嗯,因為經(jīng)常要使用DATA的指針類型,開始時有些不習(xí)慣……”小P想了想。
“呵呵,你說的這些都是事實,而且在很長一段時間都困擾者開發(fā)人員,因此才有了C++的發(fā)明啊。”老C說道,“人們覺得用這種方式開發(fā)程序是很好的事情,
使得代碼模塊更容易構(gòu)造,但是由于C語言沒有在語言層面對此種方法進行直接支持,所以在采用以數(shù)據(jù)為中心的思想進行開發(fā)時,總會有這樣或者那樣的不方便。
于是人們想,可以在C語言中加入一些特性,使得基于對象的開發(fā)更方便一些,這樣才有了C++語言的前身……”
“是嗎?那么是怎么一會事情呢?”小P問。
“哦,我們來舉個例子。”說著老C在白板上勾畫了兩段代碼。
C:
typedef struct tagDATA
{
int a_;
}DATA;
void func(DATA* data)
{
data->a_ = 1;
}
int main()
{
DATA data;
func(&data);
return 0;
}
C++:
class DATA
{
public:
void func(){ a_ = 1; }
private:
int a_;
}
int main()
{
DATA data;
data.func();
}
“看看,兩段代碼雖然語法差別很大,但是實現(xiàn)的功能是一樣的,都是對數(shù)據(jù)DATA中的a_變量賦值1。”老C解釋到,“其實這里的C++代碼可以翻譯成C代碼的。”他又在白板的空白處寫下如下代碼。
C++被翻譯為C后:
class DATA
{
public:
void func(DATA* this) { this->a_ = 1; }
private:
int a_;
}
int main()
{
DATA data;
// data.func();
DATA::func(&data);
}
“不過這些都是編譯器私下里進行的活動,你就認為上面的代碼是編譯過程的中間代碼就行了——不過最初的C++代碼的確被編譯為C代碼后再編譯的。”老C解釋,“同時這也解釋了this關(guān)鍵字在C++中是什么意思。”老C接著說道。
“哦?這樣有什么好處呢?”小P問道。
“最大的好處是在語言層面對基于對象的編程方法給予了更多的支持,這樣在開發(fā)的時候開發(fā)人員的智力負擔(dān)會小很多……”老C停了一下,加重了語氣,“我們做
事情的目的是簡化問題,任何新工具和新方法被發(fā)明的目的都是為了使問題看起來更簡單一些,而不是使問題看起來更復(fù)雜。”老C想了想,“如果你了解了某種語
言特性所針對的需求,你就可以更準(zhǔn)確的使用這種語言特性而不會出現(xiàn)誤解,同時也會說,啊,這樣多好,不得不如此。”
“是嗎?”小P有被噴暈了。
“是啊是啊,比如我們使用struct封裝數(shù)據(jù)的屬性,并將此數(shù)據(jù)與對它的操作放在同一個編譯單元中,將內(nèi)部的操作聲明為static函數(shù)……一切的一切
都是業(yè)內(nèi)流行的做法而已,語言并沒有強制你這樣做,而且也沒有提供給你更好的工具以支持這種編碼風(fēng)格。但在C++語言下,語言可以給我們提供工具使得我們
可以更方便的實現(xiàn)自己的編程思想——雖然在C下面也可以實現(xiàn),但沒有在C++下方便啊。”老C忍不住喝了一大口水,“比如我們的數(shù)據(jù),可以放在class
的private部分;而對外的接口,可以放在public部分供其它模塊調(diào)用;模塊內(nèi)部的操作可以放在private部分加以隱藏;而且還提供構(gòu)造和析
構(gòu)函數(shù)用于我們方便的初始化數(shù)據(jù)和銷毀數(shù)據(jù)。”
“嗯,這樣說來語言的確提供了便利性……”小P回答,“我以前覺得C++難以學(xué)習(xí),可能是沒有了解各種語言特性所應(yīng)對的實際需求吧。”
“是啊是啊,”老C感嘆,“我們就對比對比C語言基于對象風(fēng)格的編程和C++語言的對象模型,一探語言特性的究竟。”說著他又在白板上修改了一下C代碼。
C:
typedef struct tagDATA
{
int a_;
int b_;
}DATA;
void DataFunc(DATA* data)
{
data->a_ = 1;
}
int main()
{
DATA data;
DataFunc(&data);
return 0;
}
“當(dāng)我們寫下這樣的main()代碼的時候,到底會發(fā)生什么事情呢?”老C問,看到小P一副囧囧的樣子,決定自己回答這個問題,“首先我們在使用
typedef的時候,一些類型信息會被記錄到內(nèi)存中;當(dāng)我們寫下DataFunc()函數(shù)時,這個函數(shù)出現(xiàn)在我們代碼的全局中,也就是說它是全局可見
的;當(dāng)我們寫下Data
data語句的時候,一塊新的內(nèi)存在棧上被分配,其內(nèi)部的信息根據(jù)以前的記錄,被分配為兩個int類型,其值是隨機的。”說著他在白板上畫了幾個框框。
“看,為了避免在項目中出現(xiàn)同名函數(shù)的問題,我們不得不在對數(shù)據(jù)DATA的操作Func前加上前綴Data以表征DataFunc()函數(shù)作用于DATA
數(shù)據(jù),這樣可以在很大程度上避免命名沖突。”老C解釋道,“經(jīng)過DataFunc(&data)函數(shù)調(diào)用,內(nèi)存中&data指針?biāo)赶虻?
地址開始,a_的內(nèi)容就變?yōu)?了。”
“那么在C++中的情況呢?”小P問。
“與在C中是一模一樣的。”老C肯定的回答,“不要被語法欺騙了眼睛,在實質(zhì)上它們兩者是完全相同的。”說著他修改了C++部分的代碼。
C++:
class Data
{
public:
void func();
private:
int a_;
int b_;
}
void Data::func()
{
a_ = 1;
}
int main()
{
Data data;
data.func();
return 0;
}
“雖然我們將func()函數(shù)聲明在class
Data內(nèi)部,但是這個與Data所分配的內(nèi)存大小完全無關(guān)。在這只是表現(xiàn)出func()名字空間的關(guān)系,Data類型占內(nèi)存的大小還是根據(jù)其內(nèi)部數(shù)據(jù)所
占的內(nèi)存大小決定的。”老C解釋道,“我們來用C語言翻譯一下C++代碼的意思。”
C++被翻譯為C后:
struct Data
{
private:
int a_;
int b_;
}
void Data::func(Data* this);
void Data::func(Data* this)
{
this->a_ = 1;
}
int main()
{
Data data;
Data::func(&data);
return 0;
}
“看,其實意思是一樣的,不過就是C++的語法更特殊一些而已。”老C道,“由于func()函數(shù)被聲明在Data數(shù)據(jù)內(nèi)部,因此其名字空間就在Data內(nèi)部,這樣函數(shù)就不會和其它模塊的函數(shù)發(fā)生命名沖突。”老C說著又畫了一個框框。
“看吧,其實沒有什么不同,而且我們還省去了使用命名規(guī)范避免函數(shù)命名沖突的麻煩……”老C道,“一般業(yè)內(nèi)的習(xí)俗發(fā)展到一定程度也會變?yōu)檎Z言特性的,這在很多語言發(fā)展的歷史上都可以證明。”
“那么public和private呢?”小P問。
“public與private只能說明某操作或者數(shù)據(jù)屬性在名字空間內(nèi)部的可見性,而不影響其在內(nèi)存和名字空間的位置。”老C解釋,“以后我們解釋可見
性的時候會再次提及的。現(xiàn)在你只要記住凡是public域的函數(shù)或者數(shù)據(jù),均可以通過.運算符和->運算符操作,而在private域內(nèi)的,只能由
此命名空間內(nèi)的函數(shù)操作。ok?”
“好的,我記住了。”小P回答。
“好了,了解了C++中基于對象部分的特性,我們再使用C++改寫我們的apple
game。”老C道。他刪除了項目中除mydebug.h外的所有文件,然后又新建了
main.cpp,applegame.h,applegame.cpp和childlist.h文件,并將這些文件添加到工程中。
“同樣,根據(jù)我們的開發(fā)方法,我們先寫一個大概的框架出來。先在main.cpp中添這樣的代碼。”老C一邊說,一邊敲下如下代碼。
main.cpp:
#include "applegame.h"
int main()
{
AppleGame theGame;
theGame.play();
return 0;
}
------------------------------------------------------(樸實的分割線)
“看來我們需要AppleGame有一個play()的接口。”老C一邊自言自語,一邊寫下applegame.h文件的內(nèi)容。
applegame.h:
#if !defined(APPLE_GAME_H_)
#define APPLE_GAME_H_
#include "childlist.h"
class AppleGame
{
public:
void play();
private:
bool isGameOver() const;
void doPlay();
int lastChildSeatNum() const;
private:
ChildList childList_;
};
#endif // APPLE_GAME_H_
------------------------------------------------------(樸實的分割線)
“嗯,看來我們還需要一個ChildList模塊。”他又在childlist.h中寫下如下內(nèi)容。
childlist.h:
#if !defined(CHILD_LIST_H_)
#define CHILD_LIST_H_
class ChildList
{
public:
};
#endif // CHILD_LIST_H_
------------------------------------------------------(樸實的分割線)
“嗯,下來我們來實現(xiàn)apple game中的具體內(nèi)容。”老C自言自語道。
applegame.cpp:
#include "applegame.h"
#include "mydebug.h"
#include <iostream>
//////////////////////////////////////////////////////////////////////////
// Public
void AppleGame::play()
{
using namespace std;
MY_DEBUG("Start playing game...\n");
while (!isGameOver())
{
doPlay();
}
cout << "The last child's seat number is: " << lastChildSeatNum() << endl;
}
//////////////////////////////////////////////////////////////////////////
// Private
bool AppleGame::isGameOver() const
{
static int i = -1;
return 1 == i++;
}
void AppleGame::doPlay()
{
MY_DEBUG("Playing game.\n");
}
int AppleGame::lastChildSeatNum() const
{
return 10;
}
------------------------------------------------------(樸實的分割線)
“編譯……運行……ok!”老C打了一個響指,“我們的V3.0版本成了!”然后他依法建立了一個AppleGame_V3.0的目錄,將所有文件拷貝到
這個目錄下。“看,由于有了語言的支持,我們很容易的將模塊分開,每個部分都由清晰的接口和歸屬,模塊之間的關(guān)系也更加明顯了。”老C總結(jié)道。
“嗯,好像是的。下來我們是要對childlist模塊進行細化嗎?”小P問。
“是的是的,這些工作都由你來完成吧,我們現(xiàn)在去吃晚飯,等回來后我們再繼續(xù)。”老C回答。
“呵呵,是啊是啊,早去早回,要不一會兒人就多了。”小P回答。
兩個人也沒有關(guān)電腦,飛快的向食堂沖去占領(lǐng)有利地形……
(接著看小P的實現(xiàn)啊)