轉載 :c++面試基礎知識點一

c++編程基礎知識點:

變量聲明和定義區別?

  • 聲明僅僅是把變量的聲明的位置及類型提供給編譯器,並不分配內存空間;定義要在定義的地方爲其分配存儲空間。
  • 相同變量可以再多處聲明(外部變量extern),但只能在一處定義

“零值比較”?

  • bool類型:if(flag)
  • int類型:if(flag == 0)
  • 指針類型:if(flag == null)
  • float類型:if((flag >= -0.000001) && (flag <= 0. 000001))

strlen和sizeof區別?

  • sizeof是運算符,並不是函數,結果在編譯時得到而非運行中獲得;strlen是字符處理的庫函數。
  • sizeof參數可以是任何數據的類型或者數據(sizeof參數不退化);strlen的參數只能是字符指針且結尾是’\0’的字符串。
  • 因爲sizeof值在編譯時確定,所以不能用來得到動態分配(運行時分配)存儲空間的大小

同一不同對象可以互相賦值嗎?

  • 可以,但含有指針成員時需要注意。對比類的對象賦值時深拷貝和淺拷貝。

結構體內存對齊問題?

  • 結構體內成員按照聲明順序存儲,第一個成員地址和整個結構體地址相同。
  • 未特殊說明時,按結構體中size最大的成員對齊(若有double成員),按8字節對齊。

static作用是什麼?在C和C++中有何區別?

  • static可以修飾局部變量(靜態局部變量)、全局變量(靜態全局變量)和函數,被修飾的變量存儲位置在靜態區。對於靜態局部變量,相對於一般局部變量其生命週期長,直到程序運行結束而非函數調用結束,且只在第一次被調用時定義
  • 對於靜態全局變量,相對於全局變量其可見範圍被縮小,只能在本文件中可見;
  • 修飾函數時作用和修飾全局變量相同,都是爲了限定訪問域。
    C++的static除了上述兩種用途,還可以修飾類成員(靜態成員變量和靜態成員函數),靜態成員變量和靜態成員函數不屬於任何一個對象,是所有類實例所共有
    static的數據記憶性可以滿足函數在不同調用期的通信,也可以滿足同一個類的多個實例間的通信。
    未初始化時,static變量默認值爲0。

struct和class的區別?

  • struct的默認訪問權限是public;class的默認訪問權限是private。
  • struct默認繼承權限是public,class默認繼承權限爲private。

malloc和new的區別?

  • malloc和free是標準庫函數,支持覆蓋;new和delete是運算符,並且支持重載
  • malloc僅僅分配內存空間,free僅僅回收空間,不具備調用構造函數和析構函數功能,用malloc分配空間存儲類的對象存在風險;
  • new和delete除了分配回收功能外,還會調用構造函數和析構函數。malloc和free返回的是void類型指針(必須進行類型轉換),new和delete返回的是具體類型指針。
  • new運算符實現機制就是在底層調用malloc函數,同時也會調用構造函數;delete運算符會調用free函數以及析構函數

指針和引用區別?

  • 引用只是別名,不佔用具體存儲空間,只有聲明沒有定義;指針是具體變量,需要佔用存儲空間。
  • 引用在聲明時必須初始化爲另一變量,一旦出現必須爲typename refname &varname形式;指針聲明和定義可以分開,可以先只聲明指針變量而不初始化,等用到時再指向具體變量。
  • 引用一旦初始化之後就不可以再改變(變量可以被引用爲多次,但引用只能作爲一個變量引用);指針變量可以重新指向別的變量。不存在指向空值的引用,必須有具體實體;但是存在指向空值的指針。

宏定義和函數有何區別?

  • 宏在編譯時完成替換,之後被替換的文本參與編譯,相當於直接插入了代碼,運行時不存在函數調用,執行起來更快;函數調用在運行時需要跳轉到具體調用函數。
  • 宏函數屬於在結構中插入代碼,沒有返回值;函數調用具有返回值。
  • 宏函數參數沒有類型,不進行類型檢查;函數參數具有類型,需要檢查類型。
  • 宏函數不要在最後加分號。

宏定義和const區別?

  • 宏替換髮生在編譯階段之前,屬於文本插入替換;const作用發生於編譯過程中。
  • 宏不檢查類型;const會檢查數據類型。
  • 宏定義的數據沒有分配內存空間,只是插入替換掉;const定義的變量只是值不能改變,但要分配內存空間。

宏定義和typedef區別?

  • 宏主要用於定義常量及書寫複雜的內容;typedef主要用於定義類型別名。
  • 宏替換髮生在編譯階段之前,屬於文本插入替換;typedef是編譯的一部分。
  • 宏不檢查類型;typedef會檢查數據類型。
  • 宏不是語句,不在在最後加分號;typedef是語句,要加分號標識結束。
  • 注意對指針的操作,typedef char * p_char和#define p_char char *區別巨大。

宏定義和內聯函數(inline)區別?

  • 在使用時,宏只做簡單字符串替換(編譯前)。而內聯函數可以進行參數類型檢查(編譯時),且具有返回值。
  • 內聯函數本身是函數,強調函數特性,具有重載等功能。
  • 內聯函數可以作爲某個類的成員函數,這樣可以使用類的保護成員和私有成員。而當一個表達式涉及到類保護成員或私有成員時,宏就不能實現了。

條件編譯#ifdef, #else, #endif作用?

  • 可以通過加#define,並通過#ifdef來判斷,將某些具體模塊包括進要編譯的內容。
  • 用於子程序前加#define DEBUG用於程序調試。
  • 應對硬件的設置(機器類型等)。
  • 條件編譯功能if也可實現,但條件編譯可以減少被編譯語句,從而減少目標程序大小。

區別以下幾種變量?

const int a;
int const a;
const int *a;
int *const a;
  • int const a和const int a均表示定義常量類型a。
  • const int *a,其中a爲指向int型變量的指針,const在 * 左側,表示a指向不可變常量。(看成const (*a),對引用加const)
  • int *const a,依舊是指針類型,表示a爲指向整型數據的常指針。(看成const(a),對指針const)

volatile有什麼作用?

  • volatile定義變量的值是易變的,每次用到這個變量的值的時候都要去重新讀取這個變量的值,而不是讀寄存器內的備份。
  • 多線程中被幾個任務共享的變量需要定義爲volatile類型。

什麼是常引用?

  • 常引用可以理解爲常量指針,形式爲const typename & refname = varname。
  • 常引用下,原變量值不會被別名所修改。
    • 原變量的值可以通過原名修改。
  • 常引用通常用作只讀變量別名或是形參傳遞。

區別以下指針類型?

int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
  • int *p[10]表示指針數組,強調數組概念,是一個數組變量,數組大小爲10,數組內每個元素都是指向int類型的指針變量。
  • int (*p)[10]表示數組指針,強調是指針,只有一個變量,是指針類型,不過指向的是一個int類型的數組,這個數組大小是10。
  • int *p(int)是函數聲明,函數名是p,參數是int類型的,返回值是int *類型的。
  • int (*p)(int)是函數指針,強調是指針,該指針指向的函數具有int類型參數,並且返回值是int類型的。

常量指針和指針常量區別?

  • 常量指針是一個指針,讀成常量的指針,指向一個只讀變量。如int const *p或const int *p。
  • 指針常量是一個不能給改變指向的指針。如int *const p。

a和&a有什麼區別?

假設數組int a[10];
int (p)[10] = &a;
a是數組名,是數組首元素地址,+1表示地址值加上一個int類型的大小,如果a的值是0x00000001,加1操作後變爲0x00000005。
(a + 1) = a[1]。
&a是數組的指針,其類型爲int (*)[10](就是前面提到的數組指針),其加1時,系統會認爲是數組首地址加上整個數組的偏移(10個int型變量),值爲數組a尾元素後一個元素的地址。
若(int *)p ,此時輸出 *p時,其值爲a[0]的值,因爲被轉爲int *類型,解引用時按照int類型大小來讀取。

數組名和指針(這裏爲指向數組首元素的指針)區別?

  • 二者均可通過增減偏移量來訪問數組中的元素。
  • 數組名不是真正意義上的指針,可以理解爲常指針,所以數組名沒有自增、自減等操作。
  • 當數組名當做形參傳遞給調用函數後,就失去了原有特性,退化成一般指針,多了自增、自減操作,但sizeof運算符不能再得到原數組的大小了。

野指針是什麼?

  • 叫空懸指針,不是指向null的指針,是指向垃圾內存的指針。
  • 產生原因及解決辦法:
    指針變量未及時初始化 => 定義指針變量及時初始化,要麼置空。
    指針free或delete之後沒有及時置空 => 釋放操作後立即置空。

堆和棧的區別?

  • 申請方式不同。
    • 棧由系統自動分配。
    • 堆由程序員手動分配。
  • 申請大小限制不同。
    • 棧頂和棧底是之前預設好的,大小固定,可以通過ulimit -a查看,由ulimit -s修改。
    • 堆向高地址擴展,是不連續的內存區域,大小可以靈活調整。
  • 申請效率不同。
    • 棧由系統分配,速度快,不會有碎片。
    • 堆由程序員分配,速度慢,且會有碎片。

delete和delete[]區別?

  • delete只會調用一次析構函數。
  • delete[]會調用數組中每個元素的析構函數。

面向對象基礎

面向對象三大特性?

封裝性:數據和代碼捆綁在一起,避免外界干擾和不確定性訪問。
繼承性:讓某種類型對象獲得另一個類型對象的屬性和方法。
多態性:同一事物表現出不同事物的能力,即向不同對象發送同一消息,不同的對象在接收時會產生不同的行爲(重載實現編譯時多態,虛函數實現運行時多態)。

public/protected/private的區別?

public的變量和函數在類的內部外部都可以訪問。
protected的變量和函數只能在類的內部和其派生類中訪問。
private修飾的元素只能在類內訪問。

對象存儲空間?

非靜態成員的數據類型大小之和。
編譯器加入的額外成員變量(如指向虛函數表的指針)。
爲了邊緣對齊優化加入的padding。

C++空類有哪些成員函數?

首先,空類大小爲1字節。
默認函數有:

  • 構造函數
  • 析構函數
  • 拷貝構造函數
  • 賦值運算符

構造函數能否爲虛函數,析構函數呢?

  • 析構函數:

    • 析構函數可以爲虛函數,並且一般情況下基類析構函數要定義爲虛函數。
    • 只有在基類析構函數定義爲虛函數時,調用操作符delete銷燬指向對象的基類指針時,才能準確調用派生類的析構函數(從該級向上按序調用虛函數),才能準確銷燬數據。
    • 析構函數可以是純虛函數,含有純虛函數的類是抽象類,此時不能被實例化。但派生類中可以根據自身需求重新改寫基類中的純虛函數。
  • 構造函數:

    • 構造函數不能定義爲虛函數。在構造函數中可以調用虛函數,不過此時調用的是正在構造的類中的虛函數,而不是子類的虛函數,因爲此時子類尚未構造好。

構造函數調用順序,析構函數呢?

  • 調用所有虛基類的構造函數,順序爲從左到右,從最深到最淺
  • 基類的構造函數:如果有多個基類,先調用縱向上最上層基類構造函數,如果橫向繼承了多個類,調用順序爲派生表從左到右順序。
  • 如果該對象需要虛函數指針(vptr),則該指針會被設置從而指向對應的虛函數表(vtbl)。
  • 成員類對象的構造函數:如果類的變量中包含其他類(類的組合),需要在調用本類構造函數前先調用成員類對象的構造函數,調用順序遵照在類中被聲明的順序。
  • 派生類的構造函數。
  • 析構函數與之相反。

拷貝構造函數中深拷貝和淺拷貝區別?

  • 深拷貝時,當被拷貝對象存在動態分配的存儲空間時,需要先動態申請一塊存儲空間,然後逐字節拷貝內容。
  • 淺拷貝僅僅是拷貝指針字面值。
    當使用淺拷貝時,如果原來的對象調用析構函數釋放掉指針所指向的數據,則會產生空懸指針。因爲所指向的內存空間已經被釋放了。

拷貝構造函數和賦值運算符重載的區別?

  • 拷貝構造函數是函數,賦值運算符是運算符重載。
  • 拷貝構造函數會生成新的類對象,賦值運算符不能。
  • 拷貝構造函數是直接構造一個新的類對象,所以在初始化對象前不需要檢查源對象和新建對象是否相同;賦值運算符需要上述操作並提供兩套不同的複製策略,另外賦值運算符中如果原來的對象有內存分配則需要先把內存釋放掉。
  • 形參傳遞是調用拷貝構造函數(調用的被賦值對象的拷貝構造函數),但並不是所有出現"="的地方都是使用賦值運算符,如下:
  Student s;
  Student s1 = s;    // 調用拷貝構造函數
  Student s2;
  s2 = s;    // 賦值運算符操作

注:類中有指針變量時要重寫析構函數、拷貝構造函數和賦值運算符

虛函數和純虛函數區別?

  • 虛函數是爲了實現動態編聯產生的,目的是通過基類類型的指針指向不同對象時,自動調用相應的、和基類同名的函數(使用同一種調用形式,既能調用派生類又能調用基類的同名函數)。虛函數需要在基類中加上virtual修飾符修飾,因爲virtual會被隱式繼承,所以子類中相同函數都是虛函數。當一個成員函數被聲明爲虛函數之後,其派生類中同名函數自動成爲虛函數,在派生類中重新定義此函數時要求函數名、返回值類型、參數個數和類型全部與基類函數相同。
  • 純虛函數只是相當於一個接口名,但含有純虛函數的類不能夠實例化。

覆蓋、重載和隱藏的區別?

  • 覆蓋是派生類中重新定義的函數,其函數名、參數列表(個數、類型和順序)、返回值類型和父類完全相同,只有函數體有區別。派生類雖然繼承了基類的同名函數,但用派生類對象調用該函數時會根據對象類型調用相應的函數。覆蓋只能發生在類的成員函數中。
  • 隱藏是指派生類函數屏蔽了與其同名的函數,這裏僅要求基類和派生類函數同名即可。其他狀態同覆蓋。可以說隱藏比覆蓋涵蓋的範圍更寬泛,畢竟參數不加限定。
  • 重載是具有相同函數名但參數列表不同(個數、類型或順序)的兩個函數(不關心返回值),當調用函數時根據傳遞的參數列表來確定具體調用哪個函數。重載可以是同一個類的成員函數也可以是類外函數。

在main執行之前執行的代碼可能是什麼?

  • 全局對象的構造函數。

哪幾種情況必須用到初始化成員列表?

  • 初始化一個const成員。
  • 初始化一個reference成員。
  • 調用一個基類的構造函數,而該函數有一組參數。
  • 調用一個數據成員對象的構造函數,而該函數有一組參數。

什麼是虛指針?

  • 虛指針或虛函數指針是虛函數的實現細節。
  • 虛指針指向虛表結構。

重載和函數模板的區別?

  • 重載需要多個函數,這些函數彼此之間函數名相同,但參數列表中參數數量和類型不同。在區分各個重載函數時我們並不關心函數體。
  • 模板函數是一個通用函數,函數的類型和形參不直接指定而用虛擬類型來代表。但只適用於參個數相同而類型不同的函數。

this指針是什麼?

  • this指針是類的指針,指向對象的首地址。
  • this指針只能在成員函數中使用,在全局函數、靜態成員函數中都不能用this。
  • this指針只有在成員函數中才有定義,且存儲位置會因編譯器不同有不同存儲位置。

類模板是什麼?

  • 用於解決多個功能相同、數據類型不同的類需要重複定義的問題。
  • 在建立類時候使用template及任意類型標識符T,之後在建立類對象時,會指定實際的類型,這樣纔會是一個實際的對象。
  • 類模板是對一批僅數據成員類型不同的類的抽象,只要爲這一批類創建一個類模板,即給出一套程序代碼,就可以用來生成具體的類。

構造函數和析構函數調用時機?

  • 全局範圍中的對象:構造函數在所有函數調用之前執行,在主函數執行完調用析構函數。
  • 局部自動對象:建立對象時調用構造函數,離開作用域時調用析構函數。
  • 動態分配的對象:建立對象時調用構造函數,調用釋放時調用析構函數。
  • 靜態局部變量對象:建立時調用一次構造函數,主函數結束時調用析構函數。

面經:

Q:C++裏是怎麼定義常量的?常量存放在內存的哪個位置?

A:常量在C++裏的定義就是一個top-level const加上對象類型,常量定義必須初始化。對於局部對象,常量存放在棧區,對於全局對象,常量存放在全局/靜態存儲區。對於字面值常量,常量存放在常量存儲區。

Q:你剛剛說到了const,const修飾成員函數的目的是什麼?

A:const修飾的成員函數表明函數調用不會對對象做出任何更改,事實上,如果確認不會對對象做更改,就應該爲函數加上const限定,這樣無論const對象還是普通對象都可以調用該函數。

Q:那如果同時定義了兩個函數,一個帶const,一個不帶,會有問題嗎?

A:不會,這相當於函數的重載。

Q:C++ 類內可以定義引用數據成員嗎?

A:可以,必須通過成員函數初始化列表初始化。

Q:new/delete與malloc/free的區別是什麼?

A:首先,new/delete是C++的關鍵字,而malloc/free是C語言的庫函數,後者使用必須指明申請內存空間的大小,對於類類型的對象,後者不會調用構造函數和析構函數。

Q:隱式類型轉換,能簡單說說嗎?

A:首先,對於內置類型,低精度的變量給高精度變量賦值會發生隱式類型轉換,其次,對於只存在單個參數的構造函數的對象構造來說,函數調用可以直接使用該參數傳入,編譯器會自動調用其構造函數生成臨時對象。

Q:如何避免隱式類型轉換?

A:explicit關鍵字。

Q:說說你瞭解的類型轉換。

A:C++類型轉換有四種。dynamic_cast、reinterprt_cast、const_cast、static_cast

Q:說說reinterpret_cast.

A: 這種是不常用的,可以用於任意類型的指針之間的轉換,對轉換的結果不做任何保證,可能被用於哈希(此處不確定)。

Q:說說dynamic_cast

A: 這種其實也是不被推薦使用的,更多使用static_cast,dynamic本身只能用於存在虛函數的父子關係的強制類型轉換,對於指針,轉換失敗則返回nullptr,對於引用,轉換失敗會拋出異常。

Q:說說const_cast

A:事實上的用途還是很普遍的。對於未定義const版本的成員函數,我們通常需要使用const_cast來去除const引用對象的const,完成函數調用。另外一種使用方式,結合static_cast,可以在非const版本的成員函數內添加const,調用完const版本的成員函數後,再使用const_cast去除const限定。

Q:說說 你瞭解的RTTI.

A:運行時類型檢查,在C++層面主要體現在dynamic_cast和typeid.

Q:具體是怎麼實現的。

A:VS中虛函數表的-1位置存放了指向type_info的指針。對於存在虛函數的類型,typeid和dynamic_cast都會去查詢type_info.

Q:你剛剛提到虛函數表,具體是怎樣實現運行時多態的。

A:簡單來講,子類若重寫父類虛函數,虛函數表中,該函數的地址會被替換,對於存在虛函數的類的對象,在VS中,對象的對象模型的頭部存放指向虛函數表的指針,通過該機制實現多態。

Q:C++函數棧空間的最大值 ?

A:默認是1M,不過可以調整。

Q:extern “C” ?

A:C++調用C函數需要extern C,因爲C語言沒有函數重載。

Q:設計模式瞭解嗎,介紹一下單例模式。

A:C++的實現有兩種,一種通過局部靜態變量,利用其只初始化一次的特點,返回對象。另外一種,則是定義全局的指針,getInstance判斷該指針是否爲空,爲空時才實例化對象。

Q:你說的第二種就是所謂的懶加載。現在有一個問題,如果併發訪問,該怎麼做。

A:使用鎖機制,防止多次訪問。

Q:你的鎖是鎖住所有的代碼嗎?

A:是。

Q:這樣會多次重複判斷是否爲空,而每次都會加鎖,有什麼辦法改善?

A:可以這樣,第一次判斷爲空不加鎖,若爲空,再進行加鎖判斷是否爲空,若爲空則生成對象。

Q:你提到了鎖機制,那麼C++的鎖你知道幾種。

A:鎖包括互斥鎖,條件變量,自旋鎖和讀寫鎖(後兩個沒答出來)。

Q:說一說你用到的。

A:生產者消費者問題利用互斥鎖和條件變量可以很容易解決,條件變量這裏起到了替代信號量的作用。balabala。

Q:C++兩種map。

A:unordered_map(哈希表)和map(紅黑樹)。

Q:紅黑樹瞭解嗎?

A:只知道本質是一顆BST,插入O(logN)。

Q: 快排的時間複雜度最差是多少?

A:O(N2)

Q:什麼時候最差?

A:樞紐元左側元素都比樞紐元小(不確定)。

Q:穩定排序哪幾種?爲什麼?

A:查課本吧,很簡單。

Q:聊聊計算機網絡的內容吧,TCP三次握手是怎樣的?

A:這部分大家參考相關課本。

Q:爲什麼兩次不可以?

A:服務器端未收到客戶端的連接確認,因此必須三次。

Q:四次呢?

A:連接確認後第三次的握手已經包含了客戶端數據,不需要四次。

Q:TCP擁塞瞭解嗎。

A:沒看。(直接跳過了這部分)。

Q:死鎖產生的必要條件?

A: 四個必要條件。

Q:如何預防?

A:-請查書。

Q:最後幾個問題,你平時如何提升自己的,在語言方面?

A:一些經典的書籍,會認真去看,同時,也會看一些對語言新特性介紹的書籍,保持敏感。

Q:分別有哪些書?

A:C++ primer,經典的入門書,深入探索C++對象模型,幫助我很好的理解C++的對象模型。effective C++,經典的關於C++進階的使用經驗。effective modern C++:關於C++11/14新特性的深入探討。

Q:說說C++primer中你覺得感受最深刻的內容。

A:這本書是入門必讀,我感覺印象最深刻的就是C++ 11的新特性,比如自動類型推導,lambda,右值引用(這裏被打斷了)。

文章轉載自:https://github.com/linw7/Skill-Tree/blob/master/編程語言C++.md#stl
以及https://www.nowcoder.com/discuss/71317?type=2&order=3&pos=21&page=1
感謝作者。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章