概述
在iOS開發中,經常用到宏定義,或用const修飾一些數據類型,經常有開發者不知怎么正確使用,導致項目中亂用宏與const修飾。你能區分下面的嗎?知道什么時候用嗎?
#define HSCoder @"漢斯哈哈哈" NSString *HSCoder = @"漢斯哈哈哈"; extern NSString *HSCoder; extern const NSString *HSCoder; static const NSString *HSCoder = @"漢斯哈哈哈"; const NSString *HSCoder = @"漢斯哈哈哈"; NSString const *HSCoder = @"漢斯哈哈哈"; NSString * const HSCoder = @"漢斯哈哈哈";
當我們想全局共用一些數據時,可以用宏、變量、常量
//宏 #define HSCoder @"漢斯哈哈哈" //變量 NSString *HSCoder = @"漢斯哈哈哈"; //常量,四種寫法 static const NSString *HSCoder = @"漢斯哈哈哈"; const NSString *HSCoder = @"漢斯哈哈哈"; NSString const *HSCoder = @"漢斯哈哈哈"; NSString * const HSCoder = @"漢斯哈哈哈";
宏、變量、常量之間的區別
- 宏:只是在預處理器里進行文本替換,沒有類型,不做任何類型檢查,編譯器可以對相同的字符串進行優化。只保存一份到 .rodata 段。甚至有相同后綴的字符串也可以優化,你可以用GCC 編譯測試,"Hello world" 與 "world" 兩個字符串,只存儲前面一個。取的時候只需要給前面和中間的地址,如果是整形、浮點型會有多份拷貝,但這些數寫在指令中。占的只是代碼段而已,大量用宏會導致二進制文件變大
- 變量:共享一塊內存空間,就算項目中N處用到,也不會分配N塊內存空間,可以被修改,在編譯階段會執行類型檢查
- 常量:共享一塊內存空間,就算項目中N處用到,也不會分配N塊內存空間,可以根據const修飾的位置設定能否修改,在編譯階段會執行類型檢查
我們來看一段代碼
#define avatar @"60" if (false) { #define avatar @"80" } NSLog(avatar);
這段代碼會輸出多少,我們將“avatar”定義為了60,然后在一個永遠不會執行的代碼里面重新定義了“avatar”為80,if語句中的代碼永遠不會執行,但是在編譯時期,編譯器會編譯這段代碼,而這個時候編譯器就會將avatar這個名字替換為@“80”,所以這段代碼最后的輸出結果就是80。
當然這個時候編譯器是會有一個警告的,但是不知道有多少同學會忽略這個警告。或者你會告訴我你對警告十分敏感,不會放過他的,但是記住你不是一個人在寫代碼,可能在別人的頁面他給你重新定義了你的define,給你挖了一個大坑,還找不着.........
所以還是盡量使用const,看蘋果api也是使用常量多點,如下圖:
const的用法
const修飾符定義的變量是不可變的,比如說你需要定義一個動畫時間的常量,你可以這么做:
static const NSTimeInterval kAnimateDuration = 0.3;
當你試圖去修改“ kAnimateDuration”的值的時候,編譯器會報錯。更加重要的是用這種方法定義的常量是帶有類型信息的,而這點則是define不具備的。也許你已經發現了,如果你像如下這樣定義,你是可以修改userName的值的,(說好的常量呢~~~)
static const NSString * kUserName = @"StrongX";
首先我們需要確定的是以下的三種寫法中前兩種是一樣的(可以修改kUserName的內容,也就是說const放在類型前還是類型后是一樣的效果),第三種的效果不一樣(無法修改kUserName的內容)。
static NSString const * kUserName = @"StrongX"; static const NSString * kUserName = @"StrongX"; static NSString * const kUserName = @"StrongX";
需要注意的是const 修飾的是他右邊的部分,也就是說:
static NSString const * kUserName = static NSString const (* kUserName ) static NSString * const kUserName = static NSString * const (kUserName)
當const修飾的是(userName)的時候,不可變的是userName。當const修飾的是( * )的時候,“*”在C語言中表示指針指向符,也就是說這個時候userName指向的內存塊地址不可變,而內存保存的內容是可變的,我們來做個嘗試:
NSLog(@"內存地址: %x",& kUserName); kUserName = @"superXLX"; NSLog(@"內存地址: %x",& kUserName);
以上NSLog會打印*userName指向的內存塊地址,而他的輸出如下圖,我們已經發現當我們改變內存的內容的時候他的地址並沒有發生改變,也就是說這是符合“const”修飾符的規定的。
所以當我們需要定義一個不可變的常量的時候 ,我們還是需要將“const”修飾符放到“*”指針指向符后邊才對。
static NSString * const kUserName = @"StrongX";
extern和static的用法
在常量定義時我們經常會用到兩個關鍵字,extern和static。那么這兩個關鍵字的具體用法和作用是什么呢?下面我們就一起探究一下。
關鍵字extern
關鍵字extern主要是用來引用全局變量
,它的原理是先在本文件中查找,查找不到再到其他文件中查找。用“extern”定義的常量必須也只能初始化一次,不滿足必須以及只能一次的條件那么編譯器就會提醒你。在定義全局變量的時候需要要注意你的命名,你可以使用規定好的前綴來命名。我們一般的用法是在.h文件中用extern申明一個常量名稱,表示該常量可以讓外部引用,然后在.m文件中對該常量進行初始化。
//在"constants.h"文件中,聲明常量: extern NSString *const XUserName;
//然后在“constants.m”中定義他: NSString *const XUserName = @"StrongX";
關鍵字static
在探討static的用法之前,我們首先需要了解兩個概念:生命周期、作用域。
生命周期
:這個變量能存活多久,它所占用的內存什么時候分配,什么時候收回。作用域
:這個變量在什么區域是可見的,可以拿來用的。
static
分兩種情況:修飾局部變量、修飾全局變量。
1、static
修飾局部變量
- 局部變量:在函數/方法/代碼塊內聲明的變量。它的生命周期、作用域都是在這個代碼塊內。局部變量 存儲在棧區(stack)一旦出了這個代碼塊,存儲局部變量的這個棧內存就會被回收,局部變量也就被銷毀。
靜態局部變量:
當用static
修飾局部變量時,變量被稱為靜態局部變量
,和全局變量,靜態全局變量一樣,是存儲在‘靜態存儲區’。存儲在 靜態存儲區 的變量,其內存直到 程序結束 才會被銷毀。即,生命周期是整個源程序。
所以,靜態局部變量
的生命周期是整個源程序,但,作用域是聲明它的代碼塊內。
2、static
修飾全局變量
- 當全局變量沒有使用
static
修飾時其存儲在靜態存儲區,直到程序結束才銷毀。也就是其作用域是整個源程序。我們可以使用extern
關鍵字來引用這個全局變量。 - 當全局變量使用
static
修飾時,其生命周期沒有變,依舊是在程序結束時才銷毀。但是其作用域變了。現在只限於申明它的這個文件才可見。使用extern
關鍵字無法引用這個全局變量。 - 全局變量本來是在整個源程序的所有文件都可見,
static
修飾后,改為只在申明自己的文件可見,即修改了作用域。即如果在.m文件中用static定義了常量,那么就不能在.h文件中使用extern進行外部申明。//在.m文件中這樣定義,則該常量只能在當前.m文件中使用,並且不能再.h文件中使用extern進行外部申明使用 static NSString * const kUserName = @"userName";
他會告訴你在兩個目標文件(.0文件是.m文件編譯后的輸出文件)有一個重復的符號。(OC中沒有類似C++中的名字空間的概念)
所以當你在你自己的.m文件中需要聲明一個只有你自己可見的局部變量(k開頭)的變量的時候一定要同時使用“static”和“const”兩個符號。