C和C++的區別(下)

4. const 的使用

c語言裏const是常變量,而c++裏是常量,即立即數。

因此c語言裏const修飾的變量並不能作爲初始化數組長度的下標。

c++const修飾的變量編譯時就被當作立即數,可以當作數組初始化下標

 c語言裏用const修飾的值可以不初始化,只是之後再沒有機會對這個const修飾的變量賦值了。因此我們可以總結出c語言裏const定義的常變量和一般變量的區別很小,只是常變量定義後不能作爲左值存在。但其真身,或者說是編譯方式,和普通變量是一樣的。

 然而c++裏的const修飾的量必須初始化,否則編譯無法通過,即在編譯的時候把使用const修飾的量都替換爲其的值,因此就能作爲數組下標,因爲編譯時,編譯器看到的下標並非const 定義的量的名字,而是它對應的值的立即數。

如果寫了以下的代碼在某cpp文件中

Const int  a=10

Int  *p=int *&a

*p=30

Printf(%d %d,a,*p);

最終打印的結果將是1030,程序運行後,p指向a的內存,修改aa的內存裏的數據肯定已經變成30了,但爲什麼最終打印的a還是10呢,因爲編譯時已經把printf裏那個a換成了10這個立即數了,不會再因爲a運行後的任何改變而改變了

 

剛說了c++裏的const修飾的量必須初始化。初始化的值必須是個常量,不能是個普通變量,這樣的原因是編譯器編譯時,替換const常量將不明確,只有立即數或者之前定義過的const常量是明確的具體的數字。

Int b=8;

Const int a=b;

是不對的


const int a=8;

const int b=a;

是可以的


 並且在c++中,const修飾的數據,最終生成的符號是local符號,即只有本文件可見的。

如果想要把const修飾的數據供整個工程使用,生成global全局符號,那麼其實也有一種方法,就是在定義處前面加extern聲明。


舉例 const int a=10;是本文件可見

  extern const int a=10;本工程可見


 


6.引用

  C++的一種新的類型,通常爲數據類型+&;單獨&表示去地址

  比如簡單的引用 int &b=a;就是表示b是a的引用。

  引用就是被引用量的別名,如果改變b的值,同樣也會改變a的值。

我們來看看引用的底層彙編究竟是什麼

int a=10;

00ED439E  mov         dword ptr [a],0Ah  

 int &b=a;

00ED43A5  lea         eax,[a]  

00ED43A8  mov         dword ptr [b],eax  

  可以看到b對a引用,實際上先讓eax存放了a的地址,然後再將eax存放的a的地址賦給了b。這不就是個指針做的事情嗎。我們再看看指針的彙編是什麼樣的。

int a=10;

012D439E  mov         dword ptr [a],0Ah  

  int *p=&a;

012D43A5  lea         eax,[a]  

012D43A8  mov         dword ptr [p],eax  

可以看到指針的彙編和引用的彙編原理是一模一樣的。所以說引用其實就是被包裝了的指針,不過每次使用引用時都其實都自動做了相當於指針解引用的操作。


那麼引用變量到底開闢不開闢內存呢?

我們定義

int a=10;int &b=a;  打印&a 和&b的地址發現是一樣的,有人就會說不開闢內存。

其實不是的,剛都說了,引用的本質就是指針,指針是要開闢內存的,32位系統下指針都是4個字節的,可是爲什麼&a和&b一樣呢?因爲使用引用b時,已經相當於把b引用所隱藏的指針解過引用了,此時b就是a了,所以怎麼取地址都相當於&a,所以地址是一樣的,至於引用偷偷開闢的那個4個字節我們是沒有觀測到的。但它真實存在。


 

怎麼引用數組,指針呢?

引用一緯數組int arr[10]={0};

就相當於對int *arr 數組指針進行引用

我們看整形引用是

Int &b=int  a;

b實際上對a取地址,int  *b=&a 只不過&和*都掩蓋了,然後在b引用名前緊跟了一個&引用類型


 如果是int arr[10]呢,即對int *arr取地址

按上面的推,是不是b其實相當於一個數組指針,對arr數組指針取地址

Int (*b)[10]=&arr

然後掩蓋&取地址和一個*  

Int (b)[10]=arr

然後再緊跟着引用名b前面加上一個&引用類型名

Int (&b)[10]=arr  


 


 那麼怎麼對指針引用呢?用一級指針舉例

是不是引用相當於是一個指針的指針,即二級指針

int *p;

int **b= &p;

然後掩蓋

Int *b=p;

然後加&

Int * &b=p;

是不是慢慢地感覺規律來了,就好想好寫很多了?


 


 


 


7. const 指針 引用結合


 (1) const 與指針

  一級指針

Int a=10;

Int *p=&a;

其中const修飾第二句可以這樣2種修飾

即int const *p 和int * const p

前者修飾*p,意味着不能改變p解引用後的值,即不能通過解引用p去修改p指向的內存的值。

後者修飾的是p,表示p是常量,即可以通過p改變p指向的內存的值,但並不能改變p這個指針本身的值,即p的指向不能改變。

如果int a定義爲 const int a;那麼這個p只能定義成int const *p;

因爲const int a代表不能直接或間接的對a修改,直接就是直接修改a,當然是不行的。間接呢?間接就是把a的地址或者引用泄露出去,讓其他高權限的指針通過解引用進行修改。這種顯然不符合我們對const的要求,編譯器是要杜絕的。


還有一種情況就是這樣的。

Int a=10;

Const int *p1=&a;

Int *p2=p1;

a是可以修改的,然後定義了一個const指針p1,它表示不能通過p1修改a的值,這屬於權限縮小,當然是沒有錯誤的。

可是第三句int *p2=p1;卻是不對的,那是爲什麼?

首先可以通過權限來判斷,第三句相當於把低權限的指針p1讓高權限的指針p2使用,這是權限放大,顯然有誤。

可是有人會疑問,a不是可以修改呢?P2權限大修改a也不影響最終結果啊。

實際上是這樣的。編譯器在讀到Const int *p1=&a這時已經認爲p1的指向是個const int類型了,之後再和p1相等指針都應該是const int *類型,如果改成下面這種語序,表達的意思其實和上面一樣,但能通過編譯

Int a=10;

Int *p2=&a;

Const int *p1=&a;


 


二級指針和const 中需要注意下面這一種情況,和上面的很像。

Int a=10;

Const Int *p=&a;

Int **pp=&p;

這樣是編譯無法通過的。和

Int a=10;

Const int *p1=&a;

Int *p2=p1;

比,就是第三句不同,前者是Int **p=&p;後者是Int *p2=p1;


就是本來把p1賦值給另一個一級指針p2,變成了一個二級指針指向p。

爲什麼會編譯不通過呢。上面也說了Const int *p=&a;

 這句編譯器讀完後,就認爲p1指向的是個const int 類型了,int **pp指向const int *p就會權限擴大,肯定就會失敗。雖然 我們想着**pp修改a,a明明可以修改啊,你可以想成編譯器並沒有記憶力,先到int a了不會記住a的權限,只會記住Const int *p中p的屬性,即使pp和a有合理的關聯,但無法通過p這關。也就是常說的間接修改。這也算編譯器怕你違反 它認爲的規則 的一種預防吧。


 


 (2)Const 與函數重載

之前說過函數重載,對於參數來說,const是否能構成一種新的參數類型呢?

就是

Fx(int a);

Fx(const int a);這倆是否能構成重載呢?

答案是不能的。爲什麼不能,你可以理解爲,如果const沒有修飾*指針或&引用,那麼其類型其實並沒有變化。什麼意思呢?

Int * const p;只是修飾了p,並沒有修飾*號,即p自己隨意怎麼固定,反正int *p和int *const p最終表示的是一樣的,有人會說明顯不一樣啊,一個p可以改,一個p不可以改。再次強調我說的是對參數,其表現就是究竟對實參的值有影響,反正最終傳進來的都是p,管他p怎麼固定呢,只要p的解引用都可以修改,權限是一樣的,我就認爲他倆一樣。


下面這就不能構成重載

Fx(int * a);

Fx(int *const a);  最終fx函數裏都可以*a=10,請問有什麼不同嗎。


但是下面這倆就能構成重載

Fx(int * a);

Fx(const int * a);   最終fx裏,前者能*a=10,後者*a=10是非法的,這顯然等於是2種參數,自然可以構成重載。


 

(3) const 與  &引用


 由於&緊跟着引用變量名,因此就不會出現 int &const b這種東西

我們只用看看 int const &b

在這之前我們先看看普通引用 即 非const引用 和 各種量的結合


①引用和普通變量 這是可以的√

Int a=10;  

Int &b=a;


 ②引用和常量    這是不行的×

  Const int a=10;

  Int &b=a;   

 首先我們可以看到權限都是擴大的,怎麼引用。

 其次通過原理來看,編譯時a就變成了立即數10,語句轉化過來就是

 Int &b=10;這顯然是不對的,引用本質是地址,怎麼對10取地址?


 ③引用和立即數   這是不行的×

   Int &b=10;

  和②理由一樣,引用本質是地址,怎麼對10取地址?


 


 然後我們看看const和&結合,也就是所謂的常引用


①常引用和普通變量  這是可以的√

Int a=10;  

Int const &b=a;

相當於一個縮小權限的指針指向a,合情合理


②常引用和常量   這是可以的√

Const Int a=10;  

Int const &b=a;

相當於一個等權限的指針指向a,合情合理


②常引用和立即數   這是可以的√

Int const &b=10;

這個可能有點讓人覺得怪異,不是說b本質是指針嗎。指針怎麼給10取地址。

的確,這裏的引用和指針有些區別。

Const Int *p=&10;顯然是不對的,立即數前面怎麼加取地址都是錯誤的。

可是引用卻可以,巧就巧在引用不必&10,就不會錯誤。

並且常引用碰到立即數有特殊的解決方法。

我們來看彙編。

 const int &b=10;

00056EAE  mov         dword ptr [ebp-14h],0Ah  

00056EB5  lea         eax,[ebp-14h]  

00056EB8  mov         dword ptr [b],eax


這三句彙編意思是,將立即數10賦值到ebp棧底指針上20字節開始的地方,賦值4個字節。然後把那棧底上20個字節那塊區域的內存地址放入eax寄存器,最後把這個地址在賦值給常引用b的底層指針。


相當於什麼意思呢?就是開闢了一個臨時區域,把立即數存入到了那個臨時區域,然後常引用相當於指針,指針指向的是那個臨時區域。


 


(4)const &  *與函數


引用 函數返回值

定義函數:

int fx()

  int a=10;

Return a;

Int main()

{

 Int &a=fx();

}


這是不行的。爲什麼,因爲當函數返回值小於8個字節的時候,返回值是由寄存器帶回的,這裏就具體爲eax帶回的值,帶回的是個立即數,這是個普通引用,無法引用立即數的。


Const int &a=fx();這樣使用常引用就可以。


 指針指向函數返回值

int fx()

 int a=10;

Return a;

Int main()

{

 Int *p=&fx();

}


這就相當於int *p=&10;顯然是錯誤,也不會像引用一樣會開闢臨時區域存儲立即數返回值。

Int* fx()

Static  int a=10;

Return  &a;

Int main()

{

 Int *p=fx();

}

如果如上這樣定義呢,即fx裏有個數據a,返回a的地址,用寄存器帶回,是個立即數,相當於就是 int *const p ,賦值給int *p當然是可以的。


 


 


那麼如果返回的是指針能被引用嗎??如下

Int* fx()

 Static  int a=10;

Return  &a;

Int main()

{

 Int * &b=fx();

}


返回值是個立即數地址,相當於int *const p,


而b的引用相當於個二級指針int **pp,沒有任何的const修飾,真面目就是int *pp=&p,想一想就知道不可以了!


怎麼修改。


是不是換成常引用就可以了?


 Int * const &b=fx();


最後說返回值是引用

例如

Int & fx()

{

Static int a=10;

Return a;

}

Int main()

{

 Int a=fx();

}


這就相當於用寄存器返回了指向數據a的地址,然後main中的fx()直接就把寄存器解引用*eax,然後main中用a接受了數據a的內容。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章