變量、函數(shù)和后面要學到的類都是大量存在的,這些變量、函數(shù)和類的名稱將都存在于全局作用域中,可能會導致很多沖突。
使用命名空間的目的是對標識符的名稱進行本地化,以避免命名沖突或名字污染,namespace關(guān)鍵字的出現(xiàn)就是針對這種問題的。
使用namespace關(guān)鍵字,后面跟命名空間的名字,然后用{}將成員括起來即可,和C語言的結(jié)構(gòu)體類似
存在多個相同的命名空間的時候,編譯器編譯的時候會把他們合并,如下面
命名空間可以嵌套
PS:命名空間定義了一個新的作用域,命名空間中的所有內(nèi)容都局限于該命名空間中,但是成員的生命周期沒有改變,仍然是全局的。
命名空間有3種使用方式
以下均使用該命名空間
1.加命名空間名稱及作用域限定符
這種方法每次使用的時候都要進行::限定,比較麻煩
2.使用using將命名空間中成員引入
這樣就可以不用::限定命名空間,直接使用b
3.使用using namespace 命名空間名稱引入
可以使用所有N中內(nèi)容,但是需要注意的是這樣可能會造成名稱沖突。
1. 使用cout標準輸出(控制臺)和cin標準輸入(鍵盤)時,必須包含< iostream >頭文件以及std標準命名空間。
2. 使用C++輸入輸出更方便,不需增加數(shù)據(jù)格式控制
缺省參數(shù)就像女神的備胎,當女神有男朋友的時候,備胎被冷落在一旁,當女神分手后,備胎才可以派上用場。
缺省參數(shù)是聲明或定義函數(shù)時為函數(shù)的參數(shù)指定一個默認值。在調(diào)用該函數(shù)時,如果沒有指定實參則采用該默認值,否則使用指定的實參。
全缺省參數(shù)
半缺省參數(shù)
注意:
1. 半缺省參數(shù)必須從右往左依次來給出,不能間隔著給
2. 缺省參數(shù)不能在函數(shù)聲明和定義中同時出現(xiàn),且應(yīng)只出現(xiàn)于聲明
3. 缺省值必須是常量或者全局變量
5. 函數(shù)重載
自然語言中,一個詞可以有多重含義,人們可以通過上下文來判斷該詞真實的含義,即該詞被重載了。
比如:有兩個體育項目大家根本不用看,也不用擔心。一個是乒乓球,一個是男足。前者是“誰也贏不了!”,后者是“誰也贏不了!”
5.1 函數(shù)重載概念
函數(shù)重載:是函數(shù)的一種特殊情況,C++允許在同一作用域中聲明幾個功能類似的同名函數(shù),這些同名函數(shù)的形參列表(參數(shù)個數(shù) 或 類型 或 順序)必須不同,常用來處理實現(xiàn)功能類似數(shù)據(jù)類型不同的問題。
下面都是函數(shù)重載:
下面這兩個不是函數(shù)重載!
5.2 名字修飾
那么為什么C++支持函數(shù)重載而C語言不支持呢?
一個程序要運行起來要經(jīng)歷以下幾步:假設(shè)有以下文件
f.h f.cpp test.cpp
1.預處理——頭文件展開,宏替換,條件編譯,去掉注釋
生成f.i test.i
2.編譯——檢查語法,生成匯編代碼
生成f.s test.s
3.匯編——把匯編代碼轉(zhuǎn)換成二進制的機器碼(讓CPU能看懂)
生成f.o test.o
4.鏈接——找調(diào)用函數(shù)的定義地址,鏈接對應(yīng)上,合并到一起
生成a.out可執(zhí)行程序
在鏈接階段,編譯器看到test.o調(diào)用了哪些函數(shù),就會去f.o的符號表中找對應(yīng)函數(shù)的地址,然后鏈接在一起,那么要怎么去找呢?C++和C語言采用了不同的名字修飾,就會用不同的名字去找
我們發(fā)現(xiàn)名字由_Z+函數(shù)名長度+函數(shù)名首字母+函數(shù)按順序參數(shù)首字母,因此支持函數(shù)重載(名字不一樣,找的對象就不一樣)C:
這里我們發(fā)現(xiàn)C語言則是直接用函數(shù)名作為地址查找對象,回想我們的函數(shù)指針,是不是函數(shù)指針和函數(shù)名都可以調(diào)用函數(shù)呢。因此如果C語言中出現(xiàn)函數(shù)重載,那么調(diào)用的時候就會查找到兩個不同地址,C語言不知道要鏈接哪一個,因此出錯。
我們知道C語言可以調(diào)用C語言的靜態(tài)庫和動態(tài)庫,C++可以調(diào)用C++的靜態(tài)庫和動態(tài)庫,那么C++能不能調(diào)用C的庫?C能不能調(diào)用C++的庫?
答案是可以的。
我們先來看C++調(diào)用C的庫,這里我們用C語言實現(xiàn)的棧的代碼生成一個靜態(tài)庫
再新開一個項目,調(diào)用這個庫,這里對新項目的屬性進行更改。
然后來看一下現(xiàn)在能否調(diào)用
這是不是意味著C++不能調(diào)用C的庫?別急,這時候extern C就登場了
我們發(fā)現(xiàn)加了這個關(guān)鍵字后就鏈接成功了。這個關(guān)鍵字的意思是告訴編譯器將該部分按照C語言的規(guī)則來進行編譯,我們調(diào)用C庫,當然就要按C的規(guī)則來啦。
再來看一下C語言能否調(diào)用C++的庫,我們把后綴名更改一下
(這里是c語言了,因此只能用typedef過的別名,不能再用一個Stack,因為C語言不支持) 好像不行,我們剛剛是在C++的部分進行修改,這次我們也在C++的部分進行修改,我們來到test_01,對頭文件部分的聲明進行如下修改。
這樣是不是就順利鏈接成功了,這里的預編譯進行了判斷,如果文件是C++的,那么就用C的規(guī)則進行編譯,也可以這么寫:
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內(nèi)存空間,它和它引用的變量共用同一塊內(nèi)存空間。
類型& 引用變量名(對象名) = 引用實體;
引用類型必須和引用實體是同種類型的
1. 引用在定義時必須初始化
2. 一個變量可以有多個引用
3. 引用一旦引用一個實體,再不能引用其他實體
這里我們?nèi)e名的規(guī)則是:
對原引用變量,權(quán)限只能縮小,不能放大。這里權(quán)限指的是讀寫權(quán)限。
因此對const常量只能進行讀,也就只能用const引用。
在下面將double賦給int別名的過程中,編譯器會產(chǎn)生“臨時變量”,將double d中整數(shù)部分賦給臨時變量, 這個臨時變量再將這個整數(shù)賦給另一個int類型的臨時變量,最后int把臨時變量賦給rd,也就是說rd引用的其實是這個int臨時變量,而臨時變量具有常屬性,因此要用const引用。
1.作函數(shù)參數(shù)
在學習C語言的時候我們?nèi)绻粨Q兩個數(shù),那么就需要傳址調(diào)用函數(shù),而現(xiàn)在可以直接用引用進行修改,這就是引用做參數(shù)的好處,輸出型參數(shù)
另外,如果是傳值調(diào)用的話,我們知道形參是實參的一個臨時拷貝,而傳引用的時候并沒有拷貝這一步,這就提高了效率。
2.作函數(shù)返回值
結(jié)果是顯然的,我們返回的是靜態(tài)變量n的別名,而出了函數(shù)作用域n并沒有銷毀,那么來看下面這個代碼
n和ret地址一樣
這里n不再是靜態(tài)變量了,出了函數(shù)作用域后空間返還,而我們的ret是n的別名,這是不是就相當于野指針。因此只有第一次調(diào)用函數(shù)后ret中的值是1,調(diào)用一次函數(shù)(函數(shù)重載<<)后n的空間被制成隨機數(shù),因此剩下兩次都是隨機數(shù),因此我們用引用作返回值的時候返回的應(yīng)該是出了函數(shù)定義域還存在的變量的引用,否則就應(yīng)該按值返回,這里和指針是一個道理。
明白了這些下面這個程序的結(jié)果就應(yīng)該很明確了,應(yīng)該是7
道理是一樣的。另外引用作返回值也會提高效率
6.5 傳值、傳引用效率比較
以值作為參數(shù)或者返回值類型,在傳參和返回期間,函數(shù)不會直接傳遞實參或者將變量本身直接返回,而是傳遞實參或者返回變量的一份臨時的拷貝,因此用值作為參數(shù)或者返回值類型,效率是非常低下的,尤其是當參數(shù)或者返回值類型非常大時,效率就更低。
這里用程序來比較一下
不是很明顯的原因是我們計算機的速度實在太快,但是還是有效率差異的。
指針和引用的效率是一樣的,因為引用的底層和指針的底層是一樣的!
6.6 引用和指針的區(qū)別
從語法概念上:引用是別名,沒有額外開空間,而指針存儲變量的地址,開辟了4/8字節(jié)的空間
從底層實現(xiàn)的角度:引用實際上是有空間的,因為引用是按指針的方式來實現(xiàn)的
來看一下指針和引用的匯編
匯編指令完全一樣,也印證了底層是一樣的
那引用和指針的不同點是什么呢?
1. 引用在定義時必須初始化,指針沒有要求
2. 引用在初始化時引用一個實體后,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實體
3. 沒有NULL引用,但有NULL指針
4. 在sizeof中含義不同:引用結(jié)果為引用類型的大小,但指針始終是地址空間所占字節(jié)個數(shù)(32位平臺下占4個字節(jié))
5. 引用自加即引用的實體增加1,指針自加即指針向后偏移一個類型的大小
6. 有多級指針,但是沒有多級引用
7. 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理
8. 引用比指針使用起來相對更安全
7. 內(nèi)聯(lián)函數(shù)
7.1 概念
以inline修飾的函數(shù)叫做內(nèi)聯(lián)函數(shù),編譯時C++編譯器會在調(diào)用內(nèi)聯(lián)函數(shù)的地方展開,沒有函數(shù)壓棧的開銷,內(nèi)聯(lián)函數(shù)提升程序運行的效率。
在學習C語言的時候,我們遇到函數(shù)體短小且頻繁調(diào)用的函數(shù)的時候,采用的優(yōu)化方式是使用宏進行替換,但是宏太繁瑣啦,難以看懂,因此c++就產(chǎn)生了內(nèi)聯(lián)函數(shù)。
那么內(nèi)聯(lián)函數(shù)是如何進行優(yōu)化的呢?來看代碼。
當我們不用inline定義函數(shù)的時候,函數(shù)是怎么調(diào)用的呢?
進行函數(shù)壓棧,然后用call指令調(diào)用。那么用了inline后呢
我們發(fā)現(xiàn)在匯編代碼中直接對函數(shù)調(diào)用的代碼進行了替換,沒有壓棧和call調(diào)用指令。
7.1 特性
1. inline是一種以空間換時間的做法,省去調(diào)用函數(shù)額開銷。所以代碼很長或者有循環(huán)/遞歸的函數(shù)不適宜使用作為內(nèi)聯(lián)函數(shù)。
2. inline對于編譯器而言只是一個建議,編譯器會自動優(yōu)化,如果定義為inline的函數(shù)體內(nèi)有循環(huán)/遞歸等等,編譯器優(yōu)化時會忽略掉內(nèi)聯(lián)。
3. inline不建議聲明和定義分離,分離會導致鏈接錯誤。因為inline被展開,就沒有函數(shù)地址了,鏈接就會找不到。
例如如果在.h和.cpp文件中分別聲明和定義內(nèi)聯(lián)函數(shù),那么在鏈接的時候就會出現(xiàn)錯誤:main.obj : error LNK2019: 無法解析的外部符號 'void __cdecl f(int)' (?f@@YAXH@Z),該符號在函數(shù) _main 中被引用
那么內(nèi)聯(lián)函數(shù)相比宏有哪些優(yōu)點?
1.內(nèi)聯(lián)函數(shù)在debug版本下支持調(diào)試,而宏不支持
2.內(nèi)聯(lián)函數(shù)就是普通函數(shù)的寫法,解決了宏晦澀難懂的問題
8. auto關(guān)鍵字(C++11)
8.1 auto簡介
在早期C/C++中auto的含義是:使用auto修飾的變量,是具有自動存儲器的局部變量
C++11中,標準委員會賦予了auto全新的含義即:auto不再是一個存儲類型指示符,而是作為一個新的類型指示符來指示編譯器,auto聲明的變量必須由編譯器在編譯時期推導而得。
使用auto定義變量時必須對其進行初始化,在編譯階段編譯器需要根據(jù)初始化表達式來推導auto的實際類型。因此auto并非是一種“類型”的聲明,而是一個類型聲明時的“占位符”,編譯器在編譯期會將auto替換為變量實際的類型。
1.用auto聲明指針類型時,用auto和auto*沒有任何區(qū)別,但用auto聲明引用類型時則必須加&
2. 在同一行定義多個變量
當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因為編譯器實際只對第一個類型進行推導,然后用推導出來的類型定義其他變量。
1. auto不能作為函數(shù)的參數(shù)
2. auto不能直接用來聲明數(shù)組
3. 為了避免與C++98中的auto發(fā)生混淆,C++11只保留了auto作為類型指示符的用法
4. auto在實際中最常見的優(yōu)勢用法就是C++11提供的新式for循環(huán),還有l(wèi)ambda表達式等進行配合使用。
for循環(huán)后的括號由冒號“ :”分為兩部分:第一部分是范圍內(nèi)用于迭代的變量,第二部分則表示被迭代的范圍。
范圍for循環(huán)與普通循環(huán)類似,可以用continue來結(jié)束本次循環(huán),也可以用break來跳出整個循環(huán)。
1. for循環(huán)迭代的范圍必須是確定的對于數(shù)組而言,就是數(shù)組中第一個元素和最后一個元素的范圍;對于類而言,應(yīng)該提供begin和end的方法,begin和end就是for循環(huán)迭代的范圍。
2. 迭代的對象要實現(xiàn)++和==的操作。
在傳統(tǒng)頭文件中有如上定義,我們發(fā)現(xiàn)在C++中Null被宏替換為0,而如果我們要對一個指針賦空值,仍采用NULL的話有些不妥,因為0畢竟是一個數(shù)字,而不是指針類型,因此C++11引入了nullptr,其實也就相當于(void*)0。