C++是在C語言的基礎(chǔ)上發(fā)展來的秸滴。C++除了有C語言的指針外,還增加一個新的概念——引用募判,初學者容易把引用和指針混淆一起荡含,面試或者筆試經(jīng)常被考到。
要弄清楚這兩個概念届垫,先從變量說起释液。
一:變量的形式
什么是變量呢?變量(variable)的定義在計算機科學中到底是如何定義的装处?然后variable到底是在內(nèi)存中如何存儲值的呢误债?那么跟著上面的問題,我們來一一的解答符衔。
首先最重要的找前,變量的定義,當你申明一個變量的時候判族,計算機會將指定的一塊內(nèi)存空間和變量名進行綁定躺盛;這個定義很簡單,但其實很抽象形帮,例如:int x = 5; 這是一句最簡單的變量賦值語句了槽惫, 我們常說“x等于5”,其實這種說法是錯誤的辩撑,x僅僅是變量的一個名字而已界斜,它本身不等于任何值的。這條語句的正確翻譯應(yīng)該是:“將5賦值于名字叫做x的內(nèi)存空間”合冀,其本質(zhì)是將值5賦值到一塊內(nèi)存空間各薇,而這個內(nèi)存空間名叫做x。切記:x只是簡單的一個別名而已,x不等于任何值峭判。其圖示如下:
?變量在內(nèi)存中的操作其實是需要經(jīng)過2個步驟的:
1)找出與變量名相對應(yīng)的內(nèi)存地址开缎。
2)根據(jù)找到的地址,取出該地址對應(yīng)的內(nèi)存空間里面的值進行操作林螃。
二:指針
首先介紹到底什么是指針奕删?指針變量和任何變量一樣,也有變量名疗认,和這個變量名對應(yīng)的內(nèi)存空間完残,只是指針的特殊之處在于:指針變量相對應(yīng)的內(nèi)存空間存儲的值恰好是某個內(nèi)存地址。這也是指針變量區(qū)別去其他變量的特征之一横漏。例如某個指針的定義如下:
intx=5;
int*ptr=&x;
ptr即是一個指正變量名谨设。通過指針獲取這個指針指向的內(nèi)存中的值稱為dereference,間接引用绊茧。
其相對于內(nèi)存空間的表示如下:
使用指針的優(yōu)點和必要性:
? ??指針能夠有效的表示數(shù)據(jù)結(jié)構(gòu)铝宵;
? ??能動態(tài)分配內(nèi)存,實現(xiàn)內(nèi)存的自由管理华畏;
? ??能較方便的使用字符串;
? ??便捷高效地使用數(shù)組
? ??指針直接與數(shù)據(jù)的儲存地址有關(guān)尊蚁,比如:值傳遞不如地址傳遞高效,因為值傳遞先從實參的地址中取出值,再賦值給形參代入函數(shù)計算缴渊;而指針則把形參的地址直接指向?qū)崊⒌刂范以铮褂脮r直接取出數(shù)據(jù),效率提高琴锭,特別在頻繁賦值等情況下(注意:形參的改變會影響實參的值N酢)
三:引用
引用是C++引入的新語言特性,是C++常用的一個重要內(nèi)容之一决帖。
引用(reference)在C++中也是經(jīng)常被用到厕九,尤其是在作為函數(shù)參數(shù)的時候,需要在函數(shù)內(nèi)部修改更新函數(shù)外部的值的時候地回,可以說是引用場景非常豐富扁远。正確、靈活地使用引用刻像,可以使程序簡潔畅买、高效。
我在工作中發(fā)現(xiàn)细睡,許多人使用它僅僅是想當然谷羞,只是知道怎么應(yīng)用而已,而不去具體分析這個reference溜徙。
在某些微妙的場合湃缎,很容易出錯犀填,究其原由,大多因為沒有搞清本源雁歌。
下面我就來簡單的分析一下這個reference宏浩。首先我們必須明確的一點就是:reference是一種特殊的pointer。從這可以看出reference在內(nèi)存中的存儲結(jié)構(gòu)應(yīng)該跟上面的指針是一樣的靠瞎,也是存儲的一塊內(nèi)存的地址比庄。例如reference的定義如下:
intx=5;
int&y=x;
引用就是某一變量(目標)的一個別名,對引用的操作與對變量直接操作完全一樣乏盐。
引用的聲明方法:類型標識符 &引用名=目標變量名佳窑;
上面的代碼,定義了引用y父能,它是變量x的引用神凑,別名,這樣子何吝,目標變量有兩個名稱溉委,即該目標原名稱和引用名,且不能再把該引用名作為其他變量名的別名爱榕。
四瓣喊、引用和指針有什么區(qū)別?
(1)指針:指針是一個變量黔酥,只不過這個變量存儲的是一個地址藻三,指向內(nèi)存的一個存儲單元;而引用跟原來的變量實質(zhì)上是同一個東西跪者,只不過是原變量的一個別名而已棵帽。如:
inta=1;int*p=&a;
inta=1;int&b=a;
? ??上面定義了一個整形變量和一個指針變量p,該指針變量指向a的存儲單元渣玲,即p的值是a存儲單元的地址逗概。
而下面2句定義了一個整形變量a和這個整形a的引用b,事實上a和b是同一個東西柜蜈,在內(nèi)存占有同一個存儲單元仗谆。
(2) ?引用不可以為空,當被創(chuàng)建的時候淑履,必須初始化隶垮,初始化后就不會再改變了;而指針可以是空值秘噪,可以在任何時候被初始化狸吞,指針的值在初始化后可以改變,即指向其它的存儲單元。
(3)可以有const指針蹋偏,但是沒有const引用便斥;
(4)指針可以有多級,但是引用只能是一級(int **p威始;合法 而 int &&a是不合法的)
(5)”sizeof引用”得到的是所指向的變量(對象)的大小枢纠,而”sizeof指針”得到的是指針本身的大小黎棠;
(6)指針和引用的自增(++)運算意義不一樣晋渺;
(7)如果返回動態(tài)內(nèi)存分配的對象或者內(nèi)存,必須使用指針脓斩,引用可能引起內(nèi)存泄漏木西;
(8)從內(nèi)存分配上看,程序為指針變量分配內(nèi)存區(qū)域随静,而不為引用分配內(nèi)存區(qū)域八千,因為引用聲明時必須初始化,從而指向一個已經(jīng)存在的對象燎猛。引用不能指向空值恋捆。
? ??? ??注:標準沒有規(guī)定引用要不要占用內(nèi)存,也沒有規(guī)定引用具體要怎么實現(xiàn)重绷,具體隨編譯器 http://bbs.csdn.net/topics/320095541
(9)從編譯上看鸠信,程序在編譯時分別將指針和引用添加到符號表上,符號表上記錄的是變量名及變量所對應(yīng)地址论寨。指針變量在符號表上對應(yīng)的地址值為指針變量的地址值,而引用在符號表上對應(yīng)的地址值為引用對象的地址值爽茴。符號表生成后就不會再改葬凳,因此指針可以改變指向的對象(指針變量中的值可以改),而引用對象不能改室奏。這是使用指針不安全而使用引用安全的主要原因火焰。從某種意義上來說引用可以被認為是不能改變的指針。
(10)不存在指向空值的引用這個事實胧沫,意味著使用引用的代碼效率比使用指針的要高昌简。因為在使用引用之前不需要測試它的合法性。相反绒怨,指針則應(yīng)該總是被測試纯赎,防止其為空。
下面用通俗易懂的話來概述一下:
指針-對于一個類型T南蹂,T*就是指向T的指針類型犬金,也即一個T*類型的變量能夠保存一個T對象的地址,而類型T是可以加一些限定詞的,如const晚顷、volatile等等峰伙。見下圖,所示指針的含義:
引用-引用是一個對象的別名该默,主要用于函數(shù)參數(shù)和返回值類型瞳氓,符號X&表示X類型的引用。見下圖栓袖,所示引用的含義:
總之匣摘,可以歸結(jié)為"指針指向一塊內(nèi)存,它的內(nèi)容是所指內(nèi)存的地址叽赊;而引用則是某塊內(nèi)存的別名恋沃,引用不改變指向。"
五必指、指針傳遞和引用傳遞
在C++中囊咏,指針和引用經(jīng)常用于函數(shù)的參數(shù)傳遞,然而塔橡,指針傳遞參數(shù)和引用傳遞參數(shù)是有本質(zhì)上的不同的:
? ??? ??指針傳遞參數(shù)本質(zhì)上是值傳遞的方式梅割,它所傳遞的是一個地址值。值傳遞過程中葛家,被調(diào)函數(shù)的形式參數(shù)作為被調(diào)函數(shù)的局部變量處理户辞,即在棧中開辟了內(nèi)存空間以存放由主調(diào)函數(shù)放進來的實參的值,從而成為了實參的一個副本癞谒。值傳遞的特點是被調(diào)函數(shù)對形式參數(shù)的任何操作都是作為局部變量進行底燎,不會影響主調(diào)函數(shù)的實參變量的值弹砚。(這里是在說實參指針本身的地址值不會變)
? ??? ??而在引用傳遞過程中双仍,被調(diào)函數(shù)的形式參數(shù)雖然也作為局部變量在棧中開辟了內(nèi)存空間,但是這時存放的是由主調(diào)函數(shù)放進來的實參變量的地址桌吃。被調(diào)函數(shù)對形參的任何操作都被處理成間接尋址朱沃,即通過棧中存放的地址訪問主調(diào)函數(shù)中的實參變量。正因為如此茅诱,被調(diào)函數(shù)對形參做的任何操作都影響了主調(diào)函數(shù)中的實參變量逗物。
? ??? ??引用傳遞和指針傳遞是不同的,雖然它們都是在被調(diào)函數(shù)椛螅空間上的一個局部變量翎卓,但是任何對于引用參數(shù)的處理都會通過一個間接尋址的方式操作到主調(diào)函數(shù)中的相關(guān)變量。而對于指針傳遞的參數(shù)尔当,如果改變被調(diào)函數(shù)中的指針地址莲祸,它將影響不到主調(diào)函數(shù)的相關(guān)變量蹂安。如果想通過指針參數(shù)傳遞來改變主調(diào)函數(shù)中的相關(guān)變量,那就得使用指向指針的指針锐帜,或者指針引用田盈。
六、返回引用和返回指針
C++返回引用類型
? ??A& a(){ return *this;} 就生成了一個固定地址的指針缴阎,并把指針帶給你允瞧。
但A a() { return *this;}會生成一個臨時對象變量,并把這個臨時變量給你蛮拔,這樣就多了一步操作述暂。
當返回一個變量時,會產(chǎn)生拷貝建炫。當返回一個引用時畦韭,不會發(fā)生拷貝,你可以將引用看作是一個變量的別名肛跌,就是其他的名字艺配,引用和被引用的變量其實是一個東西,只是有了兩個名字而已衍慎。
問題的關(guān)鍵是转唉,當你想要返回一個引用而不是一個拷貝時,你要確保這個引用的有效性稳捆,比如:
? ??? ??int & fun() { int a; a=10; return a; }
這樣是不行的赠法,因為a會在fun退出時被銷毀,這時返回的a的引用是無效的乔夯。
這種情況下砖织,如果fun的返回類型不是int & 而是int就沒有問題了。
返回指針的話末荐,誰調(diào)用該函數(shù)镶苞,誰負責接觸返回的指針。
全局變量鞠评,局部靜態(tài)變量,局部動態(tài)分配變量 都可以作為函數(shù)返回值壕鹉。?
局部自動變量不行
函數(shù)內(nèi)部等局部變量剃幌,存儲在棧中的變量是不能作為返回值的,雖然可以讀取正確的值晾浴,但是這是一塊未分配的內(nèi)存负乡,當別的進程用到時就會出錯,這個指針相當于野指針脊凰。返回值可以是局部動態(tài)分配的內(nèi)存空間抖棘,這一部分分配在堆上茂腥,在主動釋放之前別的進程是無法使用的內(nèi)存區(qū)域。
不管是指針還是引用都是如此切省。
七最岗、特別之處const
為什么要提到const關(guān)鍵字呢?因為const對指針和引用的限定是有差別的:
常量指針VS常量引用
★常量指針:指向常量的指針朝捆,在指針定義語句的類型前加const般渡,表示指向的對象是常量。
定義指向常量的指針只限制指針的間接訪問操作芙盘,而不能規(guī)定指針指向的值本身的操作規(guī)定性驯用。
?常量指針定義"const int* pointer=&a"告訴編譯器,*pointer是常量儒老,不能將*pointer作為左值進行操作蝴乔。
★常量引用:指向常量的引用,在引用定義語句的類型前加const驮樊,表示指向的對象是常量薇正。也跟指針一樣不能對引用指向的變量進行重新賦值操作。
指針常量VS引用常量
在指針定義語句的指針名前加const巩剖,表示指針本身是常量铝穷。在定義指針常量時必須初始化!而這是引用與生俱來的屬性佳魔,無需使用const曙聂。
指針常量定義"int* const pointer=&b"告訴編譯器,pointer(地址)是常量鞠鲜,不能作為左值進行操作宁脊,但是允許修改間接訪問值,即*pointer(地址所指向內(nèi)存的值)可以修改贤姆。
常量指針常量VS常量引用常量
常量指針常量:指向常量的指針常量榆苞,可以定義一個指向常量的指針常量,它必須在定義時初始化霞捡。
定義"const int* const pointer=&c"
告訴編譯器坐漏,pointer和*pointer都是常量,他們都不能作為左值進行操作碧信。
而不存在所謂的"常量引用常量"赊琳,因為引用變量就是引用常量。C++不區(qū)分變量的const引用和const變量的引用砰碴。程序決不能給引用本身重新賦值躏筏,使他指向另一個變量,因此引用總是const的呈枉。如果對引用應(yīng)用關(guān)鍵字const趁尼,起作用就是使其目標稱為const變量埃碱。即
沒有:const double const& a=1;
只有const double& a=1;
double?b=1;
constdouble&?a=b;
b=2;//正確
a=3;//出錯error: assignment of read-only reference `a'
總結(jié):有一個規(guī)則可以很好的區(qū)分const是修飾指針,還是修飾指針指向的數(shù)據(jù)——畫一條垂直穿過指針聲明的星號(*)酥泞,如果const出現(xiàn)在線的左邊砚殿,指針指向的數(shù)據(jù)為常量;如果const出現(xiàn)在右邊婶博,指針本身為常量瓮具。而引用本身就是常量,即不可以改變指向凡人。