c++內(nèi)存及函數(shù)傳參

本人是一個(gè) java 選手,用 java 的思想來理解 c++ 中的函數(shù),還是有點(diǎn)別扭,所以本文先聊聊 c 和 java 很不一樣的點(diǎn),再來講講 c++ 中的函數(shù)

c 和 java 最不一樣的點(diǎn)有兩個(gè),下面總結(jié)下二者在這方面的區(qū)別:

  • 內(nèi)存回收時(shí)機(jī)
  • 內(nèi)存回收機(jī)制

內(nèi)存回收時(shí)機(jī)及機(jī)制

c 的內(nèi)存一般分成三個(gè)邏輯區(qū)域:

  • 棧內(nèi)存
  • 堆內(nèi)存
  • 靜態(tài)存儲(chǔ)區(qū)

棧內(nèi)存,就是各種臨時(shí)變量,大部分的對(duì)象都屬于棧內(nèi)存對(duì)象,對(duì)于這種對(duì)象,c 比較火急火燎,屬于有仇當(dāng)場(chǎng)就報(bào)的那種,一旦過了此對(duì)象的作用域,其就會(huì)被回收

 {
    int num = 1;
 }

例如,上面的 num 變量,它的作用域就在那個(gè)花括號(hào)中,一旦方法執(zhí)行超過了花括號(hào),那么 num 變量就算走到頭了,它就會(huì)被回收,所以說 c 是有仇當(dāng)場(chǎng)就報(bào)了,不會(huì)像 java 那樣

堆內(nèi)存,簡(jiǎn)單來說就是通過 new 或者 malloc 申請(qǐng)的的內(nèi)存,對(duì)于 c 開發(fā)者來說,主要管理這一塊的內(nèi)存。c 對(duì)這一塊的內(nèi)存,完全不管,完全由開發(fā)者自己管理,如果管理不善,比如忘記釋放內(nèi)存了,那么就會(huì)導(dǎo)致內(nèi)存泄露,有時(shí)釋放了內(nèi)存,但指針沒有置空,還會(huì)造成野指針的問題,嚴(yán)重會(huì)導(dǎo)致程序崩潰。

靜態(tài)存儲(chǔ)區(qū),也分兩種情況,如果是局部靜態(tài)變量,它的生命周期是從程序第一次執(zhí)行到它,到最后整個(gè)程序執(zhí)行完畢,它才會(huì)被釋放。

如果它是一個(gè)類靜態(tài)變量,那么此靜態(tài)變量,會(huì)隨著這個(gè)類的第一個(gè)obj生成時(shí)開始,到整個(gè)程序執(zhí)行完畢時(shí),它才會(huì)被釋放。

{
  int* ptr = new int;
}

說到這里,可能會(huì)有人有疑問了,上述代碼中的 ptr 是一個(gè)局部變量,在棧中,它指向了一個(gè)堆內(nèi)存,當(dāng) ptr 過了它的作用域時(shí),它會(huì)被回收,此時(shí)還需要通過 delete 來釋放 new 申請(qǐng)的內(nèi)存嗎?

答案是:必需要 delete。記住,ptr 自己是一個(gè)棧變量,它會(huì)被 c 回收,但它所指向的堆內(nèi)存是不會(huì)被回收的,堆內(nèi)存只能程序員自己手動(dòng)回收。

這個(gè)邏輯就和 java 的很不一樣了,如果有看過相關(guān)虛擬機(jī)的書,就知道 java 的內(nèi)存回收原理是 引用計(jì)數(shù)法 或者 根結(jié)點(diǎn)引用法。這個(gè)方法的主要用處就是來判斷堆中的變量要不要回收。java 中的內(nèi)存回收,絕不只是回收棧中的變量,它連同棧中所指向的堆變量一并回收。這也是 java 比 c 方便的一點(diǎn),不需要程序員自己處理內(nèi)存,但這也并不是說 java 就沒有內(nèi)存問題了。

方法執(zhí)行邏輯

int fun(int x) {
    ...
}
int a = 10;
int b = fun(a);

首先,c 中的方法執(zhí)行,它是按傳值來進(jìn)行的,它是什么意思呢?比如說,上面的函數(shù)。它有一個(gè)形參 x,和一個(gè) int 型的返回值。當(dāng)調(diào)用 fun 函數(shù)時(shí)

  • 先生成一個(gè)臨時(shí)變量 x,用 a 給 x 賦值,相當(dāng)于 x = a,賦值之后, x 等于10
  • 最后返回時(shí),也會(huì)生成一個(gè)臨時(shí)變量 int temp,最后再用 temp 給 b賦值

所以,在函數(shù)執(zhí)行的時(shí)候,會(huì)有這些問題產(chǎn)生:

  • 多次進(jìn)行無(wú)用復(fù)制,比如 x,比如返回值 temp,例中只是 int 值,如果是一個(gè)大的數(shù)據(jù)或者是其它復(fù)雜數(shù)據(jù),那么復(fù)制的代碼也太大了,臨時(shí)變量一會(huì)就被回收,但它卻要在一段時(shí)間內(nèi)占用大量?jī)?nèi)存,也會(huì)影響效率
  • 由于是按值傳遞,函數(shù)中真正操作的是 x,并不是 a,如果這是一個(gè)交換兩個(gè)數(shù)的函數(shù),執(zhí)行完一看,尷尬了,兩個(gè)數(shù)并沒有交換,因?yàn)楹瘮?shù)操作的不是它們,而是它們的復(fù)制品

理解了這個(gè)邏輯就知道了,一般而言,如果涉及到大的復(fù)雜的變量,一般是通過指針或者引用來作為函數(shù)的參數(shù)或者返回值,這樣即使是復(fù)制,也只是復(fù)制了一個(gè)小小的指針或者引用。指針在 c 中,32位機(jī)器里4個(gè)字節(jié),64位機(jī)器里,8個(gè)字節(jié)而已,簡(jiǎn)單得很。引用則更簡(jiǎn)單了,只是聲明了一個(gè)別名而已。

但即使用了引用或者指針,就萬(wàn)事大吉了嗎?不是的,它們還有別的限制

指針或引用形參或返回值

指針和引用作形參,一般而言,沒啥問題,不過,如果函數(shù)對(duì)傳入?yún)?shù)不作修改的話,為了安全起見,可以使用const 引用或 const指針

指針和引用在這里還有一個(gè)小區(qū)別,那就是,c++ 中,如果是數(shù)組,則必是用指針,因?yàn)閿?shù)組名就是數(shù)組第一個(gè)元素的指針

{
  int* ptr;
  int array[10];
  ptr = array;
}

按上述代碼,則 *(ptr + 1) == array[1] ,這兩個(gè)是一樣的。
但是,數(shù)組只能用指針來表示,不能用引用來表示,大家可以想想,從來沒有用引用表示過數(shù)組

引用是什么呢,引用是一個(gè)變量的別名,它在一申明的時(shí)候就要被初始化。

c++中,到底何時(shí)使用按值傳遞,何時(shí)使用指針和引用呢?

  • 如果數(shù)據(jù)對(duì)象很小,如內(nèi)置類型,則可以按值傳遞
  • 如果數(shù)據(jù)對(duì)象是數(shù)組,則只能使用指針
  • 如果數(shù)據(jù)是較大的結(jié)構(gòu),則使用指針或引用
  • 如果數(shù)據(jù)對(duì)象是類對(duì)象,則使用const引用,傳遞類對(duì)象參數(shù)的標(biāo)準(zhǔn)方式是按引用傳遞,這是 c++ 所推薦的,而不是指針,當(dāng)然,指針也可以。
  • 如果數(shù)據(jù)對(duì)象還未被初始化,方法是用于初始化對(duì)象的,則是傳遞指針的指針

針對(duì)最后一點(diǎn),大家可以自行想想,畫畫圖,想想這是為什么

講了這么多,貌似還是沒有講到指針或引用作為返回值,如果是指針和引用作用返回值,則需要看情況,如果處理不當(dāng),程序崩潰

int* fun() {
  int a;
  int* ptr = &a;
  return ptr;
}

如上例,a 是臨時(shí)變量,返回的 ptr 是指向 a,函數(shù)執(zhí)行完,a被回收,返回值豈不是指向了一個(gè)空的內(nèi)存空間。這么做肯定是不行的。

那么,如果 修改下,int* ptr = new int ,這樣做程序是不會(huì)崩潰了,因?yàn)?ptr指向的是堆內(nèi)存,系統(tǒng)不會(huì)回收,程序員要記得自己回收即可。

同理,如果返回的是引用,則更加不能將引用指向一個(gè)臨時(shí)變量了,因?yàn)橐眉词莿e名,它的正主都已經(jīng)被回收了,這個(gè)別名還能有什么用?

萬(wàn)惡的復(fù)制

其實(shí) c++ 中的函數(shù)之所以這么麻煩,個(gè)人認(rèn)為,和c中的復(fù)制邏輯有很大的關(guān)系。java中怎么不見這么費(fèi)事呢,調(diào)用函數(shù),參數(shù)直接傳對(duì)象名就行了,因?yàn)閖ava中的對(duì)象名,本質(zhì)上也是指針。。。復(fù)制了,也就是復(fù)制個(gè)指針罷了

c++中,如果一個(gè)對(duì)象中既包含基礎(chǔ)類型數(shù)據(jù),也包含指針數(shù)據(jù),那么這個(gè)類一定要重寫復(fù)制函數(shù)。因?yàn)?,如果類不寫?fù)制函數(shù),系統(tǒng)會(huì)默認(rèn)給類添加一個(gè)復(fù)制函數(shù),但添加的默認(rèn)復(fù)制函數(shù),它會(huì)有一個(gè)問題,基礎(chǔ)類型數(shù)據(jù)或者說非指針類型數(shù)據(jù),都會(huì)給copy一份,但唯獨(dú)指針數(shù)據(jù),它只是重新構(gòu)造一個(gè)指針,將指針也指向同一塊內(nèi)存區(qū)域。如果有兩個(gè)對(duì)象,a 和 b,a是通過 b 復(fù)制得來的,那么 a 中的指針和 b 中的指針?biāo)赶虻膮^(qū)域是同一塊區(qū)域,如果 b中回收了指針?biāo)傅膬?nèi)存,則a 中的指針就變成了一個(gè)野指針,一旦使用野指針,程序崩潰沒跑了。。。所以必須要重寫復(fù)制函數(shù)

必須要重寫復(fù)制函數(shù)了,但函數(shù)中參數(shù)還是按值傳遞呀,復(fù)制一次,還可能會(huì)復(fù)制堆內(nèi)存變量,復(fù)制效率太低了,就不得不用指針或者引用了。

使用指針或引用,唯一需要注意一點(diǎn),它們所指向的東西是不是臨時(shí)的,會(huì)不會(huì)被立馬回收

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容