第 9 章 變數的生命期與使用範圍

變數可分成區域 (local 或 dynamic 或 block) 變數與全域 (global) 變數,其記憶體 的分配方式 與 時段 都不太相同,

於 第 9.1 節將介紹區域變數。
於 第 9.2 節將介紹全域變數。
於 第 9.3 節將介紹一些物件(辨識體)的區域性與全域性的關係。
於 第 9.4 節將介紹加上 static 修飾詞的變數與函數的性質。

本 章 主 要 內 容 如 下:

回第 8 章
至第 10 章
回 C 程式主目錄


第 9.1 節 區域變數

在一個 block,即於 大括號內 定義的變數都是 區域變數,於 該區域變數的名稱是 唯一的。 因此,在 main 或 任意一函數內 定義的變數 都是區域性的,其生命期 -- 即有記憶體配置與該變數 -- 為程式執行該 block 的開始 至該 block 結束,程式的執行 一離開含變數定義的 block 則該 變數就不存在。

例 1. 一程式的大綱含變數的宣告如下:

    1.    void f(int i)
    2.    { int j;.....}
    3.    void main()
    4.    {int a;
    5.    .....
    6.        {int i;.....
    7.        }
    8.       f(a);
    9.    }

說明:

  1. 程式的執行由 main 開始 因此,執行時 第 4 行的區域變數 a 已經存在,但第 6 行的變數 i 則尚未成形, 函數 f 的引數 i 與變數 j 亦不存在。
  2. 程式執行 第 6 行 區域變數 i 才存在。程式執行至第 8 行,該變數 i 就消失了。
  3. 程式執行 第 8 行 呼叫函數 f 時,引數 i 及變數 j 方產生。函數 f 執行結束後,引數 i 及變數 j 均消失不存在,除非再呼叫函數 f,這些 引數及變數 才重新出現。
一個區域變數其使用範圍僅限於含變數定義的 block。因此,在該 block 各地都可 引用 該變數名稱。

由於 block 可含 block 形成階層式的架構,因此可能產生內外 block 都定義同名稱的變數 ,此時,內部 block 的區域變數可蓋過外部 block 的區域變數,即在內部 block 所引用的 變數名稱都是引用內部 block 的變數而非外部的 block 的變數。 由例 2 來說明此現象。

例 2.

      1.  main()          
      2.  {   int i,j;
      3.      i=10; j=5;
      4.      {  int i; 
      5.         i=j++;
      6.         printf("i=%d,j=%d\n",i,j);
      7.      }
      8.      printf("i=%d,j=%d\n",i,j);
      9.  }
說明:
  1. main 含 2 個區域變數 i 及 j (以 i2, j2 表之,其中下標之 2 表示 變數定義於第 2 行),又於第 4 行 至第 7 行的 block 含一區域變數 i4
  2. i2 的使用範圍為自第 2 行至第 9 行,扣除第 4 行至第 7 行,即自第 5 行 引用變數 i 是 i4而不是 i2
  3. j2 的使用範圍為自第 2 行至第 9 行。
  4. i4 的使用範圍是自第 4 行至第 7 行。
  5. 程式執行完 第 5 行 i=j++; 後, i 的值為 5,即 i4 之值為 5, 但 i2 的值仍為 10。
  6. 執行完該程式的結果為
    i=5,j=6
    i=10,j=6
回本章主目錄

第 9.2 節 全域變數

在函數之外 宣告或定義 的變數都是 全域變數。全域變數 於程式編譯 之後就 存在一直到 程式的結束 才消失,其 使用範圍 為 自檔案的宣告或定義處 開始 至檔案的 結束,其名稱於 一個檔案 也必須是 唯一的。

全域變數的使用,其好處是 減少函數呼叫的 引數個數,其壞處是程式 較不易維修、除錯。

註:函數的呼叫其 side-effect (副作用)愈少愈好,即函數的呼叫 會改變 函數外 變數的值。 將於例 3 說明。

如果 區域變數 與 全域變數 同名,則 全域變數 的使用範圍就不包含 區域變數 的 使用範圍。

於例 3 將說明 全域變數 的上述性質。

例 3. 全域變數的使用範圍。

1.   int s=0;
2.   int square(int i)  
3.   { s=i+1; printf("s=%d,i=%d\n",s,i);
4.     return i*i;
5.   }
6.   int t=1;
7.   main()
8.   { int s=10;
9.     t=square(s);
10.    printf("s=%d,t=%d\n",s,t);
11.  }

說明:

  1. 例 3 含 2 個全域變數 s1及 t6, s1的使用範圍為整個檔案扣去第 7 行至第 11 行,因 main() 有區域變數 s8
  2. 於第 9 行,呼叫函數 square,傳 10 出去,傳 100 回來給變數 t。
  3. 呼叫函數 square 的過程中,有一 side-effect(副作用) 即改變了全域變數 s1 的值,因此, square 的語意並不僅含將引數 i 平方而已, 此函數已不能形成一 獨立的模組, 因此, 維修變得 較難, 同時 程式也 較不易 讓人看懂。
  4. 程式執行的結果為
      s=11,i=10
      s=10,t=100
    
回本章主目錄

第 9.3 節 物件的區域性與全域性

不僅變數的 宣告或定義 具有 區域性與全域性, 事實上任意一 辨識體(物件) 如 struct、 typedef、 enum、 巨集的宣告、 函數的定義等,其 使用範圍的規則 也相同。

C 程式 函數的定義不像 Pascal,函數 不能 定義在 另一個函數之內。因此,函數 都具有 全域性。 一般而言, 我們會宣告一個函數 在 檔頭或 在一個 block 的開頭,如:

例 4.

int square(int i);  或  main()
main()                  { int square(int i);
{ int i=10,j=2;           int i=10,j=2;
  j=square(i);            j=square(i);
}                       }
int square()            int square(int i)
{return i*i;}           {return i+i;}

通常 一個程式的設計,可 能會因性質的不同 而 將 程式碼 分散於 不同的檔案, 對於一些 常數的定義, 全域變數的宣告, 函數的宣告, 資料型態的設定 都放在 檔頭檔, 以 #include 的方式 來達到 宣告的一致性。我們將於第 10 章 來 介紹 程式設計的一些技巧。

例 5. 設檔案 file.txt 含下列內容:

#include< stdio.h >
struct point {int x,y;};
struct rect {point topLeft, bottomRight};
int pointInRect(struct point, stuct rect);
extern point currentLoc;
設檔案 file.c 含下列內容:
#include "file.txt"
point currentLoc;

int pointInRect(struct point p, stuct rect r)
{  int x1,x2,y1,y2;
   if(r.topLeft.x < r.bottomRight.x)
   { x1=r.topLeft.x; x2=r.bottomRight.x;}
   else
   { x2=r.topLeft.x; x1=r.bottomRight.x;}

   if(r.topLeft.y < r.bottomRight.y)
   { y1=r.topLeft.y; y2=r.bottomRight.y;}
   else
   { y2=r.topLeft.y; y1=r.bottomRight.y;}

   if(x1 <= p.x && p.x <= x2 && y1 <=p.y && p.y <= y2)
        return 1;
   else return 0;
}

設檔案 main.c 含下列內容

#include "file.txt"
main()
{ struct rect r;

  r.topLeft.x=10; r.topLeft.y=10;
  r.bottomRight.x=20; r.bottomRight.y=20;

  scanf("%d %d", &currentLoc.x, &currentLoc.y);

  if(pointInRect(currentLoc,r))
     printf("in the rectangle\n");
  else
     printf("not in the rectangle\n");
}

說明:

  1. 檔案 file.c 含 全域變數 currentLoc 的定義。
  2. 檔案 file.txt 含全域變數 currentLoc 的宣告,即 extern point currentLoc, 其中 extern 表示 全域變數定義 於它處。
  3. 檔案 file.txt 含函數 pointInRect 的宣告,即 int pointInRect(struct point,struct rect);。
  4. 資料型態 struct point 及 struct rect 都宣告於檔案 file.txt 中, file.c 與 main.c 皆有 #include 指令 "file.txt",因此可直接使用 struct point 及 struct rect。

回本章主目錄


第 9.4 節 static 變數與函數

區域變數的定義加上 static,其使用範圍與沒有加上 static 是沒有區別的, 但是其生命期就 如同 全域變數一樣,以下例來解說:

例 6.

   
  int square(int i)  
  { static int count=0;

    count++;
    return i*i;
  }
說明:
  1. 函數 square 中含一 static 區域變數 count。
  2. 變數 count 於編譯後就存在了。
  3. 變數 count 不管函數 square 呼叫 多少次, 僅 initialize 一次。 每呼叫函數 square 一次, 變數 count 就增加 1。
  4. count 可以說是 專屬於 函數 square 的全域變數。

全域變數 的定義加上 static,則 該變數 就 變成 專屬於 該檔案 的 全域變數, 其它檔案 就 無法 引用該 變數, 即 其它檔案 就不能 含有 該變數的 extern 指令。

例 7. 設有三個檔案 file.h、 file.c 及 main.c 其內容分別如下:

檔案 file.h 含

  
 void Add(int n);
 void Mult(int n);
 int get();

檔案 file.c 含
 #include "file.h"
 static int s=0;
 void Add(int n) {s=s+n;} 
 void Mult(int n) {s=s*n;}
 int get(){return s;}

檔案 main.c 含
 #include < stdio.h >
 #include "file.h"
 void main()
 {Add(5);
  Mult(10);
  printf("s=%d\n",get());
 }
說明:
  1. 檔案 file.c 含一 static 全域變數 s,該 變數 僅能於 file.c 引用。
  2. 檔案 main.c 不可加上 extern int s; 而引用變數 s。
  3. 程式執行的結果為
      s=50
    
  4. C++ 程式 class 的設計方式 非常類似 file.c 與 file.h 的架構, 變數 s 就如同一個 private 的 data member。

一個函數的定義加上 static, 該 函數 也就變成 專屬該檔案 的函數, 其它檔案 就無法 呼叫該函數,於 C++ class 的設計,就好像一個 private 的 member function。

回本章主目錄


回第 8 章
至第 10 章
回 C 程式 主目錄