C語(yǔ)言,開發(fā)的基礎(chǔ)功底,iOS很多高級(jí)應(yīng)用都要和C語(yǔ)言打交道,所以,C語(yǔ)言在iOS開發(fā)中的重要性,你懂的。里面的一些問題可能并不是C語(yǔ)言問題,但是屬于計(jì)算機(jī)的一些原理性的知識(shí)點(diǎn),所以我就不再另外寫一篇文章了,直接寫在這里。
當(dāng)你寫下面的代碼時(shí)會(huì)發(fā)生什么事?
- least = MIN(*p++, b);
- 結(jié)果是:((p++) <= (b) ? (p++) : (*p++)) 這個(gè)表達(dá)式會(huì)產(chǎn)生副作用,指針p會(huì)作三次++自增操作。
用預(yù)處理指令#define聲明一個(gè)常數(shù),用以表明1年中有多少秒(忽略閏年問題)
define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL(UL無(wú)符號(hào)長(zhǎng)整形)
寫一個(gè)"標(biāo)準(zhǔn)"宏MIN ,這個(gè)宏輸入兩個(gè)參數(shù)并返回較小的一個(gè)。
define MIN(A,B) ((A) <= (B) ? (A) : (B))
寫一個(gè)標(biāo)準(zhǔn)宏Max,并給出以下代碼的輸出
int array[5] = {1, 2, 3, 4, 5};
int *p = &array[0];
int max = Max(*p++, 1);
printf("%d %d", max, *p);
參考答案: 1,2
#define Max(X, Y) ((X) > (Y) ? (X) : (Y))
當(dāng)看到宏時(shí),就會(huì)想到宏定義所帶來(lái)的副作用。對(duì)于++、–,在宏當(dāng)中使用是最容易產(chǎn)生副作用的,因此要慎用。
分析:
p指針指向了數(shù)組array的首地址,也就是第一個(gè)元素對(duì)應(yīng)的地址,其值為1.
宏定義時(shí)一定要注意每個(gè)地方要加上圓括號(hào)
*p++相當(dāng)于*p, p++,所以Max(*p++, 1)相當(dāng)于:
(*p++) > (1) ? (*p++) : (1)
=>
(1) > (1) ? (*p++) : (1)
=>
第一個(gè)*p++的結(jié)果是,p所指向的值變成了2,但是1 > 1為値,所以最終max的值就是1。而后面的(*p++)也就不會(huì)執(zhí)行,因此p所指向的地址對(duì)應(yīng)的值就是2,而不是3.
擴(kuò)展:如果上面的*p++改成*(++p)如何?
(*++p) > (1) ? (*++p) : (1)
=>
(2) > (1) ? (*++p) : (1)
=>
max = *++p;
=>
*p = 3,max = 3;
define定義的宏和const定義的常量有什么區(qū)別?
λ #define定義宏的指令,程序在預(yù)處理階段將用#define所定義的內(nèi)容只是進(jìn)行了替換。因此程序運(yùn)行時(shí),常量表中并沒有用#define所定義的宏,系統(tǒng)并不為它分配內(nèi)存,而且在編譯時(shí)不會(huì)檢查數(shù)據(jù)類型,出錯(cuò)的概率要大一些。
λ const定義的常量,在程序運(yùn)行時(shí)是存放在常量表中,系統(tǒng)會(huì)為它分配內(nèi)存,而且在編譯時(shí)會(huì)進(jìn)行類型檢查。
#define定義表達(dá)式時(shí)要注意“邊緣效應(yīng)”,例如如下定義:
#define N 2 + 3 // 我們預(yù)想的N值是5,我們這樣使用N
int a = N / 2; // 我們預(yù)想的a的值是2.5,可實(shí)際上a的值是3.5
關(guān)鍵字volatile有什么含意?并給出三個(gè)不同的例子
- 優(yōu)化器在用到這個(gè)變量時(shí)必須每次都小心地重新讀取這個(gè)變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個(gè)例子:
- 并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)
- 一個(gè)中斷服務(wù)子程序中會(huì)訪問到的非自動(dòng)變量(Non-automatic variables)
- 多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量
完成字符串拷貝可以使用sprintf、strcpy、以及memcpy函數(shù),請(qǐng)問這些函數(shù)有什么區(qū)別?你喜歡哪一個(gè)?為什么?
這些函數(shù)的區(qū)別在于實(shí)現(xiàn)功能以及操作對(duì)象不同。
- strcpy:函數(shù)操作的對(duì)象是字符串,完成從源字符串到目的字符串的拷貝功能。
- sprintf:這個(gè)函數(shù)主要用來(lái)實(shí)現(xiàn)(字符串或基本數(shù)據(jù)類型)向字符串的轉(zhuǎn)換功能。如果源對(duì)象是字符串,并且指定%s格式符,也可實(shí)現(xiàn)字符串拷貝功能。
- memcpy:函數(shù)顧名思義就是內(nèi)存拷貝,實(shí)現(xiàn)將一個(gè)內(nèi)存塊的內(nèi)容復(fù)制到另一個(gè)內(nèi)存塊這一功能。內(nèi)存塊由其首地址以及長(zhǎng)度確定。因此,memcpy 的操作對(duì)象適用于任意數(shù)據(jù)類型,只要能給出對(duì)象的起始地址和內(nèi)存長(zhǎng)度信息、并且對(duì)象具有可操作性即可。鑒于memcpy函數(shù)等長(zhǎng)拷貝的特點(diǎn)以及數(shù)據(jù)類型代表的物理意義,memcpy函數(shù)通常限于同種類型數(shù)據(jù)或?qū)ο笾g的拷貝,其中當(dāng)然也包括字符串拷貝以及基本數(shù)據(jù)類型的拷貝。
- 對(duì)于字符串拷貝來(lái)說(shuō),用上述三個(gè)函數(shù)都可以實(shí)現(xiàn),但是其實(shí)現(xiàn)的效率和使用的方便程度不同:
- strcpy 無(wú)疑是最合適的選擇:效率高且調(diào)用方便。
- snprintf 要額外指定格式符并且進(jìn)行格式轉(zhuǎn)化,麻煩且效率不高。
- memcpy 雖然高效,但是需要額外提供拷貝的內(nèi)存長(zhǎng)度這一參數(shù),易錯(cuò)且使用不便;并且如果長(zhǎng)度指定過大的話(最優(yōu)長(zhǎng)度是源字符串長(zhǎng)度 + 1),還會(huì)帶來(lái)性能的下降。其實(shí) strcpy 函數(shù)一般是在內(nèi)部調(diào)用 memcpy函數(shù)或者用匯編直接實(shí)現(xiàn)的,以達(dá)到高效的目的。因此,使用 memcpy 和 strcpy 拷貝字符串在性能上應(yīng)該沒有什么大的差別。
- 對(duì)于非字符串類型的數(shù)據(jù)的復(fù)制來(lái)說(shuō),strcpy和snprintf一般就無(wú)能為力了,可是對(duì)memcpy卻沒有什么影響。但是,對(duì)于基本數(shù)據(jù)類型來(lái)說(shuō),盡管可以用 memcpy 進(jìn)行拷貝,由于有賦值運(yùn)算符可以方便且高效地進(jìn)行同種或兼容類型的數(shù)據(jù)之間的拷貝,所以這種情況下memcpy幾乎不被使用。memcpy的長(zhǎng)處是用來(lái)實(shí)現(xiàn)(通常是內(nèi)部實(shí)現(xiàn)居多)對(duì)結(jié)構(gòu)或者數(shù)組的拷貝,其目的是或者高效,或者使用方便,甚或兩者兼有。
sprintf,strcpy,memcpy使用上有什么要注意的地方
strcpy是一個(gè)字符串拷貝的函數(shù),它的函數(shù)原型為strcpy(char *dst, const char *src);
將src開始的一段字符串拷貝到dst開始的內(nèi)存中去,結(jié)束的標(biāo)志符號(hào)為 '\0',由于拷貝的長(zhǎng)度不是由我們自己控制的,所以這個(gè)字符串拷貝很容易出錯(cuò)。
具備字符串拷貝功能的函數(shù)有memcpy,這是一個(gè)內(nèi)存拷貝函數(shù),它的函數(shù)原型為memcpy(char dst, const char src, unsigned int len);將長(zhǎng)度為len的一段內(nèi)存,從src拷貝到dst中去,這個(gè)函數(shù)的長(zhǎng)度可控。但是會(huì)有內(nèi)存讀寫錯(cuò)誤。(比如len的長(zhǎng)度大于要拷貝的空間或目的空間)
sprintf是格式化函數(shù)。將一段數(shù)據(jù)通過特定的格式,格式化到一個(gè)字符串緩沖區(qū)中去。sprintf格式化的函數(shù)的長(zhǎng)度不可控,有可能格式化后的字符串會(huì)超出緩沖區(qū)的大小,造成溢出。
static關(guān)鍵字的作用
- 隱藏。編譯多個(gè)文件時(shí),所有未加static前綴的全局變量和函數(shù)都全局可見。
- 保持變量?jī)?nèi)容的持久。全局變量和static變量都存儲(chǔ)在靜態(tài)存儲(chǔ)區(qū),程序開始運(yùn)行就初始化,只初始化一次。static控制了變量的作用范圍。
- 默認(rèn)初始化為0.在靜態(tài)數(shù)據(jù)區(qū),內(nèi)存中的所有字節(jié)都是0x00,全局變量和static變量都是默認(rèn)初始化為0.
static關(guān)鍵字區(qū)別:
- static全局變量與普通的全局變量有什么區(qū)別:static全局變量只初使化一次,防止在其他文件單元中被引用;
- static局部變量和普通局部變量有什么區(qū)別:static局部變量只被初始化一次,下一次依據(jù)上一次結(jié)果值;
- static函數(shù)與普通函數(shù)有什么區(qū)別:static函數(shù)在內(nèi)存中只有一份,普通函數(shù)在每個(gè)被調(diào)用中維持一份拷貝
關(guān)鍵字const
- const int a;int const a; 作用是一樣:a 是一個(gè)常整型數(shù)
- const int *a;int const *a; a 是一個(gè)指向常整型數(shù)的指針(整型數(shù)是不可修改的,但指針可以)
- int * const a;a 是一個(gè)指向整型數(shù)的常指針(指針指向的整型數(shù)是可以修改的,但指針是不可修改的)
- int const * const a;a 是一個(gè)指向常整型數(shù)的常指針(指針指向的整型數(shù)是不可修改的,同時(shí)指針也是不可修改的)
堆棧
管理方式:對(duì)于棧來(lái)講,是由編譯器自動(dòng)管理,無(wú)需我們手工控制;對(duì)于堆來(lái)說(shuō),釋放工作由程序員控制,容易產(chǎn)生內(nèi)存泄露 (memory leak)。
-
申請(qǐng)大小:
棧:在Windows下,棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,在Windows下,棧的大小是2M(也有的說(shuō)是1M,總之是一個(gè)編譯時(shí)就確定的常數(shù)),如果申請(qǐng)的空間超過棧的剩余空間時(shí),將提示 overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來(lái)存儲(chǔ)的空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見,堆獲得的空間比較靈活,也比較大。
碎片問題:
對(duì)于堆來(lái)講,頻繁的new/delete勢(shì)必會(huì)造成內(nèi)存空間的不連續(xù),從而造成大量的碎片,使程序效率降低。對(duì)于棧來(lái)講,則不會(huì)存在這個(gè) 問題,因?yàn)闂J窍冗M(jìn)后出的隊(duì)列,他們是如此的一一對(duì)應(yīng),以至于永遠(yuǎn)都不可能有一個(gè)內(nèi)存塊從棧中間彈出分配方式:
堆都是動(dòng)態(tài)分配的,沒有靜態(tài)分配的堆。棧有2種分配方式:靜態(tài)分配和動(dòng)態(tài)分配。靜態(tài)分配是編譯器完成的,比如局部變量的分配。動(dòng)態(tài)分配由 alloc函數(shù)進(jìn)行分配,但是棧的動(dòng)態(tài)分配和堆是不同的,他的動(dòng)態(tài)分配是由編譯器進(jìn)行釋放,無(wú)需我們手工實(shí)現(xiàn)。分配效率:
棧是機(jī)器系統(tǒng)提供的數(shù)據(jù)結(jié)構(gòu),計(jì)算機(jī)會(huì)在底層對(duì)棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行,這就決定了棧的 效率比較高。堆則是C/C++函數(shù)庫(kù)提供的,它的機(jī)制是很復(fù)雜的
數(shù)組和指針的區(qū)別
- 數(shù)組可以申請(qǐng)?jiān)跅^(qū)和數(shù)據(jù)區(qū);指針可以指向任意類型的內(nèi)存塊
sizeof作用于數(shù)組時(shí),得到的是數(shù)組所占的內(nèi)存大小;作用于指針時(shí),得到的都是4個(gè)字節(jié)的大小 - 數(shù)組名表示數(shù)組首地址,是常量指針,不可修改指向。比如不可以將++作用于數(shù)組名上;普通指針的值可以改變,比如可將++作用于指針上
- 用字符串初始化字符數(shù)組是將字符串的內(nèi)容拷貝到字符數(shù)組中;用字符串初始化字符指針是將字符串的首地址賦給指針,也就是指針指向了該字符串
引用和指針的區(qū)別
指針指向一塊內(nèi)存,內(nèi)容存儲(chǔ)所指內(nèi)存的地址。
引用是某塊內(nèi)存的別名。
引用使用時(shí)不需要解引用(*)而指針需要
引用只在定義時(shí)被初始化,之后不可變,指針可變。
引用沒有const
引用不能為空
sizeof引用得到的是所指向變量(對(duì)象)的大小,sizeof指針是指針本身的大小。
指針和引用的自增(++)運(yùn)算意義不一樣:引用++為引用對(duì)象自己++,指針++是指向?qū)ο蠛竺娴膬?nèi)存
程序需要為指針分配內(nèi)存區(qū)域,引用不需要。
用變量a給出下面的定義
一個(gè)整型數(shù)(An integer)
一個(gè)指向整型數(shù)的指針( A pointer to an integer)
一個(gè)指向指針的的指針,它指向的指針是指向一個(gè)整型數(shù)( A pointer to a pointer to an intege)r
一個(gè)有10個(gè)整型數(shù)的數(shù)組( An array of 10 integers)
一個(gè)有10個(gè)指針的數(shù)組,該指針是指向一個(gè)整型數(shù)的。(An array of 10 pointers to integers)
一個(gè)指向有10個(gè)整型數(shù)數(shù)組的指針( A pointer to an array of 10 integers)
一個(gè)指向函數(shù)的指針,該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù)(A pointer to a function that takes an integer as an argument
and returns an integer)一個(gè)有10個(gè)指針的數(shù)組,該指針指向一個(gè)函數(shù),該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù)( An array of ten pointers to functions t
hat take an integer argument and return an integer )答案是:
int a; // An integer
int *a; // A pointer to an integer
int **a; // A pointer to a pointer to an integer
int a[10]; // An array of 10 integers
int *a[10]; // An array of 10 pointers to integers
int (*a)[10]; // A pointer to an array of 10 integers
int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
請(qǐng)寫出以下代碼輸出
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d, %d", *(a + 1), *(ptr + 1));
參考答案: 2, 隨機(jī)值
這種類型題好像挺常見的。考的就是C語(yǔ)言上的指針的理解和數(shù)組的理解。
分析:
a代表有5個(gè)元素的數(shù)組的首地址,a[5]的元素分別是1,2,3,4,5。接下來(lái),a + 1表示數(shù)據(jù)首地址加1,那么就是a[1],也就是對(duì)應(yīng)于值為2.但是,這里是&a + 1,因?yàn)閍代表的是整個(gè)數(shù)組,它的空間大小為5 * sizeof(int),因此&a + 1就是a+5。a是個(gè)常量指針,指向當(dāng)前數(shù)組的首地址,指針+1就是移動(dòng)sizeof(int)個(gè)字節(jié)。
因此,ptr是指向int *類型的指針,而ptr指向的就是a + 5,那么ptr + 1也相當(dāng)于a + 6,所以最后的*(ptr + 1)就是一個(gè)隨機(jī)值了。而*(ptr – 1)就相當(dāng)于a + 4,對(duì)應(yīng)的值就是5。
簡(jiǎn)述內(nèi)存分區(qū)情況
- 代碼區(qū):存放函數(shù)二進(jìn)制代碼
- 數(shù)據(jù)區(qū):系統(tǒng)運(yùn)行時(shí)申請(qǐng)內(nèi)存并初始化,系統(tǒng)退出時(shí)由系統(tǒng)釋放,存放全局變量、靜態(tài)變量、常量
- 堆區(qū):通過malloc等函數(shù)或new等操作符動(dòng)態(tài)申請(qǐng)得到,需程序員手動(dòng)申請(qǐng)和釋放
- 棧區(qū):函數(shù)模塊內(nèi)申請(qǐng),函數(shù)結(jié)束時(shí)由系統(tǒng)自動(dòng)釋放,存放局部變量、函數(shù)參數(shù)
用NSLog函數(shù)輸出一個(gè)浮點(diǎn)類型,結(jié)果四舍五入,并保留一位小數(shù)
float money = 1.011;
NSLog(@"%.1f", money);
文章如有問題,請(qǐng)留言,我將及時(shí)更正。
滿地打滾賣萌求贊,如果本文幫助到你,輕點(diǎn)下方的紅心,給作者君增加更新的動(dòng)力。