【期末复习】转眼到了C++的复习时间(更新中)
時(shí)間過(guò)的很快。似乎昨天才剛剛開(kāi)學(xué),轉(zhuǎn)眼間已經(jīng)到了期末復(fù)習(xí)的時(shí)間了。距離C++期末考試還有一個(gè)月的時(shí)間,我正式開(kāi)始了C++的復(fù)習(xí)計(jì)劃,以期讓我在后期可以更加從容、淡定,能夠擁有更多的時(shí)間去投入到自己想去做的事情上去。
使用指南
1.里面的語(yǔ)言表達(dá)和課堂上的PPT以及一些教材很不一樣,本著對(duì)讀者友好的原則,盡量寫(xiě)得通俗易懂,就像在講故事一樣,而不是在那里羅列名詞。當(dāng)然,這樣文字會(huì)略有冗長(zhǎng),可以自己總結(jié),也可以參考PPT或書(shū)籍。
2.我在閱讀書(shū)本和博客的時(shí)候經(jīng)常會(huì)看到很多對(duì)讀者不友好的東西,尤其是一些工科教材,其內(nèi)容完全不適合于自學(xué),不得不說(shuō)這是現(xiàn)在的一個(gè)通病。我希望我能夠整合一些資源,把內(nèi)容寫(xiě)的更容易理解,避免“啃”那些質(zhì)量上實(shí)在不敢恭維的所謂的教科書(shū)。
3.可能一開(kāi)始閱讀的時(shí)候發(fā)現(xiàn)不太像之前所學(xué)的東西,別著急,靜下心來(lái)慢慢讀,假定自己是個(gè)零基礎(chǔ)的,什么都不會(huì),忘掉之前所有的C++知識(shí),咱們一起從頭開(kāi)始,建立整套體系,突然在某一時(shí)刻你會(huì)發(fā)現(xiàn)自己能夠巧妙地將這些東西和自己之前所學(xué)建立聯(lián)系,甚至有了更深刻的理解,那么,我所寫(xiě)的這些就是有用的。
4.里面會(huì)出現(xiàn)一些英文,是很常用的那些。了解這些常用的英語(yǔ),對(duì)平時(shí)的學(xué)習(xí)很有幫助。大家遇到這些英文的時(shí)候可以多注意一下。
聲明
本文初衷是自己的復(fù)習(xí)筆記,但是覺(jué)得可以作為博客等進(jìn)行發(fā)布,同時(shí)和大家一起學(xué)習(xí)進(jìn)步。
本文內(nèi)容的原型是翁愷老師在網(wǎng)易云課堂上的C++相關(guān)課程,大家可以去聽(tīng)一聽(tīng),當(dāng)然如果覺(jué)得完全觀看視頻太費(fèi)時(shí)間可以閱讀本文。
本文全部為手敲。本文是免費(fèi)、開(kāi)源的,但轉(zhuǎn)載請(qǐng)注明出處。
序言
????本次復(fù)習(xí)以翁愷老師在網(wǎng)易云課堂上的慕課和老師的PPT為主,加上一些自己踩過(guò)的坑和對(duì)于一些問(wèn)題的求解思路。期望在文章中加上自己對(duì)程序語(yǔ)言的理解,體會(huì)OOP的思想。
????本次復(fù)習(xí)的大致提綱如下:首先是類和對(duì)象,之后自然地過(guò)渡到繼承和派生。因?yàn)檫@兩者之間關(guān)系緊密,所以有很多交叉的內(nèi)容。之后就是運(yùn)算符重載、模板、異常、STL、文件,一些C++中比C語(yǔ)言多出來(lái)的東西。
讀者應(yīng)具備的知識(shí)
閱讀本文,我們假定你已經(jīng)學(xué)習(xí)了一些基本的C語(yǔ)言的知識(shí),懂得如何定義變量,寫(xiě)函數(shù),知道循環(huán)、條件、順序結(jié)構(gòu),知道指針,能夠編寫(xiě)出一些基本的程序。如果沒(méi)有這些知識(shí),建議你先把C語(yǔ)言打下一些基礎(chǔ)。
目錄
- 類和對(duì)象
- 繼承和派生
- 運(yùn)算符重載
- 模板
- 文件
- 異常
- STL工具庫(kù)
- 附錄 更新日志
目前完結(jié)進(jìn)展
截至2019.6.22,已經(jīng)完成的內(nèi)聯(lián)函數(shù)筆記。
1 類和對(duì)象
什么是對(duì)象
????首先咱們明確一下咱們所討論的東西——C++語(yǔ)言,它是“面向?qū)ο蟆钡恼Z(yǔ)言,其英文是Object Oriented Programming,簡(jiǎn)寫(xiě)就是OOP。對(duì)象就是實(shí)現(xiàn)OOP的方法。
????所以咱們先來(lái)看一看對(duì)于C++中很重要的東西。在C++中,我們要學(xué)習(xí)“面向?qū)ο蟆钡木幊谭椒?#xff0c;所以,到底什么是對(duì)象?
可以這樣去理解:對(duì)象就是“東西”。面向呢?其實(shí)Oriented指的是在對(duì)象這個(gè)層面去思考問(wèn)題。對(duì)象可能是可見(jiàn)的,比如你正在學(xué)習(xí)的那張桌子;也可以是不可見(jiàn)的,比如我說(shuō)了一句話。但是這句話可以被記錄、加工、處理,所以這也是對(duì)象。
其實(shí),在之前C語(yǔ)言里,我們就已經(jīng)一直在接觸對(duì)象了,只不過(guò)從來(lái)沒(méi)有那么去說(shuō)。我們寫(xiě)過(guò)int i;這個(gè)變量i就是一個(gè)對(duì)象。沒(méi)錯(cuò)的,變量就是對(duì)象。變量用來(lái)存儲(chǔ)數(shù)據(jù),而變量的類型決定能夠存放什么樣的數(shù)據(jù)。同樣地,對(duì)象也是會(huì)有類型的。
對(duì)象=屬性+服務(wù)
看一看上面這個(gè)形似雞蛋的圖,有沒(méi)有什么想法?
蛋黃是里面的數(shù)據(jù),比如一盞燈的功率、現(xiàn)在是否發(fā)光、能否充電;蛋清是一些操作,比如一個(gè)開(kāi)關(guān),控制通電與否。所以,這個(gè)就是對(duì)象的模型。
我們進(jìn)行對(duì)蛋黃的所有操作必須建立在對(duì)蛋清的控制之上,這也是經(jīng)常出現(xiàn)的一個(gè)問(wèn)題——很多時(shí)候人們可能會(huì)越過(guò)蛋清直接操作蛋黃,而這就違背了OOP(面向?qū)ο?的思想。
我們可以通過(guò)教室上課這個(gè)例子比較一下面向過(guò)程和面向?qū)ο蟮膮^(qū)別。
現(xiàn)在看這樣一個(gè)例子:一個(gè)三維點(diǎn)坐標(biāo)。其實(shí)C++里面的class和C里面的structure很類似,那么,在實(shí)際操作的時(shí)候有什么不同呢?
明顯,C++的操作和數(shù)據(jù)都放在一起,就像那個(gè)雞蛋圖;而C中數(shù)據(jù)和操作是分開(kāi)的。
所以,我們可以回答什么是對(duì)象了:對(duì)象是一種方法,能用來(lái)組織設(shè)計(jì)、實(shí)現(xiàn)(從問(wèn)題思路到代碼實(shí)現(xiàn))。我們關(guān)注的問(wèn)題是對(duì)象,是“東西”!
面向?qū)ο蠡驹?/h3>
我發(fā)送消息,通過(guò)傳輸以后對(duì)方接收到消息。他自己決定要不要執(zhí)行我給他的指令。也就是,我的消息一定要明確,比如我按下了電燈的開(kāi)關(guān)。通過(guò)導(dǎo)線,燈泡知道了我想讓他亮。然而,燈泡發(fā)現(xiàn)自己的燈絲壞了,因此他決定不亮燈。再比如,我現(xiàn)在讓屏幕面前的你大叫一聲。你現(xiàn)在叫了嗎?或許沒(méi)有。但我已經(jīng)傳達(dá)了明確的指令,可你知道我不過(guò)是在舉例子,因此你決定不叫。我們程序猿就是發(fā)指令的人,至于指令能不能執(zhí)行,看接收者的決定。我們做的事情僅限于發(fā)指令,不要強(qiáng)迫他做。
這個(gè)消息,可能導(dǎo)致接收者狀態(tài)改變,比如燈亮了;也可能返回結(jié)果,比如我讀取到了燈的狀態(tài)是開(kāi)著的。
類
Birds of a feather flock together.——亞里士多德
物以類聚,這是OOP的哲♂學(xué)。
類就是對(duì)對(duì)象的歸納總結(jié)。類定義了對(duì)象,對(duì)象屬于類。
比如我說(shuō),蘋(píng)果類。里面有紅的、青的各種,但是它們都長(zhǎng)成這個(gè)樣子,是這樣的生理結(jié)構(gòu),所以我手中有一個(gè)紅富士蘋(píng)果、一個(gè)黃香蕉蘋(píng)果,它們都是屬于蘋(píng)果類的。
類是抽象出來(lái)的一個(gè)概念
為什么會(huì)出現(xiàn)類?
拿蘋(píng)果來(lái)說(shuō)。先是因?yàn)橛辛艘粋€(gè)又一個(gè)的蘋(píng)果,它們都符合這些特點(diǎn),能夠顯著跟別的東西區(qū)分出來(lái),所以我們把各種各樣的蘋(píng)果統(tǒng)稱為蘋(píng)果。說(shuō)這是個(gè)蘋(píng)果,你就知道這不是西瓜,也不是桌子,也不是別的什么玩意。
在生活中,人們經(jīng)驗(yàn)總結(jié)總結(jié)出了類。
而計(jì)算機(jī)科學(xué)與技術(shù)發(fā)展卻是先出現(xiàn)了類的概念,才有了對(duì)象。因?yàn)橛?jì)算機(jī)科學(xué)屬于人類創(chuàng)造的科學(xué)而非自然科學(xué),必須需要先有統(tǒng)一的規(guī)定才能有后續(xù)的發(fā)展。但是,和咱們的認(rèn)知相同,類也是一種抽象出來(lái)的東西。我說(shuō)蘋(píng)果,你腦子里可能出現(xiàn)了各種各樣的蘋(píng)果,但你不知道我說(shuō)的是哪一個(gè)。而對(duì)象就是一個(gè)具體的蘋(píng)果,比如我手里拿著的這個(gè)(當(dāng)然隔著屏幕你們看不到)紅富士蘋(píng)果,你們都很確定我說(shuō)的是哪一個(gè)——我手里的這個(gè)蘋(píng)果。
這就是類和對(duì)象的區(qū)別。類是抽象出來(lái)的,便于我們認(rèn)知這個(gè)世界,但你沒(méi)法說(shuō)這個(gè)類指的是哪一個(gè);對(duì)象可以代表這個(gè)類——它有類應(yīng)有的特征,眾多對(duì)象組成在一起并抽象出來(lái),求出共同點(diǎn),形成了類。
OOP的原則
1.萬(wàn)物皆對(duì)象
2.程序是一堆能夠互相告訴別人"What to do"的對(duì)象的總和
3.每個(gè)對(duì)象有由其他對(duì)象組成的內(nèi)存空間
4.每種對(duì)象都有類型
5.所有特定類型對(duì)象都能接收相同的消息
對(duì)象有接口
對(duì)象提供的那個(gè)操作就是接口(interface)。
想一想,接口。生活中用過(guò)吧?比如USB接口,我可以插U盤(pán),可以插數(shù)據(jù)線,可以插充電寶的充電線……總之,you name it,只要符合這個(gè)接口的條件的都可以被插進(jìn)來(lái)。
你做的東西遵循這個(gè)接口,那么這個(gè)東西是可以替換的。比如燈泡的螺紋,你可以插上去各種型號(hào)的燈。
接口的好處:可以去通信交流、保護(hù)。這樣,我們的程序可以拆換,各部分的耦合程度會(huì)松散。比如把燈泡焊在墻上,和把燈泡使用插線板連接,前者耦合程度非常緊密,就不太可以拆卸。
所以,我們需要隱藏一些東西。比如燈絲,我們需要放在玻璃罩里面;蛋黃被蛋殼和蛋白包圍。寫(xiě)出class的程序猿就需要讓調(diào)用這些class的程序猿的手遠(yuǎn)離她們不該碰的東西。
為此我們要進(jìn)行encapsulation,即封裝,把操作放在外面,把數(shù)據(jù)放在里面。
試著建立一個(gè)類
咱們?cè)囍⒁粋€(gè)屬于自己的類和對(duì)象吧~
以上面的點(diǎn)的坐標(biāo)為例,咱們?cè)囍⑦@樣的一個(gè)類。
class Point{ private:int x;int y;int z; public:void printPoint();void makePoint(int a,int b,int c);void doublePoint(); };這個(gè)類,里面有什么?
第一,三個(gè)點(diǎn),是Pirvate的,也就是私有的。這個(gè)跟咱么提到過(guò)的“保護(hù)”很像,其實(shí)這就是把我不想讓別人直接操控的東西保護(hù)起來(lái)的辦法。把數(shù)據(jù)放到別人碰不到的位置,這就是雞蛋模型里面的蛋黃部分。
第二,那么保護(hù)起來(lái)了以后呢?需要提供接口。上文提到過(guò)這個(gè)詞,再想一想?對(duì),就是電燈的那個(gè)開(kāi)關(guān),是我向里面的數(shù)據(jù)發(fā)指令的途徑。在這個(gè)程序里,就是public的幾個(gè)函數(shù)啦。通過(guò)這幾個(gè)函數(shù),我們間接地改變?nèi)齻€(gè)坐標(biāo),正如同打開(kāi)電燈的開(kāi)關(guān)讓燈間接地亮起來(lái),而不是我把燈絲摳出來(lái)給它兩端接上電,想想,如果這樣多危險(xiǎn)!
好了,類建立好了,咱們接下來(lái)怎么做呢?把相關(guān)函數(shù)和main寫(xiě)一下。
#include <iostream> using namespace std;void Point::printPoint(){cout<<"("<<x<<","<<y<<","<<z<<")"<<endl; }void Point::setPoint(int a,int b,int c){x=a;y=b;z=c; }void Point::doublePoint(){x=2*x;y=2*y;z=2*z; }int main(){Point pt;pt.setPoint(1,2,3);pt.printPoint();pt.doublePoint();pt.printPoint();return 0; }我們做了什么?第一,建立了一個(gè)對(duì)象。想一想類和對(duì)象的區(qū)別。其次我們?cè)O(shè)置了它們的值,輸出一次,然后把每個(gè)點(diǎn)坐標(biāo)翻倍,再次輸出。當(dāng)然這些函數(shù)都很好理解。
輸出結(jié)果是:
(1,2,3)
(2,4,6)
至此,我們已經(jīng)自己建立了一個(gè)類和對(duì)象。
域解析符
::叫做resolver,也就是域解析符。
我們可以看到void Point::doublePoint()這句話里面,在說(shuō)doublePoint這個(gè)函數(shù)是有歸屬的,它的家是Point這個(gè)類。
當(dāng)然,域解析符還可以直接拿出來(lái)用,比如
::doublePoint(); ::a;此時(shí),前面沒(méi)有任何東西,直接使用,它表示全局的函數(shù)或者全局的變量。
每個(gè)類和對(duì)象分別對(duì)應(yīng)一個(gè).h和.cpp文件(選讀)
說(shuō)明:在我們的期末考試中這是用不到的,但是在現(xiàn)實(shí)的開(kāi)發(fā)中可能會(huì)經(jīng)常用到。因此這一部分可以選擇性閱讀,了解即可。以后標(biāo)注“選讀”的章節(jié)亦如此。
大家再區(qū)分一下定義和聲明的區(qū)別吧。在C語(yǔ)言中,extern int i; 是變量的聲明,告訴編譯器有這樣的一個(gè)東西; i=3; 是變量的定義,我告訴編譯器這個(gè)變量的值是多少。在類里面也是這樣子。
每個(gè)類,當(dāng)然是有數(shù)據(jù)(蛋黃)和操作(蛋清),也就是變量和函數(shù)。變量和函數(shù)都有聲明和定義的區(qū)別,在類中也是一樣的。
對(duì)于每一個(gè)類的聲明,我們都應(yīng)該把所有的變量和函數(shù)的名字寫(xiě)出來(lái);而在類的定義中我們應(yīng)當(dāng)寫(xiě)出它們的值和具體的實(shí)現(xiàn)。
我們回顧一下上面的例子:
比如,一個(gè)point.h文件中如下:
class Point{ private:int x;int y;int z; public:void printPoint();void makePoint(int a,int b,int c);void doublePoint(); };而對(duì)應(yīng)的point.cpp文件中如下:
#include “point.h”void Point::printPoint(){cout<<"("<<x<<","<<y<<","<<z<<")"<<endl; }void Point::setPoint(int a,int b,int c){x=a;y=b;z=c; }void Point::doublePoint(){x=2*x;y=2*y;z=2*z; }在使用到這個(gè)類的地方我們都需要include上這個(gè).h文件,這可以算是一種“合同”,我作為類的使用者,我使用你的頭文件,我就遵守這個(gè)合同,然后我使用合同里面規(guī)定的東西。
此時(shí),頭文件是這個(gè)接口,于是可以進(jìn)行使用。
多說(shuō)一些——頭文件里允許出現(xiàn)的東西
多說(shuō)一些吧,可能有一些比較底層的東西了。
編譯器在進(jìn)行編譯的時(shí)候每次只針對(duì)一個(gè).cpp文件獨(dú)立地進(jìn)行編譯,此時(shí)每個(gè)文件我們叫做“編譯單元”。此時(shí)里面出現(xiàn)多個(gè).cpp文件之間的一些沖突問(wèn)題都不會(huì)檢查出來(lái),而是在最后鏈接在一起的時(shí)候會(huì)有叫做ld的程序來(lái)檢查。所以,在.h文件中,不要出現(xiàn)不正確的聲明!否則,多個(gè).cpp文件共用一個(gè).h的時(shí)候,會(huì)出現(xiàn)問(wèn)題。
所以,在.h文件中只允許出現(xiàn)以下聲明:
- extern的變量,比如extern int i;
- 函數(shù)的原型
- 類和結(jié)構(gòu)體的聲明
#include的作用,就是文本的替換。把頭文件的全文放到需要被插入的地方。
一個(gè)歷史小故事,為什么#include <iostream>沒(méi)有.h?其實(shí)在早期的版本中有<iostream.h>這個(gè)頭文件,在后來(lái)C++進(jìn)行改版的時(shí)候保留了之前的.h文件,那么新的文件名字怎么解決呢?于是就使用了新的作為替換。注意,文件名是一樣的,就是.h沒(méi)有了,但是這不妨礙它成為我們的頭文件。iostream.h和iostream有一些區(qū)別,比如用了<iostream.h>后不用再加using namespace std;,其中的輸入輸出也會(huì)有些不同。其實(shí),文件后綴并不是那么重要,這個(gè)和UNIX的歷史有一些小故事。
標(biāo)準(zhǔn)頭文件結(jié)構(gòu)
所以,我們應(yīng)當(dāng)讓頭文件盡可能地標(biāo)準(zhǔn)化。我們給出了標(biāo)準(zhǔn)頭文件結(jié)構(gòu):
#ifndef HEADER_FLAG #define HEADER_FLAG//declaration here#endif怎么理解?ifndef就是if undefined,如果沒(méi)有被定義過(guò),那么我們就定義這個(gè)HEADER_FLAG宏。有什么用呢?比如里面有一個(gè)類的聲明,這個(gè)頭文件被兩個(gè).cpp都include了,如果沒(méi)有這三句,會(huì)造成這個(gè)類的重復(fù)聲明,這是不允許的。但是有了這個(gè)就不一樣了:第一次用到.h,還沒(méi)有HEADER_FLAG,所以我們定義這個(gè)宏,并把里面的內(nèi)容復(fù)制過(guò)來(lái);第二次再次用到的時(shí)候.h,已經(jīng)有了先前的HEADER_FLAG,所以不會(huì)再去把中間的內(nèi)容復(fù)制,避免了多次聲明。
因此,我們?cè)趯?xiě).h的時(shí)候總是要遵循著標(biāo)準(zhǔn)頭文件結(jié)構(gòu)。
試著一起設(shè)計(jì)一個(gè)類
我們想做一個(gè)時(shí)鐘類,有小時(shí)和分鐘。當(dāng)然這在C語(yǔ)言里面很好實(shí)現(xiàn),因?yàn)閮蓚€(gè)for循環(huán)就可以了嘛。不過(guò)在OOP中,我們嘗試去看里面有什么“東西”,把它劃分。
抽象
這是一個(gè)思想:有意地去忽略一些細(xì)節(jié),只在乎對(duì)我這個(gè)問(wèn)題有用的。
比如看到了一位同學(xué),我們會(huì)說(shuō)他是誰(shuí),多高,今天精神面貌怎么樣,但是一般不會(huì)去想他的心臟現(xiàn)在是舒張還是收縮吧?因?yàn)檫@不是我們要研究的問(wèn)題。
分析問(wèn)題
我們可以想見(jiàn),一個(gè)時(shí)鐘要怎樣去劃分里面的“東西”。首先,有兩個(gè)顯示時(shí)間的地方;其次,每個(gè)地方(小時(shí)、分鐘)可以+1,或者到頭了返回0并且告訴別人自己返回了。
雖然小時(shí)不會(huì)返回給日期(因?yàn)槲覀冞@個(gè)程序沒(méi)有要求),但是我們應(yīng)當(dāng)留下這個(gè)接口,以便以后去進(jìn)行再次探索開(kāi)發(fā)。在工作量不太大的情況下,我們應(yīng)當(dāng)從長(zhǎng)遠(yuǎn)去考慮,給未來(lái)留下一些接口。
因此,我們可以把類設(shè)計(jì)成這樣:
成員變量
下面我們來(lái)談?wù)劤蓡T變量。
我們首先回顧一下本地變量。在C語(yǔ)言中可是學(xué)過(guò)的喲~記不記得那個(gè)交換兩個(gè)數(shù)的程序,如果不用指針或引用值是交換不了的。這就是因?yàn)楸镜刈兞吭诤瘮?shù)執(zhí)行結(jié)束后立刻被“干掉”了。這就是我們所說(shuō)的“生存期”和“作用域”的問(wèn)題。在函數(shù)里面的聲明出來(lái)的變量都是本地變量。
還有個(gè)小細(xì)節(jié)。如果全局變量和函數(shù)里面的變量恰巧同名,那么會(huì)最終使用誰(shuí)呢?在C語(yǔ)言中也有所涉及,“就近”選擇本地變量;而全局變量就被屏蔽掉了。對(duì)于類里面的變量,亦是如此。
C++中有三類變量
在C++中,比C語(yǔ)言多了類,因此變量也多了這樣的類型。
- 成員變量 Field
- 參數(shù) Parameter
- 本地變量 Local Variable
一句話說(shuō)清后兩者的關(guān)系,那就是參數(shù)和本地變量一模一樣,相同的性質(zhì),相同的生存期,相同的作用域——都離不開(kāi)函數(shù)。(注:當(dāng)然是現(xiàn)階段的理解,再深入到內(nèi)存中會(huì)有一些不同)
成員變量在類里面,所以,類活多久,這個(gè)變量就活多久。就像是身體的一個(gè)器官,你活多久,它與你一樣。
可是,成員變量到底在哪里存在呢?上文提到,類是抽象出來(lái)的概念,抽象的東西并不存在于客觀世界中??墒俏覀冞€在說(shuō)“成員變量”的生存期和作用域,說(shuō)明它是可以存在的。其實(shí)這正好比“蘋(píng)果的果肉”和“我手里這個(gè)蘋(píng)果的果肉”之間的區(qū)別,前者是個(gè)概念,可一旦給你一個(gè)符合這個(gè)概念的東西,我們就知道它在哪兒存在了。
成員變量的歸宿是對(duì)象
來(lái)看這樣一個(gè)類吧,很簡(jiǎn)單的一個(gè)類:
class A{ private:int i; public:void f(); };void A::f(){int j=10;i=10; }int main(){A a;a.f();return 0; }寫(xiě)完class A以后,我們?cè)诶锩媛暶髁薸nt i,但是它還并不存在。它在什么時(shí)候開(kāi)始存在的呢?是main函數(shù)里A a;這句。A是蘋(píng)果這個(gè)東西,int i是里面的果肉,那么A a;就相當(dāng)于我買(mǎi)了一個(gè)蘋(píng)果,它的果肉就是a.i(a里面的i)了。當(dāng)然,由于i是Private的,我們不能直接這么用。所以,我們得到了答案,成員變量存在于哪里?我們的對(duì)象里。
沒(méi)錯(cuò)的,編譯器就是這樣做事情:你告訴他什么他都會(huì)相信。比如類里面的int i;我們只是告訴了編譯器一定會(huì)存在這樣的一個(gè)i,然后它就相信了,于是進(jìn)行了編譯。至于i在什么位置,編譯器并不關(guān)心,因?yàn)樵蹅円呀?jīng)告訴他“一定會(huì)存在變量i”。但是如果你在后面不給出i的歸宿(對(duì)象)就直接用,鏈接器是會(huì)報(bào)錯(cuò)的,盡管二進(jìn)制代碼已經(jīng)出來(lái)了。
函數(shù)的歸宿是類
為了說(shuō)明一些東西,咱們先讓它們都是Public好了。
class A{ public:int i;void f(); };void A::f(){i=20;cout<<i<<endl; }int main(){A a;A b;cout<<a.i<<endl;a.f();b.f();return 0; }假定吃蘋(píng)果就是函數(shù)f()。
如果每次咱們建立新的蘋(píng)果,吃蘋(píng)果的機(jī)制要每次復(fù)制一次,確實(shí)很麻煩了。畢竟函數(shù)(吃蘋(píng)果)本身就是相同的方法,方法本身就是抽象的,比如洗蘋(píng)果,咬一口,再咬一口(循環(huán)),直到吃干凈?;旧铣运械奶O(píng)果都是這個(gè)流程(不要用削皮或者別的方法來(lái)懟我orz),所以咱們所有吃蘋(píng)果的人共用這一套方法(函數(shù))好了。
所以,大家會(huì)吃蘋(píng)果了吧,更明白類里面的函數(shù)歸宿就是類本身了吧~
深入一些——函數(shù)共用,不會(huì)用混嗎(選讀)
剛剛那段代碼,里面有a.f()和b.f(),可是它怎么知道誰(shuí)是誰(shuí),這個(gè)i到底是a的還是b的?
在早期C++沒(méi)有編譯器,只有翻譯器,翻譯成C語(yǔ)言的源代碼,因此C++的全部功能可以通過(guò)C語(yǔ)言來(lái)實(shí)現(xiàn)。我們能不能想想,如果自己是C++的設(shè)計(jì)者,我們會(huì)怎么做,讓函數(shù)直到這個(gè)i是a.i還是b.i?
可以想象,在結(jié)構(gòu)體的一些函數(shù)中(咱們肯定都用過(guò),C語(yǔ)言大作業(yè)),我們可以把結(jié)構(gòu)體的地址傳進(jìn)去,對(duì)結(jié)構(gòu)體進(jìn)行操作。所以一個(gè)思路是把對(duì)象的地址傳進(jìn)去。我們可以驗(yàn)證一下:
把上文代碼的相關(guān)部分改成下面這樣:
void A::f(){printf("A::f()--&i=%p",&i);printf("this=%p",&i); //這句話可以在看完下一部分的時(shí)候加上測(cè)試一下 }int main(){A a;A b;printf("&a=%p",&a);a.f();printf("&b=%p",&b);b.f();return 0; }有興趣的可以在自己電腦上試一試,你會(huì)發(fā)現(xiàn)得到的結(jié)果是前兩個(gè)值相同,后兩個(gè)值相同,證明了C++的成員函數(shù)有能力實(shí)現(xiàn)這個(gè)事情。所以,到底是什么樣的方式呢?下面就是我們的結(jié)論:
this指針
所有的成員函數(shù),都有一個(gè)藏起來(lái)了的參數(shù),它就是我們選讀部分所說(shuō)的那個(gè)指針。
所有的成員函數(shù),
void A::f();都可以被看作
void A::f(A *p);所以,上面那個(gè)函數(shù)和下面這樣是一樣的:
void A::f(){this->i=20;cout<<this->i<<endl; }也即,this->i就是i。
我們有時(shí)候會(huì)直接用到this,當(dāng)然this是關(guān)鍵字,咱們不能去定義。
構(gòu)造和析構(gòu)
關(guān)于“燙燙燙”——電腦太熱了?
在使用Visual Studio的Debug模式下,沒(méi)有初始化的對(duì)象,他會(huì)幫助你調(diào)試:編譯器會(huì)默認(rèn)給變量賦初始值0xcd。兩個(gè)0xcd連在一起就是漢字國(guó)標(biāo)碼的“燙”。所以以后寫(xiě)程序看到輸出了“燙”,不是電腦溫度太高了,是變量沒(méi)有初始化!
C++不會(huì)默認(rèn)進(jìn)行初始化
在其他的一些OOP語(yǔ)言中,比如Java,你建立了一個(gè)變量,它會(huì)默認(rèn)給賦予一個(gè)初值;但是在C++中,他不會(huì)這樣去做。因?yàn)樗粗氐氖切?。我在?nèi)存里面給你找到了能夠放下你需要的東西的一間“屋子”,你就應(yīng)當(dāng)去進(jìn)行這間屋子的打掃工作。所以,我們應(yīng)當(dāng)自己去想辦法去打掃這個(gè)屋子。
嚇得我把上文的Point類趕緊加了個(gè)Init(初始化)函數(shù):
class Point{ private:int x;int y;int z; public:void printPoint();void makePoint(int a,int b,int c);void doublePoint();void Init(int a,int b,int c); };這樣,每次我建立新的對(duì)象,同時(shí)調(diào)用一次Init函數(shù),就解決了這個(gè)問(wèn)題。
可是,建立對(duì)象的那個(gè)程序猿,真的能每次記得都主動(dòng)調(diào)用Init()函數(shù)嗎?如果忘了,可能就會(huì)有問(wèn)題。
所以,我們需要這樣的機(jī)制,每次建立新的對(duì)象,自動(dòng)就進(jìn)行初始化。
構(gòu)造函數(shù)(Constructor)
那個(gè)能夠進(jìn)行初始化并且每次都會(huì)自動(dòng)被調(diào)用的函數(shù)就是“構(gòu)造函數(shù)”。
它長(zhǎng)這個(gè)樣子:
class X{int i; public:X(); }里面這個(gè)和X類同名而且沒(méi)有任何返回類型的函數(shù)就是構(gòu)造函數(shù)。它沒(méi)有返回類型,和類同名。
只要做了對(duì)象,它立刻會(huì)被調(diào)用。
構(gòu)造函數(shù)可以有參數(shù)
構(gòu)造函數(shù)也是成員函數(shù),所以成員函數(shù)的很多性質(zhì)它也有。比如前文的this指針,或者是帶上參數(shù)。還是剛剛那個(gè),不過(guò)把Init()改成了構(gòu)造函數(shù):
class Point{ private:int x;int y;int z; public:void printPoint();void makePoint(int a,int b,int c);void doublePoint();Point(int x,int y,int z);//constructor };析構(gòu)函數(shù)(Destructor)
對(duì)象可以被創(chuàng)建。當(dāng)然,它也有需要被消滅的時(shí)候。類似于構(gòu)造函數(shù),我們可以使用析構(gòu)函數(shù)把對(duì)象給刪掉。比如,一個(gè)不想吃的蘋(píng)果,用析構(gòu)直接扔掉。
析構(gòu)函數(shù)和構(gòu)造函數(shù)一樣,和類同名,沒(méi)有返回值類型。區(qū)別在于析構(gòu)函數(shù)名字前面加一個(gè)波浪號(hào)(~,tilde)。
析構(gòu)是毀滅者,不需要其他的參數(shù),因?yàn)樗蛔鲆患隆獎(jiǎng)h掉所有的東西,然后讓這個(gè)對(duì)象不復(fù)存在。中間是不需要引入其他參數(shù)來(lái)干預(yù)的。
什么時(shí)候去調(diào)用呢?當(dāng)這個(gè)對(duì)象走到了自己生命的尾聲,比如main函數(shù)的結(jié)束,所在的{}之間的結(jié)束……
使用域解析符調(diào)用構(gòu)造和析構(gòu)是這樣的:
Point::Point(int a,int b,int c){//... }Point::~Point(){//... }缺省構(gòu)造函數(shù)(default constructor)
看到這個(gè)名詞,先顧名思義一下。default,有默認(rèn)的意思,那么它什么意思?
我沒(méi)寫(xiě)構(gòu)造函數(shù),系統(tǒng)給我分配了一個(gè)?
不完全是。我們寫(xiě)的構(gòu)造函數(shù)是可以帶參數(shù)的,比如我們可以去定義一個(gè)Point類,還是用的之前的那個(gè)例子:
Point p(1,2,3); Point p1;對(duì)比一下兩者,有什么區(qū)別?好,一個(gè)有參數(shù),一個(gè)沒(méi)有參數(shù)。有參數(shù)那個(gè)就知道了,我是要x=1,y=2,z=3。但是第二個(gè)沒(méi)有參數(shù)呢?就應(yīng)該去尋找那個(gè)沒(méi)有參數(shù)的構(gòu)造函數(shù)了。如果此時(shí)沒(méi)有,系統(tǒng)會(huì)給分配,叫做auto default constructor。(這個(gè)可以不用記憶)。
如果我要是寫(xiě)了一個(gè)不帶參數(shù)的構(gòu)造函數(shù),是不是就實(shí)現(xiàn)了每次都可以給Point p1這樣的東西自動(dòng)賦一個(gè)默認(rèn)值?
比如我這么寫(xiě):
Point::Point(){x=0;y=0;z=0; }那么每次忘了加參數(shù)的,它的值默認(rèn)就可以得到(0,0,0),解決了之前沒(méi)有構(gòu)造函數(shù)的“燙燙燙”的問(wèn)題。
new和delete
這是一個(gè)不得不提的地方。在每次C++實(shí)驗(yàn)課中使用new和delete的題目可能AC Rate都會(huì)低一些。所以,為什么動(dòng)態(tài)分配內(nèi)存就會(huì)對(duì)大家造成一定的傷害呢?
兩個(gè)運(yùn)算符
我們可以使用new和delete進(jìn)行動(dòng)態(tài)的分配對(duì)象:我需要建立一個(gè)新的對(duì)象,new一個(gè);如果要收回,delete一個(gè)。(要是對(duì)象真的new一個(gè)就有了該多好……這樣我就有小哥哥了(╯‵□′)╯︵┻━┻)
new int; new Point; new int[10];delete p; delete[] p;每次new一個(gè)對(duì)象之后,根據(jù)上文,構(gòu)造函數(shù)是一定會(huì)被調(diào)用的。作為運(yùn)算符,它會(huì)有結(jié)果。結(jié)果是什么呢?新建的東西的地址。
再來(lái)看delete,有兩個(gè)樣子。咱們大概可以看出來(lái)用new []新建的咱們就用delete[]來(lái)回收。
當(dāng)然大家可以想見(jiàn),使用delete,標(biāo)志著一個(gè)對(duì)象的終結(jié)。而一個(gè)對(duì)象在結(jié)束的時(shí)候,通常會(huì)去調(diào)用析構(gòu)函數(shù)。
一個(gè)細(xì)節(jié)值得注意一下??聪旅娴男±踝?#xff1a;
Point * i = new Point [10];delete[] i;Point類還是咱們上文的那個(gè)。我們建立了一個(gè)10個(gè)元素的數(shù)組,然后給它回收。此時(shí)應(yīng)當(dāng)是這樣的:先分配10個(gè)Point的空間,然后對(duì)于每一個(gè)執(zhí)行構(gòu)造函數(shù)。執(zhí)行10次構(gòu)造以后到了delete,先執(zhí)行這10個(gè)的析構(gòu)函數(shù),然后回收空間。
如果我用的是delete i;會(huì)有什么不同?只有第一個(gè)的析構(gòu)被調(diào)用,然后10個(gè)的空間被回收。
因此,我們注意這樣一個(gè)事情就好了,用new []新建的咱們就用delete[]來(lái)回收。
怎么知道delete[]會(huì)刪除多少個(gè)(選讀)
在內(nèi)存里面其實(shí)有一個(gè)我們看不見(jiàn)的東西,在調(diào)用new之后一些東西會(huì)被存放到一個(gè)表中。這個(gè)表記錄了所分配空間的大小和地址。因此,假設(shè)我new了一個(gè)int,比如int*p=new int;這個(gè)表中會(huì)記錄[4,p]。4是int的大小,4個(gè)字節(jié);p是它的地址。
所以我們執(zhí)行Point* p=new Point[10];之后,表中會(huì)存儲(chǔ)[120,p]。(我們假定一個(gè)Point大小是12字節(jié))所以在進(jìn)行delete之后,根據(jù)120/12=10,就知道了我們需要執(zhí)行10次析構(gòu)函數(shù)。
new和delete的一些注意問(wèn)題
- delete去刪除new出來(lái)的東西,不要?jiǎng)h除malloc之類分配的空間。
- 不要delete一個(gè)地方兩次
- new[]配delete[]
- new配delete
- 刪除一個(gè)空指針,是安全的
- new完了一定要去delete:申請(qǐng)的空間記得去釋放——否則可能會(huì)一直占用著空間,越用越多,直至程序崩潰。
訪問(wèn)限制
在前文,我們已經(jīng)建立了OOP的一些思想。這時(shí)候,我打算再把這個(gè)圖放出來(lái):
數(shù)據(jù)要被保護(hù)起來(lái),別人能夠操控的只有外部的一些接口。所以,我們?cè)趺慈?shí)現(xiàn)這個(gè)東西呢?
想必在前面的代碼中,我沒(méi)有說(shuō),但是大家應(yīng)該很好理解:pirvate什么意思,public什么意思,就搞定啦。
訪問(wèn)控制的類型
- public
- private
- protected
public——共有,誰(shuí)都可以訪問(wèn)
private——私有,只有自己可以訪問(wèn)
“自己”是誰(shuí)——這個(gè)類的成員函數(shù)。
private是對(duì)類來(lái)說(shuō)的。在A的a和b兩個(gè)對(duì)象中,我可以在a中去訪問(wèn)b的東西,只要傳參,把b傳給a的函數(shù),就可以~
“咱們是一家人,我錢(qián)包里的錢(qián)就是你的錢(qián);你錢(qián)包里的錢(qián)也是我的錢(qián)?!?/p>
protected——保護(hù),子子孫孫可以訪問(wèn)
Friends 友元函數(shù)——你是我的好朋友
好朋友,“我錢(qián)包里的錢(qián)就是你的錢(qián);你錢(qián)包里的錢(qián)不是我的錢(qián)?!?/p>
畢竟,我說(shuō)小葩同學(xué)是我的朋友,我的錢(qián)包里的錢(qián)他可以動(dòng)。這個(gè)還相對(duì)合理。但是如果我說(shuō)“我是小葩同學(xué)的朋友,所以我可以動(dòng)他的錢(qián)包”……我怎么這么壞呢?
我們?cè)囍鴮?xiě)一個(gè):
class A{private:int i;public:A();friend void doubleI(A*);friend void plusI(A*,int);friend class Z; }void doubleI(A*a){a->i=a->i*2; }void plusI(A*a,int b){a->i=a->i+b; }同時(shí),Z這個(gè)類的所有對(duì)象可以隨心所欲地訪問(wèn)A類中的東西。
class和struct的小區(qū)別(選讀)
class和struct都是可以聲明類的。它們的區(qū)別在于:如果不自己加上private,public,protected之類的東西,那么會(huì)有默認(rèn)的屬性。
class默認(rèn)里面是private,struct默認(rèn)里面是public。
在C++里,我們首選class。
Initializer List 初始化列表
我們對(duì)類初始化的時(shí)候,除了在函數(shù)里面賦值,還可以更為直接一些:
還是之前的Point類。
class Point{ private:int x;int y;int z; public:Point(int a,int b,int c);void printPoint();void makePoint(int a,int b,int c);void doublePoint(); };對(duì)于構(gòu)造函數(shù),咱們之前是這么寫(xiě)的:
Point::Point(int a,int b,int c){x=a;y=b;z=c; }現(xiàn)在也可以這樣去寫(xiě):
Point::Point(int a,int b,int c):x(a),y(b),z(c){}剛剛后面一個(gè)冒號(hào)加上x(chóng)(a),y(b),z?這樣的語(yǔ)句就是初始化列表。這和構(gòu)造函數(shù)有什么區(qū)別呢?雖然效果是一樣的,但是深入一些,初始化列表的執(zhí)行早于構(gòu)造函數(shù)。
它可以去初始化任何類型的變量。
為什么有時(shí)用初始化列表會(huì)更好(選讀)
對(duì)比一下:
Student::Student(string s){name=s; }和
Student::Student(string s):name(s){}前者是進(jìn)行了賦值運(yùn)算。在此之前,需要先調(diào)用一個(gè)默認(rèn)構(gòu)造函數(shù)。如果里面有一個(gè)對(duì)象是另一個(gè)類的對(duì)象,那么必須存在它的默認(rèn)構(gòu)造函數(shù),否則會(huì)出錯(cuò)。而后者,是直接進(jìn)行了初始化。
來(lái)探究一下必須存在默認(rèn)構(gòu)造函數(shù)的事情:
class B{ public:int i;B(int c){i=c;} }class A{ public:A(){b=0;cout<<"A::A()<<endl;} private:B b; }這樣會(huì)報(bào)錯(cuò),說(shuō)沒(méi)有找到B::B()。這就是因?yàn)檫M(jìn)行賦值運(yùn)算必須要先調(diào)用默認(rèn)構(gòu)造函數(shù)。
一個(gè)小的建議,以后初始化的操作咱們都寫(xiě)進(jìn)初始化列表。
寫(xiě)了帶有參數(shù)的構(gòu)造函數(shù)后一定要跟隨一個(gè)無(wú)參的
根據(jù)上面選讀部分的結(jié)論,大家一定要時(shí)刻謹(jǐn)記:
如果我寫(xiě)了帶參的構(gòu)造函數(shù),一定要跟一個(gè)無(wú)參構(gòu)造函數(shù)!
2 繼承和派生
代碼重用
大家了解過(guò)sort函數(shù)吧?就是C++提供的幫你完成排序的函數(shù),不管里面是int,還是其他數(shù)據(jù)類型,都使用同樣的代碼。這些代碼是大佬們很久以前就寫(xiě)好的,你不用自己想著怎么實(shí)現(xiàn)排序,你用這個(gè)函數(shù)就可以了。
這樣,任何時(shí)候,不同數(shù)據(jù)類型都可以用sort,而sort是相同的代碼,這就實(shí)現(xiàn)了反復(fù)利用代碼,也就是“代碼重用”。
OOP的三大特性是封裝、繼承、多態(tài)性。我們其實(shí)在上一章里面著重介紹了封裝:那個(gè)雞蛋一樣的圖片。而大家可以去顧名思義一下,繼承和多態(tài)有什么含義。繼承就是我用了你已經(jīng)有的東西,實(shí)現(xiàn)“重復(fù)利用”。多態(tài)就是我利用的方式多種多樣。
而它們其實(shí)都是對(duì)代碼重用這個(gè)問(wèn)題的回答:我們要通過(guò)多次反復(fù)利用已經(jīng)寫(xiě)好的東西來(lái)實(shí)現(xiàn)我要做的事情。
代碼重用是一個(gè)很久以來(lái)的夢(mèng)想:人們從計(jì)算機(jī)軟件誕生的那一天起,就一直夢(mèng)想著去尋找方式,能夠使用我以前的一些東西來(lái)往下開(kāi)發(fā)。
誕生出OOP以后,人們找到了繼承這樣的方式。但是值得一提的是,這只是一個(gè)解決辦法,不是唯一的方法,也不一定是最好的辦法。
對(duì)象組合 Composition
組合就是像建造汽車(chē),汽車(chē)?yán)镉幸?#xff0c;有輪胎。
實(shí)現(xiàn)的方式
有兩種方法可以實(shí)現(xiàn):直接裝進(jìn)來(lái)、引用。前者就是讓對(duì)象作為成員變量,后者就是用指針。
什么時(shí)候用哪個(gè)?看情況。比如一個(gè)人,他的心臟就要直接裝進(jìn)身體內(nèi)部;而他的書(shū)包就要用指針:我可以找到我的書(shū)包并且操控他,但他不是人身體的一部分。
對(duì)象依舊邊界清晰
比如我們把兩個(gè)對(duì)象放進(jìn)了一個(gè)大的對(duì)象里面去(當(dāng)然都是先通過(guò)類實(shí)現(xiàn)的)。
class Person{//個(gè)人信息的類string name;string address; };class Currency{//錢(qián)數(shù)的類int money; };class Account{//組合成為賬戶后的類 private://對(duì)象組合Person per;Currency cur; public:Account(string a,string b,int c);//構(gòu)造~Account();//析構(gòu)void print();//輸出內(nèi)容 };//實(shí)現(xiàn)構(gòu)造函數(shù),使用Initializer List(初始化列表,上面說(shuō)過(guò)的) Account::Account(string a,string b,int c):per(a,b),cur(c){ }觀察一下這個(gè)構(gòu)造函數(shù)。我們?cè)诖蟮臉?gòu)造里面分別調(diào)用兩個(gè)小的構(gòu)造,可以說(shuō)明一些問(wèn)題吧?對(duì)象還是那個(gè)對(duì)象,構(gòu)造都需要構(gòu)造的。
使用初始化列表可以不提供默認(rèn)構(gòu)造函數(shù)。
private or public
我們可以把對(duì)象設(shè)置為public從而可以讓內(nèi)部函數(shù)得到訪問(wèn),但是這不是OOP所喜歡的:因?yàn)檫@讓雞蛋模型里面的數(shù)據(jù)對(duì)外公開(kāi)了,就相當(dāng)于你把心臟放到體外,讓別人進(jìn)行隨意操作。
所以我們應(yīng)當(dāng)把對(duì)象設(shè)置為private。
鋪墊了這一節(jié),想要說(shuō)明代碼重用不只是繼承,也可以對(duì)象組合。那么繼承是怎么用的,歡迎看下一節(jié)。
繼承 Inheritance
繼承:對(duì)已有類的改造
繼承就是把一個(gè)已經(jīng)存在的類拿過(guò)來(lái),我們做一些改造,加一些新的東西,從而就變成了新的類。因此,我們可以在新的類中使用先前存在的類的相關(guān)東西:數(shù)據(jù)成員、成員函數(shù),當(dāng)然包括public的函數(shù)和變量,也就是接口。這樣,我們就可以使用以前的類的功能進(jìn)行新的類的設(shè)計(jì)。
這是C++的一項(xiàng)核心技術(shù)。
繼承就是我們使用一個(gè)類來(lái)定義全新的類的一種辦法。
Student繼承了Person,Person顯然是等級(jí)更高的;而Student的內(nèi)容是更豐富的。顯然,人們都具有男女的區(qū)分、身高、體重等特征,學(xué)生也有;而學(xué)生有年級(jí)、就讀學(xué)校、專業(yè)等信息,這些是未必所有人都具有的。因此,學(xué)生類是人這個(gè)類的超集。
人是基類、超類、父類,學(xué)生是派生類、副類(一般不這么說(shuō),要不讀音就重了,是不是)、子類。
class A { public:A():i(0){};~A(){};void print(){cout<<i<<endl;} protected:void set(int x){ i=x; }; private:int i; };class B:public A { public:B(){};~B(){};void f(int x){set(x);} };int main(){B b;b.print();//b.set(20);//這句話就是錯(cuò)誤的b.f(20);b.print();return 0; }看上面的例子,里面B類就是繼承的語(yǔ)法。分析一下這段代碼,為什么b.set(20);這句話是錯(cuò)誤的?因?yàn)檫@是protected的,只有B內(nèi)部才能夠使用。
所以,子類能使用父類的public和protected,其他東西只能用父類的public。
子類和父類關(guān)系(選讀)
當(dāng)然是先有父親再有孩子,因此我們知道在進(jìn)行構(gòu)造前,先去執(zhí)行父類的構(gòu)造函數(shù),然后再來(lái)構(gòu)造自己。析構(gòu)的時(shí)候先析構(gòu)自己,再去析構(gòu)父類。
還可以提一句“名字隱藏”。如果父類有函數(shù)重載,恰巧子類中有同名函數(shù),那么父類中的所有同名的函數(shù)會(huì)全部被隱藏掉,因此只剩下子類中的這些函數(shù)。
在其他OOP語(yǔ)言里都不是這樣子的,它們會(huì)存在overide,但是C++中還規(guī)定了子類和父類中的同名函數(shù)無(wú)關(guān),所以必須隱藏才能保證函數(shù)不會(huì)出現(xiàn)錯(cuò)亂。
函數(shù)重載 Function Overload 默認(rèn)參數(shù) Defualt Argument
函數(shù)重載就是說(shuō)如果兩個(gè)函數(shù)的參數(shù)表不同,那么就可以去定義這兩個(gè)同名的函數(shù)。
為什么可以出現(xiàn)函數(shù)重載呢?我們回顧一下,之前說(shuō)過(guò)所有C++最終都可以翻譯成C語(yǔ)言的程序,但C不支持函數(shù)重載。如果你是設(shè)計(jì)者,你會(huì)怎么實(shí)現(xiàn)呢?
一個(gè)可行的方法就是將函數(shù)名字換掉,換掉函數(shù)名加參數(shù)類型名,這樣就可以的得到若干完全不同的函數(shù),也就解決了C語(yǔ)言不支持函數(shù)重載的問(wèn)題。
默認(rèn)參數(shù)是可以在函數(shù)定義的時(shí)候就給出它預(yù)先的值。當(dāng)然,默認(rèn)參數(shù)一定要從最右邊從左邊寫(xiě)過(guò)來(lái),否則語(yǔ)法不正確。
void set(int i,int initSize=0);在這樣的函數(shù)里如果我們用set(5),那么initSize會(huì)自動(dòng)變成0;而如果使用set(10,10),那么initSize會(huì)變成10。
void set(int i=0,int j,int k=3);//錯(cuò)誤這樣寫(xiě)是錯(cuò)誤的:必須從右往左進(jìn)行,不然,這就容易亂了套了。
一定要警覺(jué)注意的是,這個(gè)只能寫(xiě)在函數(shù)聲明里面,定義里面絕對(duì)不能出現(xiàn)!比如,下面這樣寫(xiě)就是錯(cuò)誤的:
void set(int i,int j=0){//... }一定要切記默認(rèn)參數(shù)出現(xiàn)在什么樣的位置。
再深入一點(diǎn),defalut argument是在編譯時(shí)刻的事情,只不過(guò)是編譯器看到了原型聲明他中的default argument,記住了這個(gè)函數(shù)可以有這樣的default的值,因此它就這樣去記住了,但是函數(shù)的參數(shù)還是那些參數(shù)。
雖說(shuō)考試會(huì)有這類的考察,但是在軟件工程中,我們一般不會(huì)去這樣用。因?yàn)檫@不但可能造成閱讀上對(duì)于參數(shù)個(gè)數(shù)的誤解,還有可能使得值被篡改,不符合設(shè)計(jì)者的意圖,這樣就是不安全的了。
內(nèi)聯(lián)函數(shù) Inline Function
函數(shù)調(diào)用的額外開(kāi)銷 Overhead(選讀)
每個(gè)程序都會(huì)有一個(gè)自己獨(dú)立的堆棧,其中包含本地變量和返回地址。當(dāng)調(diào)用函數(shù)的時(shí)候,函數(shù)的返回地址是會(huì)放到堆棧里面去的。函數(shù)的參數(shù)和本地變量是一樣的,它們都會(huì)放到堆棧里面去。當(dāng)調(diào)用函數(shù)的時(shí)候,我們會(huì)做兩件事情,把返回地址和臨時(shí)變量入棧,寄存器棧頂指針上移,同時(shí)跳轉(zhuǎn)到所需的地址,將這個(gè)地址入棧。經(jīng)過(guò)計(jì)算以后,pop掉所有的臨時(shí)變量,再返回原來(lái)的地址,pop掉這個(gè)地址,把寄存器ax的結(jié)果傳遞給接收函數(shù)值的變量。
上面就是簡(jiǎn)單調(diào)用一個(gè)函數(shù)的過(guò)程,甚至是最簡(jiǎn)單的不需要任何參數(shù)的函數(shù)中也會(huì)經(jīng)歷如此復(fù)雜的過(guò)程。
我們?cè)谡{(diào)用一個(gè)函數(shù)的時(shí)候,我們可能會(huì)產(chǎn)生如下額外的開(kāi)銷:將參數(shù)入棧、將返回地址入棧、準(zhǔn)備返回值、將所有不再需要的東西出棧。
因此,我們可以使用一種開(kāi)銷比較小的方式來(lái)完成這些比較簡(jiǎn)單的事情。
內(nèi)聯(lián)函數(shù) Inline Functions
如果我使用了內(nèi)聯(lián)函數(shù),我就不會(huì)真正去做上面選讀部分說(shuō)的一大堆事情,而是直接把函數(shù)的代碼嵌入在了調(diào)用的代碼塊,但仍然保持函數(shù)的獨(dú)立性。
(圖源自翁愷老師的慕課)
如果使用內(nèi)聯(lián),那么最終代碼里面是不會(huì)出現(xiàn)這樣的一個(gè)函數(shù)的。
如何使用內(nèi)聯(lián)函數(shù)呢?我們一定要在聲明和定義的時(shí)候都要寫(xiě)上inline的關(guān)鍵字。
inline int f(int i);inline int f(int i){return 2*i; }如果想要把內(nèi)聯(lián)函數(shù)放進(jìn)去,直接放進(jìn).h文件就可以了,而不需要經(jīng)過(guò).cpp文件。其實(shí)inline的definition就是它的declaration。
使用內(nèi)聯(lián)函數(shù)是一種“以空間換時(shí)間”的策略:這樣確實(shí)會(huì)加長(zhǎng)代碼的長(zhǎng)度,但是也能夠減少程序的一些開(kāi)銷。
雖說(shuō)C語(yǔ)言中使用宏也實(shí)現(xiàn)了相關(guān)的功能,但是#define是不能夠進(jìn)行類型檢查的。
當(dāng)然,如果有了遞歸或者非常大的函數(shù),編譯器可能就會(huì)拒絕。其實(shí)成員函數(shù)在類內(nèi)寫(xiě)的時(shí)候都是inline的。
咱們?cè)陬惱锩鎸?xiě)inline的時(shí)候,為了保持類的清爽,可以這樣寫(xiě):
class A{ private:int x,y,z; public:A(int a,int b,int c);~A(); };inline A::A(int a,int b,int c):x(a),y(b),z(c){}這種書(shū)寫(xiě)方式可以保證類的聲明是比較干凈的。
建議把比較小的函數(shù)或者頻繁調(diào)用(循環(huán)內(nèi)部)的函數(shù)設(shè)置成Inline的。
Const
一直會(huì)見(jiàn)到const這樣的東西。這小家伙,真別致,不是嗎?
const int本質(zhì)還是變量(選讀)
我說(shuō)const int i = 1;,就真的說(shuō)i是一個(gè)常量了嗎?看上去是這樣的,但本質(zhì)上它還是變量,只不過(guò)編譯器采取了一些措施,讓我們不能夠直接去修改這樣的值。
const帶來(lái)的麻煩——指針還是變量?(選讀)
char * const q = "abc";const char * p = "abc";這就很麻煩了:上面那個(gè)說(shuō)q是常量,也就是地址不能變。也就是q只能指向固定的一塊區(qū)域。但是,
*q='A';是完全正確的,但就不可以q++。而對(duì)于下面那個(gè),在說(shuō)目前p所指向的那個(gè)字符不能通過(guò)*p改變。因此,
*p='B';就是不正確的??墒莗所指向的區(qū)域可以變化。不是說(shuō)p指到哪里去,哪里就是const,而是你只能通過(guò)*p去訪問(wèn),不能通過(guò)*p去修改。
下面,對(duì)象是const還是指針是const?
const A * a = &a1; A const * a = &a1; A * const a = &a1;我們區(qū)別的標(biāo)記是位于*前面還是后面。前兩個(gè),對(duì)象是const,后面那個(gè),指針是const。
不要試圖對(duì)const int i中的i取地址并且傳遞給int *p,這樣是非法的。
const和字符串(選讀)
我們來(lái)看這樣一小段代碼:
int main(){char *s = "Hello world!";cout<<s<<endl;s[0]='B';cout<<s<<endl;return 0; }首先給了一個(gè)warning,之后正常輸出Hello world!,之后程序異常退出。
5:12: warning: deprecated conversion from string constant to ‘char*’
[-Wwrite-strings]
char *s = “Hello world!”;
Hello world!
[Finished in 7.3s with exit code 3221225477]
我們來(lái)分析一下上面這些結(jié)果是什么意思:
下節(jié)預(yù)告
const
附錄 更新日志
- 2019.5.26 完成了MOOC1-4節(jié)的整理。
- 2019.6.1 對(duì)前一部分進(jìn)行修訂,完成了MOOC第五節(jié)的整理。
- 2019.6.2 完成了MOOC6-8節(jié)的整理。
- 2019.6.6 完成了MOOC9-10節(jié)的整理。高考加油!
- 2019.6.8 完成了MOOC11-13節(jié)的整理。高考加油!第一部分至此完成。
總結(jié)
以上是生活随笔為你收集整理的【期末复习】转眼到了C++的复习时间(更新中)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: mysql string长度限制_Str
- 下一篇: 微信小程序与MCU基于阿里云MQTT协议