- 重要的概念
標識符: C語言中變量名, 函數名, 函數參數名都稱爲標識符.
作用域: 標識符的作用域指的是此標誌符在當前源代碼中可見(可被訪問)的範圍
鏈接屬性: 標識符的鏈接屬性指的是如何處理不同文件中出現的相同標識符
存儲時期: 變量的存儲時期指的是變量內存在程序執行中存在的時期
存儲類型: 變量的存儲類指的是存儲變量的內存的類型
存儲類說明符: C語言的存儲類說明符用來改變標識符, 變量的鏈接屬性, 存儲時期和存儲類型.
C語言的標識符有4種不同類型的作用域:
- 代碼塊作用域
位於一對花括號之間的所有語句稱爲一個代碼塊, 在任意代碼塊內 聲明 的標識符具有代碼塊作用域, 作用域爲該標識符聲明的位置到代碼塊結束花括號..
#include <stdio.h>
int main(void)
{ // 1
int num; // 具有代碼塊作用域, 作用域爲1~2
for(int i = 0; i < 10; i++){ // 3, i也具有代碼塊作用域, 作用域爲3~4
char ch; // 具有代碼塊作用域, 作用域爲3~4
...
} // 4
} // 2
- 文件作用域
一個源代碼文件表示一個文件單位, 在所有代碼塊之外聲明的標識符都具有文件作用域, 從它們聲明之處到文件結尾都可以被訪問到. 比如在 main 函數外聲明的變量.
// main.c文件
#include <stdio.h>
int n = 1; // 具有文件作用域, 在整個main.c文件中可見
void print(void); // print具有文件作用域
int main()
{
printf("%d", n); // main函數中訪問n
return 0;
}
void print(void)
{
printf("%d", n); // 自定義函數中訪問n
}
- 原型作用域
在函數原型(函數聲明)中, 形參列表中的形參名具有原型作用域. 形參名只在形參列表中可見(兩個圓括號之間).
// 函數原型
void print(int n, char ch); // n和ch只在形參列表中可見
- 函數作用域
簡單來說, 函數作用域指的是語句標籤可見範圍爲函數內. 由於語句標籤更 goto 相關, 而使用goto是不好的習慣, 所以不討論函數作用域.
C語言標識符有3種不同的鏈接屬性:
- external(外部)鏈接屬性
具有external(外部)鏈接屬性的標識符在不同源文件的均表示同一實體.
- 具有文件作用域的標識符在缺省存儲類說明符的情況下具有external(外部)鏈接屬性
- 使用extern關鍵字可以改變標識符的鏈接屬性. 詳細的用法
// main.c
#include <stdio.h>
int n = 3; // n具有文件作用域, 外部鏈接屬性
void print(void); // print具有文件作用域外部鏈接屬性
int main(void)
{
print();
return 0;
}
// print.c
void print(void)
{
printf("hello world");
}
internal(內部)鏈接屬性
具有internal(內部)連接屬性的標識符在同一源文件內的所有聲明都指向同一實體, 但是位於不同源文件的多個聲明則分屬不同的實體.- 正常情況下具有external(外部)鏈接屬性的標識符使用static存儲類說明符修飾後具有internal(內部)鏈接屬性
none(無)鏈接屬性
none(無)鏈接屬性的標識符的多個聲明均表示不同實體.- 具有代碼塊作用域或者函數原型作用域的標識符具有none(無)鏈接屬性
C語言變量有2種存儲時期
auto(自動)存儲期
具有自動存儲時期的變量在定義變量時分配內存, 在退出定義變量的代碼塊時銷燬內存.- 具有代碼塊作用域的標識符代表的變量在缺省存儲類修飾符的情況下具有auto(自動)存儲期
static(靜態)存儲期
具有靜態存儲期的變量在程序執行的整個時期都存在.- 具有文件作用域的標識符代表的變量具有static(靜態)存儲期
- 具有代碼塊作用域的標識符代表的變量在使用static存儲類修飾符後具有static(靜態)存儲期
C語言變量有3種存儲類型
靜態內存
在任何代碼塊之外聲明的變量存儲在靜態內存中.- 具有文件作用域的標識符代表的變量存儲於靜態內存中
- 具有代碼塊作用域的標識符代表的變量在使用static存儲類修飾符後也存儲於靜態內存中
運行時堆棧
在代碼塊內部聲明的變量缺省情況下是存儲在運行時堆棧中.- 具有代碼塊作用域的標識符代表的變量在缺省存儲類修飾符的情況下存儲與運行時堆棧
硬件寄存器
使用 register 關鍵字聲明的變量優先存儲在寄存器中, 可以更加快速的讀取訪問.- 具有代碼塊作用域的標識符代表的變量在使用register存儲類修飾符後可能存儲於硬件寄存器中
C語言有5種存儲類說明符
詳細的用法
關於本文的幾個關鍵點:
分清楚標識符與變量的概念
標識符是從源代碼文件的角度定義的, 指的就是源代碼中變量的名字, 函數的名字, 參數的名字等. 變量更多時候是從內存的角度定義的, 指的是一段內存空間. 顯然, 變量名標識符有時是與變量內存 “綁定” 的.標識符的屬性
作用域與鏈接屬性指的是標識符的屬性. 作用域表示某個標識符的可訪問範圍, 鏈接屬性表示看待不同文件中出現的相同的標識符的方式.變量的屬性
存儲時期和存儲類型都是針對變量來說的. 存儲時期反映了變量在何時創建何時銷燬, 存儲類型反映了變量 “放在哪”.存儲時期與存儲類型的關係
從上面可以看出, 存儲類型爲寄存器的變量是自動存儲期變量的一種特殊情況. 可以用下圖反應二者的關係:
標識符的作用域只與標誌聲明的位置有關
存儲類說明符無法改變標識符的作用域. 確定標識符的作用域至關重要.
這裏可能會有個疑問, 比如, 標識符最大的作用域是一個文件, 那麼 爲什麼在一個文件中定義的函數可以在另一個文件中調用呢? 其實這是通過 聲明 和 外部鏈接 來實現的. 首先, 既然我們想在main.c文件中調用print.c文件中定義的函數, 而函數名標識符的作用域是print,c文件, 那我們就要在main.c文件中再次聲明(注意, 不是定義)這個函數; 但是, 程序又怎麼知道main.c文件中的print函數指的就是print.c文件中的print函數呢? 所以我們需要設置函數名標識符爲外部鏈接屬性, 這樣print標識符在main.c和print.c中指向的就是同一實體. 事實上, 由於 具有文件作用域的標識符在缺省存儲類說明符的情況下具有external(外部)鏈接屬性 所以函數名默認爲外部鏈接屬性, 我們不需要顯式的設置. 那麼對於變量標識符呢? 如何實現在一個文件中使用另一個文件中定義的變量呢? 方法是同樣的. 但是對於變量標識符會帶來一個問題. 我們可以很容易的區分函數聲明與函數定義, 但是不那麼容易區分變量聲明與變量定義. 比如我們在main.c中定義了int i = 5;
想要在print.c中使用i
時就需要再次聲明i
如果簡單的在print.c中添加int i;
會造成i
的重複定義. 這是不允許的. 這時就需要使用extern
關鍵字再次聲明而不是定義i
.存儲類型說明符獨立的修改標識符鏈接屬性和變量存儲類型
存儲類型說明符可以修改標識符的鏈接屬性和變量的存儲類型(存儲時期和存儲類型相關), 並且這種修改是獨立的. 比如,static
關鍵字用於具有文件作用域的標識符時修改的就是該標識符的鏈接屬性, 而作用於代碼塊內定義變量時修改的就是該變量的存儲類型.