天天看點

iOS開發系列--C語言之存儲方式和作用域概述變量作用範圍變量存儲方式可通路性

基本上每種語言都要讨論這個話題,c語言也不例外,因為隻有你完全了解每個變量或函數存儲方式、作用範圍和銷毀時間才可能正确的使用這門語言。今天将着重介紹c語言中變量作用範圍、存儲方式、生命周期、作用域和可通路性。

<a href="http://www.cnblogs.com/kenshincui/p/3854243.html#variablerange">變量作用範圍</a>

<a href="http://www.cnblogs.com/kenshincui/p/3854243.html#store">存儲方式</a>

<a href="http://www.cnblogs.com/kenshincui/p/3854243.html#accessible">可通路性</a>

在c語言中變量從作用範圍包括全局變量和局部變量。全局變量在定義之後所有的函數中均可以使用,隻要前面的代碼修改了,那麼後面的代碼中再使用就是修改後的值;局部變量的作用範圍一般在一個函數内部(通常在一對大括号{}内),外面的程式無法通路它,它卻可以通路外面的變量。

<a href="http://11011.net/software/vspaste"></a>

c語言的強大之處在于它能直接操作記憶體(指針),但是要完全熟悉它的操作方式我們必須要弄清它的存儲方式。存儲變量的位置分為:普通記憶體(靜态存儲區)、運作時堆棧(動态存儲區)、硬體寄存器(動态存儲區),當然這幾種存儲的效率是從低到高的。而根據存儲位置的不同在c語言中又可以将變量依次分為:靜态變量、自動變量、寄存器變量。

首先說一下存儲在普通記憶體中的靜态變量,全局變量和使用static聲明的局部變量都是靜态變量,在系統運作過程中隻初始化一次(在下面的例子中雖然變量b是局部變量,在外部無法通路,但是他的生命周期一直延續到程式結束,而變量c則在第一次執行完就釋放,第二次執行時重新建立)。

被關鍵字auto修飾的局部變量是自動變量,但是auto關鍵字可以省略,是以可以得出結論:所有沒有被static修飾的局部變量都是自動變量。

當然存儲自動變量的棧和堆其實是兩個完全不同的空間(雖然都在運作時有效的空間内):棧一般是程式自動配置設定,其存儲結果類似于資料結構中的棧,先進後出,程式結束時由編譯器自動釋放;而堆則是開發人員手動編碼配置設定,如果不進行手動釋放就隻有等到程式運作完作業系統回收,其存儲結構類似于連結清單。在上面的代碼中p變量同樣是一個自動變量,同樣可以使用auto修飾,隻是它所指向的内容放在堆上(p本身存放在棧上)。

這裡說明幾點:malloc配置設定的空間在邏輯上連續,實體上未必連續;p必須手動釋放,否則直到程式運作結束它占用的記憶體将一直被占用;釋放p的過程隻是把p指向的空間釋放掉,p中存放的位址并未釋放,需要手動設定為null,否則這将是一個無用的野指針;

預設情況下無論是自動變量還是靜态變量它們都在記憶體中,不同之處就是自動變量放在一塊運作時配置設定的特殊記憶體中。但是寄存器變量卻是在硬體寄存器中,從實體上來說它和記憶體處在兩個完全不同的硬體中。大家都是知道寄存器存儲空間很小,但是它的效率很高,那麼合理使用寄存器變量就相當重要了。什麼是寄存器變量呢?使用register修飾的int或char類型的非靜态局部變量是寄存器變量。沒錯,需要三個條件支撐:register修飾、必須是int或char類型、必須是非靜态局部變量。

除了存儲位置不同外,寄存器變量完全符合自動變量的條件,是以它的生命周期其實是和自動變量完全一樣的,當函數運作結束後它就會被自動釋放。由于寄存器空間珍貴,是以我們需要合理使用寄存器變量,隻有通路度很高的變量我們才考慮使用寄存器變量,如果過多的定義寄存器變量,當寄存器空間不夠用時會自動轉化為自動變量。

上面我們說到變量的存儲類型,其實在c語言中還有兩種存儲類型:常量存儲區和代碼存儲區,分别用于存儲字元串常量、使用const修飾的全局變量以及二進制函數代碼。

在c語言中沒有其他進階語言public、private等修飾符,來限定變量和函數的有效範圍,但是卻有兩個類似的關鍵字能達到類似的效果:extern和static。

我們知道在c語言中變量的定義順序是有嚴格要求的,要使用變量則必須在使用之前定義,extern用于聲明一個已經存在的變量,這樣一來即使在後面定義一個變量隻要前面聲明了,也同樣可以使用。具體的細節通過下面的代碼相信大家都可以看明白:

iOS開發系列--C語言之存儲方式和作用域概述變量作用範圍變量存儲方式可通路性

需要注意,在上面的代碼中無論在message.h中将a定義前加上extern,還是在main.h中的a定以前加上extern結果都是一樣的,extern同樣适用。和在單檔案中一樣,不能兩個定義都添加extern,否則就沒有定義了。如果把message.c中a的定義(或聲明)去掉呢,那麼它能否通路main.c中的全局變量a呢,答案是否定的(這和在一個檔案中定義了一個函數在另一個檔案不聲明就直接用是類似的)。

extern作用于函數就不再是簡單的聲明函數了,而是将這個函數作為外部函數(當然還有内部函數,下面會說到),在其他檔案中也可以通路。但是大家應該已經注意到,在上面的代碼中message.c中的showmessage前面并沒有添加extern關鍵字,在main.c中不是照樣通路嗎?那是因為這個關鍵字是可以省略的,預設情況下所有的函數都是外部函數。

iOS開發系列--C語言之存儲方式和作用域概述變量作用範圍變量存儲方式可通路性

和作用于變量不同,上面main.c和message.c中的extern都可以省略,在這裡extern的作用就是定義或聲明一個外部函數。從上面可以看到在不同的檔案中可以定義同一個變量,它們被視為同一個變量,但是需要指出的是外部函數在一個程式中是不能重名的,否則會報錯。

其實在前面的例子中我們已經看到static關鍵字在變量中的使用了,在例子中使用static定了一個局部變量,而且我們強調static局部變量在函數中隻被初始化一次。那麼如果static作用于全局變量是什麼效果呢?如果static作用于全局變量它的作用就是定義一個隻能在目前檔案通路的全局變量,相等于私有全局變量。

iOS開發系列--C語言之存儲方式和作用域概述變量作用範圍變量存儲方式可通路性

從上面的輸出結果可以看出message.c中的變量a和main.c中的變量a并不是同一個變量,事實上message.c中的變量a隻能在message.c中使用,雖然main.c中的變量a是全局變量但是就近原則,message.c會使用自己内部的變量a。當然,上面例子中main.c中的變量a定義成靜态全局變量結果也是一樣的,隻是這樣如果還有其他源檔案就不能使用a變量了。但是main.c中的a不能聲明成extern,因為main.c不能通路message.c中的變量a,這樣在main.c中就沒變量a的定義了。

static作用于函數和作用于變量其實是類似的,如果static作用于函數則這個函數就是内部函數,其他檔案中的代碼不可以通路。下面的代碼在運作時會報錯,因為mesage.c中的showmessage()函數是私有的,在main.c中盡管進行了聲明,可以在編譯階段通過,但是在連結階段會報錯。

iOS開發系列--C語言之存儲方式和作用域概述變量作用範圍變量存儲方式可通路性

最後做一下簡單總結一下:

extern作用于變量時用于聲明一個已經定義的變量,但是并不能定義變量;使用extern你可以在其他檔案中使用全局變量(當然此時extern可以省略);

extern作用于函數時與它作用于全局變量有點類似,聲明這個函數是外部函數,其他檔案可以通路,但不同的是當它作用于函數時不僅可以聲明函數還可以定義函數(用在函數定義前面),不管是定義還是聲明都可以省略,c語言預設認為函數定義或聲明都是外部函數;

static作用于變量時,該變量隻會定義一次,以後在使用時不會重新定義,當static作用于全局變量時說明該變量隻能在目前檔案可以通路,其他檔案中不能通路;

static作用于函數時與作用于全局變量類似,表示聲明或定義該函數是内部函數(又叫靜态函數),在該函數所在檔案外的其他檔案中無法通路此函數;

繼續閱讀