在使用 Objective-C 編程的時(shí)候,偶爾也會(huì)使用到 C 語言的一些特性,extern、static、const 和 inline 這四個(gè)關(guān)鍵詞是我對(duì)于其含義較為模糊的四個(gè)關(guān)鍵詞,特寫一篇筆記用來溫習(xí)一下。
extern
要理解 extern 關(guān)鍵字的作用,首先要明白聲明和定義的區(qū)別。定義變量時(shí),編譯器為該變量分配內(nèi)存,并可能還將其內(nèi)容初始化為某個(gè)值。聲明變量時(shí),編譯器要求在其他地方定義變量。聲明通知編譯器存在由該名稱和類型的變量,但編譯器不需要為其分配內(nèi)存,因?yàn)樗窃谄渌胤椒峙涞?。extern 關(guān)鍵字表示“聲明而不定義”。換句話說,它是一種顯式聲明變量或強(qiáng)制聲明而無需定義的方法。
必須在程序的一個(gè)模塊中精確定義一個(gè)變量。 如果沒有定義或多于一個(gè)定義,則可能在鏈接階段產(chǎn)生錯(cuò)誤。 變量可以被聲明多次,只要聲明彼此一致并且與定義一致(頭文件很有用)。 它可以在許多模塊中聲明,包括定義它的模塊,甚至在同一模塊中多次。 但是在模塊中多次聲明它通常是沒有意義的。
extern 變量也可以在函數(shù)內(nèi)聲明。 在這種情況下,必須使用 extern 關(guān)鍵字,否則編譯器會(huì)將其視為本地(自動(dòng))變量的定義,該變量具有不同的范圍,生命周期和初始值。 此聲明僅在函數(shù)內(nèi)部可見,而不是在整個(gè)函數(shù)模塊中可見。
應(yīng)用于函數(shù)原型的 extern 關(guān)鍵字絕對(duì)沒有任何內(nèi)容(應(yīng)用于函數(shù)定義的 extern 關(guān)鍵字當(dāng)然是非語義的)。 函數(shù)原型始終是一個(gè)聲明,而不是一個(gè)定義。 此外,在標(biāo)準(zhǔn) C 中,函數(shù)始終是外部的,但某些編譯器擴(kuò)展允許在函數(shù)內(nèi)定義函數(shù)。
Example:
File 1:
// Explicit definition, this actually allocates
// as well as describing
int Global_Variable;
// Function prototype (declaration), assumes
// defined elsewhere, normally from include file.
void SomeFunction(void);
int main(void) {
Global_Variable = 1;
SomeFunction();
return 0;
}
File 2:
// Implicit declaration, this only describes and
// assumes allocated elsewhere, normally from include
extern int Global_Variable;
// Function header (definition)
void SomeFunction(void) {
++Global_Variable;
}
在此示例中,變量 Global_Variable 在 File 1 中定義。為了在 File 2 中使用相同的變量,必須聲明它。 無論文件數(shù)量多少,全局變量只定義一次; 但是,它必須在包含定義的文件之外的任何文件中聲明。
通常的方法是分配和實(shí)際定義進(jìn)入 .c 文件,但僅僅聲明和原型不分配,只是描述類型和參數(shù),以便編譯器可以正常工作,并且該信息屬于 .h 頭文件,其他人可以安全地 include 沒有任何可能的沖突。
static
在 C 編程語言(及其緊密的后代,如 C++ 和 Objective-C)中,static 是一個(gè)保留字,用于控制生命周期(作為靜態(tài)變量)和可見性(取決于鏈接)。
在聲明變量或函數(shù)時(shí)作為前綴的 static 關(guān)鍵字可能具有其他效果,具體取決于聲明發(fā)生的位置。
Static global variable
在源文件的頂層(在任何函數(shù)定義之外)聲明為 static 的變量?jī)H在整個(gè)文件中可見。 在此用法中,關(guān)鍵字 static 稱為“訪問說明符”。
Static function
類似地,靜態(tài)函數(shù) - 在源文件的頂層(在任何類定義之外)聲明為靜態(tài)的函數(shù) - 僅在整個(gè)文件中可見。
Static local variables
在函數(shù)內(nèi)聲明為靜態(tài)的變量是靜態(tài)分配的,因此在整個(gè)程序執(zhí)行期間保持其內(nèi)存單元,同時(shí)具有與自動(dòng)局部變量(auto 和 register)相同的可見范圍,這意味著保持函數(shù)的本地性。 因此,當(dāng)一次調(diào)用時(shí)函數(shù)放入其靜態(tài)局部變量的任何值在再次調(diào)用函數(shù)時(shí)仍然存在。
const
在 C 語言中,const 是類型的一部分,而不是對(duì)象的一部分。例如,在 C 中,const int x = 1; 聲明一個(gè) const int 類型的對(duì)象 x - const 是該類型的一部分,就好像它被解析為“(const int)x”
這有兩個(gè)微妙的結(jié)果。 首先,const 可以應(yīng)用于更復(fù)雜類型的部分 - 例如,int const * const x; 聲明一個(gè)指向常量整數(shù)的常量指針,而 int const * x; 聲明一個(gè)指向常量整數(shù)的變量指針,并且 int * const x; 聲明一個(gè)指向變量整數(shù)的常量指針。 其次,因?yàn)?const 是類型的一部分,所以它必須作為類型檢查的一部分匹配。
雖然常量在程序運(yùn)行時(shí)不會(huì)更改其值,但是在程序運(yùn)行時(shí),聲明為 const 的對(duì)象確實(shí)可能會(huì)更改其值。 一個(gè)常見的例子是嵌入式系統(tǒng)中的只讀寄存器,如數(shù)字輸入的當(dāng)前狀態(tài)。 數(shù)字輸入的數(shù)據(jù)寄存器通常聲明為 const 和 volatile。 這些寄存器的內(nèi)容可能會(huì)在程序執(zhí)行任何操作(volatile)時(shí)發(fā)生變化,但你也不應(yīng)該向它們寫入(const)。
使用方式
在 C 中,所有數(shù)據(jù)類型(包括用戶定義的數(shù)據(jù)類型)都可以聲明為 const,而 const-correctness 規(guī)定所有變量或?qū)ο蠖紤?yīng)該聲明為這樣,除非需要修改它們。 這種對(duì) const 的主動(dòng)使用使得值“更容易理解,跟蹤和推理”,從而提高了代碼的可讀性和可理解性,使團(tuán)隊(duì)工作和維護(hù)代碼更簡(jiǎn)單,因?yàn)樗鼈鬟_(dá)了有關(guān)值的預(yù)期用途的信息。 在推理代碼時(shí),這可以幫助編譯器和開發(fā)人員。 它還可以使優(yōu)化編譯器生成更高效的代碼。
簡(jiǎn)單的數(shù)據(jù)類型
對(duì)于簡(jiǎn)單的非指針數(shù)據(jù)類型,應(yīng)用 const 限定符很簡(jiǎn)單。 由于歷史原因,它可以在類型的任何一側(cè)出現(xiàn)(即,const char foo ='a';等同于 char const foo ='a';)。 在某些實(shí)現(xiàn)中,在類型的兩側(cè)使用 const(例如,const char const)會(huì)生成警告但不會(huì)生成錯(cuò)誤。
指針和引用
對(duì)于指針和引用類型,const 的含義更復(fù)雜 - 指針本身或指向的值或兩者都可以是 const。此外,語法可能令人困惑。指針可以聲明為指向可寫值的 const 指針,或指向 const 值的可寫指針,或指向 const 值的 const 指針。 const 指針不能重新分配以指向與最初分配的對(duì)象不同的對(duì)象,但它可用于修改它指向的值(稱為指針對(duì)象)。因此,引用變量是 const 指針的替代語法。另一方面,指向 const 對(duì)象的指針可以重新分配以指向另一個(gè)內(nèi)存位置(應(yīng)該是相同類型或可轉(zhuǎn)換類型的對(duì)象),但它不能用于修改它的內(nèi)存指向還可以聲明指向 const 對(duì)象的 const 指針,既不能用于修改指針,也不能重新指定指向另一個(gè)對(duì)象。以下代碼說明了這些細(xì)微之處:
void Foo( int * ptr,
int const * ptrToConst,
int * const constPtr,
int const * const constPtrToConst )
{
*ptr = 0; // OK: modifies the "pointee" data
ptr = NULL; // OK: modifies the pointer
*ptrToConst = 0; // Error! Cannot modify the "pointee" data
ptrToConst = NULL; // OK: modifies the pointer
*constPtr = 0; // OK: modifies the "pointee" data
constPtr = NULL; // Error! Cannot modify the pointer
*constPtrToConst = 0; // Error! Cannot modify the "pointee" data
constPtrToConst = NULL; // Error! Cannot modify the pointer
}
遵循通常的 C 約定聲明,聲明遵循使用,并且指針中的 * 寫在指針上,表示解除引用。 例如,在聲明 int *ptr 中,解除引用的形式 *ptr是一個(gè) int,而引用形式 ptr 是一個(gè)指向 int 的指針。 因此 const 將名稱修改為右側(cè)。
int *ptr; // *ptr is an int value
int const *ptrToConst; // *ptrToConst is a constant (int: integer value)
int * const constPtr; // constPtr is a constant (int *: integer pointer)
int const * const constPtrToConst; // constPtrToConst is a constant (pointer)
// as is *constPtrToConst (value)
inline
在 C 和 C++ 編程語言中,內(nèi)聯(lián)函數(shù)是使用關(guān)鍵字 inline 限定的函數(shù);這有兩個(gè)目的。首先,它充當(dāng)編譯器指令,建議(但不要求)編譯器通過執(zhí)行內(nèi)聯(lián)擴(kuò)展替換函數(shù)體,即通過在每個(gè)函數(shù)調(diào)用的地址處插入函數(shù)代碼,從而節(jié)省開銷一個(gè)函數(shù)調(diào)用。在這方面,它類似于寄存器存儲(chǔ)類說明符,它類似地提供了一個(gè)優(yōu)化提示。內(nèi)聯(lián)的第二個(gè)目的是改變鏈接行為;這個(gè)細(xì)節(jié)很復(fù)雜。這是必要的,因?yàn)?C/C++ 單獨(dú)的編譯+鏈接模型,特別是因?yàn)楹瘮?shù)的定義(主體)必須在使用它的所有轉(zhuǎn)換單元中重復(fù),以允許在編譯期間內(nèi)聯(lián),如果函數(shù)具有外部鏈接,在鏈接過程中導(dǎo)致沖突(它違反了外部符號(hào)的唯一性)。
內(nèi)聯(lián)函數(shù)可以用 C 或 C++ 編寫,如下所示:
static inline void swap(int *m, int *n)
{
int temp = *m;
*m = *n;
*n = temp;
}
然后,聲明如下:
swap(&x, &y);
可以被翻譯成(如果編譯器決定進(jìn)行內(nèi)聯(lián),通常需要啟用優(yōu)化):
int temp = x;
x = y;
y = temp;
當(dāng)實(shí)現(xiàn)執(zhí)行大量交換的排序算法時(shí),這可以提高執(zhí)行速度。