C++面試題


語言基礎類

 

0.各種類型和0值比較

bool類型和0值比較

假設有bool類型的flag

if (flag)    // 表示flag為真

if (!flag)    // 表示flag為假

 

整型和0值比較

  假設整型變量value

    if (value == 0)  

      if(value != 0)

浮點型和0值比較

  假設浮點型變量x

  if ((x>=-EPSINON) &&(x<=EPSINON)) 或者  if(abs(x) <= EPSINON)

  其中EPSINON是允許的誤差(即精度)。 const float EPSINON = 0.000001,至於為什么取0.000001,可以自己按實際情況定義。

指針類型和0值比較

  假設指針變量p

   if (p ==NULL)    // p與NULL顯式比較,強調p是指針變量

   if (p != NULL)

https://www.cnblogs.com/xiaokang01/p/12589794.html

1. 指針和引用的區別?

   (1)指針有自己的一塊空間,而引用只是一個別名;

 (2)使用 sizeof 看一個指針的大小為4字節(32位,如果要是64位的話指針為8字節),而引用則是被引用對象的大小。

   (3)  引用必須在定義時被初始化,指針不必;

     (4)不存在指向空值的引用,但存在指向空值的指針。

2.static和 const的用法,(能說出越多越好)(重點)

 首先說說const的用法(絕對不能說是常數)

 把變量修飾為只讀

  1. const 常量:定義時就初始化,以后不能更改。
  2. const 形參:func(const int a){};該形參在函數里不能改變
  3. const修飾類成員函數:該函數對成員變量只能進行只讀操作

下面的聲明都是什么意思?
const int a; a是一個常整型數
int const a; a是一個常整型數
const int *a; a是一個指向常整型數的指針,整型數是不可修改的,但指針可以
int * const a; a為指向整型數的常指針,指針指向的整型數可以修改,但指針是不可修改的
int const * a const; a是一個指向常整型數的常指針,指針指向的整型數是不可修改的,同時指針也是不可修改的
通過給優化器一些附加的信息,使用關鍵字const也許能產生更緊湊的代碼。

合理地使用關鍵字const可以使編譯器很自然地保護那些不希望被改變的參數,防止其被無意的代碼修改。簡而言之,這樣可以減少bug的出現。

再說說static的用法

  1. static局部變量 將一個變量聲明為函數的局部變量,那么這個局部變量在函數執行完成之后不會被釋放,而是繼續保留在內存中
  2. static 全局變量 表示一個變量在當前文件的全局內可訪問
  3. static 函數 表示一個函數只能在當前文件中被訪問
  4. static 類成員變量 表示這個成員為全類所共有
  5. static 類成員函數 表示這個函數為全類所共有,而且只能訪問靜態成員變量

 

static關鍵字的作用

(1)函數體內static變量的作用范圍為該函數體,該變量的內存只被分配一次,因此其值在下次調用時仍維持上次的值;
(2)在模塊內的static全局變量和函數可以被模塊內的函數訪問,但不能被模塊外其它函數訪問;
(3)在類中的static成員變量屬於整個類所擁有,對類的所有對象只有一份拷貝;
(4)在類中的static成員函數屬於整個類所擁有,這個函數不接收this指針,因而只能訪問類的static成員變量。

 const關鍵字的作用

(1)阻止一個變量被改變
(2)聲明常量指針和指針常量
(3)const修飾形參,表明它是一個輸入參數,在函數內部不能改變其值;
(4)對於類的成員函數,若指定其為const類型,則表明其是一個常函數,不能修改類的成員變量;
(5)對於類的成員函數,有時候必須指定其返回值為const類型,以使得其返回值不為”左值”。


3.extern c 作用

 告訴編譯器該段代碼以C語言進行編譯。

4.堆和棧的區別

(1)堆棧空間分配區別:

棧(操作系統):由操作系統自動分配釋放 ,存放函數的參數值,局部變量的值等。其操作方式類似於數據結構中的棧;
堆(操作系統): 一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收,分配方式倒是類似於鏈表。
(2)堆棧的緩存方式區別

棧:是內存中存儲值類型的,大小為2M(window,linux下默認為8M,可以更改),超出則會報錯,內存溢出
堆:內存中,存儲的是引用數據類型,引用數據類型無法確定大小,堆實際上是一個在內存中使用到內存中零散空間的鏈表結構的存儲空間,堆的大小由引用類型的大小直接決定,引用類型的大小的變化直接影響到堆的變化
(3)堆棧數據結構上的區別

堆(數據結構):堆可以被看成是一棵樹,如:堆排序;
棧(數據結構):一種先進后出的數據結構。

5. 關於靜態內存分配和動態內存分配的區別及過程

1) 靜態內存分配是在編譯時完成的,不占用CPU資源;動態分配內存運行時完成,分配與釋放需要占用CPU資源;

2)靜態內存分配是在棧上分配的,動態內存是堆上分配的;

3)動態內存分配需要指針或引用數據類型的支持,而靜態內存分配不需要;

4)靜態內存分配是按計划分配,在編譯前確定內存塊的大小,動態內存分配運行時按需分配。

5)靜態分配內存是把內存的控制權交給了編譯器,動態內存把內存的控制權交給了程序員;

6)靜態分配內存的運行效率要比動態分配內存的效率要高,因為動態內存分配與釋放需要額外的開銷;動態內存管理水平嚴重依賴於程序員的水平,處理不當容易造成內存泄漏。

6. 頭文件中的ifndef/define/endif 干什么用?

預處理,防止頭文件被重復使用

# define  N 10

知識單純的文本替換,把所有的N換成10

7. 用struct與class的區別

定義類差別:默認情況下,struct成員的訪問級別為public,而class成員的為private。語法使用也相同,直接將class改為struct即可。

繼承差別:使用class保留字的派生類默認具有private繼承,而用struct保留字定義的類某人具有public繼承。

主要點就兩個:默認的訪問級別和默認的繼承級別 class都是private

 

8.派生類與虛函數概述

 

(1) 派生類繼承的函數不能定義為虛函數。虛函數是希望派生類重新定義。如果派生類沒有重新定義某個虛函數,則在調用的時候會使用基類中定義的版本。

(2)派生類中函數的聲明必須與基類中定義的方式完全匹配。

(3) 基類中聲明為虛函數,則派生類也為虛函數

9. 虛函數與純虛函數區別

1)虛函數在子類里面也可以不重載的;但純虛必須在子類去實現

2)帶純虛函數的類叫虛基類也叫抽象類,這種基類不能直接生成對象,只能被繼承,重寫虛函數后才能使用,運行時動態動態綁定!

10.深拷貝與淺拷貝

淺拷貝:

char ori[]=“hello”;char*copy=ori;

深拷貝:

char ori[]="hello";  char *copy=new char[];  copy=ori;

淺拷貝只是對指針的拷貝,拷貝后兩個指針指向同一個內存空間,深拷貝不但對指針進行拷貝,而且對指針指向的內容進行拷貝,經深拷貝后的指針是指向兩個不同地址的指針。

淺拷貝可能出現的問題:

1) 淺拷貝只是拷貝了指針,使得兩個指針指向同一個地址,這樣在對象塊結束,調用函數析構的時,會造成同一份資源析構2次,即delete同一塊內存2次,造成程序崩潰。

2) 淺拷貝使得兩個指針都指向同一塊內存,任何一方的變動都會影響到另一方。

3) 同一個空間,第二次釋放失敗,導致無法操作該空間,造成內存泄漏。

11.什么是重載,重寫

重載 重寫重定義
重寫發生在兩個類之間
重載必須在一個類之間

重寫分為兩類:
  1虛函數重寫 將發生多態
   2非虛函數重寫 (重定義)

重載:用同一個函數名定義不同的函數,當函數名和不同的參數搭配時函數的含義不同

函數重載發生在同一個類中。
重載發生的條件:
  • 函數名稱必須相同。
  • 參數列表必須不同(個數不同、類型不同、參數排列順序不同等)。
  • 函數的返回類型可以相同也可以不相同。
  • 僅僅返回類型不同不足以成為函數的重載。
Override(覆蓋或重寫):是指派生類函數覆蓋基類函數,特征是:
(1)不同的范圍(分別位於派生類與基類);
(2)函數名字相同;
(3)參數相同;
(4)基類函數必須有virtual 關鍵字。

多態發生的條件

必須有繼承,虛函數重寫, virtual關鍵字,  實參傳入指針或者引用(形參必須是父類指針)..搭建舞台

總結來說:

1要有繼承
2要有虛函數重寫
3用父類指針(父類引用)指向子類對象....(搭建舞台和調用)
https://www.cnblogs.com/xiaokang01/p/9168933.html

12.什么是多態?多態有什么用途,什么是動態綁定和靜態綁定

什么是多態:同種形態的不同表現形式

C++ 多態有兩種:靜態多態(早綁定)、動態多態(晚綁定)。靜態多態是通過函數重載實現的;動態多態是通過虛函數實現的

多態性是一個接口,多種實現,是面向對象的核心。編譯時多態性:通過重載函數實現。運行時多態性:通過虛函數實現,結合動態綁定。

多態發生的條件:必須有繼承,虛函數重寫, virtual關鍵字,  實參傳入指針或者引用(形參必須是父類指針)..搭建舞台

  總結來說:

  1要有繼承
  2要有虛函數重寫
  3用父類指針(父類引用)指向子類對象....(搭建舞台和調用)

注:多態與非多態的實質區別就是函數地址是靜態綁定還是動態綁定。
如果函數的調用在編譯器編譯期間就可以確定函數的調用地址,並產生代碼,說明地址是靜態綁定的;
如果函數調用的地址是 需要在運行期間才確定,屬於動態綁定。

多態的目的:接口重用。封裝可以使得代碼模塊化,繼承可以擴展已存在的代碼,他們的目的都是為了代碼重用。而多態的目的則是為了接口重用。

多態的用法:聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,可以根據指向的子類的不同而實現不同的方法。

用一句話概括:在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數。如果對象類型是派生類,就調用派生類的函數;如果對象類型是基類,就調用基類的函數

 
        

13.C++特點是什么,多態實現機制?多態作用?

C++中多態機制主要體現在兩個方面,一個是函數的重載,一個是接口的重寫。接口多態指的是“一個接口多種形態”。每一個對象內部都有一個虛表指針,該虛表指針被初始化為本類的虛表。所以在程序中,不管你的對象類型如何轉換,但該對象內部的虛表指針是固定的,所以呢,才能實現動態的對象函數調用,這就是C++多態性實現的原理。

多態的基礎是繼承,需要虛函數的支持,簡單的多態是很簡單的。子類繼承父類大部分的資源,不能繼承的有構造函數,析構函數,拷貝構造函數,operator=函數,友元函數等等

作用:

  1. 隱藏實現細節,代碼能夠模塊化;2. 接口重用:為了類在繼承和派生的時候正確調用。

必要條件:

1. 一個基類的指針或者引用指向派生類的對象;2.虛函數

14.簡述面向對象三大特性

1)封裝:將客觀事物抽象成類,每個類對自身的數據和方法實行2)繼承3)多態:允許一個基類的指針或引用指向一個派生類對象

15.虛函數表

多態是由虛函數實現的,而虛函數主要是通過虛函數表(V-Table)來實現的。

https://www.cnblogs.com/xiaokang01/p/12394420.html

16. C++中哪些不能是虛函數?,為什么

1)普通函數只能重載,不能被重寫,因此編譯器會在編譯時綁定函數。
2)構造函數是知道全部信息才能創建對象,然而虛函數允許只知道部分信息。
3)內聯函數在編譯時被展開,虛函數在運行時才能動態綁定函數。
4)友元函數 因為不可以被繼承。
5)靜態成員函數 只有一個實體,不能被繼承。父類和子類共有。

為什么構造函數不能是虛函數

簡單講就是沒有意義。虛函數的作用在於通過子類的指針或引用來調用父類的那個成員函數。

而構造函數是在創建對象時自己主動調用的,不可能通過子類的指針或者引用去調用。

為什么析構函數必須是虛函數?為什么C++默認的析構函數不是虛函數?

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

C++默認的析構函數不是虛函數是因為虛函數需要額外的虛函數表和虛表指針,占用額外的內存。而對於不會被繼承的類來說,其析構函數如果是虛函數,就會浪費內存。因此C++默認的析構函數不是虛函數,而是只有當需要當作父類時,設置為虛函數。

17.C++類的六個默認成員函數:

  • 構造函數:一個特殊的成員函數,名字與類名相同,創建類類型對象的時候,由編譯器自動調用,在對象的生命周期內只且調用一次,以保證每個數據成員都有一個合適的初始值。
  • 拷貝構造函數:只有單個形參,而且該形參是對本類類型對象的引用(常用const修飾),這樣的構造函數稱為拷貝構造函數。拷貝構造函數是特殊的構造函數,創建對象時使用已存在的同類對象來進行初始化,由編譯器自動調用。
  • 析構函數:與構造函數功能相反,在對象被銷毀時,由編譯器自動調用,完成類的一些資源清理和收尾工作。
  • 賦值運算符重載:對於類類型的對象我們需要對‘=’重載,以完成類類型對象之間的賦值。
  • 取址操作符重載:函數返回值為該類型的指針,無參數。
  • const修飾的取址運算符重載。

18.C++中的inline內聯函數與普通函數的區別

內聯函數直接運行,不用進行入棧,出棧操作

可以用inline來定義內聯函數,不過,任何在類的說明部分定義的函數都會被自動的認為是內聯函數。內聯函數必須是和函數體聲明在一起才有效。

內聯函數要做參數類型檢查,這是內聯函數跟宏相比的優勢
宏定義是在預編譯的時候把所有的宏名用宏體來替換,簡單的說就是字符串替換, 內聯函數則是在編譯的時候進行代碼插入,編譯器會在每處調用內聯函數的地方直接把內聯函數的內容展開,這樣可以省去函數的調用的壓棧出棧的開銷,提高效率。
內聯函數是指嵌入代碼,就是在調用函數的地方不是跳轉,而是把代碼直接寫到那里去。對於短小簡單的代碼來說,內聯函數可以帶來一定的效率提升,而且和C時代的宏函數相比,內聯函數 更安全可靠。可是這個是以增加空間消耗為代價的
const與#define的區別:宏在預處理階段替換,const在編譯階段替換;宏沒有類型,不做安全檢查,const有類型,在編譯階段進行安全檢查

C++4中類型轉換

https://www.cnblogs.com/xiaokang01/p/12376683.html

 靜態類型轉換,static_cast,基本類型之間
例子A,double類型轉換成int。B,將子類對象轉換成基類對象。
常量類型轉換,const_cast, 去除指針變量的常量屬性。
無法將非指針的常量轉換為普通變量。
動態類型轉換,dynamic_cast,運行時進行轉換分析的,並非在編譯時進行。dynamic_cast轉換符只能用於含有虛函數的類。dynamic_cast用於類層次間的向上轉換和向下轉換,還可以用於類間的交叉轉換。在類層次間進行向上轉換,即子類轉換為父類,此時完成的功能和static_cast是相同的,因為編譯器默認向上轉換總是安全的。向下轉換時,dynamic_cast具有類型檢查的功能,更加安全。類間的交叉轉換指的是子類的多個父類之間指針或引用的轉換。該函數只能在繼承類對象的指針之間或引用之間進行類型轉換,或者有虛函數的類。

 

 

 19.什么叫智能指針?常用的智能指針有哪些?智能指針的實現?

智能指針是一個存儲指向動態分配(堆)對象指針的類,構造函數傳入普通指針,析構函數釋放指針。棧上分配,函數或程序結束自動釋放,防止內存泄露。使用引用計數器,類與指向的對象相關聯,引用計數跟蹤該類有多少個對象共享同一指針。創建類的新對象時,初始化指針並將引用計數置為1;當對象作為另一對象的副本而創建,增加引用計數;對一個對象進行賦值時,減少引用計數,並增加右操作數所指對象的引用計數;調用析構函數時,構造函數減少引用計數,當引用計數減至0,則刪除基礎對象。

std::auto_ptr,不支持復制(拷貝構造函數)和賦值(operator=),編譯不會提示出錯。

C++11引入的unique_ptr, 也不支持復制和賦值,但比auto_ptr好,直接賦值會編譯出錯。

C++11或boost的shared_ptr,基於引用計數的智能指針。可隨意賦值,直到內存的引用計數為0的時候這個內存會被釋放。還有Weak_ptr

 

20,C++中哪些運算符不可以重載?

不能重載的5個運算符:

(1) .

(2) ?:

(3) sizeof

(4) ::

(5) *

注意個別運算符重載是如何寫的

<返回類型說明符> operator <運算符符號>(<參數表>){}
重載為類的成員函數和重載為類的非成員函數。參數個數會不同,應為this指針。

https://www.cnblogs.com/xiaokang01/p/9166745.html

21. 內存對齊的原則?

A.結構體的大小為最大成員的整數倍。
B.成員首地址的偏移量為其類型大小整數倍。

https://www.cnblogs.com/xiaokang01/p/12490758.html

22. 動態分配對象和靜態分配對象的區別?

動態分配就是用運算符new來創建一個類的對象,在堆上分配內存。
靜態分配就是A a;這樣來由編譯器來創建一個對象,在棧上分配內存。

 

23.new與malloc的區別,delete和free的區別?

1.malloc/free是C/C++語言的標准庫函數,new/delete是C++的運算符
2.new能夠自動分配空間大小,malloc傳入參數。
3. new/delete能進行對對象進行構造和析構函數的調用進而對內存進行更加詳細的工作,而malloc/free不能。
既然new/delete的功能完全覆蓋了malloc/free,為什么C++還保留malloc/free呢?因為C++程序經常要調用C函數,而C程序只能用malloc/free管理動態內存。

24. 模版怎么實現?模版作用?

實現:template void swap(T& a, T& b){}
作用:將算法與具體對象分離,與類型無關,通用,節省精力

 

25. 多重類構造和析構的順序

記住析構函數的調用順序與構造函數是相反的

26.說說堆區和棧區

1).分配和管理方式不同 :

1、棧區(stack)
由編譯器自動分配釋放 ,存放函數的參數值,局部變量的值等,內存的分配是連續的,類似於平時我們所說的棧,如果還不清楚,那么就把它想成數組,它的內存分配是連續分配的,即,所分配的內存是在一塊連續的內存區域內.當我們聲明變量時,那么編譯器會自動接着當前棧區的結尾來分配內存.

2、堆區(heap)
一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由操作系統回收.類似於鏈表,在內存中的分布不是連續的,它們是不同區域的內存塊通過指針鏈接起來的.一旦某一節點從鏈中斷開,我們要人為的把所斷開的節點從內存中釋放.


2).產生碎片不同
        對堆來說,頻繁的new/delete或者malloc/free勢必會造成內存空間的不連續,造成大量的碎片,使程序效率降低。
        對棧而言,則不存在碎片問題,因為棧是先進后出的隊列,永遠不可能有一個內存塊從棧中間彈出。
3).生長方向不同
      堆是向着內存地址增加的方向增長的,從內存的低地址向高地址方向增長。
     棧是向着內存地址減小的方向增長,由內存的高地址向低地址方向增長

27.C++內存管理

棧: 存放函數參數以及局部變量,在出作用域時,將自動被釋放.棧內存分配運算內置於處理器的指令集中,效率很高,但分配的內存容量有限.

堆:new分配的內存塊(包括數組,類實例等),需delete手動釋放.如果未釋放,在整個程序結束后,OS會幫你回收掉.

自由存儲區:malloc分配的內存塊,需free手動釋放.它和堆有些相似.

全局/靜態區:保存自動全局變量和static變量(包括static全局和局部變量)。靜態區的內容在整個程序的生命周期內都存在,有編譯器在編譯的時候分配(數據段(存儲全局數據和靜態數據)和代碼段(可執行的代碼/只讀常量))。

常量存儲區:常量(const)存於此處,此存儲區不可修改.

 

 28.sizeof一個類求大小(注意成員變量,函數,虛函數,繼承等等對大小的影響)

https://blog.csdn.net/jollyhope/article/details/1895357

https://www.cnblogs.com/BeyondTechnology/archive/2010/09/21/1832369.html

29.拷貝構造函數

https://www.cnblogs.com/xiaokang01/p/9163833.html

 

30100萬個32位整數,如何最快找到中位數。能保證每個數是唯一的,如何實現O(N)算法?

1).內存足夠時:快排

 

2).內存不足時:分桶法:化大為小,把所有數划分到各個小區間,把每個數映射到對應的區間里,對每個區間中數的個數進行計數,數一遍各個區間,看看中位數落在哪個區間,若夠小,使用基於內存的算法,否則繼續划分

 

31常見的c的字符串操作函數實現

C實現常見的字符串處理函數

 

31.預處理,編譯,匯編,鏈接的過程

預處理, 展開頭文件/宏替換/去掉注釋/條件編譯                      (test.i main .i)
編譯,    檢查語法,生成匯編                                                      ( test.s  main .s)
匯編,   匯編代碼轉換機器碼                                                         (test.o main.o)
鏈接     鏈接到一起生成可執行程序                                              a.out

1.預處理,生成預編譯文件(.文件):

Gcc –E hello.c –o hello.i
2.編譯,生成匯編代碼(.s文件):

Gcc –S hello.i –o hello.s
3.匯編,生成目標文件(.o文件):
Gcc –c hello.s –o hello.o
4.鏈接,生成可執行文件:
Gcc hello.o –o hello

未完待續

網絡,數據結構等。

 

volatile可以確保每次使用變量的時候,都從內存中重新讀取,而不允許編譯器對這個變量的讀取操作進行優化

 STL方面的

stl面試總結


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM