做網(wǎng)站需要續(xù)費嗎深圳seo優(yōu)化外包
1.引用
引用不是新定義一個變量,而是給已存在變量取了一個別名,編譯器不會為引用變量開辟內存空
間。在語法層面,我們認為它和它引用的變量共用同一塊內存空間。
可以取多個別名,也可以給別名取別名。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
b/c/d本質都是別名,對d進行一個++
對于符號&,用其定義時就是引用,其他時候就是取地址
從此,改變一個變量不需要傳地址,而可以在形參處定義別名,達到類似指針的效果。
“經(jīng)典的錯誤,標準的零分”
為什么a的值還是沒有改變?
? ? 盡管傳入的是別名?,但是函數(shù)接受的參數(shù)依然是個數(shù)值,是實參的拷貝。應當在形參處建立引用,這樣才能真正修改我們希望修改的變量。
這樣就成功改變a的值了。
結論:形參是引用,則通過形參就能改變實參,不需要傳更高級的指針。
引用中需要注意的細節(jié):?
1.1引用在定義時必須先初始化
引用在定義時必須已經(jīng)初始化(不能先取綽號,再找誰合適這個綽號)
一個引用可以有多個變量,就如上文的a b c d
引用一旦引用一個實體,就不能再改變
可見,不是讓y變成z的別名,而是通過y,將z的值賦值給y和x
1.2引用中的權限問題
?存在的問題:權限的放大
m是只讀的,當n變成m的別名后,n作為int類型的變量是可讀可寫的。為了避免通過n修改了m這個const類型的變量(權限放大),所以不能通過編譯。
這樣,是權限的平移,就能通過編譯了。
對一個對象(C語言中喜歡稱為變量),權限可以縮小和平移,但是不能放大。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
此處我可以通過y修改x,修改后z的數(shù)值也會改變(相當于z只是沒有“write”的權限,z只能訪問x, 并不代表這個值真真正正地鎖死了)
回憶:const int*? ? ? int? const*? ? ? int* const?
const默認與其左邊結合,當左邊沒有任何東西則與右邊結合。
? ? ? ?換句話說,const只要在*的左邊,限制的就是*p1;const在*右邊,限制的就是這個指針,該指針只能指向這個空間,不能改變指向。
上文中的前兩種所限制的是一樣的,最后一種限制的是指針,不能進行加減法。
? ? ? ? ? ? ?
報錯的原因是:p2是可讀可寫的,我們可以通過p2去改變p1所指向的空間。但是p1指向的空間是被鎖死了的,是不能改變的,又擴大了權限,因此報錯。
數(shù)值之間沒有權限的概念,只有指針和引用之間有權限的概念。
類型轉化中的權限問題:
不管是強制類型轉化還是隱式類型轉化,其底層都是通過建立一個臨時變量來進行轉化。
我們先用double定義一個變量為12.34:?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
其轉化的本質是把d的整數(shù)部分取出,賦給整形類型的臨時變量,再通過臨時變量賦給i。
既然是這樣的賦值方法,就不難理解下圖為什么會報錯了:?
? ? ? ? ? ? ? ? ? ?
在82行代碼執(zhí)行時,d先將其整數(shù)部分賦值給臨時變量,但是臨時變量具有常性(像一個常數(shù)一樣,不可被改變),而按照int& j的方法接受該臨時變量后,j作為別名,可以通過j修改該臨時變量,這是不被允許的。
但如果我給這個變量定義為“只讀”類型,也就是const int& k=d;?
權限沒有被放大,就合規(guī)了。?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 所有的表達式運算也會產(chǎn)生臨時變量
int x=1;
int y=2;
x+y;
?沒有用變量接受x+y,但是x+y還是會進行計算,計算出的結果會放進臨時變量。
同理,有變量接受x+y時也一樣,x+y的值放入臨時變量,所以r2前面必須加const(只讀)才能保證不越界。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
1.3傳參和傳引用效率比較
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
直接傳值會拷貝整個變量(形參是實參的copy),傳參效率弱于引用傳參。?
再利用一個測試函數(shù):
(將傳值執(zhí)行10000次,再將傳引用執(zhí)行10000次,0表示其所消耗的時間是小于1ms的)?
1.4從底層看引用
我們在語法層面認為:別名不開空間,存地址的變量(指針)是需要開辟空間的。
但是在匯編層面:
通過底層可知,定義指針p和引用b的匯編代碼是一樣的。
不過在日常的語法層面,我們依然認為引用不開空間,指針變量要開空間。
1.5指針和引用的區(qū)別
1. 引用概念上定義一個變量的別名,指針存儲一個變量地址。2. 引用 在定義時 必須初始化 ,指針沒有要求3. 引用 在初始化時引用一個實體后,就 不能再引用其他實體 ,而指針可以在任何時候指向任? ? ? ?何一個同類型實體4. 沒有 NULL 引用 ,但有 NULL 指針5. 在 sizeof 中含義不同 : 引用 結果為 引用類型的大小 ,但 指針 始終是 地址空間所占字節(jié)個數(shù) (32 位平臺下占4 個字節(jié) )6. 引用自加即引用的實體增加 1 ,指針自加即指針向后偏移一個類型的大小7. 有多級指針,但是沒有多級引用8. 訪問實體方式不同, 指針需要顯式解引用,引用編譯器自己處理9. 引用比指針使用起來相對更安全
不過對于第四點,可以單獨說明一下:?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
不是說沒有NULL引用嗎?
結合底層思考為什么沒有報錯
? ?
?通過觀察匯編,我們可以發(fā)現(xiàn),并沒有發(fā)生解引用這一步驟。
因為其本質是和指針一樣的匯編代碼,所以并沒有發(fā)生報錯。
cpp的引用為什么不能替代指針?
如:鏈表:
引用不能改變指向,如果用引用的方法存下下一個節(jié)點,當你想改變鏈接方式時,如何處理?
所以next必須使用指針。這單純的是語法設計的原因,因為本賈尼是按照c為基礎設計的,并沒有想過要完全替代C語言。在Java中,引用就是可改變的,因此java沒有指針。
2.內聯(lián)函數(shù)
對于一些小型的、會大量重復調用的函數(shù),如(Swap,Add等)。不停的建立函數(shù)棧幀性價比太低,C語言使用含參數(shù)的宏來解決這個問題。
宏沒有棧幀消耗,但是容易出語法問題:復雜、沒有類型檢查、無法調試
cpp雖然兼容c的所有用法,但是cpp更傾向于使用內聯(lián)函數(shù)(inline修飾):
以 inline 修飾 的函數(shù)叫做內聯(lián)函數(shù), 編譯時 C++ 編譯器會在 調用內聯(lián)函數(shù)的地方展開 ,沒有函數(shù)調 用建立棧幀的開銷,內聯(lián)函數(shù)提升程序運行的效率。本質是一種空間換時間的做法。
用inline修飾函數(shù)
? ? ? ? ? ? ? ? ? ? ?
注意:在debug模式下,為了調試方便,依然會執(zhí)行call語句,像以前的函數(shù)一樣建立棧幀。
沒有執(zhí)行call語句,也就是沒有按照函數(shù)去調用,而是直接展開。
內聯(lián)函數(shù)的特點:
編譯器并沒有把是否展開的權利完全釋放給你,而是會自己選擇是否展開。
當函數(shù)中的語句過多時,就不會展開
inline對于編譯器只是一種建議,編譯器會自己決定是否展開(如遞歸等就一定不會展開)
為什么有的函數(shù)語句過多時不會展開?
大函數(shù)展開的缺點:
若我們要對一個100行的代碼調用10000次:
導致編譯出的可執(zhí)行程序變大。可執(zhí)行程序大了是一件很麻煩的事情。
最后,內聯(lián)函數(shù)不能聲明和定義相分離?
因為內聯(lián)函數(shù)是直接展開的,沒有函數(shù)的地址,在鏈接過程中是找不到的。
其本質就是一個小型功能直接展開。
3.auto
隨著程序越來越復雜,程序中用到的類型也越來越復雜,經(jīng)常體現(xiàn)在:1. 類型難于拼寫2. 含義不明確導致容易出錯
根據(jù)賦值的內容,自動識別i的類型。
當然,typedef有類似的功能,但是typedef有時候會有些問題:
?
pstring p1 與 char* const p1是一個意思,p1?作為一個被const的變量,也具有常性,必須初始化,所以此處報錯。
???????tips:typeid可以用來查看變量的類型名:
typeid(a).name();
auto修飾的限定?
auto可以根據(jù)后面的內容進行賦值內容的條件限制。
? ? ?規(guī)定:auto不能直接用來聲明數(shù)組
? ? ? ? ? ? ? ? ? ??
4.基于范圍的for循環(huán)
基于auto的用法,cpp抄了python的作業(yè),使用自動循環(huán):
for循環(huán)迭代的范圍必須是確定的
for (auto e: array){e/=2;
}
auto可以改成具體的類型(int、double)等都可以,只要匹配就行
但是遍歷方式是寫死了的,只能從數(shù)組首到數(shù)組尾遍歷。
但是傳參進入的數(shù)組不能使用范圍for
數(shù)組的傳參本質是傳入數(shù)組首元素地址,會退化。(c/cpp追求效率,在語言層進行了優(yōu)化,傳的是首元素地址)
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
而針對一個數(shù)組首元素地址,該數(shù)組循環(huán)迭代的范圍是不確定的,所以不能執(zhí)行。
5.nullptr和NULL
cpp的設計缺陷:
? 將NULL作為一個宏,代表0,而不是之前的空指針。因此,cpp中的NULL會被當作整形的int而不是空指針
所以,引入了關鍵字nullptr
主函數(shù)中:第一個f調用第一個函數(shù),第二個f也調用第一個函數(shù),第三個f調用第二個函數(shù),第四個函數(shù)調用第二個函數(shù)。
nullptr作為關鍵字,是不需要包含任何頭文件的
6.小結
本篇中多為零碎的c過渡到cpp的語法知識,先進行鋪墊和了解,在之后會有具體而詳細的使用。