1. 程式人生 > 其它 >巨集(define)與常量(const)

巨集(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 worldworld兩個字串,只儲存前面一個。取的時候只需要給前面和中間的地址,如果是整型、浮點型會有多分拷貝,但這些數寫在指令中,佔的只是程式碼片段而且,大量使用巨集會導致二進位制檔案變大。

    #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區別:

  1. 編譯時刻不同,巨集(define)屬於預編譯,在預處理階段進行替換;const常量在編譯階段使用;
  2. 巨集(define)可以定義程式碼(如函式),const不可以;
  3. 巨集(define)不做型別檢查,只進行替換,const常量有資料型別,會執行型別檢查;
  4. 巨集(define)定義的常量在替換後執行過程中,會不斷佔用記憶體,而const定義的常量儲存在資料段,只有一份拷貝,效率更高;

  最後建議,我們以後在開發中如果定義一個常量字串就用const,定義程式碼就用巨集。