巨集(define)與常量(const)
既然選擇了遠方,便只顧風雨兼程。
巨集(define)
一. 巨集的理解
巨集是一種批量處理的稱謂。一般說來,巨集是一種規則或模式,或稱語法替換 ,用於說明某一特定輸入(通常是字串)如何根據預定義的規則轉換成對應的輸出(通常也是字串)。這種替換在預編譯時進行,稱作巨集展開。編譯器會在編譯前掃描程式碼,如果遇到我們已經定義好的巨集那麼就會進行程式碼替換,巨集只會在記憶體中copy一份,然後全域性替換,巨集一般分為物件巨集和函式巨集。
巨集的優點:
- 提高了程式的可讀性,同時也方便進行修改,使用者只需要在一處定義,多處使用,修改也只需要修改一處;
巨集的缺點:
- 只在預處理裡做文字替換,沒有型別,不做型別檢查。大量使用巨集會導致二進位制檔案變大,會使編譯時間變長;
二. 物件巨集
語法示例: #define M_PI 3.141592
,專案中常用的物件巨集如下所示。
#ifndef HBObjectMacro_h #define HBObjectMacro_h // 獲取iOS版本號 #define kIOSVersions [[[UIDevice currentDevice] systemVersion] floatValue] // 獲取window #define kUIWindow [[[UIApplication sharedApplication] delegate] window] // 獲取螢幕寬 #define kSCREEN_WIDTH [UIScreen mainScreen].bounds.size.width // 獲取螢幕高 #define kSCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height // 獲取狀態列高度(20、44) #define kHeight_StatusBar [[UIApplication sharedApplication] statusBarFrame].size.height // 獲取導航欄高度 #define kHeight_NavigationBar 44.f // 獲取導航欄加狀態列高度 #define kHeight_NavBar (kHeight_StatusBar > 20?88.f:64.f) // 獲取非安全區高度 #define kHeight_NoSafeArea (kHeight_StatusBar > 20?34.f:0.f) // 獲取Tabbar高度加非安全區高度 #define kHeight_Tabbar (kHeight_StatusBar > 20?83.f:49.f) // 判斷是否為iPhone #define IS_IPHONE ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) // 判斷是否為iPad #define IS_IPAD ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) // 判斷是否為iPod #define IS_IPOD ([[[UIDevice currentDevice] model] isEqualToString: @"iPod touch"]) // 弱引用__weak #define kWeakObj(weakName,objName) __weak typeof(&*objName)weakName = objName; // 強引用__strong #define kStrongObj(strongName,objName) __strong typeof(&*objName)strongName = objName; // 資料持久化 #define kUserDefaults [NSUserDefaults standardUserDefaults] // 通知中心 #define kNotificationCenter [NSNotificationCenter defaultCenter] // 設定圖片基礎方法 #define kUIImage(imageName) [UIImage imageNamed:imageName] // 設定字型基礎方法 #define kUIFont(size) [UIFont systemFontOfSize:size] // 設定字型基礎方法 #define kUIBoldFont(size) [UIFont boldSystemFontOfSize:size] // 設定隨機顏色 #define kRandomColor [UIColor colorWithRed:arc4random_uniform(256)/255.0 green:arc4random_uniform(256)/255.0 blue:arc4random_uniform(256)/255.0 alpha:1.0] // 設定RGB顏色 #define kRGBColor(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0] // 設定RGBA顏色 #define kRGBAColor(r, g, b, a) [UIColor colorWithRed:(r)/255.0 green:(r)/255.0 blue:(r)/255.0 alpha:a] // 這是clear背景顏色 #define kClearColor [UIColor clearColor] #endif /* HBObjectMacro_h */
三. 函式巨集
函式巨集的作用就類似於一個函式一樣,函式一般程式碼量比較多,這時如果函式巨集過長,我們可以使用\
進行換行處理,這樣提升了程式碼的可讀性。
#define IS_LEAP_YEAR2(y) y%4==0&&y%100!=0 \
||y%400==0
四. 預編譯指令和運算子
接下來我們瞭解下巨集定義中常用的預編譯指令和運算子。
4.1 預編譯指令
# 空指令,無任何效果 #define 定義巨集 #undef 取消已定義的巨集 #if 如果給定條件為真,則編譯下面程式碼 #ifdef 如果巨集已經定義,則編譯下面程式碼 #ifndef 如果巨集沒有定義,則編譯下面程式碼 #elif 如果前面的#if給定條件不為真,當前條件為真,則編譯下面程式碼 #endif 結束一個#if……#else條件編譯塊 #error 停止編譯並顯示錯誤資訊
4.2 常用運算子
-
\
換行處理。 -
#
運算子:在OC中使用字串都需要使用@"",如果想直接使用字串可以新增一個#
運算子。出現在巨集定義中的#
運算子把跟在其後的引數轉換成一個字串。有時把這種用法的#
稱為字串化運算子。# define Test(n) "Test"#n // 呼叫: Test(Demo); // 列印:TestDemo
-
##
運算子:用於將相鄰的兩個標記(Token)連線為一個。使用時先分隔(根據空格或其他操作分隔符[+,-,*,/,”,”等]),再強制連線(去掉和前面的字串間的空格,再連線起來)。#define Data(a, b, c) a##b##c NSLog(@"%d", Data(1, 2, 3)); // 123
-
@#
:字元化操作符,只能用於有引數傳入的巨集定義中,必須置於巨集定義體引數名前,作用是將傳入的單字元引數名轉換成字元,以一對單引號括起來。
五. 預定義巨集和可變引數巨集
5.1 C語言中預定義巨集
__FILE__ :當前原始碼的檔名(字串)
__LINE__:當前原始碼中的行號(整型)
__DATE__:進行預處理的日期(”Mmm dd yyyy”形式的字串)
__TIME__:原始檔編譯時間(格式“hh:mm:ss”)
__FUNCTION__:同__func__(但IDE不支援),當前原始碼的函式名
__PRETTY_FUNCTION__:同__FUNCITON__,但在g++下會輸類名、函式名及其他函式資訊
例:
5.2. 可變引數巨集
可變引數巨集:#define DBGMSG(format, ...) fprintf (stderr, format, __VA_ARGS__)
,…
表示一個可變化的引數表,變參必須放於最後一個引數;
__VA_ARGS__
:是一個可變引數的巨集,這個可變引數的巨集是新的C99規範中新增的,目前似乎只有gcc支援(VC6.0的編譯器不支援)。巨集前面加上##
的作用在於,當可變引數的個數為0時,這裡的##
起到把前面多餘的","去掉的作用,否則會編譯出錯;
#ifdef DEBUG
#define NSLog(format, ...) NSLog(@"%s(%d): " format, __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#else
#define NSLog(...)
#endif
常量(const)
在開發中,當我們想定義全域性共用的一些資料時,比如通知名字,伺服器的地址等等,我們經常使用用到巨集定義、變數,或者用const常量
來修飾這些資料型別。然而經常有開發者不知道怎麼正確使用這些修飾符,導致專案中亂用巨集定義與const修飾符。那麼我們該怎麼正確選擇使用呢?參考蘋果API的使用,建議儘量使用const來定義,為什麼要這麼做呢?下面我們先從基本概念方面來了解下他們之間的聯絡和區別。
-
巨集:用法,一般字串抽成巨集,程式碼抽成巨集使用。巨集只是在預處理階段進行文字替換,沒有型別,不做任何型別檢查,編譯器可以對相同的字串進行優化,只儲存一份到資料段。甚至有相同字尾的字串也可以優化,你可以使用GCC編譯測試,
Hello world
與world
兩個字串,只儲存前面一個。取的時候只需要給前面和中間的地址,如果是整型、浮點型會有多分拷貝,但這些數寫在指令中,佔的只是程式碼片段而且,大量使用巨集會導致二進位制檔案變大。#define URL @"http://www.xx.xx"
-
變數:共享一塊記憶體空間,就算專案中多處用到,也不會分配多塊記憶體空間,可以被修改,在編譯階段做型別檢查。
NSString *url = @"http://www.xx.xx";
-
常量:共享一塊記憶體空間,就算專案中多處用到,也不會分配多塊記憶體空間,可以根據
const
修飾的位置設定能夠修改,在編譯階段做型別檢查。一般常用的字串定義成const(對於常量字串蘋果推薦我們使用const)。-
常量區分:
-
全域性常量:不管你定義在任何資料夾,外部都能訪問;
// ViewController.m檔案中 定義全域性常量 const NSString *url = @"http://www.xx.xx"; // AppDelegate.m檔案中 訪問全域性變數 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. // 訪問全域性常量 extern NSString *url; NSLog(@"008 - 巨集定義 - 訪問全域性常量 = %@",url); return YES; } // 輸出:2021-09-06 15:44:20.151408+0800 BaseGrammar[95798:357990] 008 - 巨集定義 - 訪問全域性常量 = http://www.xx.xx
-
區域性常量:用static修飾後,不能供外界訪問;
static const NSString *url = @"http://www.xx.xx";
-
-
const位置不同,代表什麼:
// 寫法一:*url不能被修改,url能被修改 const NSString *url = @"http://www.xx.xx"; // 寫法二:*url不能被修改,url能被修改 NSString const *url = @"http://www.xx.xx"; // 寫法三:url不能被修改,*url能被修改 NSString * const url = @"http://www.xx.xx";
由以上程式碼我們可以總結為:
const
右邊的總不能被修改。所以我們一般定義一個常量又不想被修改,應該這樣定義:NSString * const url = @"http://www.xx.xx";
-
常量的規範使用,一般專案裡,定義全域性常量,會寫在獨立檔案裡:
// HBConst.m檔案定義常量 #import "HBConst.h" // 定義常量 /** 網路請求地址*/ NSString * const HBUrl = @"http://www.xx.xx"; /** cell間距 */ CGFloat const HBCellMargin = 4.0;
// HBConst.h檔案提供外接訪問常量 /** 網路請求地址*/ UIKIT_EXTERN NSString * const HBUrl; /** cell間距 */ UIKIT_EXTERN CGFloat const HBCellMargin;
巨集與const區別:
-
- 編譯時刻不同,巨集
(define)
屬於預編譯,在預處理階段進行替換;const
常量在編譯階段使用; - 巨集
(define)
可以定義程式碼(如函式),const
不可以; - 巨集
(define)
不做型別檢查,只進行替換,const
常量有資料型別,會執行型別檢查; - 巨集
(define)
定義的常量在替換後執行過程中,會不斷佔用記憶體,而const
定義的常量儲存在資料段,只有一份拷貝,效率更高;
最後建議,我們以後在開發中如果定義一個常量字串就用const,定義程式碼就用巨集。