第一章 熟悉Objective-C—第4條:多用型別常量,少用#define預處理指令
編寫程式碼時經常要定義變數。例如:
#define ANIMATION_DURATION 0.3
上述預處理指令會把原始碼中的ANIMATION_DURATION字串替換為0.3。這可能就是你想要的效果,不過這樣定義出來的常量沒有型別資訊。"持續"(duration)這個詞看上去應該與時間有關,但是程式碼中又未明確指出。此外,預處理過程會把碰到的所有ANIMATION_DURATION一律替換成0.3,這樣的話,假設此指令宣告在某個標頭檔案中,那麼所有引入了這個標頭檔案的程式碼,其ANIMATION_DRRATION都會被替換。
要想解決此問題,應該設法利用編譯器的某些特性才對。有個辦法比用預處理指令來定義常量更好。比方說,下面這行程式碼就定義了一個型別為NSTimeInterval的常量:
static const NSTimeInterval kAnimationDuration = 0.3;
請注意,用此方式定義的常量包含型別資訊,其好處是清楚地描述了常量的含義。由此可知該常量型別為NSTimeInterval,這有助於為其編寫開發文件。如果要定義許多常量,那麼這種方式能令稍後閱讀程式碼的人更易理解其意圖。
還要注意常量名稱。常用的命名法是:若常量侷限於某"編譯單元"(translation unit,也就是"實現檔案",implementation file)之內,則在前面加字母k;若常量在類之外可見,則通常以類名為字首。
定義常量的位置很重要。我們總喜歡在標頭檔案裡宣告預處理命令,這樣做真的很糟糕,當常量名稱有可能互相沖突時更是如此。例如,ANIMATION_DURATION這個常量名就不該用在標頭檔案中,因為所有引入了這份標頭檔案的其他檔案中都會出現這個名字。其實就連用static const定義的那個常量也不該出現在標頭檔案裡。因為Objective-C沒有"名稱空間"(namespace)這一概念,所以那樣做等於宣告瞭一個名叫kAnimationDuration的全域性變數。此名稱應該加上字首,以表明其所屬的類,例如可改為EOCViewClassAnimationDuration。
若不打算公開某個常量,則應將其定義在使用該常量的實現檔案裡。比方說,要開發一個使用UIKit框架的iOS應用程式,其UIView子類中含有表示動畫播放時間的常量,那麼可以這樣寫:
//EOCAnimatedView.h
#import <UIKit/UIKit.h>
@interface EOCAnimatedView : UIView
- (void)animate;
@end
//EOCAnimatedView.m
#import "EOCAnimatedView.h"
static const NSTimeInterval kAnimationDuration = 0.3;
@implementation EOCAnimatedView
- (void)animate {
[UIView animateWithDuration:kAnimationDuration
animations:^(){
//Perform animations
}];
}
@end
變數一定要同時用static與const來宣告。如果試圖修改由const修飾符所宣告的變數,那麼編譯器就會報錯。在本例中,我們正是希望這樣:因為動畫播放時長為定值,所以不應修改。而static修飾符則意味著該變數僅在定義此變數的編譯單元中可見。編譯器每收到一個編譯單元,就會輸出一份"目標檔案"(object file)。在Objective-C的語境下,"編譯單元"一詞通常指每個類的實現檔案(以.m為字尾名)。因此,在上述範例程式碼中宣告的kAnimationDuration變數,其作用域僅限於由EOCAnimatedView.m所生成的目標檔案中。假如宣告此變數時不加static,則編譯器會為它建立一個"外部符號"(external symbol)。此時,若是另一個編譯單元中也宣告瞭同名變數,那麼編譯器就丟擲一條錯誤訊息:
duplicate symbol _kAnimationDuration in:
EOCAnimatedView.o
EOCOtherView.o
實際上,如果一個變數既宣告為static,又宣告為const,那麼編譯器根本不會建立符號,而是會像#define預處理指令一樣,把所有遇到的變數都替換為常值。不過還是要記住:用這種方式定義的常量帶有型別資訊。
有時候需要對外公開某個常量。比方說,你可能要在類程式碼中呼叫NSNotificationCenter以通知他人。用一個物件來派發通知,令其他欲接收通知的物件向該物件註冊,這樣就能實現此功能了。派發通知時,需要使用字串來表示此項通知的名稱,而這個名字就可以宣告為一個外界可見的常值變數(constant variable)。這樣的話,註冊者無須知道實際字串值,只需以常值變數來註冊自己想要接收的通知即可。
此類常量需放在"全域性符號表"(global symbol table)中,以便可以在定義該常量的編譯單元之外使用。因此,其定義方式與上例演示的static const有所不同。應該這樣來定義:
//In the header file
extern NSString *const EOCStringConstant;
//In the implementation file
NSString *const EOCStringConstant = @"VALUE";
這個常量在標頭檔案中"宣告",且實現檔案中"定義"。注意const修飾符在常量型別中的位置。常量定義應從右至左解讀,所以在本例中,EOCStringConstant就是"一個常量,而這個常量是指標,指向NSString物件"。這與需求相符:我們不希望有人改變此指標常量,使其指向另一個NSString物件。
編譯器看到標頭檔案中的extern關鍵字,就能明白如何在引入此標頭檔案的程式碼中處理該常量了。這個關鍵字是要告訴編譯器,在全域性符號表中將會有一個名叫EOCStringConstant的符號。也就是說,編譯器無須檢視其定義,即允許程式碼使用此常量。因為它知道,當連結成二進位制檔案之後,肯定能找到這個常量。
此類常量必須要定義,而且只能定義一次。通常將其定義在與宣告該常量的標頭檔案相關的實現檔案裡。由實現檔案生成目標檔案時,編譯器會在"資料段"(data section)為字串分配儲存空間。連結器會把此目標檔案與其他目標檔案相連結,以生成最終的二進位制檔案。凡是用到EOCStringConstant這個全域性符號的地方,連結器都能將其解析。
因為符號要放在全域性符號表裡,所以命名常量時需謹慎。
總之,勿使用預處理指令定義常量,而應該藉助編譯器來確保常量正確,比方說可以在實現檔案中用static const來宣告常量,也可以宣告一些全域性常量。
相關文章
- 04@多用型別常量,少用#define預處理指令型別
- 多用型別常量替代#define預處理指令型別
- 預處理指令
- Git 少用 Pull 多用 Fetch 和 MergeGit
- JS指令碼批次處理TS資料型別JS指令碼資料型別
- PHP 定義常量 define 和 const的區別PHP
- 001@多用派發佇列,少用同步鎖佇列
- 機器學習 第4篇:資料預處理(sklearn 插補缺失值)機器學習
- yai 請求預處理指令碼AI指令碼
- .net 預處理指令符的使用
- iOS開發#pragma預處理指令iOS
- 預處理指令、構建大型程式
- 多用字面量語法, 少用與之等價的方法
- SQL Server TEXT型別欄位字串替換示例處理指令碼SQLServer型別字串指令碼
- 第 4 節:基礎資料型別資料型別
- define巨集定義和const常量定義之間的區別
- Objective-C:錯誤處理Object
- Objective-C型別推斷Object型別
- 程式錯誤型別及其處理型別
- CXF--處理複雜型別型別
- 03@多用字面量語法,少用與之等價的方法
- 第一章 型別型別
- sql語句中常量的處理SQL
- 機器學習 第2篇:資料預處理(缺失值)機器學習
- 機器學習 第3篇:資料預處理(使用插補法處理缺失值)機器學習
- doxygen 宏定義/宏編譯/條件編譯/預處理/預編譯 不處理、忽略條件、分析所有條件、滿足所有條件的方法編譯
- 兄弟連go教程(2)型別-常量Go型別
- Objective-C 轉 Swift 的第一道坎——論如何正確的處理可選型別ObjectSwift型別
- Laravel 處理 MySQL geometry 空間型別LaravelMySql型別
- MySQL 數值型別溢位處理MySql型別
- Oracle LOB資料型別的處理Oracle資料型別
- sqlite資料型別 datetime處理SQLite資料型別
- 條件型別型別
- C語言的本質(20)——預處理之二:條件預處理和包含標頭檔案C語言
- [PHP]常量定義: const和define區別和運用; 附constant解釋PHP
- 處理分頁的result型別問題型別
- JDBC 處理CLob和Blob型別資料JDBC型別
- 型別預設和any型別型別