C語言中標識符的作用域、命名空間、鏈接屬性、生命週期、存儲類型

        無論學習哪一種語言,都免不了要討論這些問題。而且這些問題,深究起來有時也讓我們很迷惑。

       標識符的定義無需多講,只需注意不僅僅是指變量,還有函數標籤等。

1. 標識符的作用域

        作用域是指允許對標識符進行訪問的位置範圍。按照C99(章節6.2.1),C語言的作用域共有 4 種類型:文件作用域、代碼塊作用域、函數作用域、函數原型作用域。

類型

位置

說明

文件作用域  (file) 在所有 代碼塊和參數列表 之外 整個文件內都可以訪問
代碼塊作用域 ( block) 在“代碼塊”或者“函數的參數列表”內部 只有所在的代碼塊內可以訪問
函數作用域 (function) 函數體內 具有此作用域的只有一種語句:只有goto語句要使用的“語句標籤”。簡化爲一條規則:一個函數中的語句標籤(即label)不可相同。
函數原型作用域  (function prototype) 聲明的函數原型的參數列表中(注意與“函數定義”不同) 由於函數原型的參數名稱可以省略,即使不省略,也不要求和“函數定義”中的形參列表中名稱相同。 
只有一種情況會發生衝突:參數列表中的有重複的變量名。(這時編譯報錯: redefinition of parameter )

        說明:當出現兩個標識符名稱相同的情況,而且都屬於同一個命名空間,那麼在內層代碼塊,內層的那個標識符會隱藏外層的那個標識符。

舉例說明並分析

  1. int my_func(int a, int b);  /* myfunc是“文件作用域”;a,b是 “函數原型作用域” */   
  2. int a;/* a是文件作用域。 注意:雖然上面的函數原型中將參數名稱聲明爲a, 但是由於作用域不同,是合法的。下一行的b也是這種情況 */   
  3. static int b; /* b是文件作用域 */   
  4. int d( int n ){ /* d是“文件作用域”。因爲這是函數定義,而不是函數原型,所以形式參數n 是“代碼塊作用域” */  
  5.                 /* 由於形式參數中已經聲明n,那麼在函數體內的最外層變量的名稱就不能再爲n,因爲同一個作用域內不允許對同一個變量進行多次聲明。  
  6.                       如果聲明,編譯器會提示重複聲明變量。(在某些較老版本的編譯器是允許的,但是C99標準是不允許的)  
  7.                     在不同的作用域內可以 */  
  8.     int f;  /* f是代碼塊作用域 */  
  9.       
  10.     int g(int k);   /* 函數原型,位於函數體代碼塊內。聲明的函數名稱g是“代碼塊作用域”,參數k是“函數原型作用域” */  
  11.     
  12.   my_label:  /* 定義一個label,是“函數作用域” */       
  13.      
  14.     ...  /*  下面的代碼塊可以是while循環、for循環或if語言等等*/   
  15.     {    
  16.         int f, g, i; /* 都是代碼塊作用域,而且只是在內層代碼塊,在外層代碼塊不可見 */  
  17.                     /* 對於f,外層已經存在f,這裏會隱藏掉外層的f,即在這個內層代碼塊中無法訪問外層的f */  
  18.         int n;     /* 代碼塊作用域,由於這裏已經不是函數體內的最外層,所以可以聲明與函數的形式參數同名的變量, 
  19.                         同樣會隱藏掉外層的變量n   */   
  20.     }  
  21.     ...  /* 另外一個 代碼塊 */   
  22.     {  
  23.         int i;  /* 代碼塊作用域,雖然上面的一個內層代碼塊中已經存在i,但是由於這兩個代碼塊不存在嵌套關係,所以也不存在隱藏現象 */  
  24.     }  
  25. }  

    注意事項:

         1.  注意函數原型中的參數是“函數原型作用域”,而函數定義中的參數是“代碼塊作用域”。例如上面代碼中第一行的a,b和函數定義中的 n

         2.  由於函數定義中參數是“代碼塊作用域”,所以在函數體內的最外層的變量名稱不能再爲n,但是內層嵌套的代碼塊變量名稱可以爲n。雖然這條特性在某些較老版本的編譯器中是可以的,但是在ANSI C中師不允許的。

         3.  變量的隱藏只是針對嵌套的作用域,對於不嵌套的作用域就沒有這個說法。例如上面例子中的變量 f 是嵌套的,而 i 是不嵌套的,所以內層的 f 會隱藏掉外層的 f ,但是 i 不會相互隱藏。

2. 標識符的命名空間

        命名空間是爲了解決 “在相同作用域內如何區分 相同的標識符”。 
        說明:①只有在相同作用域的情況下才能使用到命名空間去區分標識符,在嵌套的作用域不同的作用域區分標識符都用不到命名空間的概念。 
                  相同的作用域內,如果命名空間不同,標識符可以使用相同的名稱。否則,即如果命名空間不同,編譯器會報錯,提示重複定義。

        按照C99(章節6.2.3),命名空間可以分爲四種:

            2.1  所有的標籤(label)都屬於同一個命名空間。 
                    說明:①在同一個函數內,你的標籤不能相同。在同一個函數內,標籤可以和其他變量名稱相同。因爲它們所屬的命名空間不同。

            2.2  struct、enum和union的名稱,在C99中稱之爲tag,所有的tag屬於同一個命名空間。 
                   也就是說,如果你已經聲明struct A { int a }; 就不能在聲明 union A{ int a };

                   說明:之所以讓所有的tag組成一個命名空間,由於Tag前面總是帶struct,enum或union關鍵字,所以編譯器可以將它們與其他的標識符區分開。

           2.3  struct和union的成員屬於一個命名空間,而且是相互獨立的。例如:如果你已經聲明struct A { int a }; 
                  其成員的名稱爲a,你仍然可以聲明 struct B{ int a };或者union B{ int a };

                  說明:之所以讓struct和union的成員各自成爲一個命名空間,是因爲它們的成員訪問時,需要通過 "."或"->"運算符,而不會單獨使用,所以編譯器可以將它們與其他的標識符區分開。由於枚舉類型enum的成員可以單獨使用,所以枚舉類型的成員不在這一名稱空間內。

           2.4  其他所有的標識符,屬於同一個名稱空間。包括變量名、函數名、函數參數,宏定義typedef的類型名、enum的成員 等等。 
                  注意:如果標識符出現重名的情況,宏定義覆蓋所有其它標識符,這是因爲它在預處理階段而不是編譯階段處理。除了宏定義之外其它類別的標識符,處理規則是:內層作用域會隱藏掉外層作用域的標識符。

舉例說明並分析

  1. ">#include <stdio.h>   
  2. #include <stdlib.h>  
  3.   
  4. int main(){  
  5.     struct A{   /* “結構體的tag”和“結構體成員”不在同一個命名空間,所以名稱可以相同 */   
  6.         int A;  
  7.     };  
  8.     union B{  /* 根據第二條,這個union的tag不能是A,但是根據第三條,其成員的名稱可以與struct A的成員名稱相同 */  
  9.         int A;  
  10.     };  
  11.     struct A A; /* “結構體的tag”和“普通變量”不在同一個命名空間,所以名稱可以相同 */   
  12.     union B B;  /* 上面的“結構體變量”和 這行的“聯合體變量”屬於同一個命名空間,名稱不能相同,即不能是 union B A */  
  13.     int my_label = 1; /* “普通變量”和“標籤”不屬於同一個命名空間,所以名稱可以相同 */  
  14.     A.A = 1;  
  15.     B.A = 20;  
  16.     printf("B.A == %d  /n/n", B.A);       
  17.       
  18.   my_label:     /* 這裏label 的名稱與上面變量的名稱 相同 */   
  19.     printf("A.A == %d  /n", A.A);  
  20.     A.A +=1;  
  21.     if(A.A <= 5){  
  22.         goto my_label;      
  23.     }  
  24.       
  25.     system("pause");  
  26.     return EXIT_SUCCESS;  
  27. }  

運行結果爲:

  1. B.A == 20  
  2.   
  3. A.A == 1  
  4. A.A == 2  
  5. A.A == 3  
  6. A.A == 4  
  7. A.A == 5  

 

3. 標識符的鏈接屬性

        主要用於處理多次聲明相同的標識符名稱後,如何判斷這些標識符是否是同一個。  
        原文對鏈接屬性(linkage)的定義如下:An identi

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章