標籤:
可能碰到的iOS筆試面試題(4)--C語言可能碰到的iOS筆試面試題(4)--C語言
C語言,開發的基礎功底,iOS很多進階應用程式都要和C語言打交道,所以,C語言在iOS開發中的重要性,你懂的。裡面的一些問題可能並不是C語言問題,但是屬於電腦的一些原理性的知識點,所以我就不再另外寫一篇文章了,直接寫在這裡。
當你寫下面的代碼時會發生什麼事?
- least = MIN(*p++, b);
- 結果是:((p++) <= (b) ? (p++) : (*p++)) 這個運算式會產生副作用,指標p會作三次++自增操作。
用預先處理指令#define聲明一個常數,用以表明1年中有多少秒(忽略閏年問題)define SECONDS_PER_YEAR (60
60 24 * 365)UL(UL無符號長整形)寫一個"標準"宏MIN ,這個宏輸入兩個參數並返回較小的一個。define MIN(A,B) ((A) <= (B) ? (A) : (B))寫一個標準宏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))當看到宏時,就會想到宏定義所帶來的副作用。對於++、–,在宏當中使用是最容易產生副作用的,因此要慎用。分析:p指標指向了數組array的首地址,也就是第一個元素對應的地址,其值為1.宏定義時一定要注意每個地方要加上圓括弧*p++相當於*p, p++,所以Max(*p++, 1)相當於:(*p++) > (1) ? (*p++) : (1)=>(1) > (1) ? (*p++) : (1)=>第一個*p++的結果是,p所指向的值變成了2,但是1 > 1為値,所以最終max的值就是1。而後面的(*p++)也就不會執行,因此p所指向的地址對應的值就是2,而不是3.擴充:如果上面的*p++改成*(++p)如何?(*++p) > (1) ? (*++p) : (1)=>(2) > (1) ? (*++p) : (1)=> max = *++p;=> *p = 3,max = 3;
define定義的宏和const定義的常量有什麼區別?
λ #define定義宏的指令,程式在預先處理階段將用#define所定義的內容只是進行了替換。因此程式運行時,常量表中並沒有用#define所定義的宏,系統並不為它分配記憶體,而且在編譯時間不會檢查資料類型,出錯的機率要大一些。λ const定義的常量,在程式運行時是存放在常量表中,系統會為它分配記憶體,而且在編譯時間會進行類型檢查。#define定義運算式時要注意“邊緣效應”,例如如下定義:#define N 2 + 3 // 我們預想的N值是5,我們這樣使用Nint a = N / 2; // 我們預想的a的值是2.5,可實際上a的值是3.5
關鍵字volatile有什麼含意?並給出三個不同的例子
- 最佳化器在用到這個變數時必須每次都小心地重新讀取這個變數的值,而不是使用儲存在寄存器裡的備份。下面是volatile變數的幾個例子:
- 平行裝置的硬體寄存器(如:狀態寄存器)
- 一個中斷服務子程式中會訪問到的非自動變數(Non-automatic variables)
- 多線程應用中被幾個任務共用的變數
完成字串拷貝可以使用sprintf、strcpy、以及memcpy函數,請問這些函數有什麼區別?你喜歡哪一個?為什嗎?
這些函數的區別在於實現功能以及操作對象不同。- strcpy:函數操作的對象是字串,完成從源字串到目的字串的拷貝功能。- sprintf:這個函數主要用來實現(字串或基礎資料型別 (Elementary Data Type))向字串的轉換功能。如果來源物件是字串,並且指定%s格式符,也可實現字串拷貝功能。- memcpy:函數顧名思義就是記憶體拷貝,實現將一個記憶體塊的內容複寫到另一個記憶體塊這一功能。記憶體塊由其首地址以及長度確定。因此,memcpy 的操作對象適用於任意資料類型,只要能給出對象的起始地址和記憶體長度資訊、並且對象具有可操作性即可。鑒於memcpy函數等長拷貝的特點以及資料類型代表的物理意義,memcpy函數通常限於同種類型資料或對象之間的拷貝,其中當然也包括字串拷貝以及基礎資料型別 (Elementary Data Type)的拷貝。- 對於字串拷貝來說,用上述三個函數都可以實現,但是其實現的效率和使用的方便程度不同:- strcpy 無疑是最合適的選擇:效率高且調用方便。- snprintf 要額外指定格式符並且進行格式轉化,麻煩且效率不高。- memcpy 雖然高效,但是需要額外提供拷貝的記憶體長度這一參數,易錯且使用不便;並且如果長度指定過大的話(最優長度是源字串長度 + 1),還會帶來效能的下降。其實 strcpy 函數一般是在內部調用 memcpy函數或者用彙編直接實現的,以達到高效的目的。因此,使用 memcpy 和 strcpy 拷貝字串在效能上應該沒有什麼大的差別。- 對於非字串類型的資料的複製來說,strcpy和snprintf一般就無能為力了,可是對memcpy卻沒有什麼影響。但是,對於基礎資料型別 (Elementary Data Type)來說,儘管可以用 memcpy 進行拷貝,由於有賦值運算子可以方便且高效地進行同種或相容類型的資料之間的拷貝,所以這種情況下memcpy幾乎不被使用。memcpy的長處是用來實現(通常是內部實現居多)對結構或者數組的拷貝,其目的是或者高效,或者使用方便,甚或兩者兼有。
sprintf,strcpy,memcpy使用上有什麼要注意的地方
strcpy是一個字串拷貝的函數,它的函數原型為strcpy(char dst, const char src);
將src開始的一段字串拷貝到dst開始的記憶體中去,結束的標誌符號為 ‘\0‘,由於拷貝的長度不是由我們自己控制的,所以這個字串拷貝很容易出錯。
具備字串拷貝功能的函數有memcpy,這是一個記憶體拷貝函數,它的函數原型為memcpy(char dst, const char src, unsigned int len);將長度為len的一段記憶體,從src拷貝到dst中去,這個函數的長度可控。但是會有記憶體讀寫錯誤。(比如len的長度大於要拷貝的空間或目的空間)
sprintf是格式化函數。將一段資料通過特定的格式,格式化到一個字串緩衝區中去。sprintf格式化的函數的長度不可控,有可能格式化後的字串會超出緩衝區的大小,造成溢出。
static關鍵字的作用
- 隱藏。編譯多個檔案時,所有未加static首碼的全域變數和函數都全域可見。
- 保持變數內容的持久。全域變數和static變數都儲存在靜態儲存區,程式開始運行就初始化,只初始化一次。static控制了變數的作用範圍。
- 預設初始化為0.在待用資料區,記憶體中的所有位元組都是0x00,全域變數和static變數都是預設初始化為0.
static關鍵字區別:
- static全域變數與普通的全域變數有什麼區別:static全域變數只初使化一次,防止在其他檔案單元中被引用;
- static局部變數和普通局部變數有什麼區別:static局部變數只被初始化一次,下一次依據上一次結果值;
- static函數與普通函數有什麼區別:static函數在記憶體中只有一份,普通函數在每個被調用中維持一份拷貝
關鍵字const
- const int a;int const a; 作用是一樣:a 是一個常整型數
- const int a;int const a; a 是一個指向常整型數的指標(整型數是不可修改的,但指標可以)
- int * const a;a 是一個指向整型數的常指標(指標指向的整型數是可以修改的,但指標是不可修改的)
- int const * const a;a 是一個指向常整型數的常指標(指標指向的整型數是不可修改的,同時指標也是不可修改的)
堆棧
- 管理方式:對於棧來講,是由編譯器自動管理,無需我們手工控制;對於堆來說,釋放工作由程式員控制,容易產生記憶體泄露 (memory leak)。
申請大小:
棧:在Windows下,棧是向低地址擴充的資料結構,是一塊連續的記憶體的地區。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在Windows下,棧的大小是2M(也有的說是1M,總之是一個編譯時間就確定的常數),如果申請的空間超過棧的剩餘空間時,將提示 overflow。因此,能從棧獲得的空間較小。
堆:堆是向高地址擴充的資料結構,是不連續的記憶體地區。這是由於系統是用鏈表來儲存的空閑記憶體位址的,自然是不連續的,而鏈表的遍曆方向是由低地址向高地址。堆的大小受限於電腦系統中有效虛擬記憶體。由此可見,堆獲得的空間比較靈活,也比較大。
- 片段問題:
對於堆來講,頻繁的new/delete勢必會造成記憶體空間的不連續,從而造成大量的片段,使程式效率降低。對於棧來講,則不會存在這個 問題,因為棧是先進後出的隊列,他們是如此的一一對應,以至於永遠都不可能有一個記憶體塊從棧中間彈出
- 分配方式:
堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變數的分配。動態分配由 alloc函數進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。
- 分配效率:
棧是機器系統提供的資料結構,電腦會在底層對棧提供支援:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的 效率比較高。堆則是C/C++函數庫提供的,它的機制是很複雜的
數組和指標的區別
- 數組可以申請在棧區和資料區;指標可以指向任意類型的記憶體塊
sizeof作用於數組時,得到的是數組所佔的記憶體大小;作用於指標時,得到的都是4個位元組的大小
- 數組名表示數組首地址,是常量指標,不可修改指向。比如不可以將++作用於數組名上;普通指標的值可以改變,比如可將++作用於指標上
- 用字串初始化字元數組是將字串的內容拷貝到字元數組中;用字串初始化字元指標是將字串的首地址賦給指標,也就是指標指向了該字串
引用和指標的區別
用變數a給出下面的定義
請寫出以下代碼輸出
int a[5] = {1, 2, 3, 4, 5};int *ptr = (int *)(&a + 1);printf("%d, %d", *(a + 1), *(ptr + 1));參考答案: 2, 隨機值這種類型題好像挺常見的。考的就是C語言上的指標的理解和數組的理解。分析:a代表有5個元素的數組的首地址,a[5]的元素分別是1,2,3,4,5。接下來,a + 1表示資料首地址加1,那麼就是a[1],也就是對應於值為2.但是,這裡是&a + 1,因為a代表的是整個數組,它的空間大小為5 * sizeof(int),因此&a + 1就是a+5。a是個常量指標,指向當前數組的首地址,指標+1就是移動sizeof(int)個位元組。因此,ptr是指向int *類型的指標,而ptr指向的就是a + 5,那麼ptr + 1也相當於a + 6,所以最後的*(ptr + 1)就是一個隨機值了。而*(ptr – 1)就相當於a + 4,對應的值就是5。
簡述記憶體分區情況
- 代碼區:存放函數二進位代碼
- 資料區:系統運行時申請記憶體並初始化,系統退出時由系統釋放,存放全域變數、靜態變數、常量
- 堆區:通過malloc等函數或new等操作符動態申請得到,需程式員手動申請和釋放
- 棧區:函數模組內申請,函數結束時由系統自動釋放,存放局部變數、函數參數
用NSLog函數輸出一個浮點類型,結果四捨五入,並保留一位小數
float money = 1.011;NSLog(@"%.1f", money);
文章如有問題,請留言,我將及時更正。
可能碰到的iOS筆試面試題(4)--C語言