西西軟件園多重安全檢測(cè)下載網(wǎng)站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁(yè)編程開發(fā)其它知識(shí) → 面向?qū)ο笤O(shè)計(jì)還需要指針么?

面向?qū)ο笤O(shè)計(jì)還需要指針么?

相關(guān)軟件相關(guān)文章發(fā)表評(píng)論 來(lái)源:熱情的沙漠時(shí)間:2013/9/10 8:26:04字體大小:A-A+

作者:熱情的沙漠點(diǎn)擊:4次評(píng)論:0次標(biāo)簽: 面向?qū)ο?/a>

大三那會(huì)還在搞單片機(jī)和MFC,玩的純C系的語(yǔ)言,每天和指針打交道,一切皆指針。有一天,聽說(shuō)JAVA里沒有了指針,我大驚失色,指針都沒了,這語(yǔ)言還能搞啥?

后來(lái),類似C#,JAVA的高級(jí)面向?qū)ο笳Z(yǔ)言用得多了。反過來(lái)思考,高級(jí)面向?qū)ο笳Z(yǔ)言沒有了指針,到底是好事還是壞事?這種區(qū)別體現(xiàn)在哪里?本文以C#和C++為例做個(gè)對(duì)比,JAVA機(jī)制和C#類似。與各位共同探討。

為了簡(jiǎn)單,我們先定義一個(gè)Point類, 只有X,Y 兩個(gè)變量。看看C++和C#之間的使用區(qū)別

1. 指針和引用

C++中,指針和引用的有一定的區(qū)別,指針是一個(gè)地址,而引用只是別名,引用使用起來(lái)要方便得多。因?yàn)橹羔槺旧硎堑刂,地址?dāng)然可以指向任何地方,所以便有了指針的指針,如果再和數(shù)組,函數(shù),結(jié)構(gòu)和類聯(lián)系上,那簡(jiǎn)直就是考驗(yàn)人的大腦。 C++的引用,定義了就必須在聲明時(shí)就初始化,而且不能更改。

C#中,只有引用,一切“引用型變量”都是引用。但這個(gè)引用和C++中的引用不同,它更像一個(gè)“地址”。如果你聲明了 Point p, p就是引用。但是這個(gè)p可能沒有初始化,你也可以在任何時(shí)候改變它。

點(diǎn)評(píng): 指針本來(lái)是好東西,但它太靈活,搞得太復(fù)雜了。反正我現(xiàn)在不大喜歡看*和&這類符號(hào)。

2.類的構(gòu)造

C++中,可以使用兩種方式新建一個(gè)變量:

(1) Point p, 你就構(gòu)造了一個(gè)Point變量。 它處在棧區(qū)。

(2) Point* p=new Point; 它處在堆區(qū):

最牛的在于,Point points[10]; 這樣的聲明,會(huì)直接產(chǎn)生十個(gè)Point對(duì)象,處于棧區(qū)。

也可以這么定義,Point* ps=new Point[10]; 處于堆區(qū)。

C#中,只有一種方式創(chuàng)建:Point p=new Point(); 處于堆區(qū)。

3. 交換兩個(gè)對(duì)象

大家一定都記得,初學(xué)C++時(shí)經(jīng)典的數(shù)字交換問題。

C++中, 兩種做法: void Swap(int* a,int *b) 或者是 void Swap(int& a,int& b) ,這個(gè)沒什么好說(shuō)的

有意思的是,如果交換兩個(gè)對(duì)象呢? 如果是void Swap(Point a, Point b),那你就已經(jīng)錯(cuò)了,在執(zhí)行這個(gè)函數(shù)時(shí),拷貝構(gòu)造函數(shù)會(huì)將你的實(shí)參分別拷貝到a,b兩個(gè)對(duì)象中,處在堆區(qū)。當(dāng)函數(shù)返回時(shí),你除了浪費(fèi)了一堆時(shí)間做無(wú)謂的拷貝工作外,對(duì)象還是原來(lái)的對(duì)象。 因此還得是 void Swap(Point* a, Point *b)或者 void Swap(Point& a, Point& b)。

C#中,交換值類型時(shí),可以使用void Swap(ref int a, ref int b), 如果是引用類型,直接使用void Swap(Point a, Point b)就行了。

點(diǎn)評(píng):你會(huì)發(fā)現(xiàn),C++中根本就沒有值類型和引用類型這回事,加上*,&才是“引用類型”,否則就是值類型,值類型傳遞,就會(huì)調(diào)用拷貝構(gòu)造函數(shù),造成一定的性能損失。而C#不存在這個(gè)問題。

4.函數(shù)返回值與工廠模式

設(shè)計(jì)模式的構(gòu)造模式中,工廠是最常見的,你可以非常方便的寫一個(gè)C#版本的工廠,但在C++中,怎么實(shí)現(xiàn)工廠?

Point GetProduct()
{
Point p; //設(shè)置為默認(rèn)值就可以了。
returnp;
}

調(diào)用時(shí) Point *p= &GetProduct();

你覺得這樣對(duì)么?留給讀者討論。

5.類的組合和對(duì)象拷貝

類可以組合,也可以繼承。但在C++中,類組合會(huì)帶來(lái)額外的問題,考慮如下的結(jié)構(gòu):

classPointEx
{
public:
Point A;
Point B;
//其他成員和函數(shù)
}

那么你聲明一個(gè)PointEx pex, 那么A,B兩個(gè)對(duì)象就會(huì)被自動(dòng)創(chuàng)建。如果希望能在構(gòu)造函數(shù)中傳遞A,B兩個(gè)類的參數(shù),那必須使用“內(nèi)部對(duì)象構(gòu)造函數(shù)”,具體細(xì)節(jié)參考普通C++教材。而對(duì)象拷貝時(shí),也帶來(lái)了額外的隱患,默認(rèn)的拷貝構(gòu)造函數(shù)只能拷貝普通類型,但這些組合的成員,如果直接=的話,那么兩個(gè)PointEx可能就指向同一個(gè)Point A。

在C#中,不存在這樣的問題,首先A,B在PointEx的構(gòu)造中,根本就不會(huì)被構(gòu)造。除非你在PointEx中顯式的new出A和B。

除此之外,C++中,子類和父類的構(gòu)造函數(shù)與析構(gòu)函數(shù)的執(zhí)行順序相當(dāng)重要。C#中,析構(gòu)函數(shù)被取消了(因?yàn)镚C),代之以Dispose模式。

6. 函數(shù)指針和委托

首先,函數(shù)指針是無(wú)敵強(qiáng)大的,因?yàn)榇鎯?chǔ)區(qū)分為堆,棧,代碼區(qū)和全局?jǐn)?shù)據(jù)區(qū)。所以代碼區(qū)也是能被指針訪問的,因此函數(shù)指針指向代碼區(qū),就能把函數(shù)流程指向那個(gè)區(qū)域。這是函數(shù)指針的實(shí)質(zhì)。

C++中:你可以這樣定義函數(shù)指針 int (*sqrt) (int x); (看著真惡心,你還沒見函數(shù)指針數(shù)組,返回函數(shù)指針的函數(shù)等等喪心病狂的類型) 。可以這樣賦值:

先隨便定義一個(gè)函數(shù)sqrt2, 然后func= sqrt2;就可以了,

C#中:可以這樣定義委托 delegate int sqrt(int x); 賦值類似。 當(dāng)然還有更牛的“事件”,實(shí)現(xiàn)了多播委托(我理解就是委托數(shù)組)

點(diǎn)評(píng): 委托的最大好處是強(qiáng)類型的,而且看著比函數(shù)指針優(yōu)雅的多,功能也更強(qiáng)。

最近,我認(rèn)識(shí)到了函數(shù)式編程的強(qiáng)大。如果能認(rèn)識(shí)到“函數(shù)也是變量”,那么就是一個(gè)很大的進(jìn)步。

7. 指針類型轉(zhuǎn)換

先說(shuō)普通值類型的指針類型轉(zhuǎn)換。

C++中:定義兩個(gè)指針, int*a 和float*b . 兩個(gè)是不能直接賦值的。要想賦值,只能采用空指針作為中介:

void*pv= a;
b= static_cast<float>(pv);

如果是對(duì)象,而且是有繼承關(guān)系的,例如Point3D繼承于Point,那么從Point*到Point3D*如何轉(zhuǎn)換呢?

C#中,可以采用強(qiáng)制類型轉(zhuǎn)換或as關(guān)鍵字:

Point3D p3d= (Point3D)p2d; 或者是 Point3D p3d= p2d as Point3D;

點(diǎn)評(píng):類型是任何一門語(yǔ)言中都非常復(fù)雜的一部分。 C#有強(qiáng)大得多的類型系統(tǒng)和元數(shù)據(jù)模型,處理起來(lái)要更高級(jí)一些。 不過,我有個(gè)疑問,C++中,什么區(qū)域存儲(chǔ)一個(gè)變量的類型?機(jī)器怎么知道某一塊區(qū)域內(nèi)的內(nèi)存是int而不是double? 難道存儲(chǔ)在代碼區(qū)?

8. 類和結(jié)構(gòu)體的區(qū)別

C++中,類和結(jié)構(gòu)體本質(zhì)幾乎沒有區(qū)別,結(jié)構(gòu)體也能定義函數(shù),定義不同的變量成員,只是所有成員都是公開的;而類有訪問控制,可以實(shí)現(xiàn)繼承和派生。

C#中:類是引用類型,結(jié)構(gòu)體是值類型。結(jié)構(gòu)體作為參數(shù)或返回值,都需要做拷貝。而且,結(jié)構(gòu)體無(wú)法定義除了構(gòu)造函數(shù)之外的成員函數(shù)。用結(jié)構(gòu)體,可以獲得比類更高的性能。

導(dǎo)致這些區(qū)別的原因:內(nèi)存分配

其實(shí),說(shuō)這么多,導(dǎo)致C#和C++的 這些區(qū)別的本質(zhì)原因,在于它們內(nèi)存分配機(jī)制的不同。

C++中,不論是對(duì)象還是普通的值,如果是通過 Point p這樣的語(yǔ)法生成的話,那么就在棧上。一旦函數(shù)結(jié)束,棧就被回收。只有new關(guān)鍵字生成的對(duì)象,才放在堆上。

放在棧上的數(shù)據(jù)因?yàn)殡S時(shí)可能被回收,才需要這么復(fù)雜的指針機(jī)制。指針的地址問題,這就像裝盒子一樣,一個(gè)盒子不安全,就多套幾層盒子,盒子越套越多,搞得越來(lái)越復(fù)雜。

C#中,有值類型和引用類型的區(qū)別。所有的引用類型,都在堆上,回收靠GC處理。普通函數(shù)中定義的值類型都在棧上。如果是對(duì)象當(dāng)中的“值”,那當(dāng)然也定義在堆上。因?yàn)槎驯葪7奖愣嗔,一個(gè)地址就可以了。所以使用起來(lái)要比在棧上方便的多。

要說(shuō)性能,當(dāng)然是棧比堆快,和內(nèi)存訪問的集中性有關(guān),棧的數(shù)據(jù)基本都在高速緩存當(dāng)中,命中率極高。但堆卻不一定。 正是因?yàn)镚C的作用,允許引用類型就定義在堆上。當(dāng)然,我相信對(duì)于C#這樣的語(yǔ)言,在編譯或運(yùn)行時(shí)應(yīng)該會(huì)計(jì)算哪些是數(shù)據(jù)訪問熱點(diǎn),從而優(yōu)化命中率。 C#這類高級(jí)語(yǔ)言,是靠一個(gè)強(qiáng)大的“運(yùn)行時(shí)”(runtime)和虛擬機(jī)來(lái)幫助它實(shí)現(xiàn)了這類區(qū)別。

我現(xiàn)在還不是很清楚,堆和棧的比例是怎么分配的。以前搞單片機(jī)的時(shí)候,會(huì)有一個(gè)編譯選項(xiàng)選擇它們的比例,我一般會(huì)把棧內(nèi)存(heap)拉到90%,我不愿意用堆,因?yàn)橛X得麻煩。

那么,高級(jí)面向?qū)ο笳Z(yǔ)言需要指針么?

有了上面的那一段,估計(jì)大家都有答案了。因?yàn)橛辛藦?qiáng)大的運(yùn)行時(shí)支持,有了垃圾回收器,使得堆的使用率比棧大的多。雖然性能上會(huì)有那么一點(diǎn)點(diǎn)損失吧,但帶來(lái)的確實(shí)是代碼的簡(jiǎn)潔,高效,程序員再也不需要玩指針的游戲了。它帶來(lái)了以下好處:

1. 省略了拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù)

2. 簡(jiǎn)化了函數(shù)的參數(shù)和返回值,不需要再去調(diào)用拷貝函數(shù)了,性能有所提升。

3. 簡(jiǎn)化了對(duì)象組合的復(fù)雜性,C++內(nèi)存管理本來(lái)就夠復(fù)雜了,面向?qū)ο髸?huì)變得更加復(fù)雜,稍微不注意就會(huì)造成內(nèi)存泄露和指針懸掛等。而在C#中,你可以放心大膽的使用組合和繼承。思路會(huì)清晰的多。

4.C#,JAVA代碼好看得多,起碼沒有那些奇怪的符號(hào)。

5. 其他我還沒有想到的好處。

從這個(gè)角度來(lái)說(shuō),指針的作用已經(jīng)被“引用”類型代替了。

但是,C++不需要運(yùn)行時(shí)和虛擬機(jī),直接編譯為原生代碼,性能肯定更好,但確實(shí)C++難學(xué)。C++大?隙芰谐鲆欢袰++內(nèi)存機(jī)制的好處,這個(gè)見仁見智了。

還有些遺留的問題,當(dāng)開啟C#的unsafe選項(xiàng)后,可以在C#里直接寫C++,那么這種內(nèi)存管理是如何完成的?

    相關(guān)下載

    名稱大小下載