基本語法
1、static關鍵字的作用
1、全局靜態變量
加了static關鍵字的全局變量只能在本文件中使用。
存儲在靜態存儲區,整個程序運行期間都存在。
2、局部靜態變量
作用域仍為局部作用域。
不過離開作用域之后,并沒有銷毀,而是貯存程序中,不能進行訪問,除非函數再次被調用。
3、靜態函數
只能在聲明它的文件中使用,不能被其他文件使用,也不會和其他cpp中的同名函數起沖突。
4、類的靜態成員
使用靜態成員可以實現多個對象之間的數據共享,也不會破壞隱藏原則。靜態數據成員只存儲于一處,供所有對象共用。
5、類的靜態函數
屬于類的靜態成員,對靜態成員引用不需要對象名。注意,靜態成員函數的實現中不能直接引用類中非靜態成員,可以直接引用類中的靜態成員。
2、C++四種cast轉換
const_cast : 用于將const變量轉為非const.
static_cast : 用于各種隱式轉換,用于多態向上轉換,向下轉換不安全
dynamic_cast : 用于動態類型轉換,用于類層次間的向上向下轉化。只能轉指針或者引用。
對于指針,轉換失敗返回nullptr,對于引用,轉換失敗會拋出異常。
向上:子類向基類轉化
向下:基類向子類轉化
reinterpret_cast:幾乎都可以轉化,可能會出問題
C語言的強制轉換不能進行錯誤檢查。
3、指針與引用區別
1、指針有自己空間,引用是一個別名
2、sizeof指針為4,引用是被引用對象的大小
3、指針可以初始化為NULL,引用被初始化必須是一個已有對象的引用
4、指針可以指向其他對象,引用只能是一個對象的引用,不能被改變
5、指針可以多級,引用只有1級
6、返回動態內存分配的對象,使用指針。
4、智能指針
作用:
申請的空間在函數結束時忘記釋放,造成內存泄漏。
智能指針是一個類,當超出了類的作用域,類會自動調用析構函數,析構函數自動釋放資源,這樣就不需要手動釋放內存了。
auto_ptr : 存在隱患
unique_ptr : 保證了同時只有一個智能指針指向該對象。
shared_ptr : 多個智能指針可以指向相同對象,該對象和相關的資源會在最后一個引用被銷毀的時候釋放。
其成員函數use_count() 用來查看資源所有者個數。
調用release,當前指針會釋放資源所有權,計數減一。
weak_ptr : 不控制對象生命周期,它指向一個shared_ptr管理的對象。
進行該對象的內存管理的是shared_ptr,weak_ptr 只提供了對管理對象的一個訪問手段。
weak_ptr 的構造和析構不會引起計數的增加或減少。它是用來解決shared_ptr相互引用時的死鎖問題:如果兩個shared_ptr相互引用,這兩個指針的引用計數永遠不可能降為0,資源永遠不會釋放。
舉例:
在Man類內部會引用一個Woman,Woman類內部也引用一個Man。當一個man和一個woman是夫妻的時候,他們直接就存在了相互引用問題。man內部有個用于管理wife生命期的shared_ptr變量,也就是說wife必定是在husband去世之后才能去世。同樣的,woman內部也有一個管理husband生命期的shared_ptr變量,也就是說husband必須在wife去世之后才能去世。這就是循環引用存在的問題:husband的生命期由wife的生命期決定,wife的生命期由husband的生命期決定,最后兩人都死不掉,違反了自然規律,導致了內存泄漏。
https://blog.csdn.net/shanno/article/details/7363480
5、數組與指針
6、野指針
野指針就是指向一個已刪除的對象或者未申請訪問受限內存區域的指針
7、智能指針內存泄漏如何解決
智能指針的內存泄漏主要是由于循環引用造成的。可以引入weak_ptr指針。weak_ptr的構造函數不會修改引用計數的值,從而不會對對象的內存進行管理,類似一個普通指針,但不指向引用計數的共享內存。weak_ptr可以檢測到所管理的對象是否被釋放,從而避免非法訪問。
8、析構函數必須是虛函數
如果父類的析構函數不是虛函數,那么當我們new一個子類對象,然后使用基類指針指向該子類對象,釋放基類指針時就只會釋放基類對象,子類對象的空間不會被釋放,從而造成內存泄漏。
C++默認的析構函數不是虛函數,是因為虛函數需要額外的虛函數表和虛表指針,占用額外內存。如果這個類不會被繼承,析構函數設為虛函數反而會造成內存浪費。
9、函數指針
函數指針是指向函數的指針變量。
C在編譯的時候,每個函數都有一個入口地址,該入口地址就是函數指針所指向的地址。有了指向函數的指針變量后,可以用該指針調用函數。
用途:
調用函數和做函數的參數,如回調函數
10、析構函數作用
11、靜態函數與虛函數的區別
靜態函數在編譯的時候就已經確定了運行機制,虛函數在運行的時候動態綁定。虛函數因為有虛函數表機制,調用的時候會增加一次內存開銷。
12、重載與覆寫
重載:兩個函數名字相同,但是參數列不同
覆寫:子類繼承父類,父類中的函數是虛函數,在子類中重新定義這個虛函數。
13、虛函數與多態理解
多態分為靜態與動態。靜態多態通過重載,在編譯時就確定了。
動態多態是用虛函數機制實現的,在運行期間動態綁定。
舉例:一個父類類型的指針指向一個子類對象的時候,使用父類指針調用子類中的虛函數時,調用的就是子類覆寫后的函數。
14、const修飾成員函數
表明函數調用不會對對象做出任何更改。
如果確認不會對對象做更改,更應該為函數加上const限定。
15、隱式類型轉換
對于內置類型,低精度的變量給高精度的變量賦值會發生隱式類型轉換。
16、虛函數的實現
在有虛函數的類中,類的最開始部分時一個虛函數表的指針,這個指針指向一個虛函數表,表中放了虛函數的地址,實際的虛函數在代碼段(.text)中。當子類繼承了父類的時候也會繼承其虛函數表,當子類重寫父類中虛函數時候,會將其繼承到的虛函數表地址替換為重新寫的函數地址。使用了虛函數,回增加訪問內存開銷,降低效率。
17、C++中怎么定義常量?常量存放在內存的哪個位置
常量定義必須初始化。對于局部對象,常量存放在棧區,對于全局對象,常量存放在全局/靜態存儲區。杜宇字面值常量,常量存放在常量存儲區。
18、auto、nullptr關鍵字
auto關鍵字:編譯器可以根據初始值自動推導出類型。但是不能用于函數傳參以及數組類型的推導。
nullptr關鍵字: nullptr是一種特殊類型的字面值,它可以被轉換成任意其它的指針類型;而NULL一般被宏定義為0,在遇到重載時可能會出現問題。
智能指針:C++11新增了std::shared_ptr、std::weak_ptr等類型的智能指針,用于解決內存管理的問題。
初始化列表:使用初始化列表來對類進行初始化。
右值引用:基于右值引用可以實現移動語義和完美轉發,消除兩個對象交互時不必要的對象拷貝,節省運算存儲資源,提高效率。
20、右值
C++中,左值通常指可以取地址,有名字的值就是左值,而不能取地址,沒有名字的就是右值。而在指C++11中,右值是由兩個概念構成,將亡值和純右值。純右值是用于識別臨時變量。
右值引用就是對一個右值進行引用的類型。
更加詳細的區別可以看這篇筆記:
https://blog.csdn.net/qq_42604176/article/details/110941759
21、lambda表達式
Lambda 是一個匿名函數,可以把 Lambda表達式 理解為是一段可以傳遞的代碼 (將代碼像數據一樣進行傳遞)。可以寫出更簡潔、更靈活的代碼。
比如你代碼里有一些小函數,而這些函數一般只被調用一次(比如函數指針),這時你就可以用lambda表達式替代他們,這樣代碼看起來更簡潔些,用起來也方便。
具體用途和理解看這兒:
Lambda 表達式有何用處
容器
1、STL迭代器刪除元素迭代器失效
1、對于序列容器來說,使用erase后,后面的每個元素的迭代器都會失效,但是后面每個元素都會向前移動一個位置,然后返回下一個有效的迭代器。
2、對于關聯容器來說,使用erase后,當前元素迭代器失效,但由于其內部結構,刪除當前元素不會影響到下一個元素的迭代器。只需要在調用erase之前,記錄下一個元素的迭代器即可。
3、對于list來說,它使用了不連續分配的內存,而且erase方法也會返回下一個有效的iterator,所以兩個方法都可以使用
2、STL由什么組成
容器、迭代器、仿函數、算法、分配器、容器適配器
分配器給容器分配存儲空間,算法通過迭代器獲取容器中的內容,仿函數協助算法完成操作,適配器用來適配仿函數。
3、vector 與 list區別
vector
連續存儲的容器,動態數組,在對上分配空間。
底層實現:數組
兩倍容量增長:
vector增加插入元素的時候,如果未超過當時的容量,則還有剩余空間,那么直接添加到最后位置,然后調整迭代器。
如果沒有剩余空間了,則會重新配置原有元素個數的兩倍空間,然后將原空間元素通過復制的方式初始化新空間,再向新空間增加元素,最后析構釋放掉原來空間,之前的迭代器會失效。
list
list
底層:雙向鏈表
4、STL中迭代器的作用,有指針為何還要迭代器
底層
1、C/C++內存分布
虛擬內存被分為代碼段、數據段、bss段、堆區、文件映射區、棧區。
代碼段:只讀存儲區、文本區。只讀存儲區存儲字符串常量、文本區存儲程序的代碼
數據段:存儲程序中已經初始化的全局變量和靜態變量
bss段:存儲未初始化的全局變量和靜態變量
堆區:程序員動態分配內存,并由程序員手動釋放
映射區:存儲動態鏈接庫以及調用mmap函數進行文件映射
棧:存儲函數的返回地址、參數、局部變量、返回值
2、malloc底層原理
malloc采用內存池的方式,先申請大塊內存作為堆區,然后將堆區分割為很多個內存塊,以塊作為內存管理的基本單元。當用戶申請內存時,直接從堆區分配一塊合適的空閑塊。malloc使用隱式鏈表來將堆區分成連續的、大小不一的內存塊。同時使用顯示鏈表管理所有內存塊,即使用一個雙向鏈表將空閑鏈表連接起來。
當分配內存時,malloc會通過隱式鏈表遍歷所有空閑塊,選擇滿足要求的塊進行分配;
當內存合并時,malloc采用邊界標記法,根據每個塊的前后塊是否已經分配來決定是否將塊合并。
當申請內存小于128k,會使用brk在堆區分配。當申請內存大于128k時,使用mmap在映射區分配。
3、內存泄漏分類
1、堆內存泄漏:new和delete沒有對應上
2、系統資源泄漏:程序使用系統分配的資源,然后沒有相應的函數釋放,導致系統資源浪費。
3、沒有將基類的析構函數定義為虛函數。當基類指針指向子類對象,如果基類析構函數不是虛函數,那么子類的析構函數就不會被調用,子類的資源就沒有被釋放。
4、STL內存優化
使用二級配置器結構。
1、第一級配置器
如果申請內存大于128字節的空間,如果分配不成功,就調用句柄釋放內存,如果還不能分配成功就會拋異常
2、第二級配置器,每次配置一大塊內存,并維護16個自由鏈表。
處理小于128字節的申請。
首先將申請空間擴展到8倍數,然后從freelist查找對應大小的子鏈表。
如果該自由鏈表下沒有掛內存,或者掛的內存塊太少了,就向內存池申請,一般來說申請20塊內存。
如果內存池空間足夠,取出內存。如果不夠,就分配出最多的塊數給自由鏈表。如果一塊都無法提供,則把剩余的內存掛到最符合的自由鏈表上。然后調用malloc向堆區申請空間,如果申請失敗就看自由鏈表上有沒有可用的塊,如果沒有,就調用一級空間配置。