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);
最終打印的結果將是10和30,程序運行後,p指向a的內存,修改a,a的內存裏的數據肯定已經變成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的內容。