54怔檩、形參與實(shí)參的區(qū)別?
形參變量只有在被調(diào)用時(shí)才分配內(nèi)存單元蓄诽,在調(diào)用結(jié)束時(shí)薛训, 即刻釋放所分配的內(nèi)存單元。因此仑氛,形參只有在函數(shù)內(nèi)部有效乙埃。函數(shù)調(diào)用結(jié)束返回主調(diào)函數(shù)后則不能再使用該形參變量闸英。
實(shí)參可以是常量、變量介袜、表達(dá)式甫何、函數(shù)等, 無(wú)論實(shí)參是何種類型的量遇伞,在進(jìn)行函數(shù)調(diào)用時(shí)辙喂,它們都必須具有確定的值, 以便把這些值傳送給形參鸠珠。因此應(yīng)預(yù)先用賦值巍耗,輸入等辦法使實(shí)參獲得確定值,會(huì)產(chǎn)生一個(gè)臨時(shí)變量渐排。
實(shí)參和形參在數(shù)量上炬太,類型上,順序上應(yīng)嚴(yán)格一致驯耻, 否則會(huì)發(fā)生“類型不匹配”的錯(cuò)誤娄琉。
函數(shù)調(diào)用中發(fā)生的數(shù)據(jù)傳送是單向的。即只能把實(shí)參的值傳送給形參吓歇,而不能把形參的值反向地傳送給實(shí)參。因此在函數(shù)調(diào)用過(guò)程中票腰,形參的值發(fā)生改變城看,而實(shí)參中的值不會(huì)變化。
當(dāng)形參和實(shí)參不是指針類型時(shí)杏慰,在該函數(shù)運(yùn)行時(shí)测柠,形參和實(shí)參是不同的變量,他們?cè)趦?nèi)存中位于不同的位置缘滥,形參將實(shí)參的內(nèi)容復(fù)制一份轰胁,在該函數(shù)運(yùn)行結(jié)束的時(shí)候形參被釋放,而實(shí)參內(nèi)容不會(huì)改變朝扼。
58赃阀、從匯編層去解釋一下引用
9: int x = 1;
00401048 mov dword ptr [ebp-4],1
10: int &b = x;
0040104F lea eax,[ebp-4]
00401052 mov dword ptr [ebp-8],eax
x的地址為ebp-4,b的地址為ebp-8擎颖,因?yàn)闂?nèi)的變量?jī)?nèi)存是從高往低進(jìn)行分配的榛斯,所以b的地址比x的低。
lea eax,[ebp-4] 這條語(yǔ)句將x的地址ebp-4放入eax寄存器
mov dword ptr [ebp-8],eax 這條語(yǔ)句將eax的值放入b的地址
ebp-8中上面兩條匯編的作用即:將x的地址存入變量b中搂捧,這不和將某個(gè)變量的地址存入指針變量是一樣的嗎驮俗?所以從匯編層次來(lái)看,的確引用是通過(guò)指針來(lái)實(shí)現(xiàn)的允跑。
60王凑、C++模板是什么搪柑,你知道底層怎么實(shí)現(xiàn)的?
編譯器并不是把函數(shù)模板處理成能夠處理任意類的函數(shù)索烹;編譯器從函數(shù)模板通過(guò)具體類型產(chǎn)生不同的函數(shù)工碾;編譯器會(huì)對(duì)函數(shù)模板進(jìn)行兩次編譯:在聲明的地方對(duì)模板代碼本身進(jìn)行編譯,在調(diào)用的地方對(duì)參數(shù)替換后的代碼進(jìn)行編譯术荤。
這是因?yàn)楹瘮?shù)模板要被實(shí)例化后才能成為真正的函數(shù)倚喂,在使用函數(shù)模板的源文件中包含函數(shù)模板的頭文件,如果該頭文件中只有聲明瓣戚,沒(méi)有定義端圈,那編譯器無(wú)法實(shí)例化該模板,最終導(dǎo)致鏈接錯(cuò)誤子库。
68舱权、成員列表初始化?
- 必須使用成員初始化的四種情況
① 當(dāng)初始化一個(gè)引用成員時(shí)仑嗅;
② 當(dāng)初始化一個(gè)常量成員時(shí)宴倍;
③ 當(dāng)調(diào)用一個(gè)基類的構(gòu)造函數(shù),而它擁有一組參數(shù)時(shí)仓技;
④ 當(dāng)調(diào)用一個(gè)成員類的構(gòu)造函數(shù)鸵贬,而它擁有一組參數(shù)時(shí);
- 成員初始化列表做了什么
① 編譯器會(huì)一一操作初始化列表脖捻,以適當(dāng)?shù)捻樞蛟跇?gòu)造函數(shù)之內(nèi)安插初始化操作阔逼,并且在任何顯示用戶代碼之前;
② list中的項(xiàng)目順序是由類中的成員聲明順序決定的地沮,不是由初始化列表的順序決定的嗜浮;
70、對(duì)象復(fù)用的了解摩疑,零拷貝的了解
對(duì)象復(fù)用
對(duì)象復(fù)用其本質(zhì)是一種設(shè)計(jì)模式:Flyweight享元模式危融。
通過(guò)將對(duì)象存儲(chǔ)到“對(duì)象池”中實(shí)現(xiàn)對(duì)象的重復(fù)利用,這樣可以避免多次創(chuàng)建重復(fù)對(duì)象的開(kāi)銷雷袋,節(jié)約系統(tǒng)資源吉殃。
零拷貝
零拷貝就是一種避免 CPU 將數(shù)據(jù)從一塊存儲(chǔ)拷貝到另外一塊存儲(chǔ)的技術(shù)。
零拷貝技術(shù)可以減少數(shù)據(jù)拷貝和共享總線操作的次數(shù)楷怒。
在C++中寨腔,vector的一個(gè)成員函數(shù)emplace_back()很好地體現(xiàn)了零拷貝技術(shù),它跟push_back()函數(shù)一樣可以將一個(gè)元素插入容器尾部率寡,區(qū)別在于:使用push_back()函數(shù)需要調(diào)用拷貝構(gòu)造函數(shù)和轉(zhuǎn)移構(gòu)造函數(shù)迫卢,而使用emplace_back()插入的元素原地構(gòu)造,不需要觸發(fā)拷貝構(gòu)造和轉(zhuǎn)移構(gòu)造冶共,效率更高乾蛤。舉個(gè)例子:
#include <vector>
#include <string>
#include <iostream>
using namespace std;
struct Person
{
string name;
int age;
//初始構(gòu)造函數(shù)
Person(string p_name, int p_age): name(std::move(p_name)), age(p_age)
{
cout << "I have been constructed" <<endl;
}
//拷貝構(gòu)造函數(shù)
Person(const Person& other): name(std::move(other.name)), age(other.age)
{
cout << "I have been copy constructed" <<endl;
}
//轉(zhuǎn)移構(gòu)造函數(shù)
Person(Person&& other): name(std::move(other.name)), age(other.age)
{
cout << "I have been moved"<<endl;
}
};
int main()
{
vector<Person> e;
cout << "emplace_back:" <<endl;
e.emplace_back("Jane", 23); //不用構(gòu)造類對(duì)象
vector<Person> p;
cout << "push_back:"<<endl;
// p.push_back("Mike", 36); // 這種寫(xiě)法是無(wú)法通過(guò)編譯
p.push_back(Person("Mike",36));
return 0;
}
//輸出結(jié)果:
//emplace_back:
//I have been constructed
//push_back:
//I have been constructed
//I am being moved.
而使用如下的方式每界,則兩者間無(wú)差別。
int main()
{
Person a("Jane", 23);
vector<Person> e;
cout << "emplace_back:" << endl;
e.emplace_back(a);
cout << "===================" << endl;
Person b("Mike", 36);
vector<Person> p;
cout << "push_back:" << endl;
p.push_back(b);
return 0;
}
72家卖、介紹面向?qū)ο蟮娜筇匦哉2悖⑶遗e例說(shuō)明
三大特性:繼承、封裝和多態(tài)
(1)繼承
讓某種類型對(duì)象獲得另一個(gè)類型對(duì)象的屬性和方法上荡。
它可以使用現(xiàn)有類的所有功能趴樱,并在無(wú)需重新編寫(xiě)原來(lái)的類的情況下對(duì)這些功能進(jìn)行擴(kuò)展
常見(jiàn)的繼承有三種方式:
實(shí)現(xiàn)繼承:指使用基類的屬性和方法而無(wú)需額外編碼的能力
接口繼承:指僅使用屬性和方法的名稱、但是子類必須提供實(shí)現(xiàn)的能力
可視繼承:指子窗體(類)使用基窗體(類)的外觀和實(shí)現(xiàn)代碼的能力(C++里好像不怎么用)
例如酪捡,將人定義為一個(gè)抽象類叁征,擁有姓名、性別逛薇、年齡等公共屬性捺疼,吃飯、睡覺(jué)永罚、走路等公共方法啤呼,在定義一個(gè)具體的人時(shí),就可以繼承這個(gè)抽象類呢袱,既保留了公共屬性和方法官扣,也可以在此基礎(chǔ)上擴(kuò)展跳舞、唱歌等特有方法
(2)封裝
數(shù)據(jù)和代碼捆綁在一起羞福,避免外界干擾和不確定性訪問(wèn)惕蹄。
封裝,也就是把客觀事物封裝成抽象的類坯临,并且類可以把自己的數(shù)據(jù)和方法只讓可信的類或者對(duì)象操作,對(duì)不可信的進(jìn)行信息隱藏恋昼,例如:將公共的數(shù)據(jù)或方法使用public修飾看靠,而不希望被訪問(wèn)的數(shù)據(jù)或方法采用private修飾。
(3)多態(tài)
同一事物表現(xiàn)出不同事物的能力液肌,即向不同對(duì)象發(fā)送同一消息挟炬,不同的對(duì)象在接收時(shí)會(huì)產(chǎn)生不同的行為(重載實(shí)現(xiàn)編譯時(shí)多態(tài),虛函數(shù)實(shí)現(xiàn)運(yùn)行時(shí)多態(tài))嗦哆。
多態(tài)性是允許你將父對(duì)象設(shè)置成為和一個(gè)或更多的他的子對(duì)象相等的技術(shù)谤祖,賦值之后,父對(duì)象就可以根據(jù)當(dāng)前賦值給它的子對(duì)象的特性以不同的方式運(yùn)作老速。簡(jiǎn)單一句話:允許將子類類型的指針賦值給父類類型的指針
實(shí)現(xiàn)多態(tài)有二種方式:覆蓋(override)粥喜,重載(overload)。覆蓋:是指子類重新定義父類的虛函數(shù)的做法橘券。重載:是指允許存在多個(gè)同名函數(shù)额湘,而這些函數(shù)的參數(shù)表不同(或許參數(shù)個(gè)數(shù)不同些楣,或許參數(shù)類型不同吓笙,或許兩者都不同)。例如:基類是一個(gè)抽象對(duì)象——人,那教師邮屁、運(yùn)動(dòng)員也是人,而使用這個(gè)抽象對(duì)象既可以表示教師矫废、也可以表示運(yùn)動(dòng)員岂膳。
74、成員初始化列表的概念纳猫,為什么用它會(huì)快一些婆咸?
因?yàn)轭愵愋偷臄?shù)據(jù)成員對(duì)象在進(jìn)入(構(gòu)造)函數(shù)體時(shí)已經(jīng)構(gòu)造完成。
用初始化列表會(huì)快一些的原因是续担,對(duì)于類型擅耽,它少了一次調(diào)用構(gòu)造函數(shù)的過(guò)程,而在函數(shù)體中賦值則會(huì)多一次調(diào)用物遇。而對(duì)于內(nèi)置數(shù)據(jù)類型則沒(méi)有差別乖仇。
舉個(gè)例子:
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "默認(rèn)構(gòu)造函數(shù)A()" << endl;
}
A(int a)
{
value = a;
cout << "A(int "<<value<<")" << endl;
}
A(const A& a)
{
value = a.value;
cout << "拷貝構(gòu)造函數(shù)A(A& a): "<<value << endl;
}
int value;
};
class B
{
public:
B() : a(1)
{
cout << "===========" << endl;
// 1、b在進(jìn)入函數(shù)體時(shí)询兴,調(diào)用默認(rèn)構(gòu)造函數(shù)
// 2乃沙、b再進(jìn)行拷貝構(gòu)造
b = A(2);
}
A a;
A b;
};
int main()
{
B b;
}
從代碼運(yùn)行結(jié)果可以看出,在構(gòu)造函數(shù)體內(nèi)部初始化的對(duì)象b多了一次構(gòu)造函數(shù)的調(diào)用過(guò)程诗舰,而對(duì)象a則沒(méi)有警儒。
首先把數(shù)據(jù)成員按類型分類
1。內(nèi)置數(shù)據(jù)類型眶根,復(fù)合類型(指針蜀铲,引用)
2。用戶定義類型(類類型)
分情況說(shuō)明:
對(duì)于類型1属百,在成員初始化列表和構(gòu)造函數(shù)體內(nèi)進(jìn)行记劝,在性能和結(jié)果上都是一樣的
對(duì)于類型2,結(jié)果上相同族扰,但是性能上存在很大的差別
因?yàn)轭愵愋偷臄?shù)據(jù)成員對(duì)象在進(jìn)入(構(gòu)造)函數(shù)體時(shí)已經(jīng)構(gòu)造完成厌丑,也就是說(shuō)在成員初始化列表處進(jìn)行構(gòu)造對(duì)象的工作,這是調(diào)用一個(gè)構(gòu)造函數(shù)渔呵,在進(jìn)入函數(shù)體之后怒竿,進(jìn)行的是對(duì)已經(jīng)構(gòu)造好的類對(duì)象的賦值,又調(diào)用個(gè)拷貝賦值操作符才能完成(如果并未提供扩氢,則使用編譯器提供的默認(rèn)按成員賦值行為)
為什么用成員初始化列表會(huì)快一些
75耕驰、(超重要)構(gòu)造函數(shù)為什么不能為虛函數(shù)?析構(gòu)函數(shù)為什么要虛函數(shù)录豺?
1耍属、 從存儲(chǔ)空間角度托嚣,虛函數(shù)相應(yīng)一個(gè)指向vtable虛函數(shù)表的指針,這大家都知道厚骗,但是這個(gè)指向vtable的指針事實(shí)上是存儲(chǔ)在對(duì)象的內(nèi)存空間的示启。
問(wèn)題出來(lái)了,假設(shè)構(gòu)造函數(shù)是虛的领舰,就須要通過(guò) vtable來(lái)調(diào)用夫嗓,但是對(duì)象還沒(méi)有實(shí)例化,也就是內(nèi)存空間還沒(méi)有冲秽,怎么找vtable呢舍咖?所以構(gòu)造函數(shù)不能是虛函數(shù)。
2锉桑、 從使用角度排霉,虛函數(shù)主要用于在信息不全的情況下,能使重載的函數(shù)得到相應(yīng)的調(diào)用民轴。
構(gòu)造函數(shù)本身就是要初始化實(shí)例攻柠,那使用虛函數(shù)也沒(méi)有實(shí)際意義呀。
所以構(gòu)造函數(shù)沒(méi)有必要是虛函數(shù)后裸。虛函數(shù)的作用在于通過(guò)父類的指針或者引用來(lái)調(diào)用它的時(shí)候可以變成調(diào)用子類的那個(gè)成員函數(shù)瑰钮。而構(gòu)造函數(shù)是在創(chuàng)建對(duì)象時(shí)自己主動(dòng)調(diào)用的,不可能通過(guò)父類的指針或者引用去調(diào)用微驶,因此也就規(guī)定構(gòu)造函數(shù)不能是虛函數(shù)浪谴。
3、構(gòu)造函數(shù)不須要是虛函數(shù)因苹,也不同意是虛函數(shù)苟耻,由于創(chuàng)建一個(gè)對(duì)象時(shí)我們總是要明白指定對(duì)象的類型,雖然我們可能通過(guò)實(shí)驗(yàn)室的基類的指針或引用去訪問(wèn)它但析構(gòu)卻不一定扶檐,我們往往通過(guò)基類的指針來(lái)銷毀對(duì)象凶杖。這時(shí)候假設(shè)析構(gòu)函數(shù)不是虛函數(shù),就不能正確識(shí)別對(duì)象類型從而不能正確調(diào)用析構(gòu)函數(shù)蘸秘。
4官卡、從實(shí)現(xiàn)上看蝗茁,vbtl在構(gòu)造函數(shù)調(diào)用后才建立醋虏,因而構(gòu)造函數(shù)不可能成為虛函數(shù)從實(shí)際含義上看,在調(diào)用構(gòu)造函數(shù)時(shí)還不能確定對(duì)象的真實(shí)類型(由于子類會(huì)調(diào)父類的構(gòu)造函數(shù))哮翘;并且構(gòu)造函數(shù)的作用是提供初始化颈嚼,在對(duì)象生命期僅僅運(yùn)行一次,不是對(duì)象的動(dòng)態(tài)行為饭寺,也沒(méi)有必要成為虛函數(shù)阻课。
5叫挟、當(dāng)一個(gè)構(gòu)造函數(shù)被調(diào)用時(shí),它做的首要的事情之中的一個(gè)是初始化它的VPTR限煞。
因此抹恳,它僅僅能知道它是“當(dāng)前”類的,而全然忽視這個(gè)對(duì)象后面是否還有繼承者署驻。當(dāng)編譯器為這個(gè)構(gòu)造函數(shù)產(chǎn)生代碼時(shí)奋献,它是為這個(gè)類的構(gòu)造函數(shù)產(chǎn)生代碼——既不是為基類,也不是為它的派生類(由于類不知道誰(shuí)繼承它)旺上。所以它使用的VPTR必須是對(duì)于這個(gè)類的VTABLE瓶蚂。
并且,僅僅要它是最后的構(gòu)造函數(shù)調(diào)用宣吱,那么在這個(gè)對(duì)象的生命期內(nèi)窃这,VPTR將保持被初始化為指向這個(gè)VTABLE, 但假設(shè)接著另一個(gè)更晚派生的構(gòu)造函數(shù)被調(diào)用,這個(gè)構(gòu)造函數(shù)又將設(shè)置VPTR指向它的 VTABLE征候,等.直到最后的構(gòu)造函數(shù)結(jié)束杭攻。
VPTR的狀態(tài)是由被最后調(diào)用的構(gòu)造函數(shù)確定的。這就是為什么構(gòu)造函數(shù)調(diào)用是從基類到更加派生類順序的還有一個(gè)理由倍奢∑由希可是,當(dāng)這一系列構(gòu)造函數(shù)調(diào)用正發(fā)生時(shí)卒煞,每一個(gè)構(gòu)造函數(shù)都已經(jīng)設(shè)置VPTR指向它自己的VTABLE痪宰。假設(shè)函數(shù)調(diào)用使用虛機(jī)制,它將僅僅產(chǎn)生通過(guò)它自己的VTABLE的調(diào)用畔裕,而不是最后的VTABLE(全部構(gòu)造函數(shù)被調(diào)用后才會(huì)有最后的VTABLE)衣撬。
因?yàn)闃?gòu)造函數(shù)本來(lái)就是為了明確初始化對(duì)象成員才產(chǎn)生的,然而virtual function主要是為了再不完全了解細(xì)節(jié)的情況下也能正確處理對(duì)象扮饶。另外具练,virtual函數(shù)是在不同類型的對(duì)象產(chǎn)生不同的動(dòng)作,現(xiàn)在對(duì)象還沒(méi)有產(chǎn)生甜无,如何使用virtual函數(shù)來(lái)完成你想完成的動(dòng)作扛点。
直接的講,C++中基類采用virtual虛析構(gòu)函數(shù)是為了防止內(nèi)存泄漏岂丘。
具體地說(shuō)陵究,如果派生類中申請(qǐng)了內(nèi)存空間,并在其析構(gòu)函數(shù)中對(duì)這些內(nèi)存空間進(jìn)行釋放奥帘。假設(shè)基類中采用的是非虛析構(gòu)函數(shù)铜邮,當(dāng)刪除基類指針指向的派生類對(duì)象時(shí)就不會(huì)觸發(fā)動(dòng)態(tài)綁定,因而只會(huì)調(diào)用基類的析構(gòu)函數(shù),而不會(huì)調(diào)用派生類的析構(gòu)函數(shù)松蒜。那么在這種情況下扔茅,派生類中申請(qǐng)的空間就得不到釋放從而產(chǎn)生內(nèi)存泄漏。
所以秸苗,為了防止這種情況的發(fā)生召娜,C++中基類的析構(gòu)函數(shù)應(yīng)采用virtual虛析構(gòu)函數(shù)。