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