C++常见面试题(2019年校招总结)
一、C++與C語言的聯系
c語言是面向過程的結構化語言,c++是面向對象的的程序設計語言,在c語言的基礎上進行了擴充和完善,并且c++兼容了c語言的面向過程的特點。在C++中可以使用繼承、多態進行面向對象的編程。
面向對象與面向過程的區別
面向過程
面向過程編程是就分析出解決問題題的不走,然后把這些步驟一步一步的實現,使用的時候一個一個的一次調用就可以了。
面向對象
面向對象編程就是把問題分解成各個對象,建立對象的目的不是為了完成一個步驟,而是為了描述某個市委在整個解決問題的步驟中的行為。
舉個例子(玩五子棋)
使用面向過程的思想來考慮就是:開始游戲,白棋先走、繪制畫面、輪到黑子、繪制畫面、判斷輸贏、重復之前的過程,輸出最終結果。
使用面向對象的思想來考慮就是:玩家系統、棋盤系統、判定系統、輸出系統。
面向對象就是高度的將實物抽象化,也就是功能的劃分,面向過程就是自頂向下編程,也就是步驟的劃分
具體語言的區別
1、關鍵字不同
C99有32個關鍵字
C++98有63個關鍵字
一些關鍵字細微的區別
1、struct:在C語言猴子那個struct定義的變量中不能由函數,在C++中可以有函數
2、malloc:malloc的返回值是void*,在C語言中可以賦值給任意類型的指針,在C++中必須要進行強制類型轉換,否則會報錯。
3、class和struct:class是對struct的擴展,struct的默認訪問權限是public,而class的默認訪問全顯示private
2、后綴名不同
C源文件的后綴是.c,C++源文件的后綴是.cpp,在VS中,如果在創建源文件的時候什么都不給,默認的就是.cpp
3、返回值不同
在C語言中,如果一個函數沒有指定返回值得類型,默認的返回值為int類型,并且會返回一個隨機數,一般為0xCCCCCCCC,C++中如果一個函數沒有返回值,則必須要指定為void,否則編譯不會通過。
4、參數列表不同
在C語言中,函數沒有指定參數列表的時候,默認可以接受多個參數,但是不支持無名參數,在C++中,因為嚴格的參數類型檢測,沒有參數列表的函數,默認為void,不接受任何參數,但是他支持無名參數。
5、缺省參數
缺省參數的聲明或定制函數時的參數指定一個默認值。在調用該函數時,如果沒有指定實參則可以采用該默認值,則使用指定的參數。但是這在C語言中是不支持的。
6、函數重載
函數重載是函數的一種特殊情況,指的是在同一作用域中,聲明幾個功能類似的同名函數,這些同名函數的形參列表必須不同,或者是在類中使用const修飾的函數和沒有使用const修飾的函數,常用來處理實現功能類似但是數據類型不同的問題。在C語言中沒有函數重載,是因為C語言對函數名的修飾只是在函數名前添加一個下劃線,但是C++對函數名的修飾會添加上該函數的返回值和參數列表。
7、標準輸入輸出
在C語言中使用的是scanf()和printf()來實現的,但是C++中是使用類來實現的。cin、cout對象,他們本身并不是C++語言的組成部分,在C++中不提供內在的輸入輸出運算符,這時與其他語言不相同的地方,他的輸入和輸出是通過C++中的類來實現的,cin和cout都是這些類的實例,是在C++語言的外部實現的。
8、動態內存管理
C語言使用的是malloc/free函數,C++在此基礎上還添加了new/delete兩個關鍵字。
9、const修飾
C語言中const修飾的變量不可以用在定義數組時的大小,并且在定義的時候可以不設定初始值,但是在C++中修飾的變量在定義的時候必須要設定初始值,并且可以用在定義數組的大小,,如果不進行取地址或解引用的話,是存放在符號表中的,不開辟內存。
二、C++面向對象
面向對象的特點
維護性、復用性、擴展性。
封裝體現了維護性,按照信息屏蔽的原則,把對象的屬性和操作結合在一起,構成一個獨立的對象。通過限制對屬性和操作的訪問權限,可以將屬性隱藏在對象內部,對外提供一定的接口,在對象之外只能通過接口對對象進行操作,這樣增加了對象的獨立性,外部的對象不能直接操作對象的屬性,只能使用對象提供的服務,從而保證了數據的可靠性。
繼承體現了復用性,當定義了一個類后,又需要定義一個新類但是這個類與原來的類相比只是增加或修改了部分屬性和操作,這時可以引用原來的類派生出新的類,新類中只需要描述自己特有的屬性和操作,這樣就大大的簡化了對問題的描述,提高了程序的復用性。
多態體現了擴展性,多態就是一個接口多種實現,當需要添加新的模塊功能的時候,不需要改變原來的功能,只需要添加新的即可,這樣就實現了擴展性。
面向對象的優點
易于維護:可讀性比較高,如果有改變的需求,由于繼承的存在,維護也只是局部模塊,所以說維護起來是非常方便和較低成本的。
質量高:可重用現有的,在以前的項目的領域中一杯測試過的類使系統滿足業務需求并具有較高的質量。
效率高:在軟件開發時,根據設計的需要對現實事件的事務進行抽象,產生類。這樣結局問題的方法接近于日常生活和自然的思考方式,必定會提高軟件開發的效率和質量。
1、c語言是面向過程的結構化 語言,易于調試和維護。
2、表現能力和處理能力極強,可以直接訪問內存的物理地址。
3、C語言實現了對硬件的編程操作,也適合于引用軟件的開發。
概述
封裝可以使得代碼模塊化,繼承可以擴展已經存在的代碼,他們的目的是為了代碼重用。而多態的目的是為了接口重用。
封裝
封裝是設計類的一個基本原理,是將抽象得到的數據和行為相結合,形成一個有機的整體,也就是將數據與對數據進行的操作進行有機的結合,從而形成類,其中的數據和函數都是類的成員。
繼承
如果B是繼承了A,那么就把這個B稱為是A的子類,把A稱為B的父類。繼承可以使子類具有父類的各種屬性和方法和方法,就不用再次編寫相同的代碼。子類繼承父類的同時,可以重新定義某些屬性,并重定義其中的一些方法,也就是隱藏父類中原有的屬性和方法,使其獲得于父類不同的功能。
單繼承
單繼承就是一個派生類繼承一個基類。單繼承的繼承規則為:所有繼承下來的基類成員變量存放在派生類添加的成員變量之前,也就是基類的成員變量的內存地址低于派生類的內存地址,可以看做是將基類的內存空間進行了一次拷貝,并且在拷貝的內存空間后面加上派生類自己的成員。
多繼承
菱形繼承
菱形繼承存在的問題就是數據二義性,相應的解決方案就是虛擬繼承。多繼承的繼承規則是:以單繼承的方式按照父類聲明的順序繼承每個父類,可以看做是按照聲明的順序將每個父類的內存空間拷貝到一起,并且在后面添加上派生類自己的成員。
虛擬繼承
虛擬繼承是解決C++中多重繼承問題的一種手段,從不同途徑繼承來的同一基類,會在子類中存在多份拷貝。這樣會存在兩個問題,一個是對于存儲空間的浪費,還有就是數據的二義性。虛擬繼承就是針對這兩個問題出現的,虛擬繼承的底層實現原理與編譯器相關,一般通過虛基類和虛基表實現,每個虛繼承的子類都有一個虛基類指針和虛基表,當虛擬繼承的子類被當做父類繼承時,虛基類指針也會被繼承。實際上vbptr指的是虛基類表指針,這個指針指向虛基類表,在虛基類表中記錄了虛基類與本類的偏移地址,通過偏移地址,可以找到虛基類的成員,虛擬繼承和虛函數有著相同之處,都是利用了虛指針和虛表,虛基類表中存儲的是虛基類相對于直接繼承類的便宜,而虛函數表中存儲的時候虛函數的地址。
繼承中的訪問權限
父類的私有成員在子類中無論以什么方式繼承都是不可見的,這個的不可見指的是private成員仍然被繼承到了子類中,但是在語法上限制子類對象不管實在類內還是在類外都是無法訪問它的。
子類以公有方式繼承父類時,父類的成員在子類中保持原有的屬性。
子類以保護方式繼承父類時,父類中的公有成員在子類中成了保護成員。
子類以私有方式繼承父類時,父類中所有成員在子類中都是私有的。
使用class時默認的繼承方式時私有的,使用struct時則默認的繼承方式是共有的。
還有一點就是友元是類級別的,不存在繼承的問題,也就是子類不能繼承父類的友元關系。
多態
多態可以簡單的概括為“一個接口,多種方法”,字面意思是多種形態。多態分為靜態多態和動態多態。
靜態多態
靜態多態也稱作靜態綁定或者是早綁定。地址的綁定是編譯器在編譯的時候完成的,編譯器根據函數實參的類型,可以推斷出要調用那個函數,這里可能會進行隱式的類型轉換,如果有對應的函數就調用了,否則編譯報錯。靜態多態又分為函數重載和泛型編程。
函數重載
函數重載是在相同的作用域中,只有函數的名稱相同,參數個數或參數類型不同。編譯器根據函數不同的參數表,對同名函數的名稱修飾,然后這些同名函數就成了不同的函數。這個在C語言中是不支持的,因為c語言中對函數的名稱修飾較為簡單,在VS2013編譯器中,c語言對函數名稱修飾的處理只關注到了函數名,對函數名的修飾只是簡單的在函數名前添加_,而c++語言除了函數名,還關注了函數的參數,對函數名的修飾時候要加上參數,通過對函數名稱的修飾不同,編譯器調用函數時所找的符號就不同。
泛型編程
泛型編程指的是編寫獨立于特定類型的代碼,泛型編程在C++中的主要是實現為函數模板和類模板。泛型編程的特性有如下幾點:
1、函數模板并不是真正的函數,他只是C++編譯器生成具體的函數的一個模子。
2、函數模板本身并不生成函數,實際生成的函數是替換函數模板的那個函數,這種替換在編譯期就綁定了。
3、函數模板不是只編譯一份滿足多重需要,而是為每一種替換他的函數編譯一份。
4、函數模板不允許自動類型轉換。
5、函數模板不可以設置默認模板參數。
動態多態
C++中的動態多態是基于虛函數的。對于相關的對象類型,確定他們之間的一個共同的功能集,然后在父類中把這些共同的功能聲明為多個公共的虛函數接口。各個子類重寫這些虛函數,完成具體的功能。操作函數通過指向基類的引用或指針來操作這些對象,對虛函數的調用會自動綁定到實際提供的子類對象上去。
虛函數
虛函數之所以叫做虛函數,是因為他的推遲聯編和動態聯編,一個類的虛函數的調用并不是在編譯的時候確定的,而是在運行的時候確定的,虛函數通過基類的指針或者引用指向派生類對象實現多態。
純虛函數
純虛函數指的是在基類中聲明的虛函數,但是沒有在基類中定義,要求在任何派生類中都要定義自己實現方法。如果一個類中有純虛函數,則這個類被稱為抽象類,由于這個類的構建并不完成,所以不能生成一個對象。繼承了抽象類的派生類必須要將純虛函數實現,否則同樣是抽象類,不能生成對象。
虛函數表
在C++中虛函數通話四通過虛函數表來實現的,這個表中存放的是虛函數的地址,他是屬于類的,不屬于某個具體的對象,在一個類中只有一個虛表,所有對象共享同一份虛表。為了指定對象的虛表,在對象構造的時候就在對象的內部包含了虛表指針_vfptr,一般是放在頭部。
關于虛函數表有兩種情況是要分清楚的,多繼承和多重繼承中的虛表是不一樣的。
多繼承指的是有一個子類繼承了兩個基類,比如說有A,B,C三個類,在A和B類中都有虛函數,C類依次繼承了A類和B類,這時候C類中的虛表就有了A類和B類兩個虛表,并且C類中的虛表指針是以A類虛表地址為基礎的,如果想要獲取到B類虛表的地址可以讓指針向后偏移A類的大小或者給出一個B類的指針指向C類對象發生一個天然的轉換,需要注意的是在C類中的重寫的虛函數會覆蓋A類和B類中的同名虛函數,如果C類中的虛函數在A類和B類中沒有,就添加到A類的虛函數表中,但是A類指針不可以調用,如果是只在A類或者B類中有的虛函數,在C類中沒有,那么只能是擁有虛函數的父類和C類可以調用。
多重繼承就是B類繼承了A類,C類繼承了B類,在B類中的重寫的虛函數會在虛函數表中覆蓋A類的同名虛函數,并將B類新添加的虛函數放在B類虛函數表的末尾,C類也是如此,C類的虛表是從B類中繼承的,在C類中的重寫的虛函數會在虛函數表中覆蓋B類的同名虛函數,并將C類新添加的虛函數放在C類虛函數表的末尾。
靜態多態多態的比較
靜態多態
優點
1、靜態多態通過模板編程為C++帶來了泛型設計的概念,比如STL。
2、靜態多態是在編譯期完成的,所以效率很高,編譯器可以對其進行優化。
缺點
由于模板是實現靜態多態,所以模板的不足也是靜態多態的劣勢,比如調試困難、編譯耗時、代碼膨脹。
動態多態
優點
1、實現與接口分離,可復用。
缺點
1、運行時綁定,導致一定程度上的運行時開銷。
2、編譯器無法對虛函數進行優化。
3、笨重的類繼承體系,對接口的修改影響整個類層次。
不同點
本質不同:
早晚綁定,靜態多態是在編譯期決定的,由模板實現完成,而動態多態是在運行期間決定的,由繼承、虛函數實現。
接口方式不同:
動態多態的接口是顯式的,以函數名為中心,通過虛函數在運行期間實現,靜態多態的接口是隱式的,以有效表達為中心,通過模板在編譯期間完成。
應用形式上:
靜多態是發散式的,讓相同的實現代碼應用于不同的場合。
動多態是收斂式的,讓不同的實現代碼應用于相同的場合。
思維方式上:
靜多態是泛型式編程風格,它看重的是算法的普適性。
動多態是對象式編程風格,它看重的是接口和實現的分離度。
相同點
夠可以實現多態性,靜態多態/編譯期多態,動態多態/運行期多態。
都可以是使接口和實現分離,一個是模板定義接口,類型參數定義實現,一個是基類定義接口,繼承類負責實現。
虛函數面試題
不可以,因為inline函數沒有地址,無法將他存放到虛函數表中。
不能,因為靜態成員函數中沒有this指針,使用::成員函數的嗲用用方式無法訪問虛函數表,所以靜態成員函數無法放進虛函數表。
不可以,因為對象中的虛函數指針是在對象構造的時候初始化的。
可以,最好將析構函數設置為虛函數,因為這樣可以避免內存泄漏的問題,如果一個父類的指針指向了子類的的對象,如果子類對象中的虛函數沒有寫成多態的,他只會調用父類的析構函數,不會調用自己的析構函數,但是他創建對象的時候調用了構造函數,所以說就用子類的構造函數就應該該取調用他的析構函數,這樣才能保證所有的必須釋放的資源都是放了,才可以保證不會有內存泄漏。如果是多態的,就會先去調用子類的析構函數,然后再取調用父類的析構函數,這樣子類和父類的資源就都可以釋放。
如果是普通對象,是一樣快的,如果是指針對象或者是引用對象,調用普通函數更快一些,因為構成了多態,運行時調用虛函數要先到虛函數表中去查找。這樣然后才拿到韓式的地址,這樣就不如直接可以拿到函數地址的普通函數快。
虛函數時再編譯階段生成的,他一般存放再代碼段,也就是常量區。
虛函數是在程序運行的時候通過尋址操作才能確定真正要調用的的函數,而普通的成員函數在編譯的時候就已經確定了要調用的函數。這個兩者的區別,從效率上來說,虛函數的效率要低于普通成員函數,因為虛函數要先通過對象中的虛標指針拿到虛函數表的地址,然后再從虛函數表中找到對應的函數地址,最后根據函數地址去調用,而普通成員函數直接就可以拿到地址進行調用,所以沒必要將所有的成員函數聲明成虛函數。
當類中聲明了虛函數是,編譯器會在類中生成一個虛函數表VS中存放在代碼段,虛函數表實際上就是一個存放虛函數指針的指針數組,是由編譯器自動生成并維護的。虛表是屬于類的,不屬于某個具體的對象,一個類中只需要有一個虛表即可。同一個類中的所有對象使用同一個虛表,為了讓每個包含虛表的類的對象都擁有一個虛表指針,編譯器在每個對象的頭添加了一個指針,用來指向虛表,并且這個指針的值會自動被設置成指向類的虛表,每一個virtaul函數的函數指針存放在虛表中,如果是單繼承,先將父類的虛表添加到子類的虛表中,然后子類再添加自己新增的虛函數指針,但是在VS編譯器中我們通常看不到新添加的虛函數指針,是編譯器故意將他們隱藏起來,如果是多繼承,在子類中新添加的虛函數指針會存放在第一個繼承父類的虛函數表中。
靜態綁定的多態的是通過函數的重載來實現的。動態綁定的多態是通過虛函數實現的。
為了方便使用多態特性,在很多情況下由基類生成對象是很不合理的,純虛函數在基類中是沒有定義的,要求在子類必須加以實現,這種包含了純虛函數的基類被稱為抽象類,不能被實例化,如果子類沒有實現純虛函數,那么它他也是一個抽象類。
從基類的角度出發,如果一個類中聲明了虛函數,這個函數是要在類中實現的,它的作用是為了能讓這個函數在他的子類中能被重寫,實現動態多態。純虛函數,只是一個接口,一個函數聲明,并沒有在聲明他的類中實現。對于子類來說它可以不重寫基類中的虛函數,但是他必須要將基類中的純虛函數實現。虛函數既繼承接口的同時也繼承了基類的實現,純虛函數關注的是接口的統一性,實現完全由子類來完成。
多態就是一個接口多種實現,多態是面向對象的三大特性之一。多態分為靜態多態和動態多態。靜態多態包含函數重載和泛型編程,進程多態是程序調用函數,編譯器決定使用哪個可執行的代碼塊。靜態多態是由繼承機制以及虛函實現的,通過指向派生類的基類指針或者引用,訪問派生類中同名重寫成員函數。墮胎的作用就是把不同子類對象都當作父類來看,可以屏蔽不同子類之間的差異,從而寫出通用的代碼,做出通用的編程,以適應需求的不斷變化。
三、常見關鍵字的作用
static
static有靜態局部變量、靜態全局變量和靜態方法三種使用方法,他們的共同點就是在本文件中聲明的靜態變量和靜態方法是不能被其他文件所使用的,和對應的extern關鍵字,extern關鍵字聲明的全局變量和函數在整個工程中都是可以被使用的。
全局變量
有static聲明的全局變量,只能在函數體外部被定義,并且只能在本文件中有效,這點就是區別于普通的全局變量,普通的全局變量在其他的文件中也是可見的。在函數體重可以定義同名的局部變量,這時會隱藏這個靜態的,如果要使用靜態的全局變量,需要在變量名前添加::作用域運算符。
局部變量
static局部變量同樣只能在本文件中使用,靜態局部變量的生命周期不隨著函數的結束而結束,只能在第一調用函數的時候回他進行初始化,之后調用就會跳過初始化,他會在函數結束之后在內存中保存當前的結果,而不會像普通的局部變量在清棧的時候銷毀,在內存中他區別與局部變量的是局部變量每次調用函數時分配的內存空間可能是不一樣的,但是靜態局部變量具有全局唯一性的特點,每次調用使用的時候用的都是同一塊內存空間,但是這也造成了一個不可重入的問題。(現在有兩個進程A、B都要去調用這個函數fun(),如果是A先調用函數fun,在運行函數的時候突然失去了運行權,但是已經將局部變量修改成了自己要試用的值,由于使用的是同一塊內存空間,進程B調用函數的時候也將局部變量修改成了自己要使用的值,當進程A需要繼續執行的時候,由于這塊內存空間中的值已經被修改了,所有進程A就得不到自己想要的結果)。
方法
static數據成員和成員函數
在C++中繼承了C語言中的static這個關鍵字,并且在類中給了第三種定義方法,表示只屬于一類而不是屬于類的某個對象的變量和函數。這個和普通的成員最大的區別就是在類中是唯一的,并且在內存中只有一份,普通成員函數調用的時候需要傳入this指針,但是靜態成員函數調用的時候是沒有this指針的,只能在調用的時候使用類名加作用域來調用。在設計多線程操作的時候,有POSIX庫下的線程函數要求是全局的,所以普通的成員函數是無法直接作為線程函數的,但是靜態的成員函數是可以做線程函數的。
static函數和普通函數
普通函數的定義和聲明默認是extern的,在同一個工程中的其他文件中是可見的,如果在另一個文件中定義了相同的函數就會穿線重定義錯誤,當然這個重定義和繼承中的 重定義是不一樣的,這里的重定義指定的命名沖突。靜態函數在內存中只有一份,但是普通的函數在每個被調用中都會維護一份拷貝。
extern
extern置于變量或函數前,用于標示變量或函數的定制在別的文件中,提示編譯器遇到這個變量或函數要在其他的模塊中查找。
extern “C”
如果是extern“C” void fun(int a, int b);這樣是高數編譯器在編譯fun這個函數的時候要按照C的規則去編譯,而不是按照C++的,這一點主要是與C++支持重載,C語言不支持重載和函數被C++編譯器編譯后在苦衷的名字與C語言的不同有關。
當extern不與“C”在一起修飾變量或者函數時,比如extern int a;他的作用就是聲明函數或者全局變量的作用范圍和關鍵字,其生命的函數和變量可以在本工程中的所有文件中使用。需要注意的是他只是一個聲明,并不是定義。
const
使用const修飾類的成員變量的時候,必須要在初始化列表進行初始化,并且引用類型的成員變量和沒有默認默認構造函數的對象成員也必須要在初始化列表進行初始化,如果有繼承的關系,如果父類沒有默認的構造函數,也必須要在初始化列表進行初始化,初始化列表對數據成員的初始化順序時按照數據成員的聲明順序嚴格執行的。
const修飾成員函數的時候,一般是放在成員函數的最后面,修飾的類的成員函數中隱藏的this指針,代表不可以通過this指針修改類的數據成員,這個使用方法也可以與普通的相同的成員函數構成重載。
關于const還有一個問題就是傳參和賦值的問題,一般來說使用const修飾的變量是安全的,沒有使用const修飾的變量是不安全的,在傳參的時候可以讓非const修飾的變量傳給const修飾的,但是const修飾的變量不可以傳給非const修飾的形參,這就相當于將安全的變量交給了不安全的變量。
volatile
volatile一般用來修飾變量,他的存在是因為我們的程序在進行編譯的時候編譯器會進行一系列的優化,比如說某個變量被修飾為const,編譯器就會認為這個值是只讀的,就會在寄存器中保存這個變量的值,每次需要的時候直接從寄存器中讀取,但是有的時候會在不經意間修改了這個變量的值,那么編譯器是并不知道的,還是從寄存器中進行讀取,這樣就會造成結果不匹配。但是如果使用volatile聲明后,就是相當與告訴編譯器這個變量隨時會給變,需要每次都要從內存中讀取,不需要優化,從而避免了這個問題,volatile的應用場景最多的是多線程對
define、const、inline區別
define作用域程序的預處理節點,而預處理主要的工作是宏替換、去注釋以及條件編譯,而define起作用的地方就在宏替換階段,只是單純的將宏替換為代碼。但是define只是單純的代碼替換,不會進行類型的檢查,很容易出錯。在C++中建議使用const、枚舉定義常量,這樣就會有類型檢查。于是C++中有提供了一個inline關鍵字,可以實現和define相同的功能,并且支持類型檢查和調試,一般生命在函數的定義前面,但是inline只是對編譯器的一種建議,一般建議代碼為3-5航左右,并且沒有復雜的邏輯結構,例如循環、遞歸之類的。
四、malloc/free和new/delete的區別
1、malloc是從堆上開辟空間,而new是從自由存儲區開辟空間。自由存儲區是C++抽象出來的概念,不僅可以是堆,還可以是靜態存儲區。
2、malloc是函數,而new是關鍵字。
3、malloc對開辟的空間大小需要嚴格的指定,而new只需要對象名。
4、malloc開辟的空間既可以給單個對象使用也可以給數組使用,釋放的方式都是free();而new開辟對象數組需要使用new[size],釋放是使用delete[]。
5、malloc成功的返回值是void*,需要用戶進行強轉,申請空間失敗會返回NULL,所以在使用的時候需要進行判空處理,new成功返回的是對象指針,不需要強轉,失敗拋出異常,但是為了最大程度的兼容C,C++的new也支持失敗返回NULL,但是一般不使用。
6、new不僅負責開辟空間,還會去調用對象的構造函數和析構函數。
7、new申請空間的效率要低于malloc,因為new的底層是通過malloc實現的。
五、引用和指針的區別
指針
指針是一個變量,只不過這個變量中存儲的是一個地址,指向內存的一個存儲單元。比如說一個類型T,T*就是一個指向T的指針類型。
引用
引用其實就是一個對象的一個別名,主要用于函數傳參和返回值類型,類型+&+變量名 = 被引用對象。
區別
1、引用是某塊內存的別名,而空指針指向的是一塊內存,他的內容是所指內存的地址。
2、引用在創建的時候必須要別初始化,但是指針可以為空,所以在使用使用指針的時候就要進行判空處理,引用就可以不用。
3、引用一旦進行了初始化就不能改變指向,指針可以改變自己的指向。
4、對弈引用來說,使用const修飾時,不同的const位置的意義是一樣的,都表示指向的對象是常量。對于指針來說,const int *p 說明p是指向常量的指針, int * const p 說明p本身就是一個常量。
5、引用的大小是指向對象的大小,而指針在32位機上是四字節,在64位機上是8字節,是指針本身的大小。
6、對引用++操作就是對引用指向的對象進行++操作,但是對指針++操作,表示的是地址的變化,向后移動一個指針的大小。
7、指針傳遞和引用傳遞
? 指針傳遞傳遞的是地址,在函數中定義了一個局部的指針變量,存放的是實參的地址,消耗了內存,可以對地址進行加減操作,指向另一個變量,由于傳遞的是地址,所以不需要返回值,因為實際修改的就是實參的值。
? 引用傳遞同樣是地址,但是不會在函數中消耗內存,直接對地址進行使用,對函數中的引用變量的加減操作直接影響外部的實參,并且不能指向另一個變量。在實際使用中傳遞引用的時候如果不希望實參被改變,通常要用const將其修飾。
8、引用不可以有多級只能是一級,但是指針可以是多級的。
六、深拷貝和淺拷貝
淺拷貝指的是將原始對象中的數據型字段拷貝到新對象中,將引用型對象的引用賦值到新對象中去,不把引用的對象復制進去,所以原始對象和新對象引用同一對象,新對象中的引用型字段發生變化會導致原始對象中對應的字段發生變化。
? 深拷貝是在引用方面不同,深拷貝就是重新創建一個新的和原始字段內容相同的字段,所以兩者的引用是不同的。其中一個對象發生變化并不會影響另一個對象。
編譯系統會在我們自己沒有定義拷貝構造的時候調用默認的拷貝構造函數,進行淺拷貝,也就是兩個對象使用同一份資源,當第一個對象進行了析構后,第二對象就懸空了,如果再對他進行析構,由于編譯器找不到對應的資源,就會崩潰。所以對含有指針成員的對象或類中存在資源的對象進行拷貝的時候,必須要自己定義拷貝構造函數,實現深拷貝。
七、類中的成員函數占空間嗎?怎么調用?
類中的普通成員函數和靜態成員函數是不占用類的內存的,只有在類中函數有虛函數的時候才會在類中添加一個虛函數指針,增加一個指針的大小。類中的成員函數實際上與普通的全局函數一眼,只不過是在編譯的時候在成員函數中添加了一個指向當前對象的this指針,成員函數的地址是全局已知的,所以對象的內存空間中是沒有必要去保存成員函數的地址的。在編譯的時候就已經綁定了,類的屬性值得是類中的數據成員,他們是實例化一個對象的時候就為數據成員分配了內存,但是成員函數是所有對象多公有的。
但是空類的大小是1個字節,這是為了保證兩個不同對象的地址不同。類的實例化是在內存中分配一塊地址,每個勢力在內存中歐擁有獨一無二的地址。同樣的,空類也會實例化,所以編譯器會給類隱含的添加一個字節,這樣空類實例化后就有了獨一無二的地址了。在一個空類中,在第一次實例化對象的時候就創建了默認的成員函數,并且這個成員函數是public和inline的。
八、NULL和nullptr的區別
傳統意義上來說,c++把NULL、0視為同一種東西,有些編譯器將NULL定義為 ((void*)0),有些將其定義為0 c++不允許直接將void隱式的轉化為其他類型,但是如果NULL被定義為 ((void*)0),當編譯char *p = NULL,NULL只好被定義為0。 還有:void func(int);void func(char*); 如果NULL被定義為0,func(NULL)會去調用void func(int),這是不合理的 所以引入nullptr,專門用來區分0、NULL。nullptr的類型為nullptr_t,能夠隱式的轉換為任何指針。所以用空指針就盡可能的使用nullptr。總結
以上是生活随笔為你收集整理的C++常见面试题(2019年校招总结)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: YOLO9000
- 下一篇: Android之查看网络图片和网页HTM