關於面試,我投的大部分崗位是圖像處理相關的,其中涉及到不少C++的知識點,這是前段時間總結的 C++ 面試常考題,大部分來自於牛客網和各種博客,在我實際面試中出現過的問題已經高亮表示了。
1 C 和 C++ 的區別?
1) C++是面向對象的語言,而C是面向過程的結構化編程語言
2) C++具有封裝、繼承和多態三種特性
3) C++支持范式編程,比如模板類、函數模板等
2 C++ 中指針和引用的區別?
1) 指針有自己的一塊空間,而引用只是一個別名;
2) 指針可以初始化為空,引用必須被初始化且必須是一個已有對象的引用;
3) 指針在使用時可以改變所指的對象,引用初始化的時候就固定了,不能被改變;
4) 指針++表示指向下一個對象,引用++表示所指對象加1.
3 智能指針
C++里面的四個智能指針: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三個是c++11支持,並且第一個已經被11棄用。
為什么要使用智能指針:
智能指針的作用是管理一個指針,因為存在以下這種情況:申請的空間在函數結束時忘記釋放,造成內存泄漏。使用智能指針可以很大程度上的避免這個問題,因為智能指針就是一個類,當超出了類的作用域是,類會自動調用析構函數,析構函數會自動釋放資源。
4 指針和數組的主要區別
1) 數組保存的是數據,而指針保存的是數據的地址
2) 數組直接訪問數據,而指針間接訪問數據,首先獲得指針的內容,然后將其作為地址,從該地址中提取數據
5 為什么析構函數必須是虛函數?為什么 C++ 默認的析構函數不是虛函數
1) 將可能會被繼承的父類的析構函數設置為虛函數,可以保證當我們new一個子類,然后使用基類指針指向該子類對象,釋放基類指針時可以釋放掉子類的空間,防止內存泄漏。
2) C++默認的析構函數不是虛函數是因為虛函數需要額外的虛函數表和虛表指針,占用額外的內存。而對於不會被繼承的類來說,其析構函數如果是虛函數,就會浪費內存。因此C++默認的析構函數不是虛函數,而是只有當需要當作父類時,設置為虛函數。
6 函數指針
函數指針本身首先是一個指針變量,該指針變量指向一個具體的函數。這正如用指針變量可指向整型變量、字符型、數組一樣,這里是指向函數。
C在編譯時,每一個函數都有一個入口地址,該入口地址就是函數指針所指向的地址。有了指向函數的指針變量后,可用該指針變量調用函數,就如同用指針變量可引用其他類型變量一樣,在這些概念上是大體一致的。(可以把函數當作參數進行傳遞,可以實現多態:指針指向不同的函數)
7 C++ 中的析構函數
1) 析構函數與構造函數對應,當對象結束其生命周期,如對象所在的函數已調用完畢時,系統會自動執行析構函數;
2) 析構函數名也應與類名相同,只是在函數名前面加一個位取反符~,例如~stud( ),以區別於構造函數。它不能帶任何參數,也沒有返回值(包括void類型)。只能有一個析構函數,不能重載。
3) 如果用戶沒有編寫析構函數,編譯系統會自動生成一個缺省的析構函數,如果一個類中有指針,且在使用的過程中動態的申請了內存,那么最好顯示構造析構函數在銷毀類之前,釋放掉申請的內存空間,避免內存泄漏。
4) 類析構順序:1)派生類本身的析構函數;2)對象成員析構函數;3)基類析構函數。
8 重載和覆蓋
重載:兩個函數名相同,但是參數列表不同(個數,類型),返回值類型沒有要求,在同一作用域中
重寫:子類繼承了父類,父類中的函數是虛函數,在子類中重新定義了這個虛函數,這種情況是重寫
9 static 關鍵字
1) 加了static關鍵字的全局變量只能在本文件中使用。例如在a.c中定義了static int a=10;那么在b.c中用extern int a是拿不到a的值得,a的作用域只在a.c中。
2) static定義的靜態局部變量分配在數據段上,普通的局部變量分配在棧上,會因為函數棧幀的釋放而被釋放掉。
3) 對一個類中成員變量和成員函數來說,加了static關鍵字,則此變量/函數就沒有了this指針了,必須通過類名才能訪問
10 虛函數和多態
多態的實現主要分為靜態多態和動態多態,靜態多態主要是重載,在編譯的時候就已經確定;動態多態是用虛函數機制實現的,在運行期間動態綁定。舉個例子:一個父類類型的指針指向一個子類對象時候,使用父類的指針去調用子類中重寫了的父類中的虛函數的時候,會調用子類重寫過后的函數,在父類中聲明為加了virtual關鍵字的函數,在子類中重寫時候不需要加virtual也是虛函數。
11 ++i 和 i++ 的實現
++i先自增1,再返回,i++先返回i,再自增1
1)++i實現:
int& int::operator++() { *this +=1; return *this; }
2)i++實現:
const int int::operator(int)
{
int oldValue = *this;
++(*this);
return oldValue;
}
12 C++ 函數棧空間的最大值
默認是1M,不過可以調整
13 new/delete 與 malloc/free 的區別是什么
1) new/delete是C++的關鍵字,而malloc/free是C語言的庫函數
2) 后者使用必須指明申請內存空間的大小,對於類類型的對象,后者不會調用構造函數和析構函數
14 C語言參數壓棧順序
從右到左
15 map 和 set 有什么區別
1) map和set都是C++的關聯容器,其底層實現都是紅黑樹(RB-Tree)。
2) map中的元素是key-value(關鍵字—值)對:關鍵字起到索引的作用,值則表示與索引相關聯的數據;Set與之相對就是關鍵字的簡單集合,set中每個元素只包含一個關鍵字。
3) set的迭代器是const的,不允許修改元素的值;map允許修改value,但不允許修改key。
4) map支持下標操作,set不支持下標操作。map可以用key做下標,
16 vector 和 list 的區別
1) vector, 連續存儲的容器,動態數組,在堆上分配空間 ;
底層實現:數組。
如果沒有剩余空間了,則會重新配置原有元素個數的兩倍空間,然后將原空間元素通過復制的方式初始化新空間,再向新空間增加元素。
適用場景:經常隨機訪問,且不經常對非尾節點進行插入刪除。
2)list,動態鏈表,在堆上分配空間,每插入一個元數都會分配空間,每刪除一個元素都會釋放空間。
底層:雙向鏈表
訪問:隨機訪問性能很差,只能快速訪問頭尾節點。
適用場景:經常插入刪除大量數據
2) vector在中間節點進行插入刪除會導致內存拷貝,list不會。
3) vector一次性分配好內存,不夠時才進行2倍擴容;list每次插入新節點都會進行內存申請。
4) vector擁有一段連續的內存空間,因此支持隨機訪問,如果需要高效的隨即訪問,而不在乎插入和刪除的效率,使用vector。
list擁有一段不連續的內存空間,如果需要高效的插入和刪除,而不關心隨機訪問,則應使用list。
17 STL 中迭代器的作用,有指針為何還要迭代器
1) Iterator(迭代器)模式又稱Cursor(游標)模式,用於提供一種方法順序訪問一個聚合對象中各個元素, 而又不需暴露該對象的內部表示。
2) 迭代器不是指針,是類模板,表現的像指針。他只是模擬了指針的一些功能,通過重載了指針的一些操作符,->、*、++、--等,相當於一種智能指針。
3) 迭代器產生原因:
Iterator類的訪問方式就是把不同集合類的訪問邏輯抽象出來,使得不用暴露集合內部的結構而達到循環遍歷集合的效果。
18 STL 迭代器是怎么刪除元素的呢
1) 對於序列容器vector,deque來說,使用erase(itertor)后,后邊的每個元素的迭代器都會失效,但是后邊每個元素都會往前移動一個位置,但是erase會返回下一個有效的迭代器;
2) 對於關聯容器map set來說,使用了erase(iterator)后,當前元素的迭代器失效,但是其結構是紅黑樹,刪除當前元素的,不會影響到下一個元素的迭代器,所以在調用erase之前,記錄下一個元素的迭代器即可。
3) 對於list來說,它使用了不連續分配的內存,並且它的erase方法也會返回下一個有效的iterator,
19 C++ 中類成員的訪問權限
C++通過 public、protected、private 三個關鍵字來控制成員變量和成員函數的訪問權限,它們分別表示公有的、受保護的、私有的,被稱為成員訪問限定符。在類的內部(定義類的代碼內部),無論成員被聲明為 public、protected 還是 private,都是可以互相訪問的,沒有訪問權限的限制。在類的外部(定義類的代碼之外),只能通過對象訪問成員,並且通過對象只能訪問 public 屬性的成員,不能訪問 private、protected 屬性的成員。
20 C++中 struct 和 class的區別
在C++中,可以用struct和class定義類,都可以繼承。區別在於:struct的默認繼承權限和默認訪問權限是public,而class的默認繼承權限和默認訪問權限是private.
21 左值和右值
1) 左值:能對表達式取地址、或具名對象/變量。一般指表達式結束后依然存在的持久對象。
右值:不能對表達式取地址,或匿名對象。一般指表達式結束就不再存在的臨時對象。
2) 左值可以尋址,而右值不可以。 左值可以被賦值,右值不可以被賦值,可以用來給左值賦值。左值可變,右值不可變。
22 C++11 有哪些新特性
1) auto關鍵字:編譯器可以根據初始值自動推導出類型。但是不能用於函數傳參以及數組類型的推導
2) nullptr關鍵字:nullptr是一種特殊類型的字面值,它可以被轉換成任意其它的指針類型;而NULL一般被宏定義為0,在遇到重載時可能會出現問題。
3) 智能指針:C++11新增了std::shared_ptr、std::weak_ptr等類型的智能指針,用於解決內存管理的問題。
23 C++ 源文件從文本到可執行文件經歷的過程
1)預處理階段:對源代碼文件中文件包含關系(頭文件)、預編譯語句(宏定義)進行分析和替換,生成預編譯文件。
2)編譯階段:將經過預處理后的預編譯文件轉換成特定匯編代碼,生成匯編文件
3)匯編階段:將編譯階段生成的匯編文件轉化成機器碼,生成可重定位目標文件
4)鏈接階段:將多個目標文件及所需要的庫連接成最終的可執行目標文件
24 include 頭文件的順序以及雙引號””和尖括號<>的區別
1) 雙引號和尖括號的區別:編譯器預處理階段查找頭文件的路徑不一樣。
2) 對於使用雙引號包含的頭文件,查找頭文件路徑的順序為,先查找當前頭文件目錄
24 C++ 的內存管理是怎樣的
1) 代碼段:包括只讀存儲區和文本區,其中只讀存儲區存儲字符串常量,文本區存儲程序的機器代碼。
2) 數據段:存儲程序中已初始化的全局變量和靜態變量
3) bss 段:存儲未初始化的全局變量和靜態變量(局部+全局),以及所有被初始化為0的全局變量和靜態變量。
4) 堆區:調用new/malloc函數時在堆區動態分配內存,同時需要調用delete/free來手動釋放申請的內存。
5) 棧:使用棧空間存儲函數的返回地址、參數、局部變量、返回值
25 內存泄漏,段錯誤
1) 內存泄漏通常是由於調用了malloc/new等內存申請的操作,但是缺少了對應的free/delete。判斷內存是否泄露:1 linux環境下的內存泄漏檢查工具Valgrind, 2 寫代碼時可以添加內存申請和釋放的統計功能,統計當前申請和釋放的內存是否一致.
2) 段錯誤通常發生在訪問非法內存地址的時候,具體來說分為以下幾種情況:
使用野指針
試圖修改字符串常量的內容
26 new 和 malloc 的區別
1、new分配內存按照數據類型進行分配,malloc分配內存按照指定的大小分配;
2、new返回的是指定對象的指針,而malloc返回的是void*,因此malloc的返回值一般都需要進行類型轉化。
3、new不僅分配一段內存,而且會調用構造函數,malloc不會。
4、new分配的內存要用delete銷毀,malloc要用free來銷毀;delete銷毀的時候會調用對象的析構函數,而free則不會。
5、new是一個操作符可以重載,malloc是一個庫函數。
27 軟件開發流程
1) 需求分析
2) 概要設計
3) 詳細設計
4) 編碼
5) 測試
6) 軟件交付、驗收
7) 維護
28 c/s 和 b/s 架構
1) C/S架構是第一種比較早的軟件架構,主要用於局域網內。也叫客戶機/服務器模式
分為客戶機和服務器兩層:
第一層: 在客戶機系統上結合了界面顯示與業務邏輯;
第二層: 通過網絡結合了數據庫服務器。
如果用戶要用的話需要下載一個客戶端,安裝后可以使用,比如QQ,微信等。
優點:1)C/S架構的界面和操作可以很豐富。2)安全性能可以很容易保證。3)由於只有一層交互,因此響應速度較快。
缺點:1)用戶群固定。由於程序需要安裝才可使用,因此不適合面向一些不可知的用戶。2)維護成本高,發生一次升級,則所有客戶端的程序都需要改變。
2) B/S架構的全稱為Browser/Server,即瀏覽器/服務器結構。
B/S架構有三層,分別為:
第一層表現層:主要完成用戶和后台的交互及最終查詢結果的輸出功能。
第二層邏輯層:主要是利用服務器完成客戶端的應用邏輯功能。
第三層數據層:主要是接受客戶端請求后獨立進行各種運算。
優點:1)客戶端無需安裝,有Web瀏覽器即可。2)BS架構可以直接放在廣域網上,交互性強。3)BS架構無需升級多個客戶端,升級服務器即可。
缺點:1)在速度和安全性上需要花費巨大的設計成本,這是BS架構的最大問題。2)客戶端服務器端的交互是請求-響應模式,通常需要刷新頁面。
3)C/S和B/S各有優勢,C/S在圖形的表現能力上以及運行的速度上肯定是強於B/S模式的,不過缺點就是他需要運行專門的客戶端,而且更重要的是它不能跨平台
29 數據庫事務
事務(Transaction)是由一系列對系統中數據進行訪問與更新的操作所組成的一個程序執行邏輯單元。事務是DBMS中最基礎的單位,事務不可分割。
事務具有4個基本特征,分別是:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持久性(Duration),簡稱ACID。
1) 原子性:事務被視為不可分割的最小單元,事物的所有操作要不成功,要不失敗回滾。
2) 一致性:數據庫在事務執行前后都保持一致性狀態。
3) 隔離性:一個事務所做的修改在最終提交以前,對其他事務是可不見的
4) 持久性:一旦事務提交,則其所做的修改將會永遠保存到數據庫中
30 數據庫索引
數據庫索引是為了增加查詢速度而對表字段附加的一種標識,是對數據庫表中一列或多列的值進行排序的一種結構。
DB在執行一條Sql語句的時候,默認的方式是根據搜索條件進行全表掃描,遇到匹配條件的就加入搜索結果集合。如果我們對某一字段增加索引,查詢時就會先去索引列表中一次定位到特定值的行數,大大減少遍歷匹配的行數,所以能明顯增加查詢的速度。
優點:
1)通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性。
2)可以大大加快數據的檢索速度,這也是創建索引的最主要的原因。
缺點:
1) 索引需要占物理空間
2) 當對表中的數據進行增加、刪除和修改的時候,索引也要動態的維護
31 數據庫的三大范式
1) 第一范式:當關系模式R的所有屬性都不能再分解為更基本的數據單位時,稱R是滿足第一范式,即屬性不可分
2) 第二范式:如果關系模式R滿足第一范式,並且R的所有非主屬性都完全依賴於R的每一個候選關鍵屬性。
3) 第三范式:即非主屬性不傳遞依賴於鍵碼
31 SQL 優化方法有哪些
1) 通過建立索引對查詢進行優化
2) 對查詢進行優化,應盡量避免全表掃描
32 平衡二叉樹(AVL樹)和紅黑樹
1)平衡二叉樹又稱為AVL樹,是一種特殊的二叉排序樹。其左右子樹都是平衡二叉樹,且左右子樹高度之差的絕對值不超過1。
2)紅黑樹是一種二叉查找樹,但在每個節點增加一個存儲位表示節點的顏色,可以是紅或黑(非紅即黑),紅黑樹是一種弱平衡二叉樹,相對於要求嚴格的AVL樹來說,它的旋轉次數少,所以對於搜索,插入,刪除操作較多的情況下,通常使用紅黑樹。
3)所以紅黑樹在查找,插入刪除的性能都是O(logn),且性能穩定,所以STL里面很多結構包括map底層實現都是使用的紅黑樹。
33 哈夫曼編碼
哈夫曼編碼是哈夫曼樹的一種應用,廣泛用於數據文件壓縮。哈夫曼編碼算法用字符在文件中出現的頻率來建立使用0,1表示字符的最優表示方式。
34 map 和 unordered_map的底層實現
map底層是基於紅黑樹實現的,因此map內部元素排列是有序的。而unordered_map底層則是基於哈希表實現的,因此其元素的排列順序是雜亂無序的。
35 棧溢出的原因
棧溢出指的是程序向棧中某個變量中寫入的字節數超過了這個變量本身所申請的字節數。
1) 局部數組過大。當函數內部的數組過大時,有可能導致堆棧溢出,局部變量是存儲在棧中的。
2) 遞歸調用層次太多。
3) 指針或數組越界。例如進行字符串拷貝,或處理用戶輸入等等。
36 堆和棧的區別
C語言的內存模型分為5個區:棧區、堆區、靜態區、常量區、代碼區。
1) 棧區:存放函數的參數值、局部變量等,由編譯器自動分配和釋放。棧由系統自動分配,速度快,但是程序員無法控制。
2) 堆區:就是通過new、malloc、realloc分配的內存塊,編譯器不會負責它們的釋放工作。一般是由程序員分配釋放,未被釋放可能引起內存泄漏。堆是有程序員自己分配,速度較慢,容易產生碎片,不過用起來方便。
3) 全局變量和靜態變量的存儲是放在一塊的。
4) 常量區:常量存儲在這里,不允許修改。
5) 代碼區:存放函數體的二進制代碼。
37 數組和鏈表
1) 數組是將元素在內存中連續存放,由於每個元素占用內存相同,可以通過下標迅速訪問數組中任何元素,因此數組的隨機讀取效率很高。數組的插入數據和刪除數據效率低。
數組隨機訪問性強,查找速度快,插入刪除效率低,必須要有連續的內存空間。
2) 鏈表中的元素在內存中不是順序存儲的,而是通過元素中的指針聯系到一起,鏈表中增加刪除元素很簡單,只需要修改元素的指針就行了。插入刪除速度快,內存利用率高,不會浪費內存,不能隨機查找。
38 快速排序 C++ 實現
nt once_quick_sort(vector<int> &data, int left, int right)
{
int key = data[left];
while (left < right)
{
while (left < right && key <= data[right])
{
right--;
}
if (left < right)
{
data[left++] = data[right];
}
while (left < right && key > data[left])
{
left++;
}
if (left < right)
{
data[right--] = data[left];
}
}
data[left] = key;
return left;
}
int quick_sort(vector<int> & data, int left, int right)
{
if (left >= right )
{
return 1;
}
int middle = 0;
middle = once_quick_sort(data, left, right);
quick_sort(data, left, middle-1);
quick_sort(data, middle + 1, right);
}
39 哈希表(hash表)
哈希表的實現主要包括構造哈希和處理哈希沖突:構造哈希,主要包括直接地址法,除留余數法。
處理哈希沖突:當哈希表關鍵字集合很大時,關鍵字值不同的元素可能會映射到哈希表的同一地址上,這樣的現象稱為哈希沖突。常用的解決方法有:
1) 開放定址法,沖突時,用某種方法繼續探測哈希表中的其他存儲單元,直到找到空位置為止。(如,線性探測,平方探測)
2) 再哈希法:當發生沖突時,用另一個哈希函數計算地址值,直到沖突不再發生。
3) 鏈地址法:將所有哈希值相同的key通過鏈表存儲,key按順序插入鏈表中。
40 合並兩個有序鏈表
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == NULL)
{
return l2;
}
if(l2 == NULL)
{
return l1;
}
if(l1->val < l2->val)
{
l1->next=mergeTwoLists(l1->next,l2);
return l1;
}
else
{
l2->next=mergeTwoLists(l1,l2->next);
return l2;
} } };
41 指針反轉
/*
struct ListNode {
int val;
struct ListNode *next;
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead)
{
// 反轉指針
ListNode* pNode=pHead; // 當前節點
ListNode* pPrev=nullptr;// 當前節點的上一個節點
ListNode* pNext=nullptr;// 當前節點的下一個節點
ListNode* pReverseHead=nullptr;//新鏈表的頭指針
// 反轉鏈表
while(pNode!=nullptr)
{
pNext=pNode->next; // 建立鏈接
if(pNext==NULL) // 判斷pNode是否是最后一個節點
pReverseHead=pNode;
pNode->next=pPrev; // 指針反轉
pPrev=pNode;
pNode=pNext;
}
return pReverseHead;
}
};
42 C++ 中的 const 關鍵字
1) const可以修飾變量,引用,指針等,修飾指針時,在*左邊表示指針所指數據是常量,不可改變,但指針可以指向其他內存單元。在*右邊時,表示指針本身是常量,不能指向其他內存地址,但指針所指的數據可以改變。
2) const可以修飾函數參數,傳遞過來的參數在函數內不可以改變。可以修飾成員變量,把變量變成常量,只可以讀不可以寫。
3) const修飾函數和成員函數的時候將const放在函數名后面,它修飾的成員函數不能修改任何的成員變量,const成員函數不能調用任何非const成員函數。
4) const可以修飾對象,const對象只能調用const成員函數,必須要提供一個const版本的成員函數。
43 C++中 static 關鍵字的作用
1) 最重要的一條:隱藏
當同時編譯多個文件時,所有未加static前綴的全局變量和函數都具有全局可見性。如果在變量和函數前面加了static,那么它就會對其他源文件隱藏。利用這個特性,可以在不同的文件中定義同名函數和變量,而不必擔心命名沖突。
2) 第二個作用是保持變量內容的持久(static變量中的記憶功能和全局生命周期)
存儲在靜態數據區的變量會在程序剛開始運行時就完成初始化,也是唯一的一次初始化。此外,如果作為static局部變量定義在函數內,它的生命周期是整個源程序,但是作用域與自動變量相同。只能在該函數內使用這個靜態變量。退出函數后,盡管變量仍然存在,但不能使用它。
3)static的第三個作用是默認初始化為0(static變量)
4)第四個作用:c++中的類成員聲明static
在類中聲明static變量或函數時,初始化時使用作用域運算符來標明它所屬類。
1 類的靜態成員函數是屬於整個類而非對象,所以它沒有this指針。2 不能將靜態成員函數定義為虛函數。3 靜態數據成員是靜態存儲的,所以必須對它進行初始化。
44 C++ 中的 new 和 malloc
1) New和malloc都是在堆上申請內存的。Malloc和free是庫函數,c/c++都可以用,new和delete是c++中的關鍵字。
2) New可以調用對象的構造函數,delete調用相應的析構函數。Malloc僅僅分配內存,free
僅僅釋放內存。
3) new/delete的底層調用了malloc/free。
4) c++中允許重載new/delete操作符,malloc不允許重載。
5) char *p; p=(char*)malloc(100)。int *p=new int; int *a = new int[100];
6) new返回的是對象類型的指針,如上面的p,返回的是一個int型指針。而malloc返回的是*void,即無類型指針,需要做強制類型轉換。