要聲明變量和常量,我們就不得不說內(nèi)存分配,要說內(nèi)存分配,就不得不說起內(nèi)存中數(shù)據(jù)儲存的方式,所以我們就從數(shù)據(jù)的儲存說起。
聲明:本文所有內(nèi)容都是包含個人理解在里面,可能有說的不全面的地方,或者錯誤的地方,希望看到的朋友們能夠提出來,如果我們出現(xiàn)錯誤,我們就會在后續(xù)內(nèi)容中矯正。
我們都知道,計算機的世界充滿著0和1,但是很多朋友卻不知道這個0和1是怎么判斷的,所以今天先做個簡單的了解。在課本中,我們知道了第一臺計算機的體積是非常龐大的,占地面積170平方,其中作為元器件的電子管卻只有18000只。元器件是什么呢,元器件就是我們用來表示0和1的一個硬件單位,再說具體點,這個元器件就是我們的電路,電路的開與關(guān)就代表著0和1。這也就是計算機為什么使用二進制的原因。
0和1的世界
一個元器件代表著一位,那么8個元器件就可以代表256個組合,再來看我們前面說到的int類型。一個int類型,它占用了4個字節(jié)(Byte),而一個字節(jié)是8個元器件(Bit),所以一共就是32個元器件組成了一個int類型。按照二進制每一位都用0和1來表示,一個int是不是就有4294967296種組合方式,那么相對應(yīng),如果不考慮負(fù)數(shù),是不是int類型的最大值是4294967295。而且隨著我們的技術(shù)越來越高,用來儲存數(shù)據(jù)的內(nèi)存條中元器件做的越來越精密,到現(xiàn)在的一個內(nèi)存條上放置上億個納米級元器件,這樣,電腦儲存的數(shù)據(jù)是不是就非常多了呢,我們常說的硬盤也是這道理,1G 就擁有二的三十次方再乘上八個元器件。(注意,計算機中GB,MB的單位換算是以1024來算的)
根據(jù)內(nèi)存大小不同元器件數(shù)量不同
計算機的運作離不開CPU(就是我們常說處理器),內(nèi)存雖然能儲存數(shù)據(jù),但是并不能自己運行,所以這就需要CPU從中協(xié)調(diào),通過加載器將內(nèi)存中的數(shù)據(jù)復(fù)制出來后再進行計算處理。
了解完數(shù)據(jù)儲存和內(nèi)存的儲存方式后,我們就說說內(nèi)存的分配問題。
內(nèi)存分配是區(qū)分變量和常量最重要的地方,通常情況下,我們把上面所說的字節(jié)作為操作數(shù)據(jù)的最小單位,而我們每次聲明變量或常量時,就是在內(nèi)存中劃出一片區(qū)域的操作。
先從可操作角度來講講變量和常量的區(qū)別,變量,顧名思義,從它的名字就知道它是可以更改的,這就說明,當(dāng)我們聲明數(shù)據(jù)類型后,計算機根據(jù)我們定義數(shù)據(jù)類型劃分出一片內(nèi)存區(qū)域,這片內(nèi)存區(qū)域的內(nèi)容是可以更改的,也就是我們常說的賦值。但是常量不一樣,當(dāng)它在聲明的時候,就告訴計算機說:給我分配房子的時候,把門鎖好,除了我之外,不能讓任何人進去,所以常量就是在這塊代碼運行開始的時候就告訴電腦,這塊內(nèi)存區(qū)域是不可變的。
#include
int main(int argc, char const *argv[])
{
????????//變量
????????int a = 1; //聲明了一個變量,讓a 等于 1。
????????a = 2; //我們將2賦值給a.
????????printf(" result a is %d ", a);//我們打印a
????????//常量
????????const int b = 2; //聲明常量B,并賦值位3.
????????//b = 3; //想要給b賦值為3,但是會報錯,并告訴我們這是一個不可變的量
????????printf(" result b is %d? ", b); //打印b
????????return 0;
}
從圖中我們可以看到a可以被賦值,b賦值時報錯
所以上面就是在賦值變量和常量時,計算機對這些不同操作產(chǎn)生的不同結(jié)果。
實際上,上面我們所說的這些內(nèi)容,都是可以說為計算機內(nèi)存能用來做什么,但是內(nèi)存是如何分配的,我們就要說堆和棧了,堆和棧簡單的說就是計算機的數(shù)據(jù)儲存結(jié)構(gòu),告訴計算機如何將這些數(shù)據(jù)怎樣分配到什么地方,我們就簡單的聊一聊。
在C語言中,內(nèi)存分配方式有三種:
棧:我們可以理解為內(nèi)部人員,在棧上分配的數(shù)據(jù)不需要自己去操心什么,編譯器會自動為它分配工作,分配住房。
堆:我們的外圍成員,雖然也是干活,但是并不給它分配工作和住房,只能自己向上級申請,而且在被開除的時候,還要自己把房子退回去。
從靜態(tài)存儲區(qū)域分配:這個也很好理解,這種分配方式的成員是企業(yè)元老,軟件開始運行時,它就在里面,提早就已經(jīng)占用了內(nèi)存中的這部分,公司解散的時候,它的工作和房子才會被退回去。
上面說的概念,我們經(jīng)常會聽到,但是實際應(yīng)用中,我們好像不知道它用在了哪里,所以我們下面就詳細說下:
首先是棧,經(jīng)常用到它的地方就是我們一些基本數(shù)據(jù)類型的聲明了,比如int,float等,它們都有固定的長度,所以在聲明是,系統(tǒng)就會根據(jù)其數(shù)據(jù)類型長度進行分配。
我們前面說過,數(shù)據(jù)類型并不是只有這幾種,還有一些比較復(fù)雜,或者我們自己定義的一些數(shù)據(jù)類型,計算機并不知道這些類型需要多長的內(nèi)存空間,所以需要我們主動去申請內(nèi)存區(qū)域,而這些數(shù)據(jù)類型,我們就需要儲存在堆中了。
然后就是內(nèi)存的大小了,棧的內(nèi)存就像火車一樣是連續(xù)不斷,而且長度有限沒在聲明的時候就已經(jīng)設(shè)定好了。所以當(dāng)車廂人數(shù)超載的時候,就會發(fā)生內(nèi)存溢出的情況。但是堆不一樣,堆的內(nèi)存空間并不連續(xù),就像貪吃蛇,哪里有空,它就去哪里,然后將這片區(qū)域鏈接在最后面,這也是我們后面要學(xué)到的一鏈表問題。
最后就是棧的先進后出的規(guī)則,我們先來看一段代碼:
#include
int main(int argc, char const *argv[])
{
????????int i = 1;
????????printf("%d %d %d",i,i++,i++);
}
我們預(yù)測的結(jié)果是什么呢,按道理應(yīng)該是1、2、3吧,但實際上是:
但是結(jié)果卻是3、2、1,具體原因就是棧的先進后出,在printf()方法運行到I和i++的時候,并沒有立馬去打印數(shù)據(jù),而是先進行計算,棧在計算結(jié)果出現(xiàn)后就先分配給內(nèi)存空間,所以1就先進入了,然后才是,I++。當(dāng)我們打印時,就需要取出來,所以取的時候自然就會從棧的最后一個開始取了,最后一個是3,所以是3、2、1。這樣看來,棧就是一個口袋,只有一個出口,所以就出現(xiàn)了先進后出的結(jié)果。
堆就方便了很多,因為內(nèi)存空間等都是自己申請的,所以我想怎么來都行。