一級a做片性視頻.網(wǎng)站在線觀看阿里巴巴數(shù)據(jù)分析官網(wǎng)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 創(chuàng)作不易,感謝三連 !!
一、什么是C++
? ? ? ? ?C語言是結(jié)構(gòu)化和模塊化的語言,適合處理較小規(guī)模的程序。對于復(fù)雜的問題,規(guī)模較大的程序,需要高度的抽象和建模時,C語言則不合適。為了解決軟件危機(jī), 20世紀(jì)80年代, 計算機(jī)界提出了OOP(object oriented programming:面向?qū)ο?思想,支持面向?qū)ο蟮某绦蛟O(shè)計語言應(yīng)運(yùn)而生。
? ? ? ? ?1982年,Bjarne Stroustrup博士在C語言的基礎(chǔ)上引入并擴(kuò)充了面向?qū)ο蟮母拍?#xff0c;發(fā)明了一種新的程序語言。為了表達(dá)該語言與C語言的淵源關(guān)系,命名為C++。因此:C++是基于C語言而產(chǎn)生的,它既可以進(jìn)行C語言的過程化程序設(shè)計,又可以進(jìn)行以抽象數(shù)據(jù)類型為特點(diǎn)的基于對象程序設(shè)計,還可以進(jìn)行面向?qū)ο蟮某绦蛟O(shè)計。
?
二、C++的領(lǐng)域
1. 操作系統(tǒng)以及大型系統(tǒng)軟件開發(fā)
所有操作系統(tǒng)幾乎都是C/C++寫的,許多大型軟件背后幾乎都是C++寫的,比如:Photoshop、Office、JVM(Java虛擬機(jī))等,究其原因還是性能高,可以直接操控硬件。
2. 服務(wù)器端開發(fā)
后臺開發(fā):主要側(cè)重于業(yè)務(wù)邏輯的處理,即對于前端請求后端給出對應(yīng)的響應(yīng),現(xiàn)在主流采java,但內(nèi)卷化比較嚴(yán)重,大廠可能會有C++后臺開發(fā),主要做一些基礎(chǔ)組件,中間件、緩存、分布式存儲等。服務(wù)器端開發(fā)比后臺開發(fā)跟廣泛,包含后臺開發(fā),一般對實時性要求比較高的,比如游戲服務(wù)器、流媒體服務(wù)器、網(wǎng)絡(luò)通訊等都采用C++開發(fā)的。
3. 游戲開發(fā)
PC平臺幾乎所有的游戲都是C++寫的,比如:魔獸世界、傳奇、CS、跑跑卡丁車等,市面上相當(dāng)多的游戲引擎都是基于C++開發(fā)的,比如:Cocos2d、虛幻4、DirectX等。三維游戲領(lǐng)域計算量非常龐大,底層的數(shù)學(xué)全都是矩陣變換,想要畫面精美、內(nèi)容豐富、游戲?qū)崟r性高,這些高難度需求無疑只能選C++語言。比較知名廠商:騰訊、網(wǎng)易、完美世界、巨人網(wǎng)絡(luò)等。
4. 嵌入式和物聯(lián)網(wǎng)領(lǐng)域
嵌入式:就是把具有計算能力的主控板嵌入到機(jī)器裝置或者電子裝置的內(nèi)部,能夠控制這些裝置。比如:智能手環(huán)、攝像頭、掃地機(jī)器人、智能音響等。
談到嵌入式開發(fā),大家最能想到的就是單片機(jī)開發(fā)(即在8位、16位或者32位單片機(jī)產(chǎn)品或者裸機(jī)上進(jìn)行的開發(fā)),嵌入式開發(fā)除了單片機(jī)開發(fā)以外,還包含在soc片上、系統(tǒng)層面、驅(qū)動層面以及應(yīng)用、中間件層面的開發(fā)。
常見的崗位有:嵌入式開發(fā)工程師、驅(qū)動開發(fā)工程師、系統(tǒng)開發(fā)工程師、Linux開發(fā)工程師、固件開發(fā)工程師等。
知名的一些廠商,比如:以華為、vivo、oppo、小米為代表的手機(jī)廠;以紫光展銳、樂鑫為代表的芯片廠;以大疆、??低?、大華、CVTE等具有自己終端業(yè)務(wù)廠商;以及海爾、海信、格力等傳統(tǒng)家電行業(yè)。
隨著5G的普及,物聯(lián)網(wǎng)(即萬物互聯(lián),)也成為了一種新興勢力,比如:阿里lot、騰訊lot、京東、百度、美團(tuán)等都有硬件相關(guān)的事業(yè)部。
5. 數(shù)字圖像處理
數(shù)字圖像處理中涉及到大量數(shù)學(xué)矩陣方面的運(yùn)算,對CPU算力要求比較高,主要的圖像處理算法庫和開源庫等都是C/C++寫的,比如:OpenCV、OpenGL等,大名鼎鼎的Photoshop就是C++寫的。
6. 人工智能
一提到人工智能,大家首先想到的就是python,認(rèn)為學(xué)習(xí)人工智能就要學(xué)習(xí)python,這個是誤區(qū),python中庫比較豐富,使用python可以快速搭建神經(jīng)網(wǎng)絡(luò)、填入?yún)?shù)導(dǎo)入數(shù)據(jù)就可以開始訓(xùn)練模型了。但人工智能背后深度學(xué)習(xí)算法等核心還是用C++寫的。
7. 分布式應(yīng)用
近年來移動互聯(lián)網(wǎng)的興起,各應(yīng)用數(shù)據(jù)量業(yè)務(wù)量不斷攀升;后端架構(gòu)要不斷提高性能和并發(fā)能力才能應(yīng)對大信息時代的來臨。在分布式領(lǐng)域,好些分布式框架、文件系統(tǒng)、中間組件等都是C++開發(fā)的。對分布式計算影響極大的Hadoop生態(tài)的幾個重量級組件:HDFS、zookeeper、HBase等,也都是基于Google用C++實現(xiàn)的GFS、Chubby、BigTable。包括分布式計算框架MapReduce也是Google先用C++實現(xiàn)了一套,之后才有開源的java版本。除了上述領(lǐng)域外,在:科學(xué)計算、瀏覽器、流媒體開發(fā)、網(wǎng)絡(luò)軟件等都是C++比較適合的場景,作為一名老牌語言的常青樹,C++一直霸占編程語言前5名,肯定有其存在的價值。
?
三、C++學(xué)習(xí)層次
以下引用自2010年8月號《程序員》刊登的拙文《C++強(qiáng)大背后》最后一段:
? ? ? ?C++缺點(diǎn)之一,是相對許多語言復(fù)雜,而且難學(xué)難精。許多人說學(xué)習(xí)C語言只需一本K&R《C程序設(shè)計語言》即可,但C++書籍卻是多不勝數(shù)。我是從C進(jìn)入C++,皆是靠閱讀自學(xué)。在此分享一點(diǎn)學(xué)習(xí)心得。個人認(rèn)為,學(xué)習(xí)C++可分為4個層次:
第一個層次,C++基礎(chǔ) (平平常常)
挑選一本入門書籍,如《C++ Primer》、《C++大學(xué)教程》或Stroustrup撰寫的經(jīng)典《C++程序設(shè)計語言》或他一年半前的新作《C++程序設(shè)計原理與實踐》,而一般C++課程也止于此,另外《C++ 標(biāo)準(zhǔn)程序庫》及《The C++ Standard Library Extensions》可供參考;
第二個層次,正確高效的使用C++ (駕輕就熟)
此層次開始必須自修,閱讀過《(More)Effective C++》、《(More)Exceptional C++》、《Effective STL》及《C++編程規(guī)范》等,才適宜踏入專業(yè)C++開發(fā)之路;
第三個層次,深入解讀C++ (出神入化)
關(guān)于全局問題可讀《深入探索C++對象模型》、《Imperfect C++》、《C++沉思錄》、《STL源碼剖析》,要挑戰(zhàn)智商,可看關(guān)于模版及模版元編程的書籍如《C++Templates》、《C++設(shè)計新思維》、《C++模版元編程》;
第四個層次,研究C++ (返璞歸真)
閱讀《C++語言的設(shè)計和演化》、《編程的本質(zhì)》(含STL設(shè)計背后的數(shù)學(xué)根基)、C++標(biāo)準(zhǔn)文件《ISO/IEC 14882:2003》、C++標(biāo)準(zhǔn)委員會的提案書和報告書、關(guān)于C++的學(xué)術(shù)文獻(xiàn)。由于我主要是應(yīng)用C++,大約只停留于第二、三個層次。然而,C++只是軟件開發(fā)的一環(huán)而已,單憑語言并不能應(yīng)付業(yè)務(wù)和工程上的問題。建議讀者不要強(qiáng)求幾年內(nèi)“徹底學(xué)會C++的知識”,到達(dá)第二層左右便從工作實戰(zhàn)中汲取經(jīng)驗,有興趣才慢慢繼續(xù)學(xué)習(xí)更高層次的知識。雖然學(xué)習(xí)C++有難度,但也是相當(dāng)有趣且有滿足感的。
四、C++關(guān)鍵字
C++總計63個關(guān)鍵字,C語言32個關(guān)鍵字
后面慢慢學(xué)吧,感覺一下子也學(xué)不會……?
五、命名空間
? ? ? ?在C/C++中,變量、函數(shù)和后面要學(xué)到的類都是大量存在的,這些變量、函數(shù)和類的名稱將都存在于全局作用域中,可能會導(dǎo)致很多沖突。使用命名空間的目的是對標(biāo)識符的名稱進(jìn)行本地化,以避免命名沖突或名字污染,namespace關(guān)鍵字的出現(xiàn)就是針對這種問題的
?
?5.1 命名空間的定義
? ? ? ?我們來觀察這個代碼,為什么會出現(xiàn)這樣的情況呢?因為stdlib.h的頭文件里包含了一個rand函數(shù),這時候我們創(chuàng)建一個rand的變量就會出現(xiàn)重定義。你可能會覺得,我定義的變量跟庫沖突了,那我妥協(xié)一下?lián)Q個名字不就行了???但是有些時候你不一定是和庫沖突,而是和其他人沖突
? ? ? ?首先設(shè)想一個場景,這是一個非常大的工程,被團(tuán)隊分割成了每個人各自去完成一個小任務(wù),假設(shè)你的一個變量設(shè)置成了max,而你的另一個同時也把一個變量設(shè)成了max,然后你們都用這個變量寫了幾萬行代碼了,項目合并在一起才發(fā)現(xiàn)你們的變量重定義了,這個時候誰都不愿意改,因為改的那個人得改上萬行代碼!!那怎么辦呢?總得有人要妥協(xié),說不定得干一架!!
? ? ? 我們的c++祖師爺本賈尼博士正是發(fā)現(xiàn)了c語言的這個缺陷,引入了命名空間這個概念,將命名空間包括的區(qū)域變成了一塊新的域,避免了不同程序員的變量或者是函數(shù)名沖突的問題!!
? ? ?定義命名空間,需要使用到namespace關(guān)鍵字,后面跟命名空間的名字,然后接一對{}即可,{}中即為命名空間的成員。
? ? ? ?回到剛剛那個場景,為了不出現(xiàn)這種問題,我們將自己所寫的代碼都放在了自己的命名空間里
1、命名空間中可以定義函數(shù)、變量、類型
?還有一種情況,如果我們兩個的命名空間名字是一樣的!!又無法區(qū)分開了,還有一種方法
2、命名空間其實還可以嵌套使用
5.2 命名空間的使用?
回到剛剛的場景,我們將我們兩個的max都放在了自己的命名空間,避免的沖突,那該如何去使用呢??
命名空間的使用有三種方式:
1、指定命名空間訪問
?作用域限制符的作用其實就是限定這個變量的查找范圍,比如:
上圖中,局部有個max,全局有個max,cyx和ss的命名空間也分別有個max。
(1)根據(jù)局部優(yōu)先的原則,會先在局部找,所以打印10,
(2)如果我想打印全局變量的max,就給他加上::作用域限定符,當(dāng)::前面是空的時候,默認(rèn)是優(yōu)先在全局找max
(3)如果我們想在命名空間里面找max,就在::前面加上相應(yīng)的命名空間,就可以了
2、使用using將命名空間中某個成員引入(部分展開)
? ? ?有些時候,我們?nèi)绻麑σ粋€命名空間里的變量使用頻繁,每次都要寫一個名字和一個作用域限定符,太麻煩了,所以我們可以將命名空間經(jīng)常用的一個變量把它的搞到全局來,這樣就方便很多了,使用方法如下:
3、使用using namespace 命名空間名稱引入(全局展開)——不推薦
? ? ? 還有一種情況就是,我們可以直接將命名空間在全局展開,這樣就不用每次都加個作用域限定符去訪問了!!使用方法如下
六、C++輸入&輸出
在c語言中,我們輸入輸出會用到scanf和printf,而且還得用到占位符,比較麻煩,我們的祖師爺為了方便我們使用,設(shè)置了cout和cin來進(jìn)行輸入輸出
其實std是C++標(biāo)準(zhǔn)庫的命名空間名,C++將標(biāo)準(zhǔn)庫的定義實現(xiàn)都放到這個命名空間中
6.1 使用說明
1. 使用cout標(biāo)準(zhǔn)輸出對象(控制臺)和cin標(biāo)準(zhǔn)輸入對象(鍵盤)時,必須包含< iostream >頭文件以及按命名空間使用方法使用std。?
2. cout和cin是全局的流對象,endl是特殊的C++符號,表示換行輸出,他們都包含在<iostream>頭文件中。
3. <<是流插入運(yùn)算符,>>是流提取運(yùn)算符。
4. 使用C++輸入輸出更方便,不需要像printf/scanf輸入輸出時那樣,需要手動控制格式。C++的輸入輸出可以自動識別變量類型。
5. 實際上cout和cin分別是ostream和istream類型的對象,>>和<<也涉及運(yùn)算符重載等知識
?
6.2 注意事項
1、早期標(biāo)準(zhǔn)庫將所有功能在全局域中實現(xiàn),聲明在.h后綴的頭文件中,使用時只需包含對應(yīng)頭文件即可,后來將其實現(xiàn)在std命名空間下,為了和C頭文件區(qū)分,也為了正確使用命名空間,規(guī)定C++頭文件不帶.h;舊編譯器(vc 6.0)中還支持<iostream.h>格式,后續(xù)編譯器已不支持,因此推薦使用<iostream>+std的方式。
2、關(guān)于cout和cin還有很多更復(fù)雜的用法,比如控制浮點(diǎn)數(shù)輸出精度,控制整形輸出進(jìn)制格式等等。這些又用得不是很多,而且c++兼容c的語法,所以我們?nèi)绻枰刂聘袷?#xff0c;可以用printf和scanf就行!!
C語言:基礎(chǔ)知識-CSDN博客博主這篇文章里有printf和scanf的詳細(xì)應(yīng)用
6.3 std命名空間的使用慣例
1. 在日常練習(xí)中,建議直接using namespace std即可,這樣就很方便。
2. using namespace std展開,標(biāo)準(zhǔn)庫就全部暴露出來了,如果我們定義跟庫重名的類型/對象函數(shù),就存在沖突問題。該問題在日常練習(xí)中很少出現(xiàn)(因為代碼不多,如果我們和庫沖突了可以妥協(xié)一下),但是項目開發(fā)中代碼較多、規(guī)模大,就很容易出現(xiàn)。所以建議在項目開發(fā)中使用,像std::cout這樣使用時指定命名空間 +using std::cout展開常用的庫對象/類型等方式。
七、缺省參數(shù)
7.1 缺省參數(shù)的概念
? ? ? ?缺省參數(shù)是聲明或定義函數(shù)時為函數(shù)的參數(shù)指定一個缺省值。在調(diào)用該函數(shù)時,如果沒有指定實參則采用該形參的缺省值,否則使用指定的實參
怎么理解呢,就感覺這個缺省值0就想是a的舔狗(備胎)?一樣,如果有人來找a,他會毫不猶豫地把0給甩掉,如果沒人來找a,就會湊合和0在一起。
接下來舉一個應(yīng)用場景:
如上,func函數(shù)是專門用來開辟空間的函數(shù),如果我們已經(jīng)知道我們要開多大的空間,可以直接傳n進(jìn)去,如果我們不知道我們用多大的空間,就啥也不傳,用默認(rèn)值0,等到時候再根據(jù)需求擴(kuò)容?
7.2 缺省參數(shù)的分類
7.2.1 全缺省參數(shù)
?要注意的是:使用缺省值,必須從右往左使用!!不能跳著使用!!
7.2.2?半缺省參數(shù)

??要注意的是:因為使用的時候是從右往左使用的,所以半缺省參數(shù)也必須從右往左給!!!
7.3 注意事項
1. 半缺省參數(shù)必須從右往左依次來給出,不能間隔著給,使用也是從右往左連續(xù)使用
2. 缺省參數(shù)不能在函數(shù)聲明和定義中同時出現(xiàn)
//a.h
void Func(int a = 10);
// a.cpp
void Func(int a = 20)
{}
// 注意:如果生命與定義位置同時出現(xiàn),恰巧兩個位置提供的值不同,那編譯器就無法確定到底該
//用那個缺省值。
? ? ?主要是擔(dān)心兩邊給的缺省值不一樣(編譯器會不知道用哪個),所以一般我們在頭文件里用缺省值,而定義里面不用,在頭文件定義利用用,因為頭文件一般比較小,方便修改
//a.h
void Func(int a = 10);
// a.cpp
void Func(int a)
{}
3. 缺省值必須是常量或者全局變量
4. C語言不支持(編譯器不支持)
八、函數(shù)重載
? ? 自然語言中,一個詞可以有多重含義,人們可以通過上下文來判斷該詞真實的含義,即該詞被重
載了。比如方便這個詞,有些時候表示的是上洗手間的意思,有時候表示的是有時間的意思。
8.1 函數(shù)重載的概念
? ? ? ?在c語言中,不支持函數(shù)同名,即每個函數(shù)名都只能被定義一次,這就在某些情況會造成困擾,比如Add函數(shù),我們設(shè)置的時候想讓他計算int類型,但是如果我們想再設(shè)置一個這樣的函數(shù)計算double類型,就不能直接用Add這個名字,而是還得寫成Addd,如果想計算float,得寫成Addf,這其實是不利于理解的,我們的祖師爺本賈尼博士為了解決這種情況,讓c++支持函數(shù)重載的功能。
? ? ? 函數(shù)重載:是函數(shù)的一種特殊情況,C++允許在同一作用域中聲明幾個功能類似的同名函數(shù),這些同名函數(shù)的形參列表(參數(shù)個數(shù) 或 類型 或 類型順序)不同,常用來處理實現(xiàn)功能類似數(shù)據(jù)類型
不同的問題。
// 1、參數(shù)類型不同
int Add(int left, int right)
{cout << "int Add(int left, int right)" << endl;return left + right;
}
double Add(double left, double right)
{cout << "double Add(double left, double right)" << endl;return left + right;
}
// 2、參數(shù)個數(shù)不同
void f()
{cout << "f()" << endl;
}
void f(int a)
{cout << "f(int a)" << endl;
}
// 3、參數(shù)類型順序不同
void f(int a, char b)
{cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{cout << "f(char b, int a)" << endl;
}
int main()
{Add(10, 20);Add(10.1, 20.2);f();f(10);f(10, 'a');f('a', 10);return 0;
}
8.2 C++支持函數(shù)重載的原理--名字修飾(name Mangling)
?為什么c++支持函數(shù)重載,而c不支持呢?c++在尋找同名函數(shù)的時候又是如何區(qū)分的呢??
? ? ?觀察上圖,在C語言中,我們尋找Add函數(shù)是直接通過函數(shù)名查找的,而C++中找這個函數(shù)名的時候還增加了很多符號對名字進(jìn)行修飾,根據(jù)這些修飾可以有效地區(qū)分同名函數(shù),這個就是C++中的名字修飾規(guī)則!
? ? 但是c++只是規(guī)定了命名規(guī)則,但是具體還是要通過編譯廠商去實現(xiàn)的,所以每個編譯器都有自己的函數(shù)名修飾規(guī)則。
? ? ?由于Windows下vs的修飾規(guī)則過于復(fù)雜,而Linux下g++的修飾規(guī)則簡單易懂,下面我們使
用了g++演示了這個修飾后的名字。
g++的函數(shù)修飾后變成【_Z+函數(shù)長度+函數(shù)名+類型首字母】
也就是說在c++中,我們將參數(shù)的信息添加到函數(shù)名中,參數(shù)不同,修飾出來的名字就不同,所以參數(shù)個數(shù) 或 類型 或 類型順序?不同的情況下編譯器是可以區(qū)分的!!
通過這里就理解了C語言沒辦法支持重載,因為同名函數(shù)沒辦法區(qū)分。而C++是通過函數(shù)修
飾規(guī)則來區(qū)分,只要參數(shù)不同,修飾出來的名字就不一樣,就支持了重載。
windows下vs編譯器對函數(shù)名字修飾規(guī)則相對復(fù)雜難懂
C/C++ 函數(shù)調(diào)用約定___declspec(dllexport) void test2();-CSDN博客
?思考:如果兩個函數(shù)函數(shù)名和參數(shù)是一樣的,返回值不同是可以構(gòu)成重載嗎?
? ? ?肯定是不行的啊!!因為函數(shù)調(diào)用的時候是不會寫返回類型的!!所以編譯器無法區(qū)分!!函數(shù)名修飾規(guī)則是在函數(shù)調(diào)用的時候體現(xiàn)的!!
九、引用
在有些場景下,指針傳遞會非常不方便,所以祖師爺本賈尼博士增加了引用這個概念
9.1 引用概念
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內(nèi)存空
間,它和它引用的變量共用同一塊內(nèi)存空間。
比如:李逵,在家稱為"鐵牛",江湖上人稱"黑旋風(fēng)"。
類型& 引用變量名(對象名) = 引用實體;
我們會發(fā)現(xiàn),這里相當(dāng)于給a取了一個別名r,他們指向的都是同一塊空間,所以改變a也是改變r?
注意:引用類型必須和引用實體是同種類型的
9.2 引用特性
1. 引用在定義時必須初始化
因為引用是給一個變量起別名,如果沒有起別名的標(biāo)準(zhǔn)(初始化),就會出問題
int a = 10;
// int& ra; // 該條語句編譯時會出錯
int& ra = a;//這樣才是對的
2. 一個變量可以有多個引用
也就是一個變量我們可以給他起多個別名
3. 引用一旦引用一個實體,再不能引用其他實體
就是只能給一個變量當(dāng)別名,不能給多個變量當(dāng)別名
9.3 常引用
指針和引用,賦值/初始化權(quán)限可以縮小或者平移,但是不能放大
9.4 使用場景
1. 做參數(shù)
2.做返回值?
引用返回有兩個優(yōu)點(diǎn):
1、可以減少拷貝
這個我們可以通過上圖去理解?
2、調(diào)用者可以返回修改對象?
#define N 10
typedef struct Array
{int a[N];int size;
}AY;int& PosAt(AY& ay, int i)
{return ay.a[i];
}int main()
{AY ay;for (int i = 0; i < N; ++i){PosAt(ay, i) = i * 10;}for (int i = 0; i < N; ++i){cout << PosAt(ay, i) << " ";}cout << endl;
}
?我們直接在調(diào)用函數(shù)的同時把返回值給修改了!!?
? ? ? 注意事項:我們觀察上面兩個例子,會發(fā)現(xiàn)一個特點(diǎn),就是他們的傳引用返回的變量的生命周期都是整個程序,所以在出函數(shù)棧幀后這些變量都沒有被銷毀,那如果傳引用返回的是局部變量呢??但是局部變量是一出棧幀就會被銷毀的,這樣會發(fā)生什么事情呢??我們來看看代碼——
我們再看看這個代碼
? ? 你可能會很奇怪:為什么第一個局部變量被銷毀返回的是隨機(jī)值,第二個局部變量被銷毀卻成功返回0?
? ?這說明了一個問題如果函數(shù)返回時,出了函數(shù)作用域,如果返回對象還在(還沒還給系統(tǒng)),則可以使用引用返回,如果已經(jīng)還給系統(tǒng)了,則必須使用傳值返回。如果強(qiáng)行用傳引用返回,那么結(jié)果是未定義的!!
9.5 傳值和傳引用的效率
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{A a;// 以值作為函數(shù)參數(shù)size_t begin1 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc1(a);size_t end1 = clock();// 以引用作為函數(shù)參數(shù)size_t begin2 = clock();for (size_t i = 0; i < 10000; ++i)TestFunc2(a);size_t end2 = clock();// 分別計算兩個函數(shù)運(yùn)行結(jié)束后的時間cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{TestRefAndValue();
}
?傳引用用了不到1毫秒,而傳值用了7毫秒
通過上述代碼的比較,發(fā)現(xiàn)傳值和指針在作為傳參以及返回值類型上效率相差很大。
9.6 引用和指針的區(qū)別
在語法概念上引用就是一個別名,沒有獨(dú)立空間,和其引用實體共用同一塊空間。
而指針是有自己的獨(dú)立空間的,只不過他存放的是實體對應(yīng)的地址
但引用的底層實際上也是用指針去實現(xiàn)的
注意:?引用只能完成指針較為簡單的部分,但是不能替代指針!!?
1. 引用概念上定義一個變量的別名,指針存儲一個變量地址。
2. 引用在定義時必須初始化,指針沒有要求
3. 引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實體
4. 沒有NULL引用,但有NULL指針
5. 在sizeof中含義不同:引用結(jié)果為引用類型的大小,但指針始終是地址空間所占字節(jié)個數(shù)(32位平臺下占4個字節(jié))
6. 引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小
7. 有多級指針,但是沒有多級引用
8. 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理(編譯器在底層幫我們創(chuàng)建指針)
9. 引用比指針使用起來相對更安全(因為引用必須初始化,而指針不用,沒有空引用,但是有空指針)
10.在有些需要用二級指針的場景,較難理解,用引用就可以簡化一點(diǎn)
十、內(nèi)聯(lián)函數(shù)
為什么要有內(nèi)聯(lián)函數(shù)呢??原因是c++認(rèn)為宏難以把握!
宏缺點(diǎn):
1、不能調(diào)試(預(yù)處理階段宏就被處理了)
2、沒有類型安全的檢查
3、有些場景下非常復(fù)雜,容易出錯,不容易掌握
不同的寫法都可能會造成運(yùn)算的順序不符合我們的預(yù)期
所以c++推薦:
const和enum替代宏常量
inline去替代宏函數(shù)
10.1 概念
? ? ?以inline修飾的函數(shù)叫做內(nèi)聯(lián)函數(shù),編譯時C++編譯器會在調(diào)用內(nèi)聯(lián)函數(shù)的地方展開,沒有函數(shù)調(diào)用建立棧幀的開銷,內(nèi)聯(lián)函數(shù)提升程序運(yùn)行的效率。
?注意:在release模式下才能觀察到,因為在debug條件下編譯器默認(rèn)不會對代碼進(jìn)行優(yōu)化,所以不會展開
10.2 特性
1. inline是一種以空間換時間的做法,如果編譯器將函數(shù)當(dāng)成內(nèi)聯(lián)函數(shù)處理,在編譯階段,會用函數(shù)體替換函數(shù)調(diào)用,?
缺陷:可能會使目標(biāo)文件變大,優(yōu)勢:少了調(diào)用開銷,提高程序運(yùn)行效率
這里的空間值的是編譯出來的可執(zhí)行程序——
2. inline對于編譯器而言只是一個建議,不同編譯器關(guān)于inline實現(xiàn)機(jī)制可能不同,一般建議:將函數(shù)規(guī)模較小(即函數(shù)不是很長,具體沒有準(zhǔn)確的說法,取決于編譯器內(nèi)部實現(xiàn))、不是遞歸、且頻繁調(diào)用的函數(shù)采用inline修飾,否則編譯器會忽略inline特性。
下圖為《C++prime》第五版關(guān)于inline的建議:
?
也就是說,不同編譯器對內(nèi)聯(lián)函數(shù)做出了限制,如果太長,他就不會展開,而是改成調(diào)用這個函數(shù)
?3、inline不建議聲明和定義分離,分離會導(dǎo)致鏈接錯誤。因為inline被展開,就沒有函數(shù)地址了,鏈接就會找不到。
也就是說,加了inline的函數(shù)會讓編譯器認(rèn)為這并不是一個函數(shù),所以不會被存到函數(shù)調(diào)用符號表里,因此不能將聲明和定義分離!!正確的方法是將inline的聲明和定義都放在頭文件里!這樣子在預(yù)處理的時候該定義就會被放到執(zhí)行文件里
10.3 面試題?
【面試題】
宏的優(yōu)缺點(diǎn)?
優(yōu)點(diǎn):
1.增強(qiáng)代碼的復(fù)用性。
2.提高性能。
缺點(diǎn):
1.不方便調(diào)試宏。(因為預(yù)編譯階段進(jìn)行了替換)
2.導(dǎo)致代碼可讀性差,可維護(hù)性差,容易誤用。
3.沒有類型安全的檢查 。
C++有哪些技術(shù)替代宏?
1. 常量定義 換用const enum
2. 短小函數(shù)定義 換用內(nèi)聯(lián)函數(shù)
十一、auto關(guān)鍵字
為什么會有auto函數(shù)呢?原因是隨著程序越來越復(fù)雜,程序中用到的類型也越來越復(fù)雜
11.1 類型別名的思考
隨著程序越來越復(fù)雜,程序中用到的類型也越來越復(fù)雜,經(jīng)常體現(xiàn)在:
1. 類型難于拼寫
2. 含義不明確導(dǎo)致容易出錯
#include <string>
#include <map>
int main()
{
std::map<std::string, std::string> m{ { "apple", "蘋果" }, { "orange",
"橙子" },
{"pear","梨"} };
std::map<std::string, std::string>::iterator it = m.begin();
while (it != m.end())
{
//....
}
return 0;
}
如上,std::map<std::string, std::string>::iterator 是一個類型,但是該類型太長了,特別容易寫錯。可能有的人會想到typedef,其實也可以,但是typedef會有自己的問題:
?觀察下面代碼:
typedef char* pstring;
int main()
{
const pstring p1; // 編譯成功還是失敗?
const pstring* p2; // 編譯成功還是失敗?
return 0;
}
?編譯成功的是第二段代碼!!因為typedef并不是簡單的字符串替換,const pstring p1其實轉(zhuǎn)化之后是char* const p1,因為const會給予整個指針以常量性,所以是常量指針,常量指針只有一次初始化的機(jī)會,所以第一段代碼編譯不成功,而第二段代碼const pstring* p2轉(zhuǎn)化后是char* const *p2?,p2相等于是一個二級指針,但是const并沒有直接限制p2,而是限制了他的指向,所以不初始化是可以的!!
關(guān)于typedef的總結(jié),在博主的預(yù)處理文章里有和define的詳細(xì)對比:
C語言:預(yù)處理詳解-CSDN博客
這邊再總結(jié)一下typedef的缺點(diǎn):
1、typedef遇到const的時候,就不是簡單的字符串替換,使用上容易出問題
2、typedef在語法上是一個存儲類的關(guān)鍵字,和auto、static、extern一樣,如果有一個以上的存儲關(guān)鍵字,那么typedef就不會起作用,比如typedef static int a就不行!
11.2 auto簡介
在早期C/C++中auto的含義是:使用auto修飾的變量,是具有自動存儲器的局部變量,但遺憾的是一直沒有人去使用它,因為所謂自動存儲就是函數(shù)結(jié)束(出了作用域),這個變量自動銷毀。這樣的作用沒有意義,因為現(xiàn)在的變量也是出了作用域就自動銷毀。
C++11中,標(biāo)準(zhǔn)委員會賦予了auto全新的含義即:auto不再是一個存儲類型指示符,而是作為一個新的類型指示符來指示編譯器,auto聲明的變量必須由編譯器在編譯時期推導(dǎo)而得。?
typedef( ).name( ) 可以打印出變量的類型
要注意的是:
? ? ? ?使用auto定義變量時必須對其進(jìn)行初始化,在編譯階段編譯器需要根據(jù)初始化表達(dá)式來推導(dǎo)auto的實際類型。因此auto并非是一種“類型”的聲明,而是一個類型聲明時的“占位符”,編譯器在編譯期會將auto替換為變量實際的類型。
auto的自動類型推斷發(fā)生在編譯期,所以使用auto并不會造成程序運(yùn)行時效率的降低。
11.3 auto使用細(xì)則
1. auto與指針和引用結(jié)合起來使用
? ? ? 用auto聲明指針類型時,用auto和auto*沒太大的區(qū)別,主要就是用auto*的話,那么他對應(yīng)的類型就必須是指針,否則會報錯!!!auto聲明引用類型時則必須加&
2. 在同一行定義多個變量
? ? ? ? ?當(dāng)在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因為編譯器實際只對第一個類型進(jìn)行推導(dǎo),然后用推導(dǎo)出來的類型定義其他變量。
11.4 auto的使用場景
1、簡化代碼,類型很長時,可以考慮自動推導(dǎo)
2、auto在實際中最常見的優(yōu)勢用法就是C++11提供的新式for循環(huán),還有l(wèi)ambda表達(dá)式等進(jìn)行配合使用。
3、為了避免與C++98中的auto發(fā)生混淆,C++11只保留了auto作為類型指示符的用法
11.5?auto不能推導(dǎo)的場景
1. auto不能作為函數(shù)的參數(shù)
// 此處代碼編譯失敗,auto不能作為形參類型,因為編譯器無法對a的實際類型進(jìn)行推導(dǎo)
void TestAuto(auto a)
{}
?2、auto不能直接用來聲明數(shù)組
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
十二、基于循環(huán)的范圍for
12.1 范圍for的語法
在C++98中如果要遍歷一個數(shù)組,可以按照以下方式進(jìn)行:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
cout << *p << endl;
}
?對于一個有范圍的集合而言,由程序員來說明循環(huán)的范圍是多余的,有時候還會容易犯錯誤。因此C++11中引入了基于范圍的for循環(huán)。for循環(huán)后的括號由冒號“ :”分為兩部分:第一部分是范圍內(nèi)用于迭代的變量,第二部分則表示被迭代的范圍。
int main()
{int array[] = { 1, 2, 3, 4, 5 };for (auto& e : array)e *= 2;for (auto e : array)cout << e << " ";return 0;
}
?
注意事項:
1、如果要修改數(shù)組的內(nèi)容,就得用引用
2、與普通循環(huán)類似,可以用continue來結(jié)束本次循環(huán),也可以用break來跳出整個循環(huán)。
12.2 范圍for的使用條件
for循環(huán)迭代的范圍必須是確定的
對于數(shù)組而言,就是數(shù)組中第一個元素和最后一個元素的范圍;對于類而言,應(yīng)該提供begin和end的方法,begin和end就是for循環(huán)迭代的范圍。
注意:以下代碼就有問題,因為for的范圍不確定(傳參傳數(shù)組是不會傳整個數(shù)組過去的)
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
十三、指針空值nullptr
? ? ? ?在良好的C/C++編程習(xí)慣中,聲明一個變量時最好給該變量一個合適的初始值,否則可能會出現(xiàn)不可預(yù)料的錯誤,比如未初始化的指針。如果一個指針沒有合法的指向,我們基本都是按照如下
方式對其進(jìn)行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
?NULL實際是一個宏,在傳統(tǒng)的C頭文件(stddef.h)中,可以看到如下代碼:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
? ? ?可以看到,NULL可能被定義為字面常量0,或者被定義為無類型指針(void*)的常量。不論采取何種定義,在使用空值的指針時,都不可避免的會遇到一些麻煩,比如:
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
? ? ?程序本意是想通過f(NULL)調(diào)用指針版本的f(int*)函數(shù),但是由于NULL被定義成0,因此與程序的初衷相悖。
? ? ? 在C++98中,字面常量0既可以是一個整形數(shù)字,也可以是無類型的指針(void*)常量,但是編譯器默認(rèn)情況下將其看成是一個整形常量,如果要將其按照指針方式來使用,必須對其進(jìn)行強(qiáng)轉(zhuǎn)(void*)0。
? ? ?為了解決這個問題,c11引入了nullptr
注意事項:
1. 在使用nullptr表示指針空值時,不需要包含頭文件,因為nullptr是C++11作為新關(guān)鍵字引入的
2. 在C++11中,sizeof(nullptr) 與 sizeof((void*)0)所占的字節(jié)數(shù)相同。
3. 為了提高代碼的健壯性,在后續(xù)表示指針空值時建議最好使用nullptr。