無論學習哪一種語言,都免不了要討論這些問題。而且這些問題,深究起來有時也讓我們很迷惑。
標識符的定義無需多講,只需注意不僅僅是指變量,還有函數,標籤等。
1. 標識符的作用域
作用域是指允許對標識符進行訪問的位置範圍。按照C99(章節6.2.1),C語言的作用域共有 4 種類型:文件作用域、代碼塊作用域、函數作用域、函數原型作用域。
類型 | 位置 | 說明 |
文件作用域 (file) | 在所有 代碼塊和參數列表 之外 | 整個文件內都可以訪問 |
代碼塊作用域 ( block) | 在“代碼塊”或者“函數的參數列表”內部 | 只有所在的代碼塊內可以訪問 |
函數作用域 (function) | 函數體內 | 具有此作用域的只有一種語句:只有goto語句要使用的“語句標籤”。簡化爲一條規則:一個函數中的語句標籤(即label)不可相同。 |
函數原型作用域 (function prototype) | 聲明的函數原型的參數列表中(注意與“函數定義”不同) | 由於函數原型的參數名稱可以省略,即使不省略,也不要求和“函數定義”中的形參列表中名稱相同。 只有一種情況會發生衝突:參數列表中的有重複的變量名。(這時編譯報錯: redefinition of parameter ) |
說明:當出現兩個標識符名稱相同的情況,而且都屬於同一個命名空間,那麼在內層代碼塊,內層的那個標識符會隱藏外層的那個標識符。
舉例說明並分析:
- int my_func(int a, int b); /* myfunc是“文件作用域”;a,b是 “函數原型作用域” */
- int a;/* a是文件作用域。 注意:雖然上面的函數原型中將參數名稱聲明爲a, 但是由於作用域不同,是合法的。下一行的b也是這種情況 */
- static int b; /* b是文件作用域 */
- int d( int n ){ /* d是“文件作用域”。因爲這是函數定義,而不是函數原型,所以形式參數n 是“代碼塊作用域” */
- /* 由於形式參數中已經聲明n,那麼在函數體內的最外層變量的名稱就不能再爲n,因爲同一個作用域內不允許對同一個變量進行多次聲明。
- 如果聲明,編譯器會提示重複聲明變量。(在某些較老版本的編譯器是允許的,但是C99標準是不允許的)
- 在不同的作用域內可以 */
- int f; /* f是代碼塊作用域 */
- int g(int k); /* 函數原型,位於函數體代碼塊內。聲明的函數名稱g是“代碼塊作用域”,參數k是“函數原型作用域” */
- my_label: /* 定義一個label,是“函數作用域” */
- ... /* 下面的代碼塊可以是while循環、for循環或if語言等等*/
- {
- int f, g, i; /* 都是代碼塊作用域,而且只是在內層代碼塊,在外層代碼塊不可見 */
- /* 對於f,外層已經存在f,這裏會隱藏掉外層的f,即在這個內層代碼塊中無法訪問外層的f */
- int n; /* 代碼塊作用域,由於這裏已經不是函數體內的最外層,所以可以聲明與函數的形式參數同名的變量,
- 同樣會隱藏掉外層的變量n */
- }
- ... /* 另外一個 代碼塊 */
- {
- int i; /* 代碼塊作用域,雖然上面的一個內層代碼塊中已經存在i,但是由於這兩個代碼塊不存在嵌套關係,所以也不存在隱藏現象 */
- }
- }
注意事項:
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的
成員 等等。
注意:如果標識符出現重名的情況,宏定義覆蓋所有其它標識符,這是因爲它在預處理階段而不是編譯階段處理。除了宏定義之外其它類別的標識符,處理規則是:內層作用域會隱藏掉外層作用域的標識符。
舉例說明並分析:
- ">#include <stdio.h>
- #include <stdlib.h>
- int main(){
- struct A{ /* “結構體的tag”和“結構體成員”不在同一個命名空間,所以名稱可以相同 */
- int A;
- };
- union B{ /* 根據第二條,這個union的tag不能是A,但是根據第三條,其成員的名稱可以與struct A的成員名稱相同 */
- int A;
- };
- struct A A; /* “結構體的tag”和“普通變量”不在同一個命名空間,所以名稱可以相同 */
- union B B; /* 上面的“結構體變量”和 這行的“聯合體變量”屬於同一個命名空間,名稱不能相同,即不能是 union B A */
- int my_label = 1; /* “普通變量”和“標籤”不屬於同一個命名空間,所以名稱可以相同 */
- A.A = 1;
- B.A = 20;
- printf("B.A == %d /n/n", B.A);
- my_label: /* 這裏label 的名稱與上面變量的名稱 相同 */
- printf("A.A == %d /n", A.A);
- A.A +=1;
- if(A.A <= 5){
- goto my_label;
- }
- system("pause");
- return EXIT_SUCCESS;
- }
運行結果爲:
- B.A == 20
- A.A == 1
- A.A == 2
- A.A == 3
- A.A == 4
- A.A == 5
3. 標識符的鏈接屬性
主要用於處理多次聲明相同的標識符名稱後,如何判斷這些標識符是否是同一個。
原文對鏈接屬性(linkage)的定義如下:An identi