1、C和C++的區別
1)C是面向過程的語言,是一個結構化的語言,考慮如何通過一個過程對輸入進行處理得到輸出;C++是面向對象的語言,主要特征是“封裝、繼承和多態”。封裝隱藏了實現細節,使得代碼模塊化;派生類可以繼承父類的數據和方法,擴展了已經存在的模塊,實現了代碼重用;多態則是“一個接口,多種實現”,通過派生類重寫父類的虛函數,實現了接口的重用。
2)C和C++動態管理內存的方法不一樣,C是使用malloc/free,而C++除此之外還有new/delete關鍵字。
3)C++支持函數重載,C不支持函數重載
4)C++中有引用,C中不存在引用的概念
2、C++中指針和引用的區別
1)指針是一個新的變量,存儲了另一個變量的地址,我們可以通過訪問這個地址來修改另一個變量;
引用只是一個別名,還是變量本身,對引用的任何操作就是對變量本身進行操作,以達到修改變量的目的
2)引用只有一級,而指針可以有多級
3)指針傳參的時候,還是值傳遞,指針本身的值不可以修改,需要通過解引用才能對指向的對象進行操作
引用傳參的時候,傳進來的就是變量本身,因此變量可以被修改
3、結構體struct和共同體union(聯合)的區別
結構體:將不同類型的數據組合成一個整體,是自定義類型
共同體:不同類型的幾個變量共同占用一段內存
1)結構體中的每個成員都有自己獨立的地址,它們是同時存在的;
共同體中的所有成員占用同一段內存,它們不能同時存在;
2)sizeof(struct)是內存對齊后所有成員長度的總和,sizeof(union)是內存對齊后最長數據成員的長度、
4、#define和const的區別
1)#define定義的常量沒有類型,所給出的是一個立即數;const定義的常量有類型名字,存放在靜態區域
2)處理階段不同,#define定義的宏變量在預處理時進行替換,可能有多個拷貝,const所定義的變量在編譯時確定其值,只有一個拷貝。
3)#define定義的常量是不可以用指針去指向,const定義的常量可以用指針去指向該常量的地址
4)#define可以定義簡單的函數,const不可以定義函數
5、重載overload,覆蓋override,重寫overwrite,這三者之間的區別
1)overload,將語義相近的幾個函數用同一個名字表示,但是參數和返回值不同,這就是函數重載
特征:相同范圍(同一個類中)、函數名字相同、參數不同、virtual關鍵字可有可無
2)override,派生類覆蓋基類的虛函數,實現接口的重用
特征:不同范圍(基類和派生類)、函數名字相同、參數相同、基類中必須有virtual關鍵字(必須是虛函數)
3)overwrite,派生類屏蔽了其同名的基類函數
特征:不同范圍(基類和派生類)、函數名字相同、參數不同或者參數相同且無virtual關鍵字
7、delete和delete[]的區別
delete只會調用一次析構函數,而delete[]會調用每個成員的析構函數
用new分配的內存用delete釋放,用new[]分配的內存用delete[]釋放
8、STL庫用過嗎?常見的STL容器有哪些?算法用過幾個?
STL包括兩部分內容:容器和算法
容器即存放數據的地方,比如array, vector,分為兩類,序列式容器和關聯式容器
序列式容器,其中的元素不一定有序,但是都可以被排序,比如vector,list,queue,stack,heap, priority-queue, slist
關聯式容器,內部結構是一個平衡二叉樹,每個元素都有一個鍵值和一個實值,比如map, set, hashtable, hash_set
算法有排序,復制等,以及各個容器特定的算法
迭代器是STL的精髓,迭代器提供了一種方法,使得它能夠按照順序訪問某個容器所含的各個元素,但無需暴露該容器的內部結構,它將容器和算法分開,讓二者獨立設計。
9、const知道嗎?解釋一下其作用
const修飾類的成員變量,表示常量不可能被修改
const修飾類的成員函數,表示該函數不會修改類中的數據成員,不會調用其他非const的成員函數
10、虛函數是怎么實現的
每一個含有虛函數的類都至少有有一個與之對應的虛函數表,其中存放着該類所有虛函數對應的函數指針(地址),
類的示例對象不包含虛函數表,只有虛指針;
派生類會生成一個兼容基類的虛函數表。
11、堆和棧的區別
1)棧 stack 存放函數的參數值、局部變量,由編譯器自動分配釋放
堆heap,是由new分配的內存塊,由應用程序控制,需要程序員手動利用delete釋放,如果沒有,程序結束后,操作系統自動回收
2)因為堆的分配需要使用頻繁的new/delete,造成內存空間的不連續,會有大量的碎片
3)堆的生長空間向上,地址越大,棧的生長空間向下,地址越小
12、關鍵字static的作用
1)函數體內: static 修飾的局部變量作用范圍為該函數體,不同於auto變量,其內存只被分配一次,因此其值在下次調用的時候維持了上次的值
2)模塊內:static修飾全局變量或全局函數,可以被模塊內的所有函數訪問,但是不能被模塊外的其他函數訪問,使用范圍限制在聲明它的模塊內
3)類中:修飾成員變量,表示該變量屬於整個類所有,對類的所有對象只有一份拷貝
4)類中:修飾成員函數,表示該函數屬於整個類所有,不接受this指針,只能訪問類中的static成員變量
紅黑樹是一種特殊的二叉查找樹
1)每個節點或者是黑色,或者是紅色
2)根節點是黑色
3) 每個葉子節點(NIL)是黑色。 [注意:這里葉子節點,是指為空(NIL或NULL)的葉子節點!]
4)如果一個節點是紅色的,則它的子節點必須是黑色的
5)從一個節點到該節點的子孫節點的所有路徑上包含相同數目的黑節點。
特性4)5)決定了沒有一條路徑會比其他路徑長出2倍,因此紅黑樹是接近平衡的二叉樹。
15、什么是內存泄漏?面對內存泄漏和指針越界,你有哪些方法?
動態分配內存所開辟的空間,在使用完畢后未手動釋放,導致一直占據該內存,即為內存泄漏。
方法:malloc/free要配套,對指針賦值的時候應該注意被賦值的指針是否需要釋放;使用的時候記得指針的長度,防止越界
16、定義和聲明的區別
聲明是告訴編譯器變量的類型和名字,不會為變量分配空間
定義需要分配空間,同一個變量可以被聲明多次,但是只能被定義一次
17、C++文件編譯與執行的四個階段
1)預處理:根據文件中的預處理指令來修改源文件的內容
2)編譯:編譯成匯編代碼
3)匯編:把匯編代碼翻譯成目標機器指令
4)鏈接:鏈接目標代碼生成可執行程序
18、STL中的vector的實現,是怎么擴容的?
vector使用的注意點及其原因,頻繁對vector調用push_back()對性能的影響和原因。
vector就是一個動態增長的數組,里面有一個指針指向一片連續的空間,當空間裝不下的時候,會申請一片更大的空間,將原來的數據拷貝過去,並釋放原來的舊空間。當刪除的時候空間並不會被釋放,只是清空了里面的數據。對比array是靜態空間一旦配置了就不能改變大小。
vector的動態增加大小的時候,並不是在原有的空間上持續新的空間(無法保證原空間的后面還有可供配置的空間),而是以原大小的兩倍另外配置一塊較大的空間,然后將原內容拷貝過來,並釋放原空間。在VS下是1.5倍擴容,在GCC下是2倍擴容。
在原來空間不夠存儲新值時,每次調用push_back方法都會重新分配新的空間以滿足新數據的添加操作。如果在程序中頻繁進行這種操作,還是比較消耗性能的。
19、STL中unordered_map和map的區別
map是STL中的一個關聯容器,提供鍵值對的數據管理。底層通過紅黑樹來實現,實際上是二叉排序樹和非嚴格意義上的二叉平衡樹。所以在map內部所有的數據都是有序的,且map的查詢、插入、刪除操作的時間復雜度都是O(logN)。
unordered_map和map類似,都是存儲key-value對,可以通過key快速索引到value,不同的是unordered_map不會根據key進行排序。unordered_map底層是一個防冗余的哈希表,存儲時根據key的hash值判斷元素是否相同,即unoredered_map內部是無序的
20、C++的內存管理
在C++中,內存被分成五個區:棧、堆、自由存儲區、靜態存儲區、常量區
棧:存放函數的參數和局部變量,編譯器自動分配和釋放
堆:new關鍵字動態分配的內存,由程序員手動進行釋放,否則程序結束后,由操作系統自動進行回收
自由存儲區:由malloc分配的內存,和堆十分相似,由對應的free進行釋放
全局/靜態存儲區:存放全局變量和靜態變量
常量區:存放常量,不允許被修改
21、 構造函數為什么一般不定義為虛函數?而析構函數一般寫成虛函數的原因 ?
1、構造函數不能聲明為虛函數
1)因為創建一個對象時需要確定對象的類型,而虛函數是在運行時確定其類型的。而在構造一個對象時,由於對象還未創建成功,編譯器無法知道對象的實際類型,是類本身還是類的派生類等等
2)虛函數的調用需要虛函數表指針,而該指針存放在對象的內存空間中;若構造函數聲明為虛函數,那么由於對象還未創建,還沒有內存空間,更沒有虛函數表地址用來調用虛函數即構造函數了
2、析構函數最好聲明為虛函數
首先析構函數可以為虛函數,當析構一個指向派生類的基類指針時,最好將基類的析構函數聲明為虛函數,否則可以存在內存泄露的問題。
如果析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在刪除指向派生類的基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會造成派生類對象析構不完全。
22、靜態綁定和動態綁定的介紹
靜態綁定和動態綁定是C++多態性的一種特性
1)對象的靜態類型和動態類型
靜態類型:對象在聲明時采用的類型,在編譯時確定
動態類型:當前對象所指的類型,在運行期決定,對象的動態類型可變,靜態類型無法更改
2)靜態綁定和動態綁定
靜態綁定:綁定的是對象的靜態類型,函數依賴於對象的靜態類型,在編譯期確定
動態綁定:綁定的是對象的動態類型,函數依賴於對象的動態類型,在運行期確定
只有虛函數才使用的是動態綁定,其他的全部是靜態綁定
23、 引用是否能實現動態綁定,為什么引用可以實現
可以。因為引用(或指針)既可以指向基類對象也可以指向派生類對象,這一事實是動態綁定的關鍵。用引用(或指針)調用的虛函數在運行時確定,被調用的函數是引用(或指針)所指的對象的實際類型所定義的。
24、深拷貝和淺拷貝的區別
深拷貝和淺拷貝可以簡單的理解為:如果一個類擁有資源,當這個類的對象發生復制過程的時候,如果資源重新分配了就是深拷貝;反之沒有重新分配資源,就是淺拷貝。
25、 什么情況下會調用拷貝構造函數(三種情況)
系統自動生成的構造函數:普通構造函數和拷貝構造函數 (在沒有定義對應的構造函數的時候)
生成一個實例化的對象會調用一次普通構造函數,而用一個對象去實例化一個新的對象所調用的就是拷貝構造函數
調用拷貝構造函數的情形:
1)用類的一個對象去初始化另一個對象的時候
2)當函數的參數是類的對象時,就是值傳遞的時候,如果是引用傳遞則不會調用
3)當函數的返回值是類的對象或者引用的時候
26、純虛函數
純虛函數是只有聲明沒有實現的虛函數,是對子類的約束,是接口繼承
包含純虛函數的類是抽象類,它不能被實例化,只有實現了這個純虛函數的子類才能生成對象
普通函數是靜態編譯的,沒有運行時多態
27、什么是野指針
野指針不是NULL指針,是未初始化或者未清零的指針,它指向的內存地址不是程序員所期望的,可能指向了受限的內存
成因:
1)指針變量沒有被初始化
2)指針指向的內存被釋放了,但是指針沒有置NULL
3)指針超過了變量了的作用范圍,比如b[10],指針b+11
28、線程安全和線程不安全
線程安全就是多線程訪問時,采用了加鎖機制,當一個線程訪問該類的某個數據時,進行保護,其他線程不能進行訪問直到該線程讀取完,其他線程才可以使用,不會出現數據不一致或者數據污染。
線程不安全就是不提供數據訪問保護,有可能多個線程先后更改數據所得到的數據就是臟數據。
29、C++中內存泄漏的幾種情況
內存泄漏是指己動態分配的堆內存由於某種原因程序未釋放或無法釋放,造成系統內存的浪費,導致程序運行速度減慢甚至系統崩潰等嚴重后果。
1)類的構造函數和析構函數中new和delete沒有配套
2)在釋放對象數組時沒有使用delete[],使用了delete
3)沒有將基類的析構函數定義為虛函數,當基類指針指向子類對象時,如果基類的析構函數不是virtual,那么子類的析構函數將不會被調用,子類的資源沒有正確釋放,因此造成內存泄露
4)沒有正確的清楚嵌套的對象指針
30、棧溢出的原因以及解決方法
1)函數調用層次過深,每調用一次,函數的參數、局部變量等信息就壓一次棧
2)局部變量體積太大。
解決辦法大致說來也有兩種:
1> 增加棧內存的數目;增加棧內存方法如下,在vc6種依次選擇Project->Setting->Link,在Category中選擇output,在Reserve中輸入16進制的棧內存大小如:0x10000000
2> 使用堆內存;具體實現由很多種方法可以直接把數組定義改成指針,然后動態申請內存;也可以把局部變量變成全局變量,一個偷懶的辦法是直接在定義前邊加個static,呵呵,直接變成靜態變量(實質就是全局變量)
31、C++標准庫vector以及迭代器
每種容器類型都定義了自己的迭代器類型,每種容器都定義了一隊命名為begin和end的函數,用於返回迭代器。
迭代器是容器的精髓,它提供了一種方法使得它能夠按照順序訪問某個容器所含的各個元素,但無需暴露該容器的內部結構,它將容器和算法分開,讓二者獨立設計。