預設情況下,宣告在塊或函式頭中的任何變數都屬於自動儲存類別。為了更清楚地 表達你的意圖(例如,為了表明有意覆蓋乙個外部變數定義,或者強調不要把該變數改為其他儲存類別),可以顯式使用關鍵字auto,如下所示:
int
main
(void
)
關鍵字auto是儲存類別說明符(storage-class specifier)。auto關鍵字在 c++中的用法完全不同,如果編寫c/c++相容的程式,最好不要使用auto作為儲存類別說明符。
塊作用域和無鏈結意味著只有在變數定義所在的塊中才能通過變數名訪問該變數(當然,引數用於傳遞變數的值和位址給另乙個函式,但是這是間接的方法)。另乙個函式可以使用同名變數,但是該變數是儲存在不同記憶體位置上的另乙個變數。
變數具有自動儲存期意味著,程式在進入該變數宣告所在的塊時變數存在,程式在退出該塊時變數消失。原來該變數占用的記憶體位置現在可做他用。
接下來分析一下巢狀塊的情況。塊中宣告的變數僅限於該塊及其包含的塊使用。
int
loop
(int n)
return m;
// m 的作用域,i 已經消失
}
在上面的**中,i僅在內層塊中可見。如果在內層塊的前面或後面使用i,編譯器會報錯。通常,在設計程式時用不到這個特性。然而,如果這 個變數僅供該塊使用,那麼在塊中就近定義該變數也很方便。這樣,可以在靠近使用變數的地方記錄其含義。另外,這樣的變數只有在使用時才占用內 存。變數n和 m 分別定義在函式頭和外層塊中,它們的作用域是整個函式, 而且在呼叫函式到函式結束期間都一直存在。
如果內層塊中宣告的變數與外層塊中的變數同名會怎樣?內層塊會隱藏 外層塊的定義。但是離開內層塊後,外層塊變數的作用域又回到了原來的作 用域。程式演示了這一過程。
// hiding.c -- 塊中的變數
#include
intmain()
printf
("x in outer block: %d at %p\n"
, x,
&x);
while
(x++
<33)
// 原始的 x
printf
("x in outer block: %d at %p\n"
, x,
&x);
return0;
}下面是該程式的輸出:
x in outer block:
30 at 0x7fff5fbff8c8
x in inner block:
77 at 0x7fff5fbff8c4
x in outer block:
30 at 0x7fff5fbff8c8
x in while loop:
101 at 0x7fff5fbff8c0
x in while loop:
101 at 0x7fff5fbff8c0
x in while loop:
101 at 0x7fff5fbff8c0
x in outer block:
34 at 0x7fff5fbff8c8
首先,程式建立了變數x並初始化為30,如第1條printf()語句所示。然 後,定義了乙個新的變數x,並設定為77,如第2條printf()語句所示。根據顯 示的位址可知,新變數隱藏了原始的x。第3條printf()語句位於第1個內層塊 後面,顯示的是原始的x的值,這說明原始的x既沒有消失也不曾改變。
也許該程式最難懂的是while迴圈。while迴圈的測試條件中使用的是原 始的x:
while(x++ < 33)
在該迴圈中,程式建立了第3個x變數,該變數只定義在while迴圈中。 所以,當執行到迴圈體中的x++時,遞增為101的是新的x,然後printf()語句 顯示了該值。每輪迭代結束,新的x變數就消失。然後迴圈的測試條件使用 並遞增原始的x,再次進入迴圈體,再次建立新的x。在該例中,這個x被創 建和銷毀了3次。注意,該迴圈必須在測試條件中遞增x,因為如果在迴圈體 中遞增x,那麼遞增的是迴圈體中建立的x,而非測試條件中使用的原始x。
我們使用的編譯器在建立while迴圈體中的x時,並未復用內層塊中x佔 用的記憶體,但是有些編譯器會這樣做。
該程式示例的用意不是鼓勵讀者要編寫類似的**(根據c的命名規 則,要想出別的變數名並不難),而是為了解釋在內層塊中定義變數的具體 情況。
1.沒有花括號的塊
前面提到乙個c99特性:作為迴圈或if語句的一部分,即使不使用花括 號({}),也是乙個塊。更完整地說,整個迴圈是它所在塊的子塊(subblock),迴圈體是整個迴圈塊的子塊。與此類似,if 語句是乙個塊,與其 相關聯的子語句是if語句的子塊。這些規則會影響到宣告的變數和這些變數 的作用域。程式清單12.2演示了for迴圈中該特性的用法。
// forc99.c -- 新的 c99 塊規則
#include
intmain()
printf
("after loop 2, n = %d at %p\n"
, n,
&n);
return0;
}
假設編譯器支援c語言的這個新特性,該程式的輸出如下:
initially, n =
8 at 0x7fff5fbff8c8
loop 1
: n =
1 at 0x7fff5fbff8c4
loop 1
: n =
2 at 0x7fff5fbff8c4
after loop 1
, n =
8 at 0x7fff5fbff8c8
loop 2 index n =
1 at 0x7fff5fbff8c0
loop 2
: n =
6 at 0x7fff5fbff8bc
loop 2 index n =
2 at 0x7fff5fbff8c0
loop 2
: n =
6 at 0x7fff5fbff8bc
after loop 2
, n =
8 at 0x7fff5fbff8c8
第1個for迴圈頭中宣告的n,其作用域作用至迴圈末尾,而且隱藏了原 始的n。但是,離開迴圈後,原始的n又起作用了。
第2個for迴圈頭中宣告的n作為迴圈的索引,隱藏了原始的n。然後,在 迴圈體中又宣告了乙個n,隱藏了索引n。結束一輪迭代後,宣告在迴圈體中 的n消失,迴圈頭使用索引n進行測試。當整個迴圈結束時,原始的 n 又起作 用了。再次提醒讀者注意,沒必要在程式中使用相同的變數名。如果用了, 各變數的情況如上所述。
注意 支援c99和c11
有些編譯器並不支援c99/c11的這些作用域規則(microsoft visual studio 2012就是其中之一)。有些編譯會提供啟用這些規則的選項。例如,撰寫本 書時,gcc預設支援了c99的許多特性,但是要用 選項啟用程式 清單12.2中使用的特性:
gcc –std=c99 forc99.c
與此類似,gcc或clang都要使用 或 選項,才支援 c11特性。
2.自動變數的初始化
自動變數不會初始化,除非顯式初始化它。考慮下面的宣告:
int
main
(void){
int repid;
int tents =5;
...
tents變數被初始化為5,但是repid變數的值是之前占用分配給repid的空 間中的任意值(如果有的話),別指望這個值是0。可以用非常量表示式 (non-constant expression)初始化自動變數,前提是所用的變數已在前面定義過:
int main(void)
{int ruth = 1;
int rance = 5 * ruth; // 使用之前定義的變數
C C 中的臨時變數
說到臨時變數,我們大家也許都挺熟悉,但是我自己對臨時變數的理解卻一直存在乙個誤區。通常情況下,我會把為了做某一件事情而臨時建立的乙個變數叫做臨時變數。比如說在交換兩個變數的值時,通常我們會建立第三個變數來達到我們最終的目的,而我們稱之為 臨時變數 然而,大師scott meyers告訴我們,事實不是...
重要 C C 變數的自動初始化
1 include define const 100 int p1 int a 2 int b static intc main 按 ctrl c 複製 輸出 const 100 a 0 0 b 0c 0 d 2514932 e 0f 0 1307813 p2 457819009 資料區存放已初始化...
c c 中變數存放的區域
一 預備知識 程式的記憶體分配 乙個由c c 編譯的程式占用的記憶體分為以下幾個部分 1 棧區 stack 由編譯器自動分配釋放 存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。2 堆區 heap 一般由程式設計師分配釋放,若程式設計師不釋放,程式結束時可能由os 注意它與資料結...