C++面试知识点汇总
暫時存個檔,可能有不足之處,歡迎指正。
C++ 基礎
-
new ,delete,new[],delete[],malloc,free之間的區別?
- new,delete與new[],delete[]區別在于前者管理的是單個元素空間,后者管理的是多個元素的連續空間;
- …pass
-
什么是內存泄漏,如何避免?如何檢測?
-
定義:內存泄漏是由于疏忽或錯誤程序未能釋放 不再使用的內存的情況。
-
內存泄漏場景:
- 指針重新賦值時,忘記釋放之前管理的內存;
- 結構化對象成員中有動態分配內存塊,而卻先釋放了父塊,導致丟失了子塊控制權;
- 返回的是堆區域內存的指針,但是使用后沒有正確處理返回的指針;
-
-
避免:
- 盡量避免在堆上分配內存;
- 善于利用RAII機制;
- 盡量不要使用裸指針,而是使用智能指針;
- 盡量使用STL容器替代,比如用string代替char*,vector代替int*等;
-
檢測:
- 對象計數,構造++,析構–,看最終計數是否為0;
? 2.使用檢測庫或檢測工具,庫:比如win:crtdbg,linux:mcheck,工具:比如gperftools;
win32 一個進程最多可以開幾個線程,開多了會出現什么問題?
- 2^32=4GB,win32最大可用虛擬空間為2GB = 2^31左右,一個線程要1MB棧空間
- 8bit=1byte,1kb = 1024byte=2^10*8bit,1MB = 1024kb=2^20*8bit,1GB = 1024MB
- 1MB * 1024 * 2 = 2GB,所以最多可以開2048個線程
- 開多了會導致整個進程異常退出
-
出現野指針有哪些情況,如何避免?
-
野指針定義:指向非法內存地址的的指針,也叫懸掛指針;
-
出現情況:
- 使用未初始化的指針;
- 指針指向對象已經消亡;
- 指針釋放后未置空;
-
-
避免:
- 盡量避免使用指針,比如用引用代替;
- 定義時就初始化,初始化為一個對象地址或者nullptr;
- 對指針進行delete 或free后,將其賦值為nullptr;
指針相關:
- int(*p)[n]:()優先級高,所以p是一個指針,指向int[n]的指針,也稱行指針;
行指針++就跨一行 - int* p[n]:指針數組,含有n個指針元素,常用來指向二維數組,
p[i][j] = *(p[i]+j)=*(*(p+i)+j)=*(p+i)[j]
c++ lambda表達式實現原理:
- 根據lambda表達式返回值為一個函數對象可知其實現為:生成一個lambda_xx 類,然后重載operator()
- 具體步驟為:
- 創建 lambda 類,實現構造函數,使用 lambda 表達式的函數體重載 operator()( lambda 表達式 也叫匿名函數對象)
- 創建 lambda 對象
- 通過對象調用 operator()
編譯器如何區分重載函數的?
編譯成了不同的函數名,查看**生成的匯編代碼(g++ -S xx.s xx.cpp )**發現,
大概規則舉例:print(int x)->_xxprinti,print(int x,int y)->__xprintii
C++ 的多態機制?
- 靜態多態:函數重載,泛型編程(函數模板和類模板);
- 動態多態:虛函數
什么是仿函數?
-
通過重載operator()運算符使得class或struct對象可以像函數一樣調用,比如c++提供的less,greater等;
-
函數指針雖然也可以將函數作為參數傳遞,但是不能滿足STL抽象性和積木性(和STL其他組件搭配)要求。
-
調用方式:class_name()(args) or class_obj.operator()(args)
如果new的東西,用free釋放會怎么樣?free后再調用delete會怎么樣?
-
如果new對象里面沒有再動態分配資源,則直接free也可以,不會有資源泄漏;
否則會存在資源泄漏,因為對象回收資源的的析構函數沒有被調用;
-
free之后再調用delete會free兩次,程序直接Aborted;
自己實現一個String類
class String{public:String(const char* str=nullptr){m_size = str ? std::strlen(str) : 0;m_data = new char[m_size+1];if(m_size) std::strncpy(m_data,str,m_size);m_data[m_size] = '\0'; // 得分}String(const String& s){size_t len = s.size();this->m_data = new char[len+1];std::strncpy(this->m_data,s.m_data,len);m_size = len;this->m_data[m_size] = '\0';}String& operator= (const String& s){if(this == &s) return *this; // 得分this->m_size = s.size();delete[] m_data; // 得分this->m_data = new char[this->m_size];std::strncpy(this->m_data,s.m_data,m_size);this->m_data[m_size] = '\0';return *this;}String& operator+ (const String& s){int len = s.size();char* q = new char[this->m_size+len];std::strncpy(q,m_data,m_size);std::strncpy(q+m_size,s.m_data,len);this->m_size += len;q[m_size] = '\0';delete[] m_data; // 得分m_data = q;return *this;}friend std::ostream& operator<< (std::ostream& out,String& s){for(char* q = s.m_data; *q!='\0'; ++q){out<<(*q);}return out;}size_t size()const{return m_size;}~String(){delete[] m_data;}private:char* m_data;size_t m_size; };自己實現單例模式
// 懶漢模式:需要用時再加載(創建) class Object{public:static Object* getInstance(){if(nullptr == m_single){ // 只有m_single為空才加鎖,加鎖開銷很大,避免每次調用都加鎖std::unique_lock<std::mutex> lock(m_mutex);if(nullptr == m_single){m_single = new Object();}} return m_single;}private:Object(){}Object(const Object& rhs) = delete; // 禁止拷貝構造Object& operator=(const Object& rhs) = delete; // 禁止賦值構造static Object* m_single;std::mutex m_mutex;}; // 餓漢模式: 一開始就創建好實例 class Object{public:static Object* getInstance(){static Object m_single; // 一開始就創建好放到靜態存儲區,在c++11下線程安全return &m_single;}private:Object(){}Object(const Object& rhs) = delete; // 禁止拷貝構造Object& operator=(const Object& rhs) = delete; // 禁止賦值構造 };單例模式下,如何保證多線程模式下只有一個對象實例?
- 加鎖
- 提前生成,即使用餓漢模式
char ch[] = “hello” 和char* p = "hello"的區別,如果對兩者字符進行修改會怎么樣?
-
char ch[]最后有一個字符串結束符‘\0’
-
sizeof(ch) = strlen(ch)+1而sizeof(p) = 8 or 4
-
char ch[]中字符串可以修改不會出現問題,而對p指向的字符串修改可能會出現問題,
ch存在棧區,p指向的字符串在常量區
為什么要字節對齊?字節不對齊會出現什么問題?
- 提高訪問效率,不對齊讀取數據時可能需要多訪問內存幾次
為什么x64 下指針占8字節,x64的尋址空間?
- x64地址總線有64條,尋址空間2^64,指針作用是指向一個地址(尋址空間中找一個地址),64bit = 8Bytes,
memset有虛函數的對象為0,會不會出現問題?
- 會,虛表指針被置空,導致無法調用到虛函數
簡述mp[1] 和 iter = mp.find(1)的區別,map的key有什么要求?
-
下標操作符[],1不在mp中時會插入,而find(1),1不在時不會插入,只返回mp.end();
所以如果只是判斷某個數是否在mp中時盡量使用find
-
map的key需要:
- 支持拷貝構造;
- 支持operator=;
- 可比較,即支持operator< (如果沒有operator<那么 map模板必須增加第三個模板參數);
- 默認的構造函數。
map插入,查找時間復雜度?為什么map底層采用紅黑樹而不用普通AVL? 紅黑樹插入和刪除最多需要幾次旋轉?
-
logn
-
map要做頻繁插入,由于普通AVL嚴格的平衡,一次插入需要調整需要很多次,而紅黑樹一次插入最多需要調整2次;
-
插:最多調整2次,刪:最多調整3次;
C++中死鎖的危害很大,對避免死鎖有什么建議?
-
避免多次鎖定:盡量避免同一線程對多個鎖進行鎖定;
-
按相同的順序加鎖;
-
使用定時鎖(破壞持有并等待條件);
-
提前進行死鎖檢測(銀行家算法);
class A{…virtual… }, class B :A{…},B的內存布局是怎么樣的?
虛函數有什么缺點?作為基類,其析構函數一定要聲明為virtual?
-
時間:調用需要通過虛表指針查虛表,時間上開銷更大;
-
空間:由于有虛函數,所以類對象會有虛表指針,要更多內存開銷;
-
安全:將基類強制轉換為子類,不可以直接訪問子類特有的虛函數,可通過虛表指針間接訪問;
不一定,如果不需要動態管理資源則基類析構可不聲明為virtual
虛函數和普通函數的區別?
- 虛函數動態編譯,運行期間進行綁定;而普通函數靜態編譯,在運行前就綁定好了(通過this指針進行綁定)。
簡述幾種xx_cast的作用。
-
reinterpret_cast:重新解釋二進制碼,風險很高,比如不同類型指針之間的轉換,int與指針的轉換等;
-
const_cast:const 轉為非const,volitale 轉為非volitale
(volitale表明所修飾變量是易變的,不要作讀取優化,每次都重新從內存中取);
-
static_cast:相當于傳統C語言的強制轉換,非多態的轉換,編譯時檢查,
但運行時沒有安全檢查,所以子類->父類絕對安全,而父類->子類是不安全的。
不能轉掉const,volitale,即不可將含這些關鍵字的轉換為不含這些關鍵字的類型;
-
dynamic_cast:主要用途將父類指針或引用安全的轉換為子類指針或引用,它在運行期間借助RAII進行類型轉換,
強制轉換指針不成功返回空指針,強制轉換引用不成功拋出異常
你了解服務器的哪些架構?
引用是如何實現?何時使用引用?(引用和指針的區別)
-
底層實現:可以根據使用場景進行推斷,聲明后必須初始化且之后不可修改為其他的引用,
根據這個特性可以推斷底層實現是常指針(type* const),
但sizeof(引用)是所引用對象的大小(引用是對象的別名);
-
應用: 函數傳參,減少大數據結構的拷貝;為什么要有引用?很多場合可以代替指針,提高可讀性和實用性;
說說C++的多態
- 靜態多態:重載(形參類型,個數,和返回值類型沒有關系),泛型編程(類模板,模板函數)
- 動態多態:虛函數
虛函數,一個類可以有多個虛函數表嗎?
- 可以,對于多繼承可能存在多個虛函數表(多個虛表指針)
說一下程序的編譯成可執行文件的過程
- 預處理(替換宏定義等),編譯(高級語言轉換為匯編代碼)匯編(匯編碼轉換為機器碼),鏈接
C++ STL構成:
- 容器
- 迭代器
- 適配器
- 分配器
- 算法
- 函數對象(仿函數)
你常用的stl容器,vector的擴容機制,清除里面的內容內存會釋放嗎?
-
stl容器分為順序容器:
-
vector:擴容機制根據實現而異,通常為2倍/1.5倍。clear不會釋放內存;
-
deque,
-
list(雙向鏈表),
-
forward_list(單向鏈表),
-
array,
-
string
-
-
關聯容器:
- map
- set
- multimap
- multiset
- unordered_map
- unordered_set
- unordered_multimap
- unordered_multiset
stack屬于容器適配器;
map和unordered_map的區別,以及它們的查詢復雜度:
- map底層紅黑樹(比常規AVL更適合插入修改密集型任務),查詢時間復雜度O(NlogN);
- unordered_map底層采用hash,查詢時間復雜度為常數復雜度
虛函數和普通函數的區別:
- 虛函數運行時多態,而普通函數是靜態編譯,沒有運行時多態;
計算機網絡
-
tcp如何保證可靠性?
-
校驗和:由tcp偽首部(12byte),tcp首部,tcp數據 三部分組成;
-
序列號和確認應答;
-
超時重傳:時限略大于1個RTT(往返時間);
-
流量控制:滑動窗口機制;
-
擁塞控制:利用擁塞窗口以不加重網絡負擔:慢啟動(之后指數增大),擁塞避免(之后線性增大),快重傳,快恢復;
-
連接管理:三次握手,四次揮手;
-
-
簡述tcp/ip模型,并說明各層常用的協議
osi七層:
- 應用層:
- 表示層:
- 會話層:
- 傳輸層:
- 網絡層:
- 數據鏈路層:
- 物理層:
tcp/ip:
- 該模型意味著tcp和ip協同工作;
- tcp協議在傳輸層,負責應用軟件和網絡軟件之間的通信;
- ip協議在網絡層,負責計算機之間的通信;
tcp/ip模型4層:
-
應用層(osi前三層組合):HTTP,FTP,DNS
-
傳輸層:TCP,UDP
-
網絡層:ARP,ICMP
-
網絡接口層(osi最后兩層組合):MAC,VLAN
-
ip地址(最小)占幾個字節
- ipv4由32為二進制表示,每32/8 = 4bytes
-
accept發生在三次握手哪個階段?
- 第三次握手階段accept返回
-
tcp和udp的區別,還有應用場景:
-
tcp面向連接的,面向字節流的,可靠的,點對點,有流量控制和擁塞控制(慢啟動,擁塞避免,快重傳,快恢復),
首部20字節;應用:文件傳輸,重要狀態更新;
-
udp是面向數據報,不可靠的,支持一對一,多對多,一對多,沒有擁塞控制,首部開銷8字節;應用:視頻傳輸,
實時通信;
-
-
tcp的四次揮手過程?
- pass
-
了解tcp的滑動窗口?
窗口:無需確認包的情況下一次性可以發送的最大數據包數量;
滑動窗口機制用于流量控制,有發送窗口和接收窗口,初始窗口大小根據帶寬而定,后面根據收發情況動態調整窗口大小
數據結構和算法
-
如何判斷一點在線段的左邊還是右邊或者線上,如果在左邊返回true,否則返回false.
-
計算向量之間叉積:
- 大于0 : 左側
- 小于0:右側
- 等于0:線上
-
-
字符串匹配(手寫KMP,并分析復雜度)
// 時間復雜度: O(M+N) void getNext(const char* p,int* next){// 模式串p(需要匹配的)int i = 1;int j = 0;int len = strlen(p);next[0] = 0;while(i < len){if(p[i] == p[j]){next[i++] = ++j;}else{j > 0 ? j = next[j-1] : next[i++] = 0;}} } int kmp(const char* s,const char* p){// s中找pint i = 0;int j = 0;int lens = strlen(s);int lenp = strlen(p);while(i < lens){if(s[i] == p[j]){++i;++j;}else{j > 0 ? j = next[j-1] : i++;}if(j == lenp){return i-lenp; //匹配一個// 若多個: j = next[j-1];}}return j == lenp ? (i-lenp) : -1; } -
快排思路
-
對排序的穩定性是如何理解? 快排穩定嗎?
-
進制轉換:10->8,10->7
- 除余法
-
刪除鏈表中指定元素(需要避免內存泄漏)
-
遍歷,同時記錄前驅結點pre,遇到目標元素now,pre->next=now->next,delete 結點時需要注意;
struc Node{Node():val(0),next(nullptr){}int val;Node* next; }; Node* deleteNode(Node* head,int val){Node* new_head = new Node();new_head->next = head;Node* pre = new_head;Node* now = head;Node* del = nullptr;while(now){if(now->val==val){pre->next = now->next;del = now;now = now->next;delete del;}else{pre = now;now = now->next; }}head = new_head->next;delete new_head;return head; }
-
-
TopK問題以及時間復雜度
- 快速選擇,O(N)
- 維護大小為k的堆,O(NlogK)
- 如果有多臺機器,分治法
-
如何解決hash碰撞:
- 鏈地址法
- 開放地址法(線性探測,二次方探測)
- 再hash法
- 建立公共溢出區(溢出區再沖突可以結合其他解決hash碰撞的方法)
操作系統
-
什么是死鎖,舉個簡單例子,死鎖條件?如何避免?
-
定義:多個進程因為爭奪資源而造成的一種僵局,各自都無法往下推進;
比如進程A,B,資源C,D,A持有了C還想要D,B持有了D還想要C;
-
條件:
- 互斥
- 不可剝奪
- 持有并保持
- 循環等待
-
避免:
- 按相同的順序加鎖
- 一定時間內沒有獲取到其他鎖,釋放已有的鎖(定時鎖)
- 提前檢測死鎖(銀行家算法)
-
-
簡述消費者和生產者模型和讀寫者模型,并簡述它們的區別;
-
生產者和消費者一段時間內共用一段內存(緩沖區),生:放,消:取;無數據:消費者阻塞,數據滿:生產者阻塞;
-
區別:
- 讀寫者模型出現了讀者共享的關系,而生產者和消費者模型只有互斥和同步關系(生與消:互斥和同步;生和生,消與消:互斥);
- 讀者不會使得緩沖區改變;
-
線程通信方式,如何保證數據一致性?
通信方式:
- 全局變量
- 信號機制
同步:
? 臨界區:通過多線程的串行化來訪問公共資源或一段代碼,速度快,適合控制數據訪問;
? 互斥量Synchronized/Lock:采用互斥對象機制,只有擁有互斥對象的線程才有訪問公共資源的權限。因為互斥對象只有一個,所以可以保證公共資源不會被多個線程同時訪問
? 信號量Semphare:為控制具有有限數量的用戶資源而設計的,它允許多個線程在同一時刻去訪問同一個資源,但一般需要限制同一時刻訪問此資源的最大線程數目。
? 事件(信號),Wait/Notify:通過通知操作的方式來保持多線程同步,還可以方便的實現多線程優先級的比較操作;
進程的通信方式:
-
管道(PIPE和命名管道FIFO)
-
系統IPC(inter-process communication):
- 消息隊列
- 信號量
- 信號
- 共享內存
-
Socket通信
線程同步和異步
-
同步:多個線程訪問同一資源,當資源互斥時,其他線程盲目等待;
-
異步:多個線性訪問互斥資源時,不盲目等待先去干其他事情;
阻塞和非阻塞
-
阻塞:會掛起線程(盲目)
-
非阻塞:不會掛起線程(不盲目)
數據庫
-
什么是主鍵?什么是索引?如何使用索引?索引適用于什么場景?
ref1:【面試】面試常問之數據庫索引
-
主鍵:能夠唯一標識表中一條記錄的最小屬性集(候選碼中選一個)
-
索引:是數據庫對表中一列或多列進行排序的結構,便于快速找到數據;
-
使用:
-
創建:
way1: create index_type index_name on table_name(column_name_tuple)
way2: alter table table_name add index_type index_name(column_name_tuple)
-
刪除:drop index index_name on table_name
-
查詢:show index from table_name
-
-
適用場景:頻繁查詢,較少增加,刪除,修改的情況(因為維護索引開銷大);
-
索引的種類(ref2:什么是數據庫索引):
大類:
-
聚簇索引(將數據存儲與索引放到了一塊,一個表只能有一個聚簇索引,聚簇索引默認為主鍵):
在數據庫中,表中數據會按主鍵排序;
-
非聚簇索引(輔助索引):給普通字段加上索引,查找時:類似于先找目錄再找頁碼;
小類:
- 普通索引和唯一索引
- 單值索引和復合索引
-
-
-
寫sql:
- 不能使用min,查找char表中age最小的記錄
- 查找table(ID,Name)中具有2個以上ID的Name(即一個Name有2+個ID)
- 查找info表中interest中含有football的用戶
- 查找info表中interest中含有football并且age小于30的用戶
- 查找info表中用戶平均年齡
- 查找ipinfo中相同ip中datetime最大的信息
python
- 有哪些方式class 中某個屬性的值?
- 正則匹配
其他
- 你覺得你的優勢是什么?優點?缺點?
- 你覺得你在哪方面會讓別人眼前一亮?即你擅長什么?
- 實習遇到什么困難?
總結
以上是生活随笔為你收集整理的C++面试知识点汇总的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 已知向量坐标求三角形面积
- 下一篇: 2345电脑管家_如何彻底清除流氓的23