1. 程式人生 > >C語言面試題

C語言面試題

關於“一年有多少秒”的巨集定義

網上關於這個問題的答案都是(365*24*60*60)UL,是錯誤的。
正解:數字和型別是一個整體,()是外人。

#include <stdio.h>

#define SECONDS_PER_YEAR (365*24*60*60UL) 

int main()
{
    printf("one year has %ld sec\r\n",SECONDS_PER_YEAR);
    return 0;
}

這裡寫圖片描述

寫一個標準巨集MIN,這個巨集輸入兩個引數並返回較小的那個。另外當寫下least=MIN(*p++,b)時會發生什麼

  1. 謹慎地將巨集定義中的“引數”和整個巨集虹括號括起來
    。所以嚴格地將講,下述解答#define MIN(A,B) (A) <= (B) ? (A) : (B) 以及 #define MIN(A,B) (A <= B ? A : B) 都是不對的。
  2. 防止巨集的副作用。巨集定義#define MIN(A,B) ((A) <= (B) ? (A) : (B)) 對MIN(*p++,
    b)的作用結果是 ((*p++) <= (b) ? (*p++) : (*p++))這個表示式會產生副作用,指標p會作三次++自增操作
  3. 除此之外,另一個不好的解答是#define MIN(A,B) ((A) <= (B) ? (A) : (B));
    這個解答在巨集定義的後面加了“;“
#include <stdio.h>

#define MIN(A,B) ((A) <= (B) ? (A) : (B)) 

int a = 100 ;
int b = 200 ;
int* p = &a ;

int main()
{
    printf("p is %d\r\n",(int)p);
    printf("the min is %d\r\n",MIN(*p++,b));
    printf("p is %d\r\n",(int)p);
    printf("a is %d\r\n",a);
    return 0;
}

這裡寫圖片描述

用C編寫死迴圈

while(1)
{

}

do
{


}while(1);

for(;;)
{


}

Loop:

...

goto Loop ;

指標陣列與陣列指標

  1. 指標陣列:指標陣列可以說成是”指標的陣列”,首先這個變數是一個數組,其次,”指標”修飾這個陣列,意思是說這個陣列的所有元素都是指標型別,在32位系統中,指標佔四個位元組。
  2. 陣列指標:陣列指標可以說成是”陣列的指標”,首先這個變數是一個指標,其次,”陣列”修飾這個指標,意思是說這個指標存放著一個數組的首地址,或者說這個指標指向一個數組的首地址。
char *arr[4] = {"hello", "world", "shannxi", "xian"};
//arr就是我定義的一個指標陣列,它有四個元素,每個元素是一個char *型別的指標,這些指標存放著其對應字串的首地址。

記憶體映像圖
這裡寫圖片描述
陣列指標

首先來定義一個數組指標,既然是指標,名字就叫pa

char (*pa)[4];

既然pa是一個指標,存放一個數組的地址,那麼在我們定義一個數組時,陣列名稱就是這個陣列的首地址,那麼這二者有什麼區別和聯絡呢?

  • a是一個長度為4的字元陣列,a是這個陣列的首元素首地址。既然a是地址,pa是指向陣列的指標,那麼能將a賦值給pa嗎?答案是不行的!因為a是陣列首元素首地址,pa存放的卻是陣列首地址,a是char型別,a+1,a的值會實實在在的加1,而pa是char[4]型別的,pa+1,pa則會加4,雖然陣列的首地址和首元素首地址的值相同,但是兩者操作不同,所以型別不匹配不能直接賦值,但是可以這樣:pa = &a,pa相當與二維陣列的行指標,現在它指向a[4]的地址。

C語言中static關鍵字的作用

  1. 第一、在修飾變數的時候,static修飾的靜態區域性變數只執行一次,而且延長了區域性變數的生命週期,直到程式執行結束以後才釋放。
  2. 第二、static修飾全域性變數的時候,這個全域性變數只能在本檔案中訪問,不能在其它檔案中訪問,即便是extern外部宣告也不可以。
  3. 第三、static修飾一個函式,則這個函式的只能在本檔案中呼叫,不能被其他檔案呼叫。Static修飾的區域性變數存放在全域性資料區的靜態變數區。初始化的時候自動初始化為0;

const用途

const int *ptr; /*ptr為指向整型常量的指標,ptr的值可以修改,但不能修改其所指向的值*/
int *const ptr;/*ptr為指向整型的常量指標,ptr的值不能修改,但可以修改其所指向的值*/
const int *const ptr;/*ptr為指向整型常量的常量指標,ptr及其指向的值都不能修改*/

用途

  • 修飾函式形參,使得形參在函式內不能被修改,表示輸入引數。
int fun(const int a);
int fun(const char *str);
  • 修飾函式返回值,使得函式的返回值不能被修改。
const char *getstr(void); //使用:const *str= getstr();
const int getint(void);   //使用:const int a =getint();

volatile關鍵字的作用

volatile指定的關鍵字可能被系統、硬體、程序/執行緒改變,強制編譯器每次從記憶體中取得該變數的值,而不是從被優化後的暫存器中讀取。例子:硬體時鐘;多執行緒中被多個任務共享的變數等。

extern關鍵字的作用

用於修飾變數或函式,表明該變數或函式都是在別的檔案中定義的,提示編譯器在其他檔案中尋找定義.

extern “c”

extern “c”的作用就是為了能夠正確實現C++程式碼呼叫其他C語言程式碼。加上extern “C”後,會指示編譯器這部分程式碼按C語言的編譯方式進行編譯,而不是C++的。

#ifdef __cplusplus 
extern “C”
{
#endif
extern void fun(int a, int b);
#ifdef __cplusplus
}
#endif

不可以將extern”C” 新增在函式內部

sizeof關鍵字的作用

sizeof是在編譯階段處理,且不能被編譯為機器碼。sizeof的結果等於物件或型別所佔的記憶體位元組數。sizeof的返回值型別為size_t

  • 變數:int a; sizeof(a)為4;
  • 指標:int *p; sizeof(p)為4;
  • 陣列: int b[10]; sizeof(b)為陣列的大小,4*10; int c[0]; sizeof(c)等於0
  • 結構體:struct (int a; char ch;)s1; sizeof(s1)為8 與結構體位元組對齊有關。
  • 注意:不能對結構體中的位域成員使用sizeof
  • sizeof(void)等於1
  • sizeof(void *)等於4

typedef

typedef struct st
{
        int a;
        int b;
        char c[0];
}st_t;

sizeof(st_t)等於8,即char c[0]的大小為0.

ASSERT() 和 assert()

:ASSERT()是一個除錯程式時經常使用的巨集,在程式執行時它計算括號內的表示式,如果表示式為FALSE (0), 程式將報告錯誤,並終止執行。如果表示式不為0,則繼續執行後面的語句。這個巨集通常原來判斷程式中是否出現了明顯非法的資料,如果出現了終止程式以免導致嚴重後果,同時也便於查詢錯誤。

  • ASSERT只有在Debug版本中才有效,如果編譯為Release版本則被忽略。
  • assert()的功能類似,它是ANSI C標準中規定的函式,它與ASSERT的一個重要區別是可以用在Release版本中。

strlen字串長度改錯

void test1()
{
  char string[10];
  char* str1 = "0123456789";
  strcpy(string, str1);
}

解答:字串str1有11個位元組(包括末尾的結束符’\0’),而string只有10個位元組,故而strcpy會導致陣列string越界。

void test2()
{
  char string[10], str1[10];
  int i;
  for(i=0; i<10; i++)
  {
      str1= 'a';
  }
  strcpy(string, str1);
}

解答:因為str1沒有結束符’\0’,故而strcpy複製的字元數不確定。

void test3(char* str1)
{
  char string[10];
  if(strlen(str1) <= 10 )
  {
      strcpy(string, str1);
  }
}

解答:應修改為if (strlen(str1) < 10),因為strlen的結果未統計最後的結束符’\0’

void GetMemory(char *p)
{
  p = (char *)malloc( 100 );
}
void Test( void )
{
  char *str = NULL;
  GetMemory(str);
  strcpy(str,"hello world");
  printf(str);
}

解答:C語言中的函式引數為傳值引數,在函式內對形參的修改並不能改變對應實參的值。故而呼叫GetMemory後,str仍為NULL。

char *GetMemory( void )
{
  char p[] = "hello world";
  return p;
}
void Test( void )
{
  char *str = NULL;
  str = GetMemory();
  printf(str);
}

解答:GetMemory中,p為區域性變數,在函式返回後,該區域性變數被回收。故而str仍為NULL

void GetMemory( char **p, int num )
{
  *p = (char *)malloc(num);
}
void Test( void )
{
  char *str = NULL;
  GetMemory(&str, 100);
  strcpy(str, "hello");
  printf(str);
}

解答:試題6避免了試題4的問題,但在GetMemory內,未對*p為NULL情況的判斷。當*p不為NULL時,在printf後,也未對malloc的空間進行free

void Test( void )
{
  char *str = (char *)malloc( 100 );
  strcpy(str, "hello" );
  free(str);
  ... //省略的其它語句
}

解答:未對str為NULL的情況的判斷,在free(str)後,str未設定為NULL,可能變成一個野指標(後面對str的操作可能會導致踩記憶體)。

swap(int* p1,int* p2)
{
  int *p;
  *p = *p1;
  *p1 = *p2;
  *p2 = *p;
}

解答:在swap函式中,p是個野指標,可能指向系統區,導致程式執行的崩潰。故而,程式應改為:

swap(int* p1,int* p2)
{
  int p;
  p = *p1;
  *p1 = *p2;
  *p2 = p;
}

程式設計題:判斷字串str2是否在字串str1裡

#include <stdio.h>  

#define OK    1  
#define ERROR 0  

int str_str(const char *str1, const char *str2)  
{  
    const char *s1 = NULL;   
    const char *s2 = NULL;   

    if (str1 == NULL)  
    {  
        return (str2 == NULL) ? OK : ERROR;  
    }  

    if (str2 == NULL)  
    {  
        return OK;  
    }  

    for (; *str1 != '\0'; str1++)  
    {  
        if (*str1 == *str2)  
        {  
            for (s1 = str1, s2 = str2; ; )  
            {  
                if (*++s2 == '\0')  
                {  
                    return OK;  
                }    
                else if (*++s1 != *s2)  
                {  
                    break;  
                }  
            }  
        }  
    }  

    return ERROR;  
}  

結構體大小、記憶體對齊方式

首先是各編譯器下sizeof()值:

  • 32位編譯器:32位系統下指標佔用4位元組

char :1個位元組
char*(即指標變數): 4個位元組(32位的定址空間是2^32, 即32個bit,也就是4個位元組。同理64位編譯器)
short int : 2個位元組
int: 4個位元組
unsigned int : 4個位元組
float: 4個位元組
double: 8個位元組
long: 4個位元組
long long: 8個位元組
unsigned long: 4個位元組

  • 64位編譯器:64位系統下指標佔用8位元組

char :1個位元組
char*(即指標變數): 8個位元組
short int : 2個位元組
int: 4個位元組
unsigned int : 4個位元組
float: 4個位元組
double: 8個位元組
long: 8個位元組
long long: 8個位元組
unsigned long: 8個位元組

結構體內對齊規則:

  • 每個成員分別按自己的對齊位元組數和PPB(指定的對齊位元組數,32位機預設為4)兩個位元組數最小的那個對齊,這樣可以最小化長度。如在32bit的機器上,int的大小為4,因此int儲存的位置都是4的整數倍的位置開始儲存。
  • 複雜型別(如結構)的預設對齊方式是它最長的成員的對齊方式
  • 結構體對齊後的長度必須是成員中最大的對齊引數(PPB)的整數倍,這樣在處理陣列時可以保證每一項都邊界對齊。
  • 結構體作為資料成員的對齊規則:在一個struct中包含另一個struct,內部struct應該以它的最大資料成員大小的整數倍開始儲存。struct A 中包含 struct B, struct B 中包含資料成員 char, int, double,則 struct B應該以sizeof(double)=8的整數倍為起始地址。(感覺跟2優點像,分不清)
struct A
{
char a;   //記憶體位置:  [0]
double b;   // 記憶體位置: [8]...[15]
int c;    // 記憶體位置: [16]...[19]  ----  規則1
};       // 記憶體大小:sizeof(A) = (1+7) + 8 + (4+4) = 24, 補齊[20]...[23]  ----  規則3
struct A
{
char a;   //記憶體位置:  [0]
double b;   // 記憶體位置: [8]...[15]
int c;    // 記憶體位置: [16]...[19]  ----  規則1
};       // 記憶體大小:sizeof(A) = (1+7) + 8 + (4+4) = 24, 補齊[20]...[23]  ----  規則3

共用體和大小端

  • 大端模式:記憶體地址從低地址開始讀,
  • 小端模式:記憶體地址從高地址開始讀。
  • 陣列地址:地址從低地址開始。

STM32微控制器的儲存方式為小端模式

首先我們要明白,什麼是大端,什麼是小端。拿二進位制的1來舉例,當它在機器中儲存時會轉換成‘00000000000000000000000000000001’
這裡寫圖片描述

#include <stdio.h>  
union UN  
{  
    int a;  
    char c;  
};  
int main()  
{  
    union UN un;  
    un.a = 1;  
    if (un.c == 1)  
        printf("小端儲存");  
    else  
        printf("大端儲存");  
    return 0;  
}  
#include <stdio.h>  
int main()  
{  
    int val = 1;  
    char *ret = (char *)&val;  
    if (*ret == 1)  
        printf("小端儲存");  
    else  
        printf("大端儲存");  
    return 0;  
}  

指標函式與函式指標的區別

  • 指標函式是指帶指標的函式,即本質是一個函式,函式返回型別是某一型別的指標
int *f(xy);
  • 函式指標是指向函式的指標變數,即本質是一個指標變數
  int (*f) (int x); /*宣告一個函式指標 */

 f=func; /* 將func函式的首地址賦給指標f */
  • 把函式的地址賦值給函式指標,可以採用下面兩種形式:
fptr=&Function;
fptr=Function;

取地址運算子&不是必需的,因為單單一個函式識別符號就標號表示了它的地址,如果是函式呼叫,還必須包含一個圓括號括起來的引數表。

  • 可以採用如下兩種方式來通過指標呼叫函式:
x=(*fptr)();
x=fptr();

第二種格式看上去和函式呼叫無異。但是有些程式設計師傾向於使用第一種格式,因為它明確指出是通過指標而非函式名來呼叫函式的。

下面舉一個例子:

void (*funcp)();
void FileFunc();
void EditFunc();

main()
{
  funcp=FileFunc;
  (*funcp)();
  funcp=EditFunc;
  (*funcp)();
}

void FileFunc()
{
  printf(FileFunc\n);
}

void EditFunc()
{
  printf(EditFunc\n);
}

程式輸出為:

FileFunc
EditFunc

請寫出bool flag 與“零值”比較的if 語句

【標準答案】if ( flag ) if ( !flag )

請寫出float x 與“零值”比較的if 語句:

【標準答案】

const float EPSINON = 0.00001;                    
if ((x >= - EPSINON) && (x <= EPSINON)           

不可將浮點變數用“==” 或“!=” 與數字比較,應該設法
轉化成“>=” 或“<=” 此類形式。

請寫出char *p 與“零值”比較的if 語句

【標準答案】

if (p == NULL)    if (p != NULL) 

用變數a 給出下面的定義

e) 一個有10個指標的陣列,該指標是指向一個整型數
的;
f) 一個指向有10個整型數陣列的指標;
g) 一個指向函式的指標,該函式有一個整型引數並返
回一個整型數;
h) 一個有10個指標的陣列,該指標指向一個函式,該
函式有一個整型引數並返回一個整型數;
【標準答案】
e)int * a[10];
f)int (*a)[10]
g)int (*a)(int);
h) int (*a[10])(int)

設有以下說明和定義:

typedef union {long i; int k[5]; char c;} DATE;
struct data { int cat; DATE cow; double dog;} too;
DATE max;

則語句

printf("%d",sizeof(struct date)+sizeof(max)); 

的執行結果是:_
【標準答案】DATE是一個union, 變數公用空間. 裡面最
大的變數型別是int[5], 佔用20個位元組. 所以它的大小是
20
data 是一個struct, 每個變數分開佔用空間. 依次為int4 +
DATE20 + double8 = 32.
所以結果是20 + 32 = 52.
當然… 在某些16位編輯器下, int 可能是2位元組,那麼結果
是int2 + DATE10 + double8 = 20

請問以下程式碼有什麼問題:

char* s="AAA";
printf("%s",s);
s[0]='B';
printf("%s",s);

有什麼錯?
【標準答案】”AAA” 是字串常量。s是指標,指向這個
字串常量,所以宣告s的時候就有問題。
cosnt char* s=”AAA”;
然後又因為是常量,所以對是s[0] 的賦值操作是不合法
的。

int (*s[10])(int) 表示的是什麼啊

【標準答案】int (*s[10])(int) 函式指標陣列,每個指標
指向一個int func(intp aram) 的函式。

c和c++ 中的struct有什麼不同?

【標準答案】c和c++ 中struct的主要區別是c中的struct
不可以含有成員函式,而c++ 中的struct可以。c++ 中
struct和class的主要區別在於預設的存取許可權不同,
struct預設為public ,而class預設為private

會出現什麼問題?列印結果是是多少?

void main()
{
    char aa[10];
    printf(“%d”,strlen(aa));
}                                          

【標準答案】sizeof()和初不初始化,沒有關係,
strlen()和初始化有關,列印結果值未知。

問sizeof(A) = ?

struct A
{
    char t:4;
    char k:4;
    unsigned short i:8;
    unsigned long m;
}; 

【標準答案】8 ,(每個成員分別按自己的對齊位元組數和PPB(指定的對齊位元組數,32位機預設為4)兩個位元組數最小的那個對齊,這樣可以最小化長度)

求sizeof(name1)?

struct name1{
char str;
short x;
int num;
} 

【標準答案】8

求sizeof(name2)?

struct name2{
char str;
int num;
short x;
}; 

【標準答案】12

要對絕對地址0x100000賦值,我們可以用

(unsigned int*)0x100000 = 1234;

那麼要是想讓程式跳轉到絕對地址是0x100000去執行,應該怎麼做

【標準答案】((void ()( ))0x100000 ) ( );
首先要將0x100000強制轉換成函式指標,即:

(void (*)())0x100000

然後再呼叫它:

*((void (*)())0x100000)();

關鍵字volatile有什麼含意? 並給出三個不同的例子

【參考答案】一個定義為volatile的變數是說這變數可
能會被意想不到地改變,這樣,編譯器就不會去假設
這個變數的值了。精確地說就是,優化器在用到這個
變數時必須每次都小心地重新讀取這個變數的值,而
不是使用儲存在暫存器裡的備份。下面是volatile變數
的幾個例子:
1). 並行裝置的硬體暫存器(如:狀態暫存器)
2). 一箇中斷服務子程式中會訪問到的非自動變數
(Non-automatic variables)
3). 多執行緒應用中被幾個任務共享的變數

<> ” “標頭檔案區別

【標準答案】對於#include  <filename.h>   ,編譯器從
標準庫路徑開始搜尋filename.h ;                                
對於#include  “filename.h” ,編譯器從使用者的工作路
徑開始搜尋filename.h

佇列和棧有什麼區別?

【標準答案】佇列先進先出,棧後進先出。

用巨集定義寫出swap(x,y),即交換兩數

標準答案】

#define swap(x, y) (x)=(x)+(y);(y)=(x)–(y);(x)=(x)–(y);