目錄
- 內存分配
- 變量:全局與局部
- const,static蝴猪,extern
- 宏
- 結構體调衰,枚舉(typeof與typedef)
1. 內存分配
在計算機系統(tǒng)中,運行的應用程序的數(shù)據(jù)都是保存在內存中的自阱,不同類型的數(shù)據(jù)窖式,保存的內存區(qū)域不同。
引用計數(shù):當創(chuàng)建對象或者指針指向這個對象時动壤,此對象引用計數(shù)+1萝喘,不讓它銷毀,保證生命周期琼懊。當不指向該對象時阁簸,引用計數(shù)-1,直到計數(shù)為0哼丈,對象銷毀启妹,回收內存。
一醉旦、內存分區(qū)
- 棧區(qū)(stack) :由編譯器自動分配并釋放饶米,存放函數(shù)的參數(shù)值,局部變量等车胡。棧是系統(tǒng)數(shù)據(jù)結構檬输,對應線程/進程是唯一的。
優(yōu)點是快速高效匈棘,缺點時有限制丧慈,數(shù)據(jù)不靈活。[先進后出]
椫魑溃空間分靜態(tài)分配 和動態(tài)分配兩種逃默。
靜態(tài)分配是編譯器完成的鹃愤,比如自動變量(auto)的分配。
動態(tài)分配由alloca函數(shù)完成完域。
棧的動態(tài)分配無需釋放(是自動的)软吐,也就沒有釋放函數(shù)。
為可移植的程序起見吟税,棧的動態(tài)分配操作是不被鼓勵的凹耙!
- 堆區(qū)(heap) 由程序員分配和釋放,如果程序員不釋放乌妙,程序結束時,可能會由操作系統(tǒng)回收 建钥,比如在ios 中 alloc 都是存放在堆中藤韵。
優(yōu)點是靈活方便,數(shù)據(jù)適應面廣泛熊经,但是效率有一定降低泽艘。[順序隨意]
堆是函數(shù)庫內部數(shù)據(jù)結構,不一定唯一镐依。
不同堆分配的內存無法互相操作匹涮。
堆空間的分配總是動態(tài)的
雖然程序結束時所有的數(shù)據(jù)空間都會被釋放回系統(tǒng),但是精確的申請內存槐壳,釋放內存匹配是良好程序的基本要素然低。
- 全局區(qū)(靜態(tài)區(qū)) (static) 全局變量和靜態(tài)變量的存儲是放在一起的,初始化的全局變量和靜態(tài)變量存放在一塊區(qū)域务唐,未初始化的全局變量和靜態(tài)變量在相鄰的另一塊區(qū)域雳攘,程序結束后有系統(tǒng)釋放。
注意:全局區(qū)又可分為未初始化全局區(qū):
.bss段和初始化全局區(qū):data段枫笛。
舉例:int a;未初始化的吨灭。int a = 10;已初始化的。
例子代碼:
int a = 10; 全局初始化區(qū)
char *p; 全局未初始化區(qū)
main{
int b; 棧區(qū)
char s[] = "abc" 棧
char *p1; 棧
char *p2 = "123456"; 123456\\0在常量區(qū)刑巧,p2在棧上捺檬。
static int c =0并鸵; 全局(靜態(tài))初始化區(qū)
w1 = (char *)malloc(10);
w2 = (char *)malloc(20);
分配得來得10和20字節(jié)的區(qū)域就在堆區(qū)。
}
- 文字常量區(qū) 存放常量字符串,程序結束后由系統(tǒng)釋放
- 程序代碼區(qū) 存放函數(shù)的二進制代碼
二碗旅、申請后的系統(tǒng)響應
棧:存儲每一個函數(shù)在執(zhí)行的時候都會向操作系統(tǒng)索要資源,棧區(qū)就是函數(shù)運行時的內存收毫,棧區(qū)中的變量由編譯器負責分配和釋放辰企,內存隨著函數(shù)的運行分配,隨著函數(shù)的結束而釋放蚯斯,由系統(tǒng)自動完成薄风。
注意:只要棧的剩余空間大于所申請空間饵较,系統(tǒng)將為程序提供內存,否則將報異常提示棧溢出遭赂。
堆:
- 首先應該知道操作系統(tǒng)有一個記錄空閑內存地址的鏈表循诉。
- 當系統(tǒng)收到程序的申請時,會遍歷該鏈表撇他,尋找第一個空間大于所申請空間的堆結點茄猫,然后將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序困肩。
- 由于找到的堆結點的大小不一定正好等于申請的大小划纽,系統(tǒng)會自動的將多余的那部分重新放入空閑鏈表中
三、 申請大小的限制
棧:棧是向低地址擴展的數(shù)據(jù)結構锌畸,是一塊連續(xù)的內存的區(qū)域勇劣。是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預先規(guī)定好的,棧的大小是2M(也有的說是1M潭枣,總之是一個編譯時就確定的常數(shù) ) ,如果申請的空間超過棧的剩余空間時比默,將提示overflow。因此盆犁,能從棧獲得的空間較小命咐。
堆:堆是向高地址擴展的數(shù)據(jù)結構,是不連續(xù)的內存區(qū)域谐岁。這是由于系統(tǒng)是用鏈表來存儲的空閑內存地址的醋奠,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址伊佃。堆的大小受限于計算機系統(tǒng)中有效的虛擬內存钝域。由此可見,堆獲得的空間比較靈活锭魔,也比較大例证。
棧:由系統(tǒng)自動分配,速度較快迷捧,不會產生內存碎片
堆:是由alloc分配的內存织咧,速度比較慢,而且容易產生內存碎片漠秋,不過用起來最方便
2. 變量:全局與局部
1. 局部變量
概念:
局部變量就是定義在函數(shù),生命周期也是在函數(shù)內笙蒙,代碼塊和函數(shù)形參列表中的變量, 我們就稱之為局部變量。作用范圍:
從定義的那一行開始一直直到遇到大括號結束或者遇到return為止特點:
相同作用域范圍內不能出現(xiàn)同名的局部變量
不同作用域范圍內出現(xiàn)同名的局部變量, 內部的局部變量會覆蓋外部的局部變量
注意:
局部變量沒有固定的初始化值, 如果沒有對局部變量進行初始化, 那么局部變量中是一些隨機的值, 所以在開發(fā)中千萬不要使用未初始化的局部變量
- 存儲位置:
- 局部變量存儲在棧中, 當作用域結束系統(tǒng)會自動釋放棧中的局部變量
- 只要使用static修改局部變量之后, 當執(zhí)行到定義局部變量的代碼就會分配存儲空間, 但是只有程序結束才會釋放該存儲空間
- 當使用static來修飾局部變量, 那么會延長局部變量的生命周期, 并且會更改局部變量存儲的位置 , 將局部變量從棧轉移到靜態(tài)區(qū)中
應用場景:
2. 全局變量
概念:
寫在函數(shù),代碼塊,形參列表外的變量, 我們就稱之為全局變量作用范圍:
從定義的那一行開始一直直到文件末尾(暫時這樣認為)庆锦⊥蔽唬可以在其他文件中訪問的變量。特點:
全局變量和局部變量可以同名
如果存在和全局變量同名的局部變量, 那么局部變量會覆蓋全局變量
注意:
全局變量如果沒有進行初始化, 那么系統(tǒng)默認會將全局變量初始化為0
- 存儲位置:
全局變量存儲在靜態(tài)區(qū)中, 他會隨著程序的啟動而創(chuàng)建, 隨著程序的結束而結束。
3. 內部全局變量和外部全局變量
外部全局變量
默認情況下所有的全局變量都是外部全局變量, 可以被其它文件訪問的全局變量我們稱之為外部全局變量
外部全局變量特點:可以定義同名的外部全局變量
內部全局變量:
- 內部全局變量, 只要給全局變量加上static關鍵字就是內部全局變量
- 內部全局變量特點:也可以定義多個同名的內部全局變量艇搀。
3. const尿扯、staic、extern
1.const
我們在看一些大牛的第三方時焰雕,里面會出現(xiàn)很多const衷笋、static和extern,尤其是const和static矩屁,const和extern的結合使用辟宗,直接令很多小伙伴懵逼了,今天就詳細講解一下這三個關鍵字的正確使用方式吝秕。
const的作用和宏是很類似的泊脐,當有字符串常量的時候,蘋果推薦我們使用const烁峭,蘋果經常把常用字符串定義成const容客。
- const僅僅用來修飾右邊的變量(基本數(shù)據(jù)變量p,指針變量*p则剃,對象變量)
- 被const修飾的變量是只讀的且是全局的耘柱。
int * const p; // p:只讀 *p:變量
const int *p1; // *p1:只讀 p1:變量
int const *p2; // *p2:只讀 p2:變量
int const * const p3; // *p3:只讀 p3:只讀
const int * const p4; // *p4:只讀 p4:只讀
const開發(fā)中使用場景:
1. const和extern的使用
使用場景:在多個文件中經常使用的同一個字符串常量如捅,可以使用extern與const組合棍现。
開發(fā)時有個規(guī)定,為了避免重復報錯镜遣,全局變量不能定義在自己的類中己肮,我們需要自己創(chuàng)建一個全局文件管理全局東西。
在Const文件中聲明與定義:
在.h 中聲明:extern換成UIKIT_EXTERN,倆其實一樣悲关。蘋果推薦使用UIKIT_EXTERN
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
UIKIT_EXTERN NSString *testStr1;//字符串能被修改
UIKIT_EXTERN NSString * const testStr2;//字符串不能被修改
在.m 中定義(#import "Const.h")
#import "Const.h"
const NSString *testStr1 = @"const_test1";//字符串能被修改
NSString * const testStr2 = @"const_test2";//字符串不能被修改
使用:(比如在RunTimeViewController.h文件中使用)
- (void)viewDidLoad {
[super viewDidLoad];
extern NSString *testStr1;//先聲明一下
testStr1= @"change";
NSLog(@"%@", testStr1);字符串被修改了
extern NSString *const testStr2;
testStr2 = @"change";
NSLog(@"%@", testStr2); // 結果會報錯
}
2.extern
作用:
用于聲明一個外部全局變量谎僻,聲明變量是其他文件的外部變量。
不能定義變量
聲明只需要在使用變量之前聲明就可以了
extern工作原理:
先在當前文件查找有沒有全局變量寓辱,沒有找到艘绍,才會去其他文件查找。
聲明和定義的區(qū)別:
- 聲明不會開辟存儲空間
- 定義會開辟存儲空間
注意:UIKIT_EXTERN == extern
3. staic
作用:
- 修飾局部變量:
1.修飾局部變量,被static修飾局部變量,延長生命周期,整個應用程序都可以使用
2.程序一啟動就會分配,局部變量只會生成一份內存,只會初始化一次 - 修飾全局變量
1.全局變量被static修飾,全局變量的作用域就會被更改: 全局變量只能在當前文件下使用
static修飾變量注意點
- 用于定義一個內部全局變量
- 為了提高數(shù)據(jù)的安全性, 不讓別人在其它文件中修改我們的全局變量
- 只要用static修改的全局變量就是內部全局變量, 只能在當前文件中使用
- 如果多個文件中存在同名的內部全局變量, 相互不會影響
- 如果既有外部全局變量也有內部全局變量, 那么會優(yōu)先訪問內部全局變量
使用場景:
- 單獨使用
static NSString * name = @"name";
-
static與const聯(lián)合使用:
staic和const聯(lián)合的作用:聲明一個在本文件中靜態(tài)的全局只讀常量
開發(fā)使用場景:在一個文件中經常使用的字符串常量秫筏,可以使用static與const組合
// 開發(fā)中常用static修飾全局變量,只改變作用域
// 開發(fā)中聲明的全局變量诱鞠,有些不希望外界改動,只允許讀取这敬。比如一個基本數(shù)據(jù)類型不希望別人改動.
static const int a = 20;// 聲明一個靜態(tài)的全局只讀常量,不能被修改否則會報錯
// static修飾全局變量
static int number = 20;
如果在某方法內重復定義number的值航夺,值會改變,在方法外還是原來的值崔涂。
NSString *str = @"str";//僅限本文件使用阳掐,字符串能被修改
// 如果 const修飾 * str1,表示* str1只讀,str1還是能改變。
static NSString *str1 = @"str1"; //僅限本文件使用缭保,字符串能被修改
// 如果 const修飾 * str2,表示* str2只讀汛闸,str2還是能改變。
static const NSString *str2 = @"str2";//僅限本文件使用涮俄,字符串能被修改
// 開發(fā)中經常拿到str3修改值蛉拙,因此用const修飾str3,表示str3只讀,不允許修改彻亲。
static NSString * const str3 = @"str3";//字符串不能被修改
總結:
在iOS開發(fā)中請盡量多使用const孕锄、enum來代替宏定義(#define),隨著項目工程的逐漸增大,過多的宏定義還可能影響項目的編譯速度苞尝。
為何不將上面常量直接導入pch文件?
蘋果不建議使用pch畸肆,pch的原理是將其中定義的宏或全局常量/變量等copy到每個執(zhí)行文件(.m文件), 如果在其中定義全局常量, 則會報重復定義全局常量錯誤.iOS項目中經常會用到一些常量字符串,如果單獨的在一個.m中使用宙址,可以用宏定義轴脐,可以用static + const 來規(guī)定一個字符串,比如最常用的cell的reuserID抡砂,一般我們很少會用到宏來修飾大咱,因為宏不做檢查,不會報編譯錯誤注益,而且預編譯的時候碴巾,會延長編譯時間,蘋果也不推薦我們大量的使用宏丑搔。
查閱到很多第三方庫和類似的蘋果的用法厦瓢,可以看到,很多加以區(qū)分的const字符串都會寫到一個專門使用的類中啤月,比如MJRefresh的MJRefreshConst.h 和 MJRefreshConst.m煮仇,這種寫法有很多好處,其中之一就是:在規(guī)定全局變量谎仲,并且在其他地方引用的時候浙垫,容易造成字符串重復的問題,特別是在協(xié)同開發(fā)的過程中郑诺。因此夹姥,管理人員都是會規(guī)定好一個類來專門的管理這些 只讀字符串 ,也就是MJRefreshConst 或者是上面的Const间景,這樣的話佃声,在引用過程中,直接import這個類就可以使用了倘要,避免了很多不必要的麻煩圾亏。
使用注釋加以區(qū)分的話十拣,能夠把這些字符串分類管理,更加提高團隊工作效率志鹃。
用法總結:
1. 在函數(shù)外夭问,直接定義對象,不需要額外添加修飾符曹铃,對象可被修改缰趋; 一般形式的全局變量
2. 只用static修飾,對象可以被修改陕见,但是生命周期在APP運行期秘血; 用static修飾就是靜態(tài)變量(也叫全局靜態(tài)變量),不用static修飾的就是全局非靜態(tài)變量评甜;
修飾局部變量:讓局部變量只初始化一次灰粮;局部變量在程序中只有一份內存;并不會改變局部變量的作用域忍坷,僅僅是改變了局部變量的生命周期(只到程序結束粘舟,這個局部變量才會銷毀)
修飾全局變量:全局變量的作用域僅限于當前文件
3. 用const修飾:一般不定義在本文件內,字符串可以被修改佩研,也可以不被修改(主要)柑肴;用const修飾的就是常量;全局非靜態(tài)/靜態(tài)常量
4. 用static旬薯,const一起修飾:全局靜態(tài)常量晰骑,在本文件內字符串可以被修改,也可以不被修改(主要)袍暴;
常用的三種:
1. static 修飾全局靜態(tài)變量些侍,比如cell的標識符
2. static+const在本文件內使用隶症,修飾不可變字符串政模,全局靜態(tài)常量
3. const在單獨一個文件內使用,修飾字符串蚂会,變成不可變的常量淋样。
*/
4. 宏
1. 宏
宏的常見用法:
- 常見字符串抽成宏
- 常見代碼抽成宏
當有字符串常量的時候,蘋果推薦我們使用const胁住,蘋果經常把常用字符串定義成const
宏與const的區(qū)別:
- 編譯時刻:宏是預編譯(編譯之前處理)趁猴,const是編譯階段。
- 編譯檢查:宏不做檢查彪见,不會報編譯錯誤儡司,只是替換,const會編譯檢查余指,會報編譯錯誤捕犬。
- 宏的好處:宏能定義一些函數(shù)跷坝,方法。 const不能碉碉。
- 宏的壞處:使用大量宏柴钻,容易造成編譯時間久,每次都需要重新替換垢粮。
注意:swift中, 蘋果已經將宏殺掉, 所以項目開發(fā)中, 須用const代替宏.
宏定義:
- 不帶參數(shù)的宏定義:
格式: #define 標示符 字符串(“字符串”可以是常數(shù)贴届、表達式、格式串等蜡吧。)
宏名的有效范圍是從定義位置到文件結束毫蚓。如果需要終止宏定義的作用域,可以用#undef命令昔善。
例子:
#define NAME @"henry"
- 帶參數(shù)的宏定義:
對帶參數(shù)的宏,在調用中,不僅要宏展開,而且要用實參去代換形參绍些。
格式:#define 宏名(形參表) 字符串
例子1:
#define average(a, b) (a+b)/2
例子2:
#ifndef PrefixHeader_pch
#define PrefixHeader_pch
#define K_width [UIScreen mainScreen].bounds.size.width
#define K_height [UIScreen mainScreen].bounds.size.height
#define k_screenW ([UIScreen mainScreen].bounds.size.width)/(375)
#define k_screenH ([UIScreen mainScreen].bounds.size.height)/(667)
#endif /* PrefixHeader_pch */
例子3:
// 常見的常量:抽成宏
#define XMGAccount @"account"
#define XMGUserDefault [NSUserDefaults standardUserDefaults]
// 字符串常量
static NSString * const account = @"account";
- (void)viewDidLoad {
[super viewDidLoad];
// 偏好設置存儲
// 使用宏
[XMGUserDefault setValue:@"123" forKey:XMGAccount];
// 使用const常量
[[NSUserDefaults standardUserDefaults] setValue:@"123" forKey:account];
}
注意:
1.宏名和參數(shù)列表之間不能有空格,否則空格后面的所有字符串都作為替換的字符串耀鸦。
2.帶參數(shù)的宏在展開時柬批,只作簡單的字符和參數(shù)的替換,不進行任何計算操作袖订。所以在定義宏時氮帐,一般用一個小括號括住字符串的參數(shù)。并且計算結果最好也括起來防止錯誤洛姑。
#define Pow(a) ( (a) * (a) )
條件編譯:
在很多情況下上沐,我們希望程序的其中一部分代碼只有在滿足一定條件時才進行編譯,否則不參與編譯(只有參與編譯的代碼最終才能被執(zhí)行)楞艾,這就是條件編譯参咙。換句話說,在條件編譯中硫眯,不滿足條件的會直接被刪去蕴侧,并不會參與編譯。
條件編譯和宏定義經常一起使用两入。
//條件編譯后面的條件表達式中不能識別變量,它里面只能識別常量和宏定義
#if 條件1
...code1...
#elif 條件2
...code2...
#else
...code3...
#endif
#define SCORE 67
#if SCORE > 90
printf("優(yōu)秀\n");
#elif SCORE > 60
printf("良好\n");
#else
printf("不及格\n");
#endif
#ifdef和#ifndef
#ifdef MACRO_Define // 如果已定義MACRO_Define這個宏
代碼塊1
#else
代碼塊2
#endif
或
#ifndef MACRO_Define // 如果未定義MACRO_Define這個宏
代碼塊1
#else
代碼塊2
#endif
實際場景
#ifdef DEBUG
#define NSLog(FORMAT, ...) fprintf(stderr,"%s:%d\t %s\n",[[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String], __LINE__, [[NSString stringWithFormat:FORMAT, ##__VA_ARGS__] UTF8String]);
#else
#define NSLog(FORMAT, ...) nil
#endif
#ifdef DEBUG
#ifndef DLog
# define DLog(fmt, ...) {NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);}
#endif
#endif
5. 結構體净宵,枚舉(typeof與typedef)
理解
- typeof 是一個一元運算,放在一個運算數(shù)之前裹纳,運算數(shù)可以是任意類型择葡。可以理解為:我們根據(jù)typeof()括號里面的變量剃氧,自動識別變量類型并返回該類型敏储。
typeof 常見運用于Block中,避免循環(huán)引用發(fā)生的問題朋鞍。
用法:
//定義一個和self相同數(shù)據(jù)類型的bself 已添,并賦值為self迫横,在block中使用
__weak typeof (self) weakSelf = self;
注意: typeof 括號中的值和等于后面的值是相同的類型。
__weak typeof(self.contentView) ws = self.contentView;
- typedef:定義一種類型的別名酝碳,而不只是簡單的宏替換矾踱。
typedef 常用于命名(枚舉和Block)
用法:
用法1-結構體
//結構體
typedef struct Myrect {
float width;
float height;
}myRect;
用法2-枚舉
//枚舉值 它是一個整形(int) 并且,它不參與內存的占用和釋放,枚舉定義變量即可直接使用,不用初始化.
//在代碼中使用枚舉的目的只有一個,那就是增加代碼的可讀性.
typedef NS_ENUM (NSUInteger,direction){
left=0,
right=1,
top =2,
down =3
};
typedef NS_ENUM (NSUInteger,direction_2){
left2 = 0,
right2 = 1 << 0,
top2 = 1 << 1,
down2 = 1 << 2
};
用法3-Block
//1.重新起個名字
typedef void(^PassValueBlock)(NSString *);
@interface BlockViewController : BaseViewController
//2.聲明block屬性
@property(nonatomic,copy)PassValueBlock passValueBlock;