日韩久久久精品,亚洲精品久久久久久久久久久,亚洲欧美一区二区三区国产精品 ,一区二区福利

C++轉(zhuǎn)向C#的疑惑:難道C#中沒有拷貝構(gòu)造函數(shù) ?

系統(tǒng) 2716 0

不可否認(rèn), C++ 在過去十年乃至現(xiàn)在一直都是 windows 平臺(tái)上的主流開發(fā)語言,而來勢(shì)兇猛的 .NET 勢(shì)必開辟一個(gè)嶄新的局面,從目前的種種跡象來看, .NET 是大勢(shì)所趨,而 C# 作為 .NET 平臺(tái)上的第一開發(fā)語言自然備受關(guān)注,于是有很多程序員紛紛轉(zhuǎn)向 C# ,這其中當(dāng)然不乏 C++ 程序員。情況往往是這樣,從一種語言過渡到另一種語言,哪怕是比較相似的語言,程序員也經(jīng)常無意識(shí)地陷入原開發(fā)語言的思維定勢(shì),這樣的結(jié)果通常只有一個(gè),那就是導(dǎo)致連程序員自己也始終想不通的錯(cuò)誤。本文由某 C++ 程序員提出的“難道 C# 中沒有拷貝構(gòu)造函數(shù)?”這一問題引出 C++ C# 某些語言特性的對(duì)比。

一.發(fā)生了什么?

如果你是正在轉(zhuǎn)向 C# C++ 程序員,你一定對(duì) C# 中的類沒有拷貝構(gòu)造函數(shù)和很少發(fā)生賦值運(yùn)算符的調(diào)用感到不可理解,而且你看到的很多語句并不是像你想象的那樣執(zhí)行,比如

// 假設(shè) Student 是一個(gè)類 C#

Student s1 = new Student() ;

Student s2 ;

s2 = s1 ; // 此語句將發(fā)生什么?

因?yàn)槟闶且粋€(gè)熟練的 C++ 程序員,所以你在潛意識(shí)中就已經(jīng)斷定語句 Student s2 ; 將會(huì)在棧中生成一個(gè) Student 對(duì)象(即實(shí)例),而語句 s2 = s1 ; 將會(huì)調(diào)用賦值運(yùn)算符。即上述語句執(zhí)行完后,會(huì)產(chǎn)生如下內(nèi)存布局

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"></shapetype><stroke joinstyle="miter"></stroke><formulas></formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f><lock aspectratio="t" v:ext="edit"></lock><shape id="_x0000_i1025" style="WIDTH: 248.25pt; HEIGHT: 115.5pt" type="#_x0000_t75"></shape><imagedata o:title="stack&amp;heap03" src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image001.jpg"></imagedata>

錯(cuò)了,全錯(cuò)了!!!

C# 中卻不是這樣。我先解釋上述語句在 C# 中是怎樣執(zhí)行的。

Student s1 = new Student() ;

上面的代碼將會(huì)在堆中生成一個(gè)對(duì)象,并且讓引用 s1 指向這個(gè)對(duì)象,而引用 s1 本身位于棧中,占用四個(gè)字節(jié)(在 32 位處理器上,即一個(gè)指針的長度)。

<shape id="_x0000_i1026" style="WIDTH: 220.5pt; HEIGHT: 102pt" type="#_x0000_t75" o:ole=""></shape><imagedata o:title="" src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image003.wmz"></imagedata>

Student s2 ;

該聲明將會(huì)在棧中生成一個(gè)長度位 4 字節(jié)的引用變量 s2 ,并且缺省為 null ,即該引用不指向任何實(shí)例。

s2 = s1 ;

該賦值語句并沒有調(diào)用賦值運(yùn)算符,而是僅僅使 s2 指向 s1 所指向的對(duì)象。所以上述語句執(zhí)行完后,內(nèi)存布局大致如下圖

<shape id="_x0000_i1027" style="WIDTH: 239.25pt; HEIGHT: 111pt" type="#_x0000_t75"></shape><imagedata o:title="stack &amp; heap 02" src="file:///C:/DOCUME~1/ADMINI~1/LOCALS~1/Temp/msohtml1/01/clip_image005.jpg"></imagedata>

想要明白為什么,先要知道 C# C++ 引用的區(qū)別。

二. C# C++ 的引用區(qū)別

由上述簡單的例子就會(huì)看到“引用”在 C# C++ 中的表現(xiàn)是多么的不一樣,其主要區(qū)別可以表述為一句話:

C++ 中的引用是緊綁定的,而 C# 中的引用是松綁定的。

C++ 中的引用使用“ & ”符號(hào)聲明,而且聲明時(shí)必須被初始化為一個(gè)有效的對(duì)象,而且引用一經(jīng)初始化后,就不能再次賦值(即不能再令其指向其它對(duì)象),因此在 C++ 中編譯器認(rèn)為所有的引用都是有效的,不必進(jìn)行類型檢查等,這是 C++ 中引用沒有指針靈活卻比指針高效的原因。可以這么說,在 C++ 中,因?yàn)橐门c對(duì)象是緊綁定的,我們可以認(rèn)為 引用就是對(duì)象本身。 正如棧對(duì)象的名字就是棧對(duì)象本身一樣。也可以這么想, C++ 中的引用只是某個(gè)對(duì)象的一個(gè)別名,這個(gè)名字僅僅因?yàn)檫@個(gè)對(duì)象的存在而存在。

請(qǐng)看如下 C++ 代碼

// c++

Student s1 ;

Student& s2 = s1; //s2 是棧對(duì)象 s1 的別名

... ...

Student s3 ;

s2 = s3; // 非法!!! s2 不能再成為對(duì)象 s3 的別名

s3 = s2 ; // 將調(diào)用賦值運(yùn)算符

正是由于 C++ 中的緊綁定特性,所以上面最后一條語句會(huì)調(diào)用賦值運(yùn)算符,使對(duì)象 s2 s3 處于一樣的狀態(tài)。

再看看 C#

C# 中只有兩種類型的數(shù)據(jù):值類型和引用類型。值類型通常是基本數(shù)據(jù)類型,如 int double ,還有 struct 等;而所有的自定義的類,還有數(shù)組、代表、接口等都是引用類型。由于這樣的約定,所以你就不必對(duì) C# 中沒有“ & ”引用符而感到奇怪了。

所有的值類型對(duì)象永遠(yuǎn)只在棧中分配,即使你對(duì)值類型使用了 new

//C#

int age = new int(24) ; // 仍然在棧中為 age 分配空間 ,與語句 int age = 24 ; 等價(jià)。

同樣所有引用類型對(duì)象永遠(yuǎn)只在堆中創(chuàng)建。要生成引用類型的實(shí)例一定要用 new ,而 new 返回的引用通常保存在棧中。僅僅聲明引用類型對(duì)象,就相當(dāng)于聲明了一個(gè)空指針(即 C# 中的引用可以為空,這在 C++ 中是不允許的),只是在棧中分配了 4 個(gè)字節(jié)給這個(gè)引用,在該引用沒有賦值之前(即沒有指向有效的堆內(nèi)存),不能使用該引用,因?yàn)樵撘脼榭铡?

//C#

Student s5 ; // 僅僅聲明一個(gè)引用,并沒有創(chuàng)建任何對(duì)象

s5 = new Student() ; // 在堆中創(chuàng)建一個(gè)對(duì)象,并讓 s5 指向該對(duì)象

其實(shí), C# 中的引用更像 C++ 中的指針,也就是說

C# 中的引用是具有指針語義的引用。

所以, C# 中的引用賦值就像 C++ 中的指針賦值一樣,僅僅是讓其指向另外的對(duì)象。也就是說 C# 中使用的是最淺層次的拷貝。引用相互賦值時(shí),僅僅是引用的值(表示邏輯內(nèi)存地址數(shù)據(jù))發(fā)生了改變,而對(duì)引用指向的對(duì)象的狀態(tài)沒有絲毫的影響――如果說有影響,那就是僅僅改變了 GC 對(duì)該對(duì)象的引用計(jì)數(shù)。

正是由于 C# 中的引用具有指針的語義,才方便了 GC 對(duì)對(duì)象的引用計(jì)數(shù)。當(dāng)某個(gè)對(duì)象的引用計(jì)數(shù)變?yōu)? 0 時(shí), GC 就會(huì)釋放這個(gè)對(duì)象,這就是 C# 中自動(dòng)內(nèi)存管理的基本原理。

三.引用傳遞和值傳遞

C++ 中按值傳遞對(duì)象時(shí),會(huì)調(diào)用拷貝構(gòu)造函數(shù)生成對(duì)象的副本,那么對(duì)應(yīng)的 C# 中也是這樣的嗎?

無論是在 C++ 中還是在 C# 中,當(dāng)變量或?qū)ο笞鳛楹瘮?shù)參數(shù)進(jìn)行傳遞時(shí)都有兩種方式:按值傳遞和按引用傳遞。

所謂按值傳遞是指在函數(shù)體內(nèi)部使用的是對(duì)象的副本,在 C++ 中這個(gè)副本是調(diào)用對(duì)象的拷貝構(gòu)造函數(shù)完成的,而函數(shù)對(duì)副本的修改不會(huì)影響原來的對(duì)象。如

//C++

void Fun1(Student ss)

{

... ... // 對(duì) ss 進(jìn)行處理和修改――實(shí)際處理的是傳入對(duì)象的副本

}

... ...

Student s7 ;

Fun1(s7) ;// 此函數(shù)調(diào)用結(jié)束后,對(duì)象 s7 的狀態(tài)并沒有改變

... ...

所謂按引用傳遞是指?jìng)鹘o函數(shù)的實(shí)際上是對(duì)象的地址,這樣函數(shù)對(duì)對(duì)象的修改就會(huì)反應(yīng)在對(duì)象中,使對(duì)象的狀態(tài)發(fā)生變化。如

//C++

void Fun2(Student& ss)

{

... ... // 對(duì) ss 進(jìn)行處理和修改

}

... ...

Student s8 ;

Fun2(s8) ;// 此函數(shù)調(diào)用結(jié)束后,對(duì)象 s8 的狀態(tài)發(fā)生改變

... ...

C++ 中,可以通過指針和“ & ”引用符實(shí)現(xiàn)引用傳遞。上面的例子用到了“ & ”引用符號(hào),其實(shí)換成指針也可以達(dá)到同樣的效果。如果我們?cè)龠M(jìn)一步去想,可以發(fā)現(xiàn),當(dāng)用指針進(jìn)行引用傳遞時(shí),也發(fā)生了復(fù)制,只不過復(fù)制的是指針的值(即對(duì)象的地址),而不是復(fù)制指針指向的對(duì)象。這可以通過如下例子得到證明。

//C++

void Fun3(Student* ss)

{

... ... // 對(duì) ss 指向的對(duì)象進(jìn)行處理和修改

ss = NULL ;

}

... ...

Student* s9 ;

Fun3(s9) ;// 此函數(shù)調(diào)用結(jié)束后, s9 指向的對(duì)象的狀態(tài)發(fā)生了改變

... ...

但是在 Fun3 s9 )調(diào)用完后, s9 并不是 NULL ,這說明 Fun3 中使用的是指針 s9 的副本。如果再進(jìn)一步,我們可以猜測(cè)用“ & ”符實(shí)現(xiàn)引用傳遞時(shí)也發(fā)生了同樣的故事。事實(shí)上也是這樣, C++ 中的引用只是一個(gè)受限卻更加安全的指針而已。

那么按引用傳遞和按值傳遞各有什么好處了?

按引用傳遞不需要發(fā)生拷貝行為,因此速度快,特別是大對(duì)象時(shí),這種優(yōu)勢(shì)很明顯。按值傳遞時(shí)對(duì)傳入對(duì)象的修改實(shí)際是對(duì)對(duì)象副本的修改,不會(huì)影響原對(duì)象的狀態(tài)。

你也許會(huì)想到如果采用 const 引用傳遞那么就可以得到雙倍的好處,可以這么說,但是不要走極端。

一般而言,將不允許改變的大對(duì)象作為 const 引用傳遞給函數(shù)是很合適的,但如果是簡單類型或自定義的小對(duì)象直接用值傳遞就可以了。

如果外界一定要看到函數(shù)對(duì)對(duì)象的修改,那么只有一條路 ―― 按引用傳遞。

C# 中情況卻發(fā)生了變化, C# 中的引用類型的對(duì)象都是按引用傳遞且只能按引用傳遞。而值類型對(duì)象(或者稱為變量),通常情況下是按值傳遞的。如果要按引用傳遞值類型對(duì)象,那么就要使用關(guān)鍵字 ref out ref out 的唯一區(qū)別是 ref 用修飾參數(shù)時(shí)要求傳入的變量被初始化過。

由于類是引用類型,而所有的引用類型的對(duì)象的傳遞都是引用傳遞,所以在此過程中根本不會(huì)發(fā)生拷貝函數(shù)的調(diào)用。照這樣看來,根本就沒有必要有拷貝構(gòu)造函數(shù)了。

我想現(xiàn)在你已經(jīng)知道了 C# 中為什么不需要拷貝構(gòu)造函數(shù)和很少調(diào)用賦值運(yùn)算符了。你也許會(huì)問既然是很少調(diào)用賦值運(yùn)算符,那一定還有調(diào)用賦值運(yùn)算符的情況存在,那么這種情況是怎樣的?那是因?yàn)轭惖南喾麦w――結(jié)構(gòu) struct

四. struct

C++ 中的 struct class 幾乎沒有任何差別,唯一的差別在于 struct 的成員默認(rèn)是公有的,而 class 的成員默認(rèn)是私有的。然而情況在 C# 中發(fā)生了本質(zhì)的變化,因?yàn)? C# 中的 struct 是值類型的,而 class 是引用類型的。從下面的分析可以看出 C# 的創(chuàng)造者在這點(diǎn)設(shè)計(jì)上真是獨(dú)具匠心。那么好處在哪里?

C# 中所有值類型都在棧中創(chuàng)建,在棧中創(chuàng)建對(duì)象較之在堆中創(chuàng)建對(duì)象的優(yōu)勢(shì)在于:效率更高。因?yàn)樵诙阎蟹峙鋵?duì)象之前要采用一定的算法尋找合適的內(nèi)存塊,而這可能是很費(fèi)時(shí)間的,而創(chuàng)建值類型對(duì)象直接壓棧就可以了;還有棧對(duì)象在函數(shù)返回時(shí)會(huì)自動(dòng)釋放,而堆對(duì)象要由 GC 來處理。如果我們?cè)O(shè)計(jì)的是一個(gè)不太大的類,而且其實(shí)例很少在函數(shù)間傳遞(因?yàn)楹瘮?shù)間按非引用傳遞值類型對(duì)象會(huì)發(fā)生復(fù)制),那么我們可以考慮將其實(shí)現(xiàn)為 struct 來代替 class

既然 struct 是值類型,當(dāng)兩個(gè)同類型的 struct 相互賦值時(shí),自然就會(huì)調(diào)用 struct 的賦值運(yùn)算符。

另外,經(jīng)過我的驗(yàn)證,在 C# 中確實(shí)沒有提供拷貝構(gòu)造函數(shù),但是你可以通過重載構(gòu)造函數(shù)來變相地得到拷貝構(gòu)造函數(shù),這個(gè)技術(shù)的實(shí)現(xiàn)是很簡單的,此處就不多說了。

講到這里,已經(jīng)差不多了,所以你不必在為像“為什么 C# 中沒有拷貝構(gòu)造函數(shù)?”、“為什么 C# 中很少看到賦值運(yùn)算符的調(diào)用?”這樣的問題而疑惑了 :)

C++轉(zhuǎn)向C#的疑惑:難道C#中沒有拷貝構(gòu)造函數(shù) ?


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號(hào)聯(lián)系: 360901061

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對(duì)您有幫助就好】

您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會(huì)非常 感謝您的哦!!!

發(fā)表我的評(píng)論
最新評(píng)論 總共0條評(píng)論
主站蜘蛛池模板: 婺源县| 南投县| 油尖旺区| 墨玉县| 古交市| 寻乌县| 金乡县| 阳西县| 洪江市| 满城县| 麻栗坡县| 安丘市| 辉县市| 凤山市| 朝阳市| 广宁县| 呼和浩特市| 花垣县| 阳原县| 桃江县| 岐山县| 晋中市| 绥棱县| 邵阳县| 垫江县| 南充市| 遂宁市| 荣昌县| 松潘县| 鲁山县| 建宁县| 巴彦淖尔市| 娄烦县| 扎鲁特旗| 祥云县| 衡东县| 高碑店市| 当雄县| 林口县| 正蓝旗| 化德县|