C語言入門(十四)變量的作用域和存儲類型

變量的作用域和存儲類型

一、作用域和生存期
C程序的標識符作用域有三種:局部、全局、文件。標識符的作用域決定了程序中的哪些語句可以使用它,換句話說,就是標識符在程序其他部分的可見性。通常,標識符的作用域都是通過它在程序中的位置隱式說明的。
1.局部作用域
前面各個例子中的變量都是局部作用域,他們都是聲明在函數內部,無法被其他函數的代碼所訪問。函數的形式參數的作用域也是局部的,它們的作用範圍僅限於函數內部所用的語句塊。
void add(int);
main()
{
int num=5;
add(num);
printf(%d\n,num); /*輸出5*/
}
void add(int num)
{
num++;
printf(%d\n,num); /*輸出6*/
}
上面例子裏的兩個num變量都是局部變量,只在本身函數裏可見。前面我們說了,在兩個函數出現同名的變量不會互相干擾,就是這個道理。所以上面的兩個輸出,在主函數裏仍然是5,在add()函數裏輸出是6。

2.全局作用域
對於具有全局作用域的變量,我們可以在程序的任何位置訪問它們。當一個變量是在所有函數的外部聲明,也就是在程序的開頭聲明,那麼這個變量就是全局變量。
void add(int);
int num;
main()
{
int n=5;
add(n);
printf(%d\n,num); /*輸出6*/
}
void add(num) /*形式參數沒有指定類型*/
{
num++;
printf(%d\n,num); /*輸出6*/
}
上面的main()和add()裏面,並沒有聲明num,但是在最後輸出的時候卻要求輸出num,這是由於在程序的開始聲明瞭num是全局變量,也就是在所有函數裏都可以使用這個變量。這時候一個函數裏改變了變量的值,其他函數裏的值也會出現影響。上面的例子輸出都是6,因爲在add()函數裏改變了num的值,由於num是全局變量,就好象它們兩個函數共用一個變量,所以在main()函數裏的num也隨之改變了。

3.文件作用域
在很多C語言書上,都沒有說明文件作用域,或者只是略微的提到,其實文件作用域在較大程序中很有作用(在多文件系統中)。文件作用域是指外部標識符僅在聲明它的同一個轉換單元內的函數彙總可見。所謂轉換單元是指定義這些變量和函數的源代碼文件(包括任何通過#include指令包含的源代碼文件)。static存儲類型修飾符指定了變量具有文件作用域。
static int num;
static void add(int);
main()
{
scanf(%d,&num);
add(num)
printf(%d\n,num);
}
void add(num)
{
num++;
}
上面的程序中變量num和函數add()在聲明是採用了static存儲類型修飾符,這使得它們具有文件作用域,僅愛定義它們的文件內可見。
由於我們提到的大多數程序都只有一個編譯文件組成,所以這種寫法沒有實際意義。但是實際工程上的文件有很多,它們不是由一個人寫成的,由很多人共同完成,這些文件都是各自編譯的,這難免使得某些人使用了一樣的全局變量名,那麼爲了以後程序中各自的變量和函數不互相干擾,就可以使用static修飾符,這樣在連接到同一個程序的其他代碼文件而言就是不可見的。

二、變量存儲類型
前面我們說了,聲明變量時用如下類似的形式:
int num;
float total;

它們都沒有存儲類型修飾符,我們在聲明時也可以通過存儲類型修飾符來告訴編譯器將要處理什麼類型的變量。存儲類型有以下四種:自動(auto)、靜態(static)、外部(extern)、寄存器(regiser)。
1.自動存儲類型
自動存儲類型修飾符指定了一個局部變量爲自動的,這意味着,每次執行到定義該變量的語句塊時,都將會爲該變量在內存中產生一個新的拷貝,並對其進行初始化。實際上,如果不特別指明,局部變量的存儲類型就默認爲自動的,因此,加不加auto都可以。
main()
{
auto int num=5;
printf(%d\n,num);
}
在這個例子中,不論變量num的聲明是否包含關鍵字auto,代碼的執行效果都是一樣的。函數的形式參數存儲類型默認也是自動的。

2.靜態存儲變量
前面已經使用了static關鍵字,但是對於局部變量,靜態存儲類型的意義是不一樣的,這時,它是和自動存儲類型相對而言的。靜態局部變量的作用域仍然近侷限於聲明它的語句塊中,但是在語句塊執行期間,變量將始終保持它的值。而且,初始化值只在語句塊第一次執行是起作用。在隨後的運行過程中,變量將保持語句塊上一次執行時的值。看下面兩個對應的程序:
/*1.C*/ /*2.C*/
int add(); int add();
main()
{ 
int result; int result;
result=add() result=add();
printf(%d ,result); printf(%d ,result);
result=add(); result=add();
printf(%d ,result); printf(%d ,result);
result=add(); result=add();
printf(%d,result); printf(%d,result);
} 

int add() 
{ 
int num=50; static int num=50;
num++; num++;
return num; return num;
} 
上面兩個源文件,只有函數add()裏的變量聲明有所不同,一個是自動存儲類型,一個是靜態存儲類型。
對於1.C文件,輸出結果爲51 51 51;這很好理解,每次初始值都是50,然後加1上來。
對於2.C文件,輸出結果爲51 52 53;這是由於變量是靜態的,只在第一次初始化了50,以後都是使用上次的結果值。當第一次調用add()時,初始化爲50,然後加1,輸出爲51;當第二次調用時,就不初始化了,這時num的值爲上次的51,然後加1,輸出52;當第三次調用時,num爲52,加1就是53了。
比較就會發現它們的不同之處了。靜態變量在下一節要說的遞歸函數中經常使用到。
當第一次不指明靜態變量的初始值時,默認爲0。

下面舉一個例子,把我們說到的靜態變量理解一下。
求1+2+……+100的值
void add();
int result;
main()
{
int i;
result=0;
for(i=0;i<100;i++) add();
printf(%d\n,result);
}

void add()
{
static int num=0;
num++;
result+=num;
}
add()函數被調用了100次,num的值從1一直變到100,這樣就可以求出它們的和了。如果寫成int num=0;那就是求1+1+……+1這100個1的值了。
實際上類似的這類問題我們可以通過遞歸函數來解決,什麼是遞歸,我們下一節介紹。
3.外部存儲類型
外部存儲類型聲明瞭程序將要用到的、但尚未定義的外部變量。通常,外部存儲類型都是用於聲明在另一個轉換單元中定義的變量。下面舉一個例子,這個例子包括兩個文件。
/*1.C*/
void a();
main()
{
extern int num;
a();
printf(%d\n,num);
}

/*2.C*/
int num;
void a()
{
num=5;
}
這兩個程序是分別編譯的,然後連接成一個執行文件。具體如何操作,可以查看一些手冊,這兒我簡單說了一下。把上面兩個文件都編譯好後,再製作一個.prj文件,裏面的內容是:
1.c
2.c

只有這兩行,這可在編輯狀態下寫成,存盤,取名爲1.prj。
然後選擇project選項,選擇project name,填入1.prj文件名,按F9後,即可生成1.exe文件。

main()函數中變量num是在另一個文件中定義的。因此,當編譯器編譯1.c時,無法確定該變量的地址。這時,外部存儲類型聲明告訴編譯器,把所有對num的引用當作暫且無法確定的引用,等到所有便宜好的目標代碼連接成一個可執行程序模塊時,再來處理對變量num的引用。
外部變量的聲明既可以在引用它的函數的內部,也可以在外部。如果變量聲明在函數外部,那麼同一轉換單元內的所有函數都可以使用這個外部變量。反之,如果在函數內部,那麼只有這一個函數可以使用該變量。

前面說了文件作用域的問題,如果在聲明全局變量時,加上static修飾符,那麼該變量只在當前文件內可見,而extern又可以引用其它文件裏的變量。所以在一個大型程序中,每個程序員只是完成其中的一小塊,爲了讓自己的變量不讓其他程序員使用,保持一定的獨立性,經常在全局變量前加static。我們可以這樣來說明一下:
還是上面的兩個文件,現在再增加一個文件3.c,內容爲:
static int num;
void a()
{
num=6;
}
把1.prj文件後面加上3.c 這樣,我們生成的1.exe文件,執行時輸出是5,而不是6。因爲3.c文件的num變量增加了文件作用域,在其他文件中是無法使用它的。
4.寄存器存儲類型
被聲明爲寄存器存儲類型的變量,除了程序無法得到其地址外,其餘都和自動變量一樣。至於什麼是變量地址,以後說指針時會詳細介紹。
main()
{
register int num;
num=100;
printf(%d,num);
}
使用寄存器存儲類型的目的是讓程序員指定某個局部變量存放在計算機的某個硬件寄存器裏而不是內存中,以提高程序的運行速度。不過,這只是反映了程序員的主觀意願,編譯器可以忽略寄存器存儲類型修飾符。
寄存器變量的地址是無法取得的,因爲絕大多數計算機的硬件寄存器都不佔用內存地址。而且,即使編譯器忽略寄存器類型修飾符把變量放在可設定地址的內存中,我們也無法取地址的限制仍然存在。
要想有效的利用寄存器存儲類型,必須象彙編語言程序員那樣瞭解處理器的內部構造,知道可用於存放變量的寄存器的數量和種類,以及他們是如何工作的。但是,不同計算機在這些細節上未必是一樣的,因此對於一個可移植的程序來說,寄存器存儲類型的作用不大。特別是現在很多編譯器都能提供很好的優化效果,遠比程序員來選擇有效的多。不過,寄存器存儲類型還是可以爲優化器提供重要的參考。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章