面試必備之C/C++基礎問題和答案匯總(二)

云峰小羅手機拍攝

剛剛畢業找工作時,整理了一些C/C++基礎問題和答案,有些是日常遇到的問題,有些是網上其他人的分享,其中涉及到程序輸出的問題我都親自編程驗證過,在問題后面也用紅色字體標注了“驗證by云峰小羅”。最近應該還有些畢業生還在找工作,所以分享出來,希望能有些幫助。畢竟我當年畢業時就是對這些題目特別留心,然后順利校招進入鵝廠的。本文介紹關于計算機、網絡、操作系統原理和常考算法,關于C/C++語法類原理類題目請參考:C/C++基礎問題和答案匯總(一)

1、為什么內存對齊可以提高訪問內存的速度?

一個周期存取一個字長(32位機就是32位),但是這個字長,是對齊的,就是只能從00-01等等,所以要是一個單位跨了兩個這樣的單元,就要多讀一次。

例如:大家都知道一個byte是8個bit,而現在流行的32位機指的是一次可以存取32個bit,也就是4個byte,在這種情況下,最有效率的作法當然是一次讀4個byte。也就是即便你只取一個byte的內容,實際上,機器一次也是取了4個byte,然后把其中的一個byte給你。當然取4個byte并不是隨機組合的,而是按照一定的次序,比如一次取0、1、2、3四個單元的內容,下次訪問就是4、5、6、7。由此,如果你的數據恰好在0、1、2、3,則機器只需訪問一次,就可以把所有的內容取出來,然而,如果你的數據跨越了這個邊界,比如在2、3、4、5,機器在第一次訪問的時候,只能取出2、3的內容,還需要進行一次訪問才能將4、5的內容取出。如此一來,必須進行兩次訪問才能取出,所以效率當然會降低。如果讀一個數據值(32bit)要讀兩次cache或者要讀兩個頁,甚至引發缺頁中斷,是非常劃不來的,計算機系統的設計中要盡量避免可能導致執行時間不確定的情況。


2、全局變量和局部變量

一個程序將操作系統分配給其運行的內存塊分為4個區域:


程序內存空間示意

(1)代碼區,存放程序的代碼,即程序中的各個函數代碼塊。

(2)全局數據區,存放程序的全局數據和靜態數據。

(3)堆區,存放程序的動態數據。

(4)棧區,存放程序的局部數據,即各個函數中的數據。

全局變量是整個程序都可訪問的變量,誰都可以訪問,生存期在整個程序從運行到結束(在程序結束時所占內存釋放);而局部變量存在于模塊(子程序,函數)中,只有所在模塊可以訪問,其他模塊不可直接訪問,模塊結束(函數調用完畢),局部變量消失,所占據的內存釋放。操作系統和編譯器,可能是通過內存分配的位置來知道的,全局變量分配在全局數據段并且在程序開始運行的時候被加載.局部變量則分配在堆棧里面。

3、什么是預編譯?

預編譯又稱為預處理,是做些代碼文本的替換工作。處理#開頭的指令,比如拷貝#include包含的文件代碼,#define宏定義的替換,條件編譯等,就是為編譯做的預備工作的階段,主要處理#開始的預編譯指令,預編譯指令指示了在程序正式編譯前就由編譯器進行的操作,可以放在程序中的任何位置。編譯系統在對程序進行通常的編譯之前,先進行預處理。提供的預處理功能主要有以下三種:1)宏定義2)文件包含 3)條件編譯

4、堆棧溢出一般是什么原因導致的?

1、沒有對數組進行邊界檢查,數組越界訪問;

2、沒有回收垃圾資源導致內存泄露,最后內存耗盡

3、循環的遞歸調用或者太深層次的遞歸調用

4、如果使用占用內存過大的數據結構的局部變量,導致堆棧溢出

5、堆與棧的去區別

A.申請方式不同

Stack由系統自動分配,而heap需要程序員自己申請,并指明大小。

B.申請后系統的響應不同

棧:只要棧的剩余空間大于申請空間,系統就為程序提供內存,否則將拋出棧溢出異常。堆:當系統收到程序申請時,先遍歷操作系統中記錄空閑內存地址的鏈表,尋找第一個大于所申請空間的堆結點,然后將該結點從空間結點鏈表中刪除,并將該結點的空間分配給程序。另外,大多數系統還會在這塊內存空間中的首地址處記錄本次分配的大小,以便于delete語句正確釋放空間。而且,由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動將多余的那部分重新放入空閑鏈表。

C.申請大小限制的不同

Stack:在windows下,棧的大小是2M(也可能是1M它是一個編譯時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。堆:堆是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

D.申請效率的比較:

棧由系統自動分配,速度較快。但程序員是無法控制的。堆是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度快,也最靈活。

E.堆和棧中的存儲內容

棧:在函數調用時,第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可執行語句)的地址,然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧的,然后是函數中的局部變量。注意靜態變量是不入棧的。當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。

6、不用數組,輸出二進制數。

//以下兩種解法都在VC++6.0中驗證by云峰小羅

解1:int main()

{

? ? ?int n;

? ? ?cout << "input n:";

? ? ? cin >> n;

? ? ? for(int i=1; i<=32; i++){

? ? ? cout << (n<0?1:0) << (i%8==0?" ":"");

? ? ? ?n = n<<1;

}

解2:void bit_print(int a)

{

? ? ?int i;

? ? ?int n = sizeof(int) * CHAR_BIT;

? ? ?int mask = 1 << (n - 1);

? ? for(i = 1; i <= n; ++i)

? ? {

? ? ? ? ? putchar(((a & mask) == 0) ? '0' : '1');

? ? ? ? ? ? a <<= 1;

? ? ? ? ? ?if(i % CHAR_BIT == 0 && i < n)

? ? ? ? ? ?putchar(' ');

? ? ? ?}

}

7、求1000!的末尾有幾個0.

(用素數相乘的方法來做,如72=2*2*2*3*3);只要求出1,2,。。。,1000中含因子2的個數比含5的個數多,一個2和一個5相乘得一個0,所以求出所有含因子5的個數,相加就行了。

int find5(int x)

{

? ? ? int num=0;

? ? ? while (x%5==0)

? ? ? {

? ? ? ? ? ? ?num++;

? ? ? ? ? ? ? x/=5;

? ? ? ? ?}

? ? ? ? ? return num;

}

void main()

{

? ? ? ? ? ?int result=0;

? ? ? ? ? ?for(int i=5;i<=1000;i+=5)

? ? ? ? ? ?result+=find5(i);

? ? ? ? ? cout<<result<<endl;

}

//輸出249,VC++6.0驗證 by云峰小羅

8、不用任何局部和全局變量(遞歸)實現int strlen(char *a)

int strlen(char *a)

{

? ? ? ? if('\\\\\\\\0' == *a)

? ? ? ? ? ? ? return 0;

? ? ? ? ?else

? ? ? ? ? ? ? ?return 1 + strlen(a + 1);

}//VC++6.0驗證 by云峰小羅

9、實現任意長度的整數相加或者相乘功能

void bigadd(char* num,char* str,int len)

{

? ? ? ? ? ?for(int i=len;i>0;i--)

? ? ? ? ?{

? ? ? ? ? ? ? ? num[i] += str[i];

? ? ? ? ? ? ? ? int j = i;

? ? ? ? ? ? ? ? while(num[j]>=10)

? ? ? ? ? ? ? ? ?{

? ? ? ? ? ? ? ? ? ? ? ? num[j--] -= 10;

? ? ? ? ? ? ? ? ? ? ? ? ?num[j] += 1;

? ? ? ? ? ? ? ? ? ?}

? ? ? ? ? ?}

}//VC++6.0驗證 by云峰小羅


10、寫一算法檢測單向鏈表中是否存在環。

注意,要求算法復雜度是O(n)并只使用常數空間。你只知道一個指向單向鏈表頭的指針。鏈表的長度是不定的,而且環出現的地方也是不定的,環有可能在頭,有可能在中間。而且要求是檢測,不能破壞環的結構.

答:用兩個指針來遍歷這個單向鏈表,第一個指針p1,每次走一步;第二個指針p2,每次走兩步;當p2指針追上p1的時候,就表明鏈表當中有環路了。

int testLinkRing(Link *head)

{

? ? ?Link *t1=head,*t2=head;

? ? ? while( t1->next && t2->next)

? ? ? {

? ? ? ? ? ? ? t1 = t1->next;

? ? ? ? ? ? ? ?if (NULL == (t2 = t2->next->next))

? ? ? ? ? ? ? ? return 0; //無環

? ? ? ? ? ? ? ? if (t1 == t2)

? ? ? ? ? ? ? ? ? ?return 1;

? ? ? ? ? ? }

? ? ? ? ? return 0;

}//VC++6.0驗證 by云峰小羅

如果要定位環路在鏈表當中的開始點,發現p2和p1重合,確定了單向鏈表有環路了。接下來,讓p2回到鏈表的頭部,重新走,P1也繼續走,每次步長都走1,那么當p1和p2再次相遇的時候,就是環路的入口了。

微信公眾號:云峰小羅,分享 編程.生活.段子

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容