1. 程式人生 > >C++面試題目解析01

C++面試題目解析01

1,new  malloc的實現區別,

【{      0.       屬性

 

      new/delete是C++關鍵字,需要編譯器支援。malloc/free是庫函式,需要標頭檔案支援。

 

      1.       引數

 

      使用new操作符申請記憶體分配時無須指定記憶體塊的大小,編譯器會根據型別資訊自行計算。而malloc則需要顯式地指出所需記憶體的尺寸。

 

      2.       返回型別

 

      new操作符記憶體分配成功時,返回的是物件型別的指標,型別嚴格與物件匹配,無須進行型別轉換,故new是符合型別安全性的操作符。而malloc記憶體分配成功則是返回void * ,需要通過強制型別轉換將void*指標轉換成我們需要的型別。

 

      3.       分配失敗

 

      new記憶體分配失敗時,會丟擲bac_alloc異常。malloc分配記憶體失敗時返回NULL。

 

      4.      自定義型別

 

      new會先呼叫operator new函式,申請足夠的記憶體(通常底層使用malloc實現)。然後呼叫型別的建構函式,初始化成員變數,最後返回自定義型別指標。delete先呼叫解構函式,然後呼叫operator delete函式釋放記憶體(通常底層使用free實現)。

 

      malloc/free是庫函式,只能動態的申請和釋放記憶體,無法強制要求其做自定義型別物件構造和析構工作。

 

      5.      過載

 

      C++允許過載new/delete操作符,特別的,佈局new的就不需要為物件分配記憶體,而是指定了一個地址作為記憶體起始區域,new在這段記憶體上為物件呼叫建構函式完成初始化工作,並返回此地址。而malloc不允許過載。

 

      6.       記憶體區域

 

      new操作符從自由儲存區(free store)上為物件動態分配記憶體空間,而malloc函式從堆上動態分配記憶體。自由儲存區是C++基於new操作符的一個抽象概念,凡是通過new操作符進行記憶體申請,該記憶體即為自由儲存區。而堆是作業系統中的術語,是作業系統所維護的一塊特殊記憶體,用於程式的記憶體動態分配,C語言使用malloc從堆上分配記憶體,使用free釋放已分配的對應記憶體。自由儲存區不等於堆,如上所述,佈局new就可以不位於堆中。}】

 

2,解釋struct的位元組對齊,估計是昨天的試卷這個錯誤比較嚴重,,為什麼要對齊,

 

     如何實現兩個結構體的比較相等,可以用compar()按位元組比較嗎

【什麼是位元組對齊

 

  現代計算機中記憶體空間都是按照byte劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何地址開始,但實際情況是在訪問特定型別變數的時候經常在特定的記憶體地址訪問,這就需要各種型別資料按照一定的規則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。

 

位元組對齊的原因和作用

 

  各個硬體平臺對儲存空間的處理上有很大的不同。一些平臺對某些特定型別的資料只能從某些特定地址開始存取。比如有些架構的CPU在訪問 一個沒有進行對齊的變數的時候會發生錯誤,那麼在這種架構下程式設計必須保證位元組對齊.其他平臺可能沒有這種情況,但是最常見的是如果不按照適合其平臺要求對 資料存放進行對齊,會在存取效率上帶來損失。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設為32位系統)如果存放在偶地址開始的地方,那 麼一個讀週期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀週期,並對兩次讀出的結果的高低位元組進行拼湊才能得到該32bit數 據。顯然在讀取效率上下降很多。

】【

 

3,指標和引用的區別

。【1.指標和引用的定義和性質區別:

(1)指標:指標是一個變數,只不過這個變數儲存的是一個地址,指向記憶體的一個儲存單元;而引用跟原來

的變數實質上是同一個東西,只不過是原變數的一個別名而已。如:

int a=1;int *p=&a;

int a=1;int& b=a;

上面定義了一個整形變數和一個指標變數p,該指標變數指向a的儲存單元,即p的值是a儲存單元的地址。

而下面2句定義了一個整形變數a和這個整形a的引用b,事實上a和b是同一個東西,在記憶體佔有同一個儲存單

元。

(2)引用不可以為空,當被建立的時候,必須初始化,而指標可以是空值,可以在任何時候被初始化。

(3)可以有const指標,但是沒有const引用;

(4)指標可以有多級,但是引用只能是一級(int **p;合法 而 int&& a是不合法的)

(5)指標的值可以為空,但是引用的值不能為NULL,並且引用在定義的時候必須初始化;

(6)指標的值在初始化後可以改變,即指向其它的儲存單元,而引用在進行初始化後就不會再改變了。

(7)”sizeof引用”得到的是所指向的變數(物件)的大小,而”sizeof指標”得到的是指標本身的大小;

(8)指標和引用的自增(++)運算意義不一樣;

(9)如果返回動態記憶體分配的物件或者記憶體,必須使用指標,引用可能引起記憶體洩漏;

2.指標和引用作為函式引數進行傳遞時的區別。

(1)指標作為引數進行傳遞:

使用指標傳遞引數,可以實現對實參進行改變的目的,是因為傳遞過來的是實參的地址將指標作為引數進行傳遞時,事實上也是值傳遞,只不過傳遞的是地址。當把指標作

為引數進行傳遞時,也是將實參的一個拷貝傳遞給形參,(2)將引用作為函式的引數進行傳遞。

在講引用作為函式引數進行傳遞時,實質上傳遞的是實參本身,即傳遞進來的不是實參的一個拷貝,因此對

形參的修改其實是對實參的修改,所以在用引用進行引數傳遞時,不僅節約時間,而且可以節約空間。以在引用進行引數傳遞時,事實上傳遞的是實參本身而不是拷貝副本。所以在上述要達到同時修改指標的

目的的話,就得使用引用了。】

 

4,什麼技術可以代替巨集定義,

【《      常量定義

#define NUM 100

      《EffectiveC++》的第一個條款,討論的就是這個巨集。由於巨集是預編譯程式來處理,所以NUM這個名字不會加入到符號表中,如果出現編譯錯誤時,提示資訊中就不會出現NUM,而是100,為排除錯誤增加了額外的障礙。

 

      替代方案就是使用const來定義常量,或者使用列舉enum。

 

      const int NUM = 100;

 

      const常量放在標頭檔案中,也不必擔心存在多個例項的問題,對於const修飾的變數,編譯器一般也會對其進行優化,不會出現多重定義的問題。

 

      C語言中還有一個特殊的常量定義:NULL。其一般的定義為 #define NULL 0,指標的內容卻是一個整型,這不符合常理。所以在C++11中使用nullptr代替了NULL。

 

      2.函式定義

 

      由於巨集只是在程式碼中做字串替代展開,所以,用巨集定義的函式,實際上並沒有減少程式碼的體積。另外,還有一些天然的缺陷,假設一個求平方的函式巨集

 

[cpp] view plain copy #definesquare(x) (x*x) voidf(double d, int i) { square(d); //OK square(i++); //糟糕, (i++*i++) square(d+1); //更糟,(d+1*d+1) }

      縱然可以把引數加上括號來解決,#define square(x) ((x)*(x)),但i++被執行兩次這個問題還是無法解決。

 

      C++中的替代方案,就是使用inline函式。

 

      [cpp] view plain copy

      inline int square(intvalue)

      {

      return value*value;

      }

 

      inline函式具有函式的性質,引數傳遞不管是傳值還是傳引用,都不會對引數進行重複計算;同時會對引數做型別檢查,保證程式碼的正確性;inline函式也是在程式碼中做程式碼展開,效率上並不比巨集遜色。

 

      如果整型不滿足需求,還可以定義為模板:

 

      [cpp] view plain copy

      template

      inline T square(T& value)

      {

      return value*value;

      }

 

      還有一種更離譜的函式定義形式:

 

[cpp] view plain copy #defineNull_Check(p)\ if(p == NULL) return;

      這種使用反斜槓定義的函式,更得注意,如果在反斜槓後多了個空格的話,有的編譯器會出現變異錯誤,而提示資訊嘛,你可能會困擾很久的。因為反斜槓會對空格這個字元做反義,就會在程式碼中的引入非法字元,人眼是很難發現這種錯誤的。

 

      3.型別重定義

 

#defineDWORD unsigned int

      這種型別重定義完全可以使用 typedef unsigned int DWORD 來替代。

 

      4.條件編譯

 

[cpp] view plain copy #ifdefSystemA testA(); #else//SystemB testB(); #endif

      這種條件編譯巨集,一般在不同的產品或平臺使用同一套程式碼的情況,大量出現。定義了SystemA的時候,SystemB的程式碼是不編譯的,也就意味著你的程式碼沒有時刻處於編譯器的監控中。可以使用template技術來解決。

 

      [cpp] view plain copy

      constint SystemA = 1;

      constint SystemB = 2;

 

      template

      void test()

      {}

      //定義不同的系統的特化版本

      template<> void test(){ //SystemA的實現 }

      template<> void test(){ //SystemB的實現 }

 

      這樣,不同的系統使用自己的模板即可,別人的程式碼也會同時接受編譯器的檢查,不至於出現遺漏編譯錯誤的情況。

 

      5.標頭檔案包含

 

[cpp] view plain copy #ifndeftest_h #definetest_h //test.h的實現 #endif

      為了防止標頭檔案重複包含,C++中現在也只能這麼做,目前沒有別的替代方案。且看看原委,Bjarne Stroustrup在《C++語言的設計與演化》一書中,提供了一個include的設計,可惜的是並沒有真正實現。(Cpp, 即C語言前處理器)

 

      我曾經建議可以給C++本身增加一個include指示字,作為Cpp的#include的替代品。C++的這種include可以在下面三個方面與Cpp的#include不同:

 

      1)如果一個檔案被include兩次,第二個include將被忽略。這解決了一個實際問題,而目前這個問題是通過#define和#ifdef,以非常低效而笨拙的方式處理的。

 

      2)在include的正文之外定義的巨集將不在include的正文內部展開。這就提供了一種能夠隔離資訊的機制,可以使這些資訊不受巨集的干擾。

 

      3)在include的正文內容定義的巨集在include正文之後的正文處理中不展開。這保證了include正文內部的巨集不會包含它的編譯單位強加上某種順序依賴性,並一般地防止了由巨集引起的奇怪情況。

 

      對於採用預編譯標頭檔案的系統而言一般地說,對於那些要用獨立部分組合軟體的人們而言,這種機制都將是一個福音。請注意,無論如何這還只是一個思想而不是一個語言特徵。》】

5,描述下快速排序的思想,時間複雜度是多少,什麼情況下複雜度最大

【《快速排序使用分治法來把一個串(list)分為兩個子串(sub-lists)。具體演算法描述如下:

 

      從數列中挑出一個元素,稱為 “基準”(pivot);

      重新排序數列,所有元素比基準值小的擺放在基準前面,所有元素比基準值大的擺在基準的後面(相同的數可以到任一邊)。在這個分割槽退出之後,該基準就處於數列的中間位置。這個稱為分割槽(partition)操作;

      遞迴地(recursive)把小於基準值元素的子數列和大於基準值元素的子數列排序。》】《在最優的情況下,快速排序演算法的時間複雜度為O(nlogn)。

 

      在最壞的情況下,待排序的序列為正序或者逆序,每次劃分只得到一個比上一次劃分少一個記錄的子序列,注意另一個為空。如果遞迴樹畫出來,它就是一棵斜樹。此時需要執行n‐1次遞迴呼叫,且第i次劃分需要經過n‐i次關鍵字的比較才能找到第i個記錄,也就是樞軸的位置,最終其時間複雜度為O(n2)。》

6,寫下二分查詢,現場和平時寫程式還是有很大區別的,無形中估計是很緊張,特簡單的一個程式都寫的特別亂

【《《#include<iostream>

usingnamespacestd;

intbinary_search(intarr[],intn,intkey)

{

          intleft = 0;  //陣列的首位置,即arr[0]處

          intright = n - 1;//陣列的最後一個位置,即arr[n-1],陣列大小為n

 

          //迴圈條件一定要注意

          while(left< = right)

          {

                   intmid = left + ((right - left) >> 1);//此處的mid的計算一定要放在while迴圈內部,否則mid無法正確更新;並且此處用移位代替除以2可以提高效率,而且可以防止溢位。

                   if(arr[mid] > key)//陣列中間的位置得數大於要查詢的數,那麼我們就在中間數的左區間找

                   {

                             right = mid - 1;

                   }

                   elseif(arr[mid]< key)//陣列中間的位置得數小於要查詢的數,那麼我們就在中間數的右區間找

                   {

                             left = mid + 1;

                   }

                   else

                   {

                             returnmid;//中間數剛好是要查詢的數字。

                   }

          }

 

          //執行流如果走到此處說明沒有找到要查詢的數字。

          return-1;

}

測試用例如下所示:

voidTest()

{

          intarr[5] = { 1, 3, 5, 9, 10 };

          intret1 = 0,ret2 = 0,ret3 = 0;

          ret1 = binary_search(arr, 5, 9);

          cout<< ret1<< endl;

          ret2 = binary_search(arr, 5, 5);

          cout<< ret2<< endl;

          ret3 = binary_search(arr, 5, 2);

          cout<< ret3<< endl;

}》》】

 

7,動態連結庫的介面函式是什麼《

【《》】》

 

8,雲上傳是怎麼實現秒傳的

《【原理:

   當我們上傳檔案的時候,軟體會有短暫的延遲(提示“正在準備上傳檔案”),這段延遲就是先校驗你的檔案的MD5(詳解見下文),然後再在該雲盤的伺服器中通過MD5查詢伺服器中是否有相同的檔案,如果有的話,那麼就是將伺服器中的檔案直接複製一份到你的雲盤中,而不是將你的檔案傳到雲盤。這樣的話就有了“秒傳”的功能。當然,如果伺服器中沒有相同的檔案,那麼必須經過一點點的上傳才可以,這時候上傳的時間就得看你上傳檔案的大小和網速了。

   如果大家沒有這個經歷的話,嘗試一下通過別人的分享,然後直接儲存到自己的網盤,這樣可以很快的體驗到“秒傳”的快感!

【《{}MD5演算法的簡要敘述:

MD5以512位分組來處理輸入的資訊,且每一分組又被劃分為16個32位子分組,經過了一系列的處理後,演算法的輸出由四個32位分組組成,將這四個32位分組級聯後將生成一個128位雜湊值。

MD5演算法特點:

1、壓縮性:任意長度的資料,算出的MD5值長度都是固定的(把一個任意長度的位元組串變換成一定長的十六進位制數字串)。

2、容易計算:從原資料計算出MD5值很容易。

3、抗修改性:對原資料進行任何改動,哪怕只修改1個位元組,所得到的MD5值都有很大區別。

4、弱抗碰撞:已知原資料和其MD5值,想找到一個具有相同MD5值的資料(即偽造資料)是非常困難的。

5、強抗碰撞:想找到兩個不同的資料,使它們具有相同的MD5值,是非常困難的。》】】》