C++常見面試問題彙總1——基本語法

參考:

https://www.jianshu.com/p/9aae0e5dc21a 

https://www.cnblogs.com/hit-ycy/p/11208052.html

  • 指針和引用的區別

  1. 指針有自己的一塊空間,而引用只是一個別名。
  2. 不存在空引用。引用必須連接到一塊合法的內存。
  3. 一旦引用被初始化爲一個對象,就不能被指向到另一個對象。指針可以在任何時候指向到另一個對象。
  4. 引用必須在創建時被初始化。指針可以在任何時間被初始化。
  • 堆和棧的區別

  1. C++ 程序中的內存分爲兩個部分:
  • 棧:在函數內部聲明的所有變量都將佔用棧內存。
  • 堆:這是程序中未使用的內存,在程序運行時可用於動態分配內存。
  • new和delete是如何實現的,new 與 malloc的異同處

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

  • C和C++的區別

設計思想上:

C++是面向對象的語言,而C是面向過程的結構化編程語言

語法上:

C++具有封裝、繼承和多態三種特性

C++相比C,增加多許多類型安全的功能,比如強制類型轉換

C++支持範式編程,比如模板類、函數模板等

  • C++、Java的聯繫與區別,包括語言特性、垃圾回收、應用場景等(java的垃圾回收機制)

語言特性的話:c++ 有指針多重繼承強制類型轉換

Java 有垃圾回收機制能夠自動釋放掉申請的內存,而C++是以能夠操作內存著稱,但是很容易帶來一些錯誤

Java是完全面向對象的語言,所有函數和變量部必須是類的一部分。除了基本數據類型之外,其餘的都作爲類對象,包括數組。對象將數據和方法結合起來,把它們封裝在類中,這樣每個對象都可實現自己的特點和行爲。而c++允許將函數和變量定義爲全局的。此外,Java中取消了c/c++中的結構和聯合,消除了不必要的麻煩。 

應用場景:c++用於底層和中間件,java用於高層

  • Struct和class的區別

在C++中,可以用struct和class定義類,都可以繼承。

區別在於:struct的默認繼承權限和默認訪問權限是public,而class的默認繼承權限和默認訪問權限是private。

另外,class還可以定義模板類形參,比如template <class T, int i>。

  • 內聯函數(inline)和宏定義(#define)的區別

(1)內聯函數在編譯時展開,宏在預編譯時展開;

(2)內聯函數直接嵌入到目標代碼中,宏是簡單的做文本替換;

(3)內聯函數有類型檢測、語法判斷等功能,而宏沒有;

(4)inline函數是函數,宏不是;

(5)宏定義時要注意書寫(參數要括起來)否則容易出現歧義,內聯函數不會產生歧義;

  • define 和const的區別(編譯階段、安全性、內存佔用等)

區別
(1)就起作用的階段而言: #define是在編譯的預處理階段起作用,而const是在編譯、運行的時候起作用。
(2)就起作用的方式而言: #define只是簡單的字符串替換沒有類型檢查。而const有對應的數據類型,是要進行判斷的,可以避免一些低級的錯誤。 
(3)就存儲方式而言:#define只是進行展開,有多少地方使用,就替換多少次,它定義的宏常量在內存中有若干個備份;const定義的只讀變量在程序運行過程中只有一份備份
(4)從代碼調試的方便程度而言: const常量可以進行調試的,define是不能進行調試的,因爲在預編譯階段就已經替換掉了。

const優點
(1)const常量有數據類型,而宏常量沒有數據類型。編譯器可以對前者進行類型安全檢查。而對後者只進行字符替換,沒有類型安全檢查,並且在字符替換可能會產生意料不到的錯誤。
(2)有些集成化的調試工具可以對const常量進行調試,但是不能對宏常量進行調試。
(3)const可節省空間,避免不必要的內存分配,提高效率

  • 在C++中const和static的用法(定義,用途)
  • const和static在類中使用的注意事項(定義、初始化和使用)

static成員不屬於任何對象,而是被他們共享。

靜態數據成員應該在類的外部定義。

因爲沒有this指針,static成員函數不能用const修飾,也不能訪問非靜態成員。

  • C++中的const類成員函數(用法和意義),以及和非const成員函數的區別

非靜態成員函數後面加const(加到非成員函數或靜態成員後面會產生編譯錯誤),表示成員函數隱含傳入的this指針爲const指針,決定了在該成員函數中,任意修改它所在的類的成員的操作都是不允許的(因爲隱含了對this指針的const引用);唯一的例外是對於 mutable修飾的成員。加了const的成員函數可以被非const對象和const對象調用,但不加const的成員函數只能被非const對象調用

  • C++的頂層const和底層const

頂層const表示指針本身是個常量,底層const表示指針所指的對象是一個常量。

Int *const p1 = &i;  不能改變P1的值,這是一個頂層const

Const int *p2 = &i;  允許改變P2的值,這是一個底層const

用實參初始化形參時會忽略到頂層const。換句話說,形參的頂層const被忽略掉了

  • C++中的重載和重寫的區別

  • 重載:兩個函數名相同,但是參數列表不同(個數,類型),返回值類型沒有要求,在同一作用域中
    重寫:子類繼承了父類,父類中的函數是虛函數,在子類中重新定義了這個虛函數,這種情況是重寫

  • final和override關鍵字

final限定某個類不能被繼承或某個虛函數不能被重寫

override關鍵字保證了派生類中聲明重寫的函數與基類虛函數有相同的簽名,可避免一些拼寫錯誤,如加了此關鍵字但基類中並不存在相同的函數就會報錯,也可以防止把本來想重寫的虛函數聲明成了重載。同時在閱讀代碼時如果看到函數聲明後加了此關鍵字就能立馬知道此函數是重寫了基類虛函數。保證重寫虛函數的正確性的同時也提高了代碼可讀性。

  • 拷貝初始化和直接初始化,初始化和賦值的區別

當用於類類型對象時,初始化的複製形式和直接形式有所不同:直接初始化直接調用與實參匹配的構造函數,複製初始化總是調用複製構造函數。複製初始化首先使用指定構造函數創建一個臨時對象,然後用複製構造函數將那個臨時對象複製到正在創建的對象

  • extern "C"的用法

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

  • 模板函數和模板類的特例化
  • C++內存管理,內存池技術(熱門問題),與csapp中幾種內存分配方式對比學習加深理解

c++的內存管理延續c語言的內存管理,但是也增加了其他的,例如智能指針,除了常見的堆棧的內存管理之外,c++支持智能指針,智能指針的對象進行賦值拷貝等操作的時候,每個智能指針都有一個關聯的計數器,該計數器記錄共享該對象的指針個數,當最後一個指針被銷燬的時候,計數器爲0,會自動調用析構函數來銷燬函數。

  • 介紹面向對象的三大特性,並且舉例說明每一個

繼承,多態,封裝。

繼承:本質是代碼複製。

多態:多態和虛函數

封裝:將數據和和對操作封裝到放在一起。

  • C++多態的實現

多態通過覆蓋和重載來完成。

  • C++虛函數相關(虛函數表,虛函數指針),虛函數的實現原理(包括單一繼承,多重繼承等)(拓展問題:爲什麼基類指針指向派生類對象時可以調用派生類成員函數,基類的虛函數存放在內存的什麼區,虛函數表指針vptr的初始化時間)

虛函數分爲兩種,純虛函數虛函數,純虛函數適用於抽象基類,不需要定義,類似一種接口,是多態的典型處理方式。

一個類如果定義了虛函數,那麼編譯器會自動爲它加上一個虛函數表,並提供一個指向虛函數表的指針,子類通過繼承,可以覆蓋父類的虛函數,當用戶調用虛函數的時候,會調用指針,去虛函數表中找匹配的虛函數,如果當前對象有覆蓋的虛函數,則去執行覆蓋的虛函數,否則執行父類的虛函數。

  • C++中類的數據成員和成員函數內存分佈情況《深度探索 C++對象模型》

  1. 空類 一個字節填充
  2. 數據成員
  • this指針

每個對象都有一個特殊的指針 this,它指向對象本身。

  • 析構函數一般寫成虛函數的原因

將可能會被繼承的父類的析構函數設置爲虛函數,可以保證當我們new一個子類,然後使用基類指針指向該子類對象,釋放基類指針時可以釋放掉子類的空間,防止內存泄漏

delete父類指針時,可以調用對象真正的析構函數。否則可能會出錯,如子類中有指針類型,並且申請了內存,這時就會造成內存泄漏。

  • 構造函數、拷貝構造函數和賦值操作符的區別

構造函數:對象不存在,沒用別的對象初始化

拷貝構造函數:對象不存在,用別的對象初始化

賦值運算符:對象存在,用別的對象給它賦值

  • 構造函數聲明爲explicit
  • 構造函數爲什麼一般不定義爲虛函數虛函數表

  • 虛函數表在構造函數調用後才建立,因而構造函數不可能爲虛函數

  • 構造函數的幾種關鍵字(default delete 0)

= default:將拷貝控制成員定義爲=default顯式要求編譯器生成合成的版本

= delete:將拷貝構造函數和拷貝賦值運算符定義刪除的函數,阻止拷貝(析構函數不能是刪除的函數 C++Primer P450)

= 0:將虛函數定義爲純虛函數(純虛函數無需定義,= 0只能出現在類內部虛函數的聲明語句處;當然,也可以爲純虛函數提供定義,不過函數體必須定義在類的外部)

  • 構造函數或者析構函數中調用虛函數會怎樣

1. 從語法上講,調用完全沒有問題。
2. 但是從效果上看,往往不能達到需要的目的。
Effective 的解釋是:
派生類對象構造期間進入基類的構造函數時,對象類型變成了基類類型,而不是派生類類型。
同樣,進入基類析構函數時,對象也是基類類型。

  • 純虛函數

純虛函數的格式   virtual  f()=0;

代表我不實現,留給子類去實現,帶有純虛函數的類也叫抽象類,不能夠實例對象出來,子類必須實現,主要解決的是多態問題,面向對象的思想

動物基類不能實現,老虎,獅子等派生類可以給出對象,所以嘛出現了純虛函數抽象類這個東西

  • 靜態類型和動態類型,靜態綁定和動態綁定的介紹

1、對象的靜態類型(static type):就是它在程序中被聲明時所採用的類型(或理解爲類型指針或引用的字面類型),在編譯期確定;

  2、對象的動態類型(dynamic type):是指“目前所指對象的類型”(或理解爲類型指針或引用的實際類型),在運行期確定;

 這兩個概念一般發生在基類和派生類之間。

 

 

  3、靜態綁定(statically bound):又名前期綁定(eraly binding),綁定的是靜態類型,所對應的函數或屬性依賴於對象的靜態類型,發生在編譯期;

  4、動態綁定(dynamically bound):又名後期綁定(late binding),綁定的是動態類型,所對應的函數或屬性依賴於對象的動態類型,發生在運行期;

  比如常見的,virtual函數是動態綁定,non-virtual函數是靜態綁定,缺省參數值也是靜態綁定。當缺省參數和虛函數一起出現的時候情況有點複雜,極易出錯。我們知道,虛函數是動態綁定的,但是爲了執行效率,缺省參數是靜態綁定的  

https://blog.csdn.net/chgaowei/article/details/6427731

  • 引用是否能實現動態綁定,爲什麼引用可以實現

可以實現,因爲動態綁定是發生在程序運行階段的,c++中動態綁定是通過對基類的引用或者指針調用虛函數時發生。

引用和指針的靜態類型和動態類型可以不一樣。

 

靜態類型:變量聲明時的類型或表達式生成的類型。編譯時已經知道。

動態類型:變量或表達式表示的內存的對象的類型。

  • 深拷貝和淺拷貝的區別(舉例說明深拷貝的安全性)

在有指針成員的情況下,淺拷貝只是將指針指向已存在的內存。即兩個對象的指針成員指向的是同一內存區域。

深拷貝的做法是申請一個內存複製一份,並將新對象指針指向備份區。

安全性:淺拷貝如果修改了指針指向的內容,將對兩個對象都有影響。

  • 對象複用的瞭解,零拷貝的瞭解

1.對象池:對象池通過對象複用的方式來避免重複創建對象,它會事先創建一定數量的對象放到池中,當用戶需要創建對象的時候,直接從對象池中獲取即可,用完對象之後再放回到對象池中,以便複用。

適用性:類的實例可重用。類的實例化過程開銷較大。類的實例化的頻率較高。

 

2.對象複用指得是設計模式,對象可以採用不同的設計模式達到複用的目的,最常見的就是繼承和組合模式了

零拷貝:零拷貝主要的任務就是避免CPU將數據從一塊存儲拷貝到另外一塊存儲,主要就是利用各種零拷貝技術,避免讓CPU做大量的數據拷貝任務,減少不必要的拷貝,或者讓別的組件來做這一類簡單的數據傳輸任務,讓CPU解脫出來專注於別的任務。這樣就可以讓系統資源的利用更加有效。

  • 介紹C++所有的構造函數

  1. 默認構造函數,默認無參
  2. 普通構造函數,默認有參
  3. 拷貝(複製)構造函數:複製構造函數參數爲類對象本身的引用,用於根據一個已存在的對象複製出一個新的該類的對象,一般在函數中會將已存在對象的數據成員的值複製一份到新創建的對象中
  • 什麼情況下會調用拷貝構造函數(三種情況)

(1)用類的一個對象去初始化另一個對象時

(2)當函數的形參是類的對象時(也就是值傳遞時),如果是引用傳遞則不會調用

(3)當函數的返回值是類的對象或引用時

  • 結構體內存對齊方式和爲什麼要進行內存對齊?

保證計算機可以一次讀出

結構體不像數組,結構體中可以存放不同類型的數據,它的大小也不是簡單的各個數據成員大小之和,限於讀取內存的要求,而是每個成員在內存中的存儲都要按照一定偏移量來存儲,根據類型的不同,每個成員都要按照一定的對齊數進行對齊存儲,最後整個結構體的大小也要按照一定的對齊數進行對齊。

同樣的結構體,裏面的數據排放順序不一樣,最後佔用的內存也不一樣大

  • 內存泄露的定義,如何檢測與避免?

內存泄漏通常是由於調用了malloc/new等內存申請的操作,但是缺少了對應的free/delete。爲了判斷內存是否泄露,我們一方面可以使用linux環境下的內存泄漏檢查工具Valgrind,另一方面我們在寫代碼時可以添加內存申請和釋放的統計功能,統計當前申請和釋放的內存是否一致,以此來判斷內存是否泄露。

解決內存泄漏最有效的辦法就是使用智能指針

  • 手寫智能指針的實現(shared_ptr和weak_ptr實現的區別)

智能指針主要用於管理在上分配的內存,它將普通的指針封裝爲一個棧對象。當棧對象的生存週期結束後,會在析構函數中釋放掉申請的內存,從而防止內存泄漏。C++ 11中最常用的智能指針類型爲shared_ptr,它採用引用計數的方法,記錄當前內存資源被多少個智能指針引用。該引用計數的內存在堆上分配。當新增一個時引用計數加1,當過期時引用計數減一。只有引用計數爲0時,智能指針纔會自動釋放引用的內存資源。對shared_ptr進行初始化時不能將一個普通指針直接賦值給智能指針,因爲一個是指針,一個是類。可以通過make_shared函數或者通過構造函數傳入普通指針。並可以通過get函數獲得普通指針。

  • 智能指針的循環引用
  • 遇到coredump要怎麼調試

core dump又叫核心轉儲。當程序運行過程中發生異常, 程序異常退出時, 由操作系統把程序當前的內存狀況存儲在一個core文件中, 叫core dump。

  • 內存檢查工具的瞭解

linux可以使用開源的Valgrind工具包,包含多個工具:Memcheck常用語檢測malloc和new這類的問題,callgrind用來檢查函數調用,cachegrind緩存使用,helgrind多線程程序中的競爭。除了valgrind還可以用mtrace這些工具

  • 模板的用法與適用場景

模板是C++泛型編程的基礎。

使用:template  //關鍵字+<模板參數列表>

應用場景:除了參數類型不一樣外,其他的內容全部一樣(函數體),用模板,而不是每一個類型都寫一個函數。

特化:模板的一個獨立的定義,其中一個或多個參數被指定爲特定的類型。通用模板不能適應所有情況。(特殊情況特殊處理)

代碼可重用,泛型編程,在不知道參數類型下,函數模板和類模板

  • 成員初始化列表的概念,爲什麼用成員初始化列表會快一些(性能優勢)?

使用初始化列表可以直接調用成員的構造函數,不用去賦值產生臨時變量,所以更快。如果不用,可能會去調用成員的構造函數和賦值構造函數(多出來的)。

  • 用過C++ 11嗎,知道C++ 11哪些新特性?

Lambda表達式, 智能指針 移動,auto,範圍for,decltype,array,forward_list,tuple(元組),正則表達式庫,隨機數庫,bitset運算

c++11提供的<random>實現了隨機數庫,它通過隨機數引擎(random_number_engines)產生隨機數序列,隨機數分佈類(random-number distribution)使用隨機數引擎生成服從特定概率分佈的隨機數。

https://www.cnblogs.com/byhj/p/4149467.html

tuple是一個固定大小的不同類型值的集合,是泛化的std::pair

https://blog.csdn.net/fjb2080/article/details/15809097

tuple<const char*, int>tp = make_tuple(sendPack,nSendSize); //構造一個tuple

這個tuple等價於一個結構體

struct A

{

char* p;

int len;

};

用tuple<const char*, int>tp就可以不用創建這個結構體了,而作用是一樣的,是不是更簡潔直觀了。還有一種方法也可以創建元組,用std::tie,它會創建一個元組的左值引用。

auto tp = return std::tie(1, "aa", 2);

//tp的類型實際是:std::tuple<int&,string&, int&>

再看看如何獲取它的值:

const char* data = tp.get<0>(); //獲取第一個值

int len = tp.get<1>(); //獲取第二個值

引入了emplace_front、emplace、emplace_back,這些操作構造而不是拷貝元素。這些操作分別對應於push_front、insert、push_back。Emplace成員使用這些參數在容器管理的內存空間中直接構造函數。例如,在c的末尾構造一個Sales_data

   c. emplace_back( “666-556695” , 25 , 52.36);///直接使用Sales_data的三個參數就可以了

   c.push_back(“666-556695” , 25 , 52.36); //錯誤,.push_back不接受3個參數

c.push_back( Sales_data(“666-556695” , 25 , 52.36) ); //正確,創建一個臨時對象

  • C++的調用慣例(簡單一點C++函數調用的壓棧過程)

函數調用大家都不陌生,調用者向被調用者傳遞一些參數,然後執行被調用者的代碼,最後被調用者向調用者返回結果。

對於程序,編譯器會對其分配一段內存,在邏輯上可以分爲代碼段,數據段,堆,棧

代碼段:保存程序文本,指令指針EIP就是指向代碼段,可讀可執行不可寫

數據段:保存初始化的全局變量和靜態變量,可讀可寫不可執行

BSS(靜態內存分配):未初始化的全局變量和靜態變量

堆(Heap):動態分配內存,向地址增大的方向增長,可讀可寫可執行

棧(Stack):存放局部變量,函數參數,當前狀態,函數調用信息等,向地址減小的方向增長,非常非常重要,可讀可寫可執行

程序開始,從main開始,首先將參數壓入棧,然後壓入函數返回地址,進行函數調用,通過跳轉指定進入函數,將函數內部的變量去堆棧上開闢空間,執行函數功能,執行完成,取回函數返回地址,進行下一個函數。

  • C++的四種強制轉換

  1. static_cast  可以實現C++中內置基本數據類型之間的相互轉換。
  2. dynamic_cast
  3. const_cast 用於將const變量轉爲非const
  4.  reinterpret_cast
  • C++中將臨時變量作爲返回值的時候的處理過程(棧上的內存分配、拷貝過程)
  • C++的異常處理
  • volatile關鍵字

volatile關鍵字是一種限定符用來聲明一個對象在程序中可以被語句外的東西修改,比如操作系統、硬件或併發執行線程。

遇到該關鍵字,編譯器不再對該變量的代碼進行優化,不再從寄存器中讀取變量的值,而是直接從它所在的內存中讀取值,即使它前面的指令剛剛從該處讀取過數據。而且讀取的數據立刻被保存。

一般說來,volatile用在如下的幾個地方:

(1)、中斷服務程序中修改的供其它程序檢測的變量需要加volatile;

(2)、多任務環境下各任務間共享的標誌應該加volatile;

(3)、存儲器映射的硬件寄存器通常也要加volatile說明,因爲每次對它的讀寫都可能有不同

  • 優化程序的幾種方法

1、 選擇合適的算法和數據結構

2、 使用盡量小的數據類型

3、 減少運算的強度

4、 結構體成員的佈局

5、 循環優化

6、 提高CPU的並行性(使用並行代碼,把長的代碼分解成短的)

7、 循環不變計算(不使用的循環變量,放到外面)

8、 函數優化(內聯函數)

  • public,protected和private訪問權限和繼承
  • decltype()和auto

decltype類型指示符

Const int c = 0,&b = c;

Decltype(c) x =3;//x是const int 類型

Decltype(b) y = x;//y是const int&類型;所以定義y時必須初始化

auto

使用auto也能在一條語句中聲明多個變量,因爲一條語句只能有一個基本的數據類型。

Auto定義的變量必須有初值,這樣才能推導出類型是什麼

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