C语言再学习 -- 详解C++/C 面试题 2
(經典)C語言測試:想成為嵌入式程序員應知道的0x10個基本問題。
參看:嵌入式程序員面試問題集錦
1、用預處理指令#define 聲明一個常數,用以表明1年中有多少秒(忽略閏年問題)?
#define SENCONDS_PER_YEAR (60*60*24*365)UL
解答:
#define 聲明一個常量,使用計算常量表達式的值來表明一年中有多少秒,顯得就更加直觀了。再有這個表達式的值為無符號長整形,因此應使用符號 UL。?
2、寫一個“標準”宏MIN ,這個宏輸入兩個參數并返回較小的一個。?
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
解答:
實現輸入兩個參數并返回較小的一個,應使用三目表達式。使用必須的足夠多的圓括號來保證以正確的順序進行運行和結合。
參看:C語言再學習 -- C 預處理器使用#define需要注意下面幾點:
(1)宏的名字中不能有空格,但是在替代字符串中可以使用空格。ANSI C 允許在參數列表中使用空格。
(2)用圓括號括住每個參數,并括住宏的整體定義。
(3)用大寫字母表示宏函數名,便于與變量區分。
(4)有些編譯器限制宏只能定義一行。即使你的編譯器沒有這個限制,也應遵守這個限制。
(5)宏的一個優點是它不檢查其中的變量類型,這是因為宏處理字符型字符串,而不是實際值。
(6)在宏中不要使用增量或減量運算符。
3、預處理器標識#error的目的是什么?
#error??字符串 => 表示產生一個錯誤信息
解答:
#error??字符串 => 表示產生一個錯誤信息
#warning?字符串 => 表示產生一個警告信息
?
4、嵌入式系統中經常要用到無限循環,你怎么樣用C編寫死循環呢?
while (1)?{...} for (;;){...} Loop; ... goto Loop 解答: 參看:C語言再學習 -- 循環語句while(邏輯表達式)
{
反復執行的語句
}? ? ? ? ? ? ? ? ? ? ? ? ? ??
只要邏輯表達式結果為真就反復不停執行大括號里的語句,直到邏輯表達式結果為假循環結束,只要把邏輯表達式寫成1則循環成為死循環。
while 很好理解,下面講講 for 循環。
例如:for(num=1; num <10; num++);
在關鍵字for之后的圓括號中包含了由兩個分號分開的三個表達式:
第一個表達式進行初始化,它在for循環開始的時候執行一次,可以使用逗號為多個變量進行初始化。
第二個表達式是判斷條件,在每次執行之前都要對它進行求值。當表達式為假時,循環結束。
第三個表達式進行改變或者稱為更新,它在每次循環結束時進行計算。
for循環的靈活性
(1)可以讓一個或多個表達式為空(但是不要遺漏分號)。只須確保在循環中包含一些能是循環最終結束的語句。(2)順便說一句,中間的那個控制表達式為空會被認為是真,所以下面的循環會永遠執行:
for (; ;)
printf ("hello world\n");
(3)第一個表達式不必初始化一個變量,它也可是某種類型的 printf() 語句,要記住第一個表達式只在執行循環的其他部分之前被求值或執行一次。
(4)for循環可使用逗號運算符把兩個表達式鏈接為一個表達式,并保證最左邊的表達式最先計算。
例如:
for (n = 2, m = 0; m < 1000; n *=2) m +=n; (5)在for循環中使用數組,可使用#define來指定數組大小 例如: #define SIZE 10 for (int n = 0; n < SIZE; n++) printf ("hello world\n");
5、用變量a給出下面的定義?
a)一個整型數 int a; b)一個指向整型數的指針 int *a; ? c)一個指向指針的的指針,它指向的指針是指向一個整型數 int **a;d)一個有10個整型數的數組 int a[10]; ?
e)一個有10個指針的數組,該指針是指向一個整型數的。 int *a[10];
f)一個指向有10個整型數數組的指針 int (*a)[10];
g)一個指向函數的指針,該函數有一個整型參數并返回一個整型數 int (*a)(int);
h)一個有10個指針的數組,該指針指向一個函數,該函數有一個整型參數并返回一個整型數 int (*a[10])(int); 解答: 參看:C語言再學習 -- 再論數組和指針
指針數組:首先它是一個數組,數組的元素都是指針,例如:int *p1[10];
數組指針:首先它是一個指針,它指向一個數組,例如:int (*p2)[10];
函數指針:首先它是一個指針,它指向一個函數,例如:int (*p3)(int);
這里需要明白一個符號之間優先級的問題,"[ ]"的優先級比"*"要高。p1 先與“ []”結合,構成一個數組的定義,數組名為 p1, int *修飾的是數組的內容,即數組的每個元素。那現在我們清楚,這是一個數組,其包含 10 個指向 int 類型數據的指針,即指針數組。
至于 p2 就更好理解了,在這里"( )"的優先級比"[ ]"高,"*"號和 p2 構成一個指針的定義,指針變量名為 p2, int 修飾的是數組的內容,即數組的每個元素。數組在這里并沒有名字,是個匿名數組。那現在我們清楚 p2 是一個指 針,它指向一個包含 10 個 int 類型數據的數組,即數組指針。
再至于p3也不難理解,在這里"( )"的結合方向是 從左到右,也就是說首先它是一個指針,它指向一個函數,即函數指針。
6、關鍵字static的作用是什么??
static 修飾全局變量
static 修飾局部變量
static 修飾函數
解答:
參看:C語言再學習 -- 存儲類型關鍵字
(1)static 修飾的全局變量也叫靜態全局變量,該類具有靜態存儲時期、文件作用域和內部鏈接,僅在編譯時初始化一次。如未明確初始化,它的字節都被設定為0。static全局變量只初使化一次,是為了防止在其他文件單元中被引用;利用這一特性可以在不同的文件中定義同名函數和同名變量,而不必擔心命名沖突。
(2)static 修飾的局部變量也叫靜態局部變量,該類具有靜態存儲時期、代碼作用域和空鏈接,僅在編譯時初始化一次。如未明確初始化,它的字節都被設定為0。函數調用結束后存儲區空間并不釋放,保留其當前值。
(3)static 修飾的函數也叫靜態函數,只可以在定義它的文件中使用。
7、關鍵字const有什么含意?
const 修飾的數據類型是指常類型,常類型的變量或對象的值是不能被更新的。或者說const意味著 只讀。
解答:
參看:C語言再學習 -- 關鍵字const
(1)在定義該const 變量時,通常需要對它進行初始化,因為以后就沒有機會再去改變它了;
(2)對指針來說,可以指定指針本身為const,也可以指定指針所指的數據為 const,或二者同時指定為const;
(3)在一個函數聲明中,const可以修飾形參,表明它是一個輸入參數,在函數內部不能改變其值;
(4)對于類的成員函數,若指定其為const 類型,則表明其是一個常函數,不能修改類的成員變量;
(5)對于類的成員函數,有時候必須指定其返回值為const 類型,以使得其返回值不為“左值”。
作用的話,可以保護被修改的東西,防止意外的修改,增強程序的健壯性。
8、關鍵字volatile有什么含意?并給出三個不同的例子。?
volatile 關鍵字是一種類型修飾符。volatile 的作用是作為指令關鍵字,確保本條指令不會因編譯器的優化而省略,且要求每次直接讀值。直接讀值是指從內存重新裝載內容,而不是直接從寄存器拷貝內容。? volatile 使用: (1)并行設備的硬件寄存器(如:狀態寄存器) 這些寄存器里面的值是隨時變化的。如果我們沒有將這個地址強制類型轉換成 volatile,那么我們在使用GPC1CON 這個寄存器的時候,?會直接從 CPU 的寄存器中取值。因為之前GPC1CON ?被訪問過,也就是之前就從內存中取出?GPC1CON 的值保存到某個寄存器中。之所以直接從寄存器中取值,而不去內存中取值,是因為編譯器優化代碼的結果(訪問 CPU寄存器比訪問 RAM 快的多)。用 volatile 關鍵字對?0xE0200080 ?進行強制轉換,使得每一次訪問?GPC1CON 時,執行部件都會從?0xE0200080 ?這個內存單元中取出值來賦值給?GPC1CON ?。 (2)一個中斷服務子程序中會訪問到的非自動變量 由于訪問寄存器的速度要快過RAM,所以編譯器一般都會作減少存取外部RAM的優化。 (3)多線程應用中被幾個任務共享的變量 當兩個線程都要用到某一個變量且該變量的值會被改變時,應該用 volatile 聲明,該關鍵字的作用是防止優化編譯器把變量從內存裝入CPU寄存器中。如果變量被裝入寄存器,那么兩個線程有可能一個使用內存中的變量,一個使用寄存器中的變量,這會造成程序的錯誤執行。volatile的意思是讓編譯器每次操作該變量時一定要從內存中真正取出,而不是使用已經存在寄存器中的值。解答: 參看:C語言再學習 -- 關鍵字volatile
9、嵌入式系統總是要用戶對變量或寄存器進行位操作。給定一個整型變量a,寫兩段代碼,第一個設置a的bit 3,第二個清除a 的bit 3。在以上兩個操作中,要保持其它位不變。
#define BIT3 (0x1 << 3) static int a; void set_bit3 (void) {a |= BIT3; } void clear_bit3 (void) {a &= ~BIT3; } 解答: 參看:C語言再學習 -- 位操作10、嵌入式系統經常具有要求程序員去訪問某特定的內存位置的特點。在某工程中,要求設置一絕對地址為0x67a9的整型變量的值為0xaa55。編譯器是一個純粹的ANSI編譯器。寫代碼去完成這一任務。
int *ptr;ptr = (int *)0x67a9;
*ptr = 0xaa55; 解答: 強制類型轉換。
11、中斷是嵌入式系統中重要的組成部分,這導致了很多編譯開發商提供一種擴展—讓標準C支持中斷。具代表事實是,產生了一個新的關鍵字 __interrupt。下面的代碼就使用了__interrupt關鍵字去定義了一個中斷服務子程序(ISR),請評論一下這段代碼的。
__interrupt double compute_area (double radius) { double area = PI * radius * radius; printf("\nArea = %f", area); return area; } (1)ISR 不能返回一個值。 (2)ISR 不能傳遞參數 (3)?在許多的處理器/編譯器中,浮點一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點運算。此外,ISR應該是短而有效率的,在ISR中做浮點運算是不明智的。 (4)與第三點一脈相承,printf() 經常有重入和性能上的問題。12、下面的代碼輸出是什么,為什么??
void foo(void) { unsigned int a = 6; int b = -20; (a+b > 6) ? puts("> 6") : puts("<= 6"); } 當表達式中存在有符號類型和無符號類型時所有的操作數都自動轉換為無符號類型。因此-20變成了一個非常大的正整數,所以該表達式計算出的結果大于6。 解答: 除了有符號類型和無符號類型混合使用時自動轉換為無符號類型,較小的類型和較大的類型混合使用會被轉換成較大的類型,防止數據丟失。 #include <stdio.h> #include <string.h> #include <stdlib.h> int main (void) { int a = 10; printf ("sizeof ((a > 5) ? 4 : 8.0) = %d\n", sizeof ((a > 5) ? 4 : 8.0)); return 0; } 輸出結果: sizeof ((a > 5) ? 4 : 8.0) = 813、評價下面的代碼片斷:?
unsigned int zero = 0; unsigned int compzero = 0xFFFF; /*1's complement of zero */ 對于一個int型不是16位的處理器為說,上面的代碼是不正確的。應編寫如下:?unsigned int compzero = ~0;?
14、盡管不像非嵌入式計算機那么常見,嵌入式系統還是有從堆(heap)中動態分配內存的過程的。那么嵌入式系統中,動態分配內存可能發生的問題是什么??
(1)越界,理論上可以申請4G,但超過程序內存的大小就會返回空指針,所以要檢查返回的指針是否為空。? (2)用完記得釋放,以免內存泄漏。 還有使用的過程中不要指針越界,這樣會導致致命錯誤。 (3)使用 free 或 delete 釋放了內存后,沒有將指針設置為 NULL。導致產生“野指針”。 解答: 參看:C語言再學習 -- 詳解C++/C 面試題 1函數用法:
type *p;
p = (type*)malloc(n * sizeof(type));
if(NULL == p)
/*請使用if來判斷,這是有必要的*/
{
? ? perror("error...");
? ? exit(1);
}
.../*其它代碼*/
free(p);
p = NULL;/*請加上這句*/
函數使用需要注意的地方:
1、malloc 函數返回的是 void * 類型,必須通過 (type *) 來將強制類型轉換。
2、malloc 函數的實參為 sizeof(type),用于指明一個整型數據需要的大小。
3、申請內存空間后,必須檢查是否分配成功
4、當不需要再使用申請的內存時,記得釋放,而且只能釋放一次。如果把指針作為參數調用free函數釋放,則函數結束后指針成為野指針(如果一個指針既沒有捆綁過也沒有記錄空地址則稱為野指針),所以釋放后應該把指向這塊內存的指針指向NULL,防止程序后面不小心使用了它。
5、要求malloc和free符合一夫一妻制,如果申請后不釋放就是內存泄漏,如果無故釋放那就是什么也沒做。釋放只能一次,如果釋放兩次及兩次以上會出現錯誤(釋放空指針例外,釋放空指針其實也等于啥也沒做,所以釋放空指針釋放多少次都沒有問題)。
15、Typedef 在C語言中頻繁用以聲明一個已經存在的數據類型的同義字。也可以用預處理器做類似的事。例如,思考一下下面的例子:?
#define dPS struct s *?
typedef struct s * tPS;?
以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個指向結構s指針。哪種方法更好呢?(如果有的話)為什么??
typedef更好 解答: 參看:C語言再學習 -- 關鍵字typedef 首先你要了解 typedef 和 define 的區別,宏定義只是簡單的字符串代換,是在預處理完成的,而typedef是在編譯時處理的,它不是作簡單的代換,而是對類型說明符重新命名。被命名的標識符具有類型定義說明的功能。 上面兩種情況,從形式上看這兩者相似,但在實際使用中卻不相同。 dPS p1,p2; 在宏代換后變成: struct s* p1, p2; ?定義p1為一個指向結構的指針,p2為一個實際的結構。tPS p3,p4;? 而typedef代換后,正確地定義了p3 和p4 兩個指針。
?
總結,typedef和#define的不同之處:
1、與#define不同,typedef 給出的符號名稱僅限于對類型,而不是對值。
2、typedef 的解釋由編譯器,而不是是處理器執行。
3、雖然它的范圍有限,但在其受限范圍內,typedef 比 #define 更靈活。
16、C語言同意一些令人震驚的結構,下面的結構是合法的嗎,如果是它做些什么??
int a = 5, b = 7, c;?
c = a+++b;?
上面的例子是完全合乎語法的。 解答: C語言再學習 -- 運算符與表達式 #include <stdio.h>int main (void)
{int a = 5,b = 7,c;c = a+++b;printf ("c = %d\n", c);return 0;
}
輸出結果:
c = 12 c = a+++b; 可以看做:c ?= a++ + b; 或者 c ?= a + ++b; 兩者結果是不同的。 ++ 自增運算符 為單目運算符 結合方向 是從右到左;+ 加 雙目運算符 結合方向 從左到右;++ 優先級 高于 +。 上面的代碼被編譯器處理成:
c = a++ + b;
總結
以上是生活随笔為你收集整理的C语言再学习 -- 详解C++/C 面试题 2的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SaaS 通识系列 1:云计算是什么
- 下一篇: 国内首家,每周到岗上班3天,携程率先推出