12.1 存儲類數組
C爲變量提供了5種不一樣的存儲模型,或稱爲存儲類。還有基於指針的第6種存儲模型,本章稍後(「分配內存malloc()和free()」小節)將會提到。能夠按照一個變量(更通常地,一個數據對象)的存儲時期來描述它,也能夠按照它的做用域(SCOPE)以及它的連接(linkage)來描述它。存儲時期就是變量在內存中保留的時間,變量的做用域和連接一塊兒代表程序的哪些部分能夠經過變量名來使用該變量。不一樣的存儲類提供了變量的做用域、連接以及存儲時期的不一樣組合。ide
您能夠擁有供多個不一樣的源代碼文件共享的變量、某個特定文件中全部函數均可以使用的變量、只有在某個特定函數中才可使用的變量、甚至只有某個函數的一小部份內可使用的變量。函數
您能夠擁有在整個程序運行期間都存在的變量,或者只有在包含該變量的函數執行時才存在的變量。您也可使用函數調用 爲數據的存儲顯示的分配和釋放內存。oop
12.1.1 做用域ui
做用域描述了程序中能夠訪問一個標識符的一個或多個區域。spa
一個C變量的做用域能夠是代碼塊做用域、函數原型做用域,或者文件做用域。prototype
到目前爲止的程序實例中使用的都是代碼塊做用域變量。在代碼塊中定義的變量具備代碼塊做用域(block scope),從該變量被定義的地方到包含該定義的代碼塊的末尾該變量都可見。另外,函數的形式參量儘管在函數的開始花括號前進行定義,一樣也具備代碼塊做用域,隸屬於包含函數體的代碼塊。設計
所以,下面的代碼中的變量cleo和patrick都有直到結束花括號的代碼塊做用域。指針
double blocky(double cleo) { double patrick = 0.0; ... return patrick; }
在一個內部代碼塊中聲明的變量,其做用域只侷限於該代碼塊:調試
double blocky(double cleo) { double patrick = 0.0; int i; for(i=0;i<10;i++) { double q = cleo*i; //q做用域的開始 ... patrick *= q; //q做用域的結束 } ... return patrick; }
在這個例子中,q的做用域被限制在內部代碼塊內,只有該代碼塊內的代碼能夠訪問q。
傳統上,具備代碼塊做用域的變量都必須在代碼塊開始處進行聲明。C99放寬了這一規則,容許在一個代碼塊中任何位置聲明變量。一個新的多是變量聲明能夠出如今for循環的控制部分,也就是說,如今能夠這樣作:
for (int i=0;i<10;i++) printf("A C99 feature: i = %d",i);
做爲這一新功能的一部分,C99把代碼塊的概念擴大到包括由for循環、while循環、do while循環或者if語句所控制的代碼---即便這些代碼並無用花括號括起來。所以在前述for循環中,變量i被認爲是for循環代碼塊的一部分。這樣 它的做用域就限於這個for循環,程序的執行離開該for循環後就再也不能看到變量i了。
函數原型做用域(function prototype scope)適用於函數原型中使用的變量名,以下所示:
int mighty(int mouse,double large);
函數原型做用域從變量定義處一直到原型聲明的末尾。這意味着,編譯器在處理一個函數原型的參數時,它所關心的只是該參數的類型;您使用什麼名字,一般是可有可無的,不須要使它們和函數定義中使用的變量名保持一致。名字起做用的一種情形是變長數組參量:
void use_a_VLA(int n, int m, ar[n][m]);
若是在方括號中使用了變量名,則該變量名必須是在原型中已經聲明瞭的。
一個在全部函數以外定義的變量具備文件做用域(file scope)。具備文件做用域的變量從它定義處到包含該定義的文件結尾處都是可見的。看看下面的例子:
#include <stdio.h> int units = 0; /*具備文件做用域的變量*/ void critic(void); int main(void) { ... } void critic(void) { ... }
這裏,變量units具備文件做用域,在main()和critic()中均可以使用它。由於它們能夠在不止一個函數中使用,文件做用域變量一般也被稱爲全局變量(global variable)。
另外,還有一種被稱爲函數做用域(function scope)的做用域,但它只適用於goto語句使用的標籤。函數做用域意味着一個特定函數中的goto標籤對該函數中任何地方的代碼都是可見的,不管該標籤出如今哪個代碼塊中。
12.1.2 連接
一個C變量具備下列連接之一:外部連接(external linkage),內部連接(internal linkage),或者空連接(no linkage)。
具備代碼塊做用域或者函數原型做用域的變量具備空連接,意味着它們是由其定義所在的代碼塊或函數原型所私有的。具備文件做用域的變量可能有內部或外部連接。一個具備外部連接的變量能夠在一個多文件程序 的任何地方使用。一個具備內部連接的變量能夠在一個文件的任何地方使用。
那麼怎麼樣知道一個文件做用域變量具備內部連接仍是外部連接?您能夠看看在外部定義中是否使用了存儲類說明符static:
int giants=5; //文件做用域,外部連接 static int dodgers=3; //文件做用域,內部連接 int main() { ... } ...
和該文件屬於同一程序的其餘文件可使用變量giants。變量dodgers是該文件私有的,可是能夠被該文件中的任一函數使用。
12.1.3 存儲時期
一個C變量有如下兩種存儲時期:靜態存儲時期(static storage duration)和自動存儲時期(automatic storage duration)。若是一個變量具備靜態存儲時期,它在程序執行期間將一直存在。具備文件做用域的變量具備靜態存儲時期。注意,對於具備文件做用域的變量,關鍵詞static代表連接類型,並不是存儲時期。一個使用static聲明瞭的文件做用域變量具備內部連接,而全部的文件做用域變量,不管它具備內部連接,仍是具備外部連接,都具備靜態存儲時期。
具備代碼塊做用域的變量通常狀況下具備自動存儲時期。在程序進入定義這些變量的代碼塊時,將爲這些變量分配內存;當退出這個代碼塊時,分配的內存將被釋放。該思想把自動變量使用的內存視爲一個能夠重複使用的工做區或者暫存內存。例如,在一個函數調用結束後,它的變量所佔用的內存能夠被用來存儲下一個被調用函數的變量。
迄今爲止咱們使用的局部變量都屬於自動類型。例如,在下列的代碼中,變量number和index在每次開始調用函數bore()時生成,在每次結束調用時消失:
void bore(int number) { int index; for(index=0;index<number;index++) puts("They don't make them the way they used to .\n"); return 0; }
C使用做用域、連接和存儲時期來定義5種存儲類:自動、寄存器、具備代碼塊做用域的靜態、具備外部連接的靜態和具備內部連接的靜態。
5種存儲類
存儲類 | 時期 | 做用域 | 連接 | 聲明方式 |
自動 | 自動 | 代碼塊 | 空 | 代碼塊內 |
寄存器 | 自動 | 代碼塊 | 空 | 代碼塊內,使用關鍵詞register |
具備外部連接的靜態 | 靜態 | 文件 | 外部 | 全部函數以外 |
具備內部連接的靜態 | 靜態 | 文件 | 內部 | 全部函數以外,使用 static |
空連接的靜態 | 靜態 | 代碼塊 | 空 | 代碼塊內使用關鍵字 static |
12.1.4 自動變量
屬於自動存儲類的變量具備自動存儲時期、代碼塊做用域和空連接。默認狀況下,在代碼塊或函數的頭部定義的任意變量都屬於自動存儲類。也能夠以下面所示的那樣顯式地使用關鍵字auto使您的這一意圖更加清晰:
int main(void) { auto int plox; ...
例如,爲了代表有意覆蓋一個外部函數定義時,或者爲了代表不能把變量改變爲其餘存儲類這一點很重要時,能夠這樣作。關鍵字auto稱爲存儲類說明符(storage class specifier)。
代碼塊做用域和空連接意味着只有變量定義所在的代碼塊才能夠經過名字訪問該變量(固然,能夠用參數向其餘函數傳送該變量的地址和值,但那是以間接的方式知道的)。另外一個函數可使用具備一樣名字的變量,但那將是存儲在不一樣內存位置中的一個獨立變量。
再來看一下嵌套代碼塊。只有定義變量的代碼塊及其內部的任何代碼塊能夠訪問這個變量:
int loop(int n) { int m; //m的做用域 scanf("%d",&m); { int i; //m和i的做用域 for(i=m;i<n;i++) puts("i is local to a sub-block\n"); } return m; //m的做用域,i已經消失 }
在這段代碼 中,變量i僅在內層花括號中是可見的。若是試圖在內層代碼塊以前 或以後使用該變量,將獲得一個編譯錯誤。一般,設計程序時不使用這一特性。然而有些時候,若是其餘地方 用不到這個變量的話,在子代碼 塊中定義 一個變量是有用的。經過這種方式 ,您能夠在使用變量的位置附近說明變量的含義 。並且,變量只會在須要 它的時候 才佔用內存。變量n和m在函數頭部和外層代碼塊中定義 ,在整個函數中可用,並一直存在直到函數終止。
若是在內層代碼塊定義了一個具備和外層代碼塊變量同一名字的變量,將發生什麼?那麼在內層代碼塊定義的名字是內層代碼塊所使用的變量。咱們稱之爲內層定義覆蓋了(hide)外部定義,但當運行離開內層代碼塊時,外部變量從新恢復做用。
程序清單hiding.c程序
#include <stdio.h> int main() { int x = 30; /*初始化x*/ printf("x in outer block:%d\n",x); { int x = 77; /*新的x,覆蓋第一個x */ printf("x in inner block:%d\n",x); } printf("x in outer block:%d\n",x); while (x++ < 33) { int x = 100; /*新的x,覆蓋第一個x*/ x++; printf("x in while loop:%d\n",x); } printf("x in outer block:%d\n",x); return 0; } 輸出以下: x in outer block: 30 x in inner block: 77 x in outer block: 30 x in whlie loop: 101 x in whlie loop: 101 x in whlie loop: 101 x in outer block: 34
該語句最使人迷惑的地方也許是while循環。這個while循環的判斷使用了起始的x:
while (x++ < 33)
然而在循環內部,程序看到了第三個x的變量,即在while循環代碼內定義的一個變量。所以,當循環體中的代碼 使用x++時,是新的x被遞增到101,接着被顯示。每次循環結束之後,新的x 就消失了。而後循環條件判斷語句使用並遞增起始的x,又進入循環代碼塊,再次建立新的x。注意,該 循環必須在條件判斷語句中遞增x,由於若在循環體內遞增x的話,遞增的將是另外一個x而非判斷所用的那個x。
1、不帶{ }的代碼塊
先前曾提到C99有一個特性,語句若爲循環或者if語句的一部分,即便沒有使用{},也認爲是一個代碼塊。更完整的說,整個循環是該 循環所在代碼 塊的子代碼 塊,而循環體是整個循環代碼 塊的子代碼 塊。與之相似,if語句 是一個代碼 塊,其相關子語句 也是if 語句的子代碼塊。這一規則影響到您可以在休息定義 變動 以及該 變量的做用域。程序清單12.2顯示 了在一個for 循環中該 特性是如何做用的。
程序清單 12.2 for c99.c程序
#include <stdio.h> int main() { int n = 10; printf("Initially,n=%d\n",n); for (int n = 1;n<3;n++) printf("loop 1: n = %d\n",n); printf("After loop1,n = %d\n",n); for (int n=1;n<3;n++) { printf("loop 2 index n = %d\n",n); int n=30; printf("loop 2 n = %d\n",n); n++; } printf("After loop 2,n = %d\n",n); return 0; } /*輸出以下,假設編譯器支持這一特定的c99特性 Initially,n = 10 loop 1 :n = 1 loop 1 :n = 2 After loop 1,n = 10 loop 2 index n = 1 loop 2 n = 30 loop 2 index n = 2 loop 2 n = 30 After loop 2, n = 10 */
在第一個for循環的控制部分中聲明 的n到該 循環末尾一直起做用,覆蓋了初始的n。但在運行完該 循環後,初始的n恢復做用。
在第二個for 循環中,n 聲明爲一個循環索引,覆蓋了初始的n。接着,在循環體內聲明的n覆蓋了循環索引 n。當程序執行完循環體後,在循環體內聲明 的n消失,循環判斷使用索引n。整個循環終止 時,初始的n又恢復做用。
2、自動變量的初始化
除非您顯式的初始化自動變量,不然它不會被自動初始化。考慮下列聲明:
int main(void) { int repid; int tents = 5;
變量tents初始化爲5,而變量repid的初始值則是先前佔用分配給它的空間的任意值。不要期望這個值是0。假若一個很是量 表達式中所用到的變量先前都定義過的話,可將自動變量初始化爲該表達式:
int main(void) { int ruth = 1; int rance = 5 * ruth; //使用先前定義過的變量
12.1.5 寄存器變量
一般,變量存儲在計算機內存中。若是 幸運,寄存器變量能夠被存儲在CPU寄存器中,或者更通常地,存儲在速度最快的可用內存中,從而能夠比普通變量更快地被訪問和操做。由於寄存器變量可能是存放 在一個寄存器而非內存中,因此沒法得到寄存器變量的地址。但在其餘許多方面,寄存器變動和自動變量是同樣的。也就是說,他們都具備代碼塊做用域、空連接以及自動的存儲時期。經過使用存儲類說明符register能夠聲明寄存器變量:
int main(void) { register int quick; ...
咱們說「若是幸運」是由於聲明一個寄存器變量僅是一個請求,而非一條直接指令。編譯器必須在您的請求與可用寄存器的個數或可用調整內存的數量之間作個權衡,因此您可能達不到本身的願望。這種狀況下,變量變成一種普通的自動變量;然而,您依然不能對他使用地址運算符。
能夠把一種形式參量請求爲寄存器變量。只需在函數頭部使用register關鍵字:
void macho(register int n)
可使用register聲明的類型是有限的。例如,處理器可能沒有足夠大的寄存器來容納double類型。
12.1.6 具備代碼塊做用域的靜態變量
靜態變量(static variable)這一名稱聽起來很矛盾,像是一個不可變的變量。實際上,「靜態」是指變量的位置固定不動。具備文件做用域的變量自動(也是必須)具備靜態存儲時期。也 能夠建立具備代碼塊做用域,兼具靜態存儲的局部變量。這些變量和自動變量具備相同的做用域,但當包含這些變量的函數完成工做時,它們並不消失 。也就是說,這些變量具備代碼塊做用域、空連接,卻有靜態存儲時期。從一次函數調用到下一次調用 ,計算機都記錄着它們的值。這樣的變量經過使用存儲類說明符static(這提供了靜態存儲時期)在代碼塊內聲明(這提供了代碼塊做用域和空連接)建立。程序清單12.3中的例子說明了這一技術。
程序清單12.3 loc_stat.c程序
/* loc_stat.c --使用一個局部靜態變量*/ #include <stdio.h> void trystat(void); int main(void) { int count; for(count = 1;count <= 3;count++) { printf("Here comes iteration %d: \n",count); trystat(); } return 0; } void trystat(void) { int fade = 1; static int stay = 1; printf("fade = %d and stay = %d\n",fade++,stay++); }
注意,trystat()在打印出每一個變量的值後遞增變量。運行程序將返回下列結果:
Here comes iteration 1:
fade = 1 and stay = 1
Here comes iteration 2:
fade = 1 and stay = 2
Here comes iteration 3:
fade = 1 and stay = 3
靜態變量stay記得它的值曾被加1,而變量fade每次都從新開始。這代表了初始化的不一樣:在每次調用trystat()時fade都被初始化,而stay只在編譯trystat()時被初始化一次。若是不顯式的對靜態變量進行初始化,它們將被初始化爲0.
下面的兩個聲明看起來很類似:
int fade = 1;
static int stay = 1;
然而,第一個語句確實 是函數trystat()的一部分,每次調用該函數時都會執行它。它是個運行時的動做。而第二個語句 實際 上並非函數trystat()的一部分。若是用調試程序逐步運行該程序,您會發現程序看起來跟過了那一步。那是由於靜態變量和外部變量在 程序調入內存時 已經就位了。把這個語句放在trystat()函數中是爲了 告訴編譯器只有函數trystat()能夠看到該 變量。它不是在運行時執行的語句。
對函數參量不能使用static:
int wontwork( static int flu); //不容許
12.1.7 具備外部連接的靜態變量
具備外部連接的變量具備文件做用域、外部連接和靜態存儲時期。這一類型有時被稱爲外部存儲類(external storage class),這一類型的變量被稱爲外部變量(external variable)。把變量的定義聲明放在全部函數以外 ,即建立了一個外部變量。爲了使程序更加清晰,能夠在使用外部變量的函數中經過使用extern關鍵字來再次聲明它。若是變量是在別的文件中定義的,使用extern來聲明該變量就是必須的。應該像這樣聲明:
int Errupt; /*外部定義的變量 */ double Up[100]; /*外部定義的數組 */ extern char Coal; /*必須的聲明 */ /*由於Coal在其餘文件中定義*/ void next(void); int main(void) { extern int Errupt; /*可選的聲明 */ extern double Up[]; /*可選的聲明 */ ... } void next(void) { ... }
Errupt 的再次聲明是個連接的例子,由於它們都指向同一變量。
請注意沒必要在double Up的可選聲明中指明數組大小。第一次聲明已經提供了這一信息。由於外部變量具備文件做用域,它們從被聲明外到文件結尾都是可見的,因此main()中的一組extern聲明徹底能夠省略掉。而它們出如今那裏,做用只不過是代表main()函數使用這些變量。
若是函數中的聲明漏掉了extern ,就會創建 一個獨立 的自動變量。也就是說,若是在main()中用:
extern int Errupt ;
替換
int Ettupt ;
將使編譯器建立一個名爲Errupt的自動變量。它將是一個獨立的局部變量,而不一樣於初始的Errupt。在程序執行main()時該 局部變量會起做用;但在像next()這種同一文件內的其餘函數中,外部的Errupt將起做用。簡言之,在程序執行代碼塊內語句時,代碼塊做用域的變量覆蓋了具備文件做用域的同名變量。
外部變量具備靜態存儲時期。所以,數組Up一直存在並保持其值,無論程序是否在執行main( )、next( )仍是其餘函數。
下列的3個例子展現了外部變量和自動變量的4種可能組合。
例1中有一個外部變量Hocus。它對main( )和magic( )都是可見的。
/*例1*/ int Hocus; int magic(); int main(void) { extern int Hocus; //聲明Hocus爲外部變量 ... } int magic() { extern int Hocus; //與上面的Hocus是同一變量 ... }
例2中一個外部變量Hocus,對兩個函數都是可見的,此次,magic()經過默認的方式獲知外部變量。
/*例1*/ int Hocus; int magic(); int main(void) { extern int Hocus; //聲明Hocus爲外部變量 ... } int magic() { //未聲明Hocus,可是知道該變量 ... }
在例3 中,建立了4個獨立 的變量。main()中的Hocus默認爲自動變量,並且是main()的局部變量。magic()中的Hocus被顯式的聲明 爲自動變量,只對magic可見。外部變量Hocus對main()或magic()不可見,但對文件中其餘不單獨擁有局部Hocus的函數可見。最後,Pocus是一個外部變量,對magic()可見而對main()不可見,由於Pocus的聲明在main()以後 。
/*例3*/ int Hocus; int magic(); int main(void) { int Hocus; //聲明Hocus默認爲自動變量 ... } int Pocus; int magic() { auto int Hocus; //把局部變量Hocus顯式地聲明爲自動變量 ... }
這些例子說明了外部變量的做用域:從聲明的位置開始到文件結尾爲止。它們也說明了外部變量的生存期。外部變量Hocus和Pocus存在的時間與程序運行時間同樣,而且他們不侷限於任一函數,在一個特定函數返回時並不消失 。
1、外部變量初始化
和自動變量同樣,外部變量能夠被顯式地初始化。不一樣於外部變量的是,若是您不對外部變量進行初始化,它們將自動被賦初始值0。這一原則也適用於外部定義的數組元素。不一樣於自動變量,只能夠用常量表達式來初始化文件做用域變量。
int x = 10; //能夠,10是常量 int y = 3 + 20; //能夠,一個常量表達式 size_t z = sizeof(int); //能夠,一個常量表達式 (只要類型不是一個變長數組,sizeof表達式就被認爲是常量表達式) int x2 = 2*x; //不能夠,x 是一個變量
2、外部變量的使用
假設須要兩個分別叫做main()和critic()的函數來訪問變量units。能夠如程序清單12.4所示,在這兩個函數以外的開始處聲明變量units。
程序清單12.4 global.c 程序
#include <stdio.h> int units = 0; /*一個外部變量*/ void critic(void); int main(void) { extern int units; /*可選的二次聲明*/ printf("How many pounds to a firkin of butter?\n"); scanf("%d",&units); while(units != 56) critic(); printf("You must have looked it up!\n"); return 0; } void critic(void) { /*這裏省略了可選的二次聲明*/ printf("No luck,chummy.Try again.\n"); scanf("%d",&units); }
下面是一個輸出示例
How many pounds to a firkin of butter? 14 No luck,chummy.Try again. 56 You must have looked it up!
注意函數critic()是怎麼樣讀取units的第二個值的;當main()結束while循環時,也知道 了新值。所以,兩個函數main()和critic()都用標識符units來訪問同一個變量。在c的術語中,稱units具備文件做用域、外部連接以及靜態存儲時期。
經過在全部函數定義的外面定義變量units,它成爲一個外部變量。要使units對文件中隨後的所有函數可用,只須要像前面這樣作便可。
來看一些細節。首先,units聲明所在的位置使得它對後的函數可用,而不需採起其餘任何操做。這樣,critics()就可使用變量units。
與之相似,也不須要作任何事來容許main()訪問units。然而,main()中確實有以下聲明:
extern int units;
在這個例子中,聲明主要是使程序的可讀性更好。存儲類說明符extern告訴編譯器在該函數中用到的units都是指同一個在函數外部(甚至在文件以外)定義的變量。再次,main()和critic()都使用了外部定義的units。
3、外部名字
c99標準要求編譯器識別局部標識前63個字符和外部標識符的前31個字符。
對外部變量名字規定比對局部變量名字規定更嚴格, 是由於外部名字須要遵照局部環境規則,而該規則多是有更多的限制。
4、定義和聲明
咱們來更爲仔細的看一下變量定義和變量聲明的區別。考慮下面的例子:
int tern = 1; /*定義tern*/ main( ) { extern int tern; /*使用在其餘地方定義的tern變量*/
這裏,tern聲明瞭兩次。第一次聲明爲變動留出了存儲空間,它構成了變量的定義 。第二次聲明只是告訴編譯器要使用先前定義的變量tern,所以不是一個定義。第一次聲明被稱爲定義聲明(defining declaration),第二次聲明稱爲引用聲明(referencing declaration)。關鍵字extern代表該聲明不是一個定義,由於它指示編譯器參考其餘地方。
若是這樣作:
extern int tern; int main(void) {
那麼編譯器假定tern的真正定義在程序中其餘某個地方,也許是在另外一個文件中。這樣的聲明不會引發空間分配 。所以,不要用關鍵字extern來進行外部定義;只用它來引用一個已經存在的外部定義。
一個外部變量只可進行一次初始化,並且必定是在變量被定義時進行。
下面的語句是錯誤的:
extern char permis = 'Y'; /*錯誤*/
由於關鍵字extern的存在標誌着這是一個引用聲明,而非定義聲明。
12.1.8 具備內部連接的靜態變量
這種存儲類的變量具備靜態存儲時期、文件做用域以及內部連接。經過使用存儲類說明符static在全部函數外部進行定義(正如定義外部變量那樣)來建立一個這樣的變量:
static int svil = 1; //具備內部連接的靜態變量 int main(void) {
普通的外部變量能夠被程序的任一文件中所包含的函數使用,而具備內部連接的靜態變量只能夠被與它在同一個文件中的函數使用。能夠在函數中使用存儲類說明符extern來再次聲明任何具備文件做用域的變量。這樣的聲明並不改變連接。例如:
int traveler = 1; //外部連接 static int stayhome = 1; //內部連接 int main() { extern int traveler; //使用全局變量traveler extern int stayhome; //使用全局變量stayhome
對於這個文件來講,traveler 和stayhome都是全局變量,但只有traveler 能夠被其餘文件中的代碼使用。使用extern的兩個聲明代表main()在使用兩個全局變量,但stayhome仍具備內部連接。
12.1.9 多文件
複雜的C程序每每使用多個獨立的代碼文件。有些時候,這些文件可能須要共享一個外部變量。ANSI C經過在一個文件中定義變量,在其餘文件中引用 聲明這個變量來實現共享。也就是說,除了一個聲明(定義聲明)外,其餘全部聲明都必須使用關鍵字extern,並具只有在定義聲明中才能夠對該變量進行初始化。
注意:除非在第二個文件也聲明 了該 變量(經過使用extern),不然在一個文件定義的外部變量不能夠用於第二個文件。一個外部變量聲明自己只是使一個變量可能對其餘文件可用。