文章程式碼顯示

2017年11月13日 星期一

《筆記》C語言 - 05_1:區域變數、static、全域變數、const、變數識別範圍、生命週期

區域變數

  先談 "內外層區塊的隱藏性質" ,假設我們在 main 函式裡面宣告 int x = 5; 則稱這個 x 為區域變數

  區域變數會具有識別範圍(也稱為作用域)。當我們在某 "區塊內" 宣告一個 "區域變數" 時,此變數 "只能在這個區塊或其內的巢狀區塊調用" 。宣告在區塊之內的變量都具有識別範圍,其終止的位置在該區塊的右大括弧 } 結束。

  如果在區塊內還有一個內層區塊,且在內層區塊內又宣告了一個變量與外層區塊的某一個變量相同名稱時,外層區塊的變量將會被暫時隱藏起來,直到內層區塊結束為止

這表示當執行到內層區塊時,內層區塊看到的是它自己宣告的變量的值,而不是外層區塊那個與它同名稱的變量的值。

說起來有點饒口,簡單來說就是如果在內層區塊內 "宣告" 了一個變數 x = 5  ,則在內層區塊內凡是對 x 進行操作的都會認為 x 是 5 ,不管在外層區塊宣告的 x 是多少。這就是區塊內變數的可視範圍

Hint : 兩處都進行相同名稱的宣告才會出現這樣的外層隱藏效果  

#include "stdio.h"

void useLocal(void);

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 int x = 5; /* local variable*/
 printf( "x in main() is %d\n", x );

  if(x==5){
   printf("local x is %d after entering 'if' \n", x );/*此時還沒宣告內層的區域變量*/
   int x = 10;
   printf("local x is %d before exiting 'if' \n", x );
  }

 printf( "Now, x in main() is %d\n", x );

 return 0;
}


一開始我們在 main 區塊內宣告區域變數 x 初始值為 5 ,接著進入了 if 的巢狀(內層)區塊

我們先列印顯示出目前的 x 數值,依舊是 5 (此時還沒在內層宣告變數 x ,所以它自動去抓取外層區塊的 x )。

接著我們在第 13 行又宣告了一樣名為 x 的區域變數,列印出來是 10 。第 17 行的列印結果可以看出 x 的值又回復成原本的 5 ,這是因為在內層區塊裡面的 x 在 } 後,其生命週期就結束了。所以當但當內層區塊結束後,外層的 x 又被回復。

這意謂著內層的區塊沒辦法去改變外層區塊的値嗎? 不是的

#include "stdio.h"

void useLocal(void);

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 int x = 5; /* local variable*/
 printf( "x in main() is %d\n", x );

  if(x==5){
   printf("local x is %d after entering 'if' \n", x );/*此時還沒宣告內層的區域變量*/
   x = 10;
   printf("local x is %d before exiting 'if' \n", x );
  }

 printf( "Now, x in main() is %d\n", x );

 return 0;
}


在這裡我們可以看到,第 13 行時我們改變了 x 的値為 10 。而最後返回到外層區塊後 x 也變成了 10 。

因為我們在內層區塊沒有對 x 進行任何的宣告


#include "stdio.h"

void useLocal(void);

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 int x = 5;/* local variable*/
 printf( "x in main() is %d\n", x );

 useLocal();

 printf( "Now, x in main() is %d\n", x );

 return 0;
}

void useLocal(void){
 int x = 20; 
 printf("local x in useLocal is %d after entering useLocal\n", x );
 x = x +1 ;
 printf("local x in useLocal is %d before exiting useLocal\n", x );
}


若我們使用"函數呼叫"的方式,在 useLocal 裡面宣告了相同名稱的變量會怎麼樣?

實際上  useLocal 函數區塊內根本看不見第 8 行宣告的變數 x。其原因在於"區域變數只能在這個區塊或其內的巢狀區塊調用" main是一個函式,use_local是另外一個函式,其各自有各自的區塊 

所以我們可以說在 useLocal 函式內對於區域變數 x 的宣告(第19行)是"獨立的"

函式呼叫一般而言會抓到全域變數,若在函數內自己定義了區域變數,則會回到本章一開始講的情況那樣。


#include "stdio.h"

void useLocal(void);

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 int x = 5;/* local variable*/
 printf( "x in main() is %d\n", x );

 useLocal();

 printf( "Now, x in main() is %d\n", x );

 useLocal();

 printf( "Again, x in main() is %d\n", x );

 return 0;
}

void useLocal(void){
 int x = 20; /* initialized each time useLocal is called*/
 printf("local x in useLocal is %d after entering useLocal\n", x );
 x = x +1 ;
 printf("local x in useLocal is %d before exiting useLocal\n", x );
}



除此之外,我們還發現位在 useLocal 的區域變數不具備記憶性,也就是說當我們每次執行完 useLocal 區塊時,內部的區域變數便會自動清除,這部分也根本章一開始講述的相符,因為區域變數是具有生命週期的,在碰到該區塊的 } 後就會結束

重新呼叫 useLocal 函式時,內部的 x 又被重新初使化為 20 。

static

倘若我們真的想要使用相同的變數名稱,且想要保存 useLocal 內部的 x 參數不想被自動清除時該怎麼辦? C 語言裡面提供 static 的區域變數宣告,當區域變數宣告為 static 後,內部的區域變數將只會被初始化一次,且當我們執行完該區塊,內部的區域變數會被保留,不會因為遇到 } 而結束生命週期。

#include "stdio.h"

void useLocal(void);

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 int x = 5;/* local variable*/
 printf( "x in main() is %d\n", x );

 useLocal();

 printf( "Now, x in main() is %d\n", x );

 useLocal();

 printf( "Again, x in main() is %d\n", x );

 return 0;
}

void useLocal(void){
 static int x = 20; /* initialized only first time useLocal is called */
 printf("local x in useLocal is %d after entering useLocal\n", x );
 x = x +1 ;
 printf("local x in useLocal is %d before exiting useLocal\n", x );
}



宣告成 static 可以使區域變數僅被初始化一次,且在離開該區塊後,值仍舊被保存。

(與全域變數不同的是,一個全域變數的建立會發生在程式進入 main() 之前;一個 static 區域變數的建立則會等到該行語句被執行時才會在電腦的記憶體中產生)

全域變數

  若我們將變數宣告在"任何函式(包括 main)之外" 則此變數稱為"全域變數",從這個識別字宣告的位置開始一直到檔案結束,所有的函式中都會知道它的存在。

#include "stdio.h"

void useGlobal(void);

int x = 1; /* Global variable */

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 int x = 5;/* local variable*/
 printf( "x in main() is %d\n", x ); /* local variable*/

 useGlobal();

 printf( "Now, x in main() is %d\n", x ); /* local variable*/

 useGlobal();

 printf( "Again, x in main() is %d\n", x ); /* local variable*/

 return 0;
}

void useGlobal(void){
 printf("x in useGlobal is %d after entering useGlobal\n", x );
 x = x * 10 ;
 printf("x in useGlobal is %d before exiting useGlobal\n", x );
}



我們定義了一個名為 useGlobal 的函式,並且在 useGlobal 內並沒有宣告任何變數,所以 useGlobal 將會取用全域變數的值(第 5 行) 。

縱使我們在第 10 行中為 mian 函式定義了區域變數 x 的值,但如前例所述, useGlobal 看不見在 main 裡面所定義的區域變數

由結果可以發現, useGlobal 不僅取用了全域變數,還更改了全域變數的值。

將變數宣告為全域變數,有時可能會發生誤更改的問題

例如我們在許多不同的函式中去調用全域變數,可能因為某些運算式使我們不小心更改了全域變數(這在大型專案中特別容易出現),這時後續的函數再調用全域變數時就會調用新的值,可能會造成非預期上的數值運算問題。

基於上述原因,原則上我們應該保有 "只在某個函數內使用的變數應宣告為函式的區域變數, 而不要宣告為全域變數" 的習慣

另外,因為一般的區域變數在函式執行完畢時會被清除,這意謂著記憶體的釋放。且這樣的作法才符合最小權限原則


#include "stdio.h"

void useGlobal(void);

int x = 1; /* Global variable */

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 int x = 5;/* local variable*/
 printf( "x in main() is %d\n", x );

 if(x==5){ /*Use local x*/
  printf("local x is %d after entering 'if' \n", x );
  x = 8;
  printf("local x is %d before exiting 'if' \n", x );
 }

 printf( "Now, x in main() is %d\n", x );

 useGlobal();

 printf( "Again, x in main() is %d\n", x );

 for(;x<=10;x++){/*Use local x*/

  printf("%d\n",x);
 }

 printf( "Again, x in main() is %d\n", x );

 return 0;
}

void useGlobal(void){
 printf("x in useGlobal is %d after entering useGlobal\n", x );
 x = x * 10 ; /* use Global x */
 printf("x in useGlobal is %d before exiting useGlobal\n", x );
}



我們發現

main 內部的 if 以及 for 迴圈的判斷式(巢狀區塊調用),都是參考了區域變數 x 。

而 useGlobal 因為不滿足"區域變數只能在這個區塊或其內的巢狀區塊調用" ,所以會去參考全域變數 x。

總結一下

在一個函式內想要存取一個變數,若沒有在內層區塊宣告區域變數,則會找看看在外層區塊有沒有宣告該區域變數,若還是沒有,則會去找看看全域變數有沒有宣告該變數。

若三者都沒有宣告,那麼編譯器肯定會報錯

const


#include "stdio.h"

void useGlobal(void);

const int x = 1; /* Global variable */

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 printf( "x in main() is %d\n", x );

 useGlobal();

 printf( "Now, x in main() is %d\n", x );

 useGlobal();

 printf( "Again, x in main() is %d\n", x );

 return 0;
}

void useGlobal(void){
 printf("x in useGlobal is %d after entering useGlobal\n", x );
 x = x * 10 ;
 printf("x in useGlobal is %d before exiting useGlobal\n", x );
}




很多人因為這方面搞不太清楚,所幸把全部的變數都宣告成全域變數,程式當然可以執行,若小心謹慎一點也不會有錯誤產生,但這就違反了最小權限原則,實話說不是一個好的編程習慣

為了避免全域變數不小心被更改,我們可以使用 const 的方式來宣告變數。當我們將變數宣告成 const 後,此變數就變成了唯讀(read-only) ,任何嘗試更改此變數的語句都會被編譯器所阻擋,在編譯時就會報錯而不能進行編譯。


補充說明:

#include "stdio.h"

int x = 50; /* Global variable */

int square( int y ); /* square function prototype */

int main(void) {
setvbuf(stdout,NULL,_IONBF,0);

 printf( "Now, x in main() is %d\n", x );
 int x = 40; /* Local variable */
 int result;
 printf( "Now, x in main() is %d\n", x );

 result = square( x );
 printf( "The answer is %d\n", result );

 printf( "Now, x in main() is %d\n", x );
 return 0;
}

/* square function */
int square ( int y ){
 return y * y; /* Use Local variable */
}


我們額外宣告了區域變數 x ,此時 square 函式將會引入區域變數的 x。內部的 printf 也會以區域變數 x 的值為準。你可能會覺得疑惑,為什麼這時候外面調用的函數又看的見 main 所定義的區域變數了呢? 不是說看不見嗎? 不是說獨立嗎?

仔細觀察可以發現,在前面的例子中我們並沒有讓函式具有任何輸入參數,而是直接處理原參數;但在此例,我們是使用 call by value 的方式。其差異全在於 "函式原型" 的定義。

也就是說此例跟 《筆記》C語言 - 05:函式基本、傳值呼叫 文末是一模一樣架構的例子(call_by_value 有回傳値),大家別搞混了


↓↓↓ 連結到部落格方針與索引 ↓↓↓

Blog 使用方針與索引