🌈 C++ Primer 的學習筆記
前言
這篇blog 主要是想具體講講新學到的const 當然不止是const 而是基于這個const引申出來的指針和引用。還是需要捋一捋的,這還是有點困難的。
我會把每一節的重點都摘出來,放在前面~
1??首先講講const
2??const引用和const指針 ?? 特別容易混淆
這邊blog 常量指針/引用 指針/引用常量 詳細講的不錯 可以看看~
文章目錄
- 前言
- const 限定符
- 初始化和const
- 默認情況下,const對象只在文件內有效
- 練習
- 關于const 引用
- 初始化和對const 的引用
- 對const的引用可能引用一個并非const 的對象
- 指針和const
- 指向常量的指針
- 常量指針
- 練習
const 限定符
🌈敲重點!
🍎因為const對象一旦創建后其值就不能再改變,所以const對象必須初始化
🍐只能在const類型的對象上執行不改變其內容的操作
有時我們希望定義這樣一種變量,它的值不能被改變。例如,用一個變量來表示緩沖區的大小。使用變量的好處是當我們覺得緩沖區大小不再合適時,很容易對其進行調整。另一方面,也應隨時警惕防止程序一不小心改變了這個值。為了滿足這一要求,可以用關鍵字const對變量的類型加以限定:
const int bufsize = 512;//輸入緩沖區大小
這樣就把 bufsize定義成了一個常量。任何試圖為 bufsize賦值的行為都將引發錯誤:
bufSize = 512;//錯誤:試圖向const對象寫值
因為const對象一旦創建后其值就不能再改變,所以const對象必須初始化。 一如既往,初始值可以是任意復雜的表達式:
const int i = get_size();//正確:運行時初始化
const int j=42;//正確:編譯時初始化
const int k;//錯誤:k是一個未經初始化的常量
初始化和const
正如之前反復提到的,對象的類型決定了其上的操作。與非 const類型所能參與的操作相比,const類型的對象能完成其中大部分,但也不是所有的操作都適合。主要的限制就是只能在const類型的對象上執行不改變其內容的操作。例如,const int和普通的int一樣都能參與算術運算,也都能轉換成一個布爾值,等等。
在不改變 const 對象的操作中還有一種是初始化,如果利用一個對象去初始化另外一個對象,則它們是不是const都無關緊要:
int i =42;
const int ci = i;//正確:i的值被拷貝給了ci
int j= ci;//正確:ci的值被拷貝給了j
盡管ci是整型常量,但無論如何ci中的值還是一個整型數。ci的常量特征僅僅在執行改變ci的操作時才會發揮作用。當用ci去初始化j時,根本無須在意ci是不是一個常量。拷貝一個對象的值并不會改變它,一旦拷貝完成,新的對象就和原來的對象沒什么關系了。
默認情況下,const對象只在文件內有效
當以編譯時初始化的方式定義一個const對象時,就如對bufsize的定義一樣:
const int bufSize = 512;//輸入緩沖區大小
編譯器將在編譯過程中把用到該變量的地方都替換成對應的值。也就是說,編譯器會找到代碼中所有用到bufsize的地方,然后用512替換。
為了執行上述替換,編譯器必須知道變量的初始值。如果程序包含多個文件,則每個用了const對象的文件都必須得能訪問到它的初始值才行。要做到這一點,就必須在每一個用到變量的文件中都有對它的定義(參見2.2.2節,第41頁)。為了支持這一用法,同時避免對同一變量的重復定義,默認情況下,const對象被設定為僅在文件內有效。當多個文件中出現了同名的const變量時,其實等同于在不同文件中分別定義了獨立的變量。
某些時候有這樣一種const變量,它的初始值不是一個常量表達式,但又確實有必要在文件間共享。這種情況下,我們不希望編譯器為每個文件分別生成獨立的變量。相反,我們想讓這類const對象像其他(非常量)對象一樣工作,也就是說,只在一個文件中定義const,而在其他多個文件中聲明并使用它。
解決的辦法是,對于const變量不管是聲明還是定義都添加extern關鍵字,這樣只需定義一次就可以了:
// file_1.cc定義并初始化了一個常量,該常量能被其他文件訪問
extern const int bufSize = fcn() ;
// file_ 1.h頭文件
extern const int bufSize;//與file_1.cc中定義的bufsize是同一個
如上述程序所示,file_1.cc定義并初始化了bufsize。因為這條語句包含了初始值,所以它(顯然)是一次定義。然而,因為bufsize是一個常量,必須用extern加以限定使其被其他文件使用。
file 1.h頭文件中的聲明也由extern做了限定,其作用是指明bufsize并非本文件所獨有,它的定義將在別處出現。
如果想在多個文件之間共享const對象,必須在變量的定義之前添加extern關鍵字。
練習
//練習2.26:下面哪些句子是合法的?如果有不合法的句子,請說明為什么?
(a) const int buf;
(b) int cnt =0
(c) const int sz= cnt;
(d)++cnt; ++SZ
(a) const int buf;
這個句子是不合法的。在C++中,const
關鍵字用于聲明一個常量,它必須被初始化。因此,const int buf;
缺少初始化表達式,正確的聲明應該是const int buf = 0;
。
(b) int cnt = 0;
這個句子是合法的。它聲明了一個整型變量cnt
并初始化為0。
? const int sz = cnt;
這個句子是不合法的。這里有兩個問題:首先,const
變量必須在聲明時初始化,不能在聲明后賦值。其次,即使cnt
已經被聲明并初始化,sz
作為const
變量也不能從非const
變量cnt
那里賦值。正確的做法是直接在聲明時初始化,例如const int sz = 5;
。
(d) ++cnt; ++SZ;
首先,++cnt;
是合法的,它表示對變量cnt
進行自增操作。然而,++SZ;
是不合法的,因為SZ
沒有被聲明為一個變量,而且變量名通常不以大寫字母開頭,這是C++中常見的命名約定,盡管這不是語法錯誤。如果SZ
是一個未聲明的變量,那么這個句子將導致編譯錯誤。
關于const 引用
敲重點
🍌常量的引用 不能用作修改被綁定的對象
也就是說 原來的數據類型是const xx 類型 那么,對這個數據引用后就不能利用引用的變量去更改原有的值(好像有點繞 具體看下文)
??注意 常量的引用 和 **常量引用 ** 的不同 一個是被引用的變量 一個是變量本身 下面看的時候要認真一些
常量的引用 數據類型需要一致 相當于對一個const類型的變量進行引用,這個時候就叫常量的引用
而常量引用 是一個引用類型的變量
🍑在初始化常量引用時允許用任意表達式作為初始值,允許為一個常量引用綁定非常量的對象、字面值,甚至是個一般表達式。
🍊常量引用僅對引用可參與的操作做出了限定,對于引用的對象本身是不是一個常量未作限定。因為對象也可能是個非常量,所以允許通過其他途徑改變它的值:
可以把引用綁定到const對象上,就像綁定到其他對象上一樣,我們稱之為對常量的引用(reference to const)。與普通引用不同的是,對常量的引用不能被用作修改它所綁定的對象:
const int ci = 1024;
const int &r1 = ci; //正確:引用及其對應的對象都是常量
r1 = 42;//錯誤:r1是對常量的引用
int &r2=ci;//錯誤:試圖讓一個非常量引用指向一個常量對象
因為不允許直接為ci賦值,當然也就不能通過引用去改變ci。因此,對r2的初始化是錯誤的。假設該初始化合法,則可以通過r2來改變它引用對象的值,這顯然是不正確的。
常量引用是對const的引用
程序員們經常把詞組“對 const的引用”簡稱為“常量引用”,這一簡稱還是挺靠譜的,不過前提是你得時刻記得這就是個簡稱而已。
嚴格來說,并不存在常量引用。因為引用不是一個對象,所以我們沒法讓引用本身恒定不變。事實上,由于C+語言并不允許隨意改變引用所綁定的對象,所以從這層意義上理解所有的引用又都算是常量。引用的對象是常量還是非常量可以決定其所能參與的操作,卻無論如何都不會影響到引用和對象的綁定關系本身
初始化和對const 的引用
2.3.1節(第46頁)提到,引用的類型必須與其所引用對象的類型一致,但是有兩個例外。第一種例外情況就是在初始化常量引用時允許用任意表達式作為初始值,只要該表達式的結果能轉換成(參見2.1.2節,第32頁)引用的類型即可。尤其,允許為一個常量引用綁定非常量的對象、字面值,甚至是個一般表達式:
int i =42;·
const int &r1 = i;//允許將const int&綁定到一個普通int對象上
const int &r2=42; //正確:r1是一個常量引用
const int &r3 = r1 * //正確:r3是一個常量引用
int &r4 = r1 * 2;//錯誤:r4是一個普通的非常量引用
要想理解這種例外情況的原因,最簡單的辦法是弄清楚當一個常量引用被綁定到另外一種類型上時到底發生了什么:
double dval = 3.14;
const int &ri = dval;
此處ri引用了一個int 型的數。對ri的操作應該是整數運算,但dval卻是一個雙精度浮點數而非整數。因此為了確保讓ri綁定一個整數,編譯器把上述代碼變成了如下形式:
const int temp =dval;
//由雙精度浮點數生成一個臨時的整型常量
const int &ri = temp;
// 讓ri綁定這個臨時量
接下來探討當ri不是常量時,如果執行了類似于上面的初始化過程將帶來什么樣的后果。如果ri不是常量,就允許對ri賦值,這樣就會改變ri所引用對象的值。注意,此時綁定的對象是一個臨時量而非 dval。程序員既然讓 ri引用dval,就肯定想通過ri改變dval的值,否則為什么要給ri賦值呢? 如此看來,既然大家基本上不會想著把引用綁定到臨時量上,C++語言也就把這種行為歸為非法。
對const的引用可能引用一個并非const 的對象
必須認識到,常量引用僅對引用可參與的操作做出了限定,對于引用的對象本身是不是一個常量未作限定。因為對象也可能是個非常量,所以允許通過其他途徑改變它的值:
int i =42;
int &r1 = i;
//引用ri綁定對象i
const int &r2 = i;
// r2也綁定對象i,但是不允許通過r2修改i的值
r1 = 0;
//r1并非常量,i的值修改為0
r2 = 0;
//錯誤:r2是一個常量引用
r2綁定(非常量)整數i是合法的行為。然而,不允許通過r2修改i的值。盡管如此,i的值仍然允許通過其他途徑修改,既可以直接給i賦值,也可以通過像r1一樣綁定到i的其他引用來修改。
指針和const
🌈 敲重點?
和上一節一樣 常量指針和指向常量的指針 這倆是不一樣的!
??指向常量的指針 指向常量的指針不能用于改變其所指向對象的值。存放常量對象的地址 只能使用指向常量的指針。和引用一樣,指向常量的指針沒有規定所指向的對象是一個常量,僅僅要求不能通過該指針改變對象的值,沒有規定那個對象的值不能通過其他途徑改變。
🌊 常量指針 必須初始化 一旦初始化, 他的值(也就是存放再指針的地址)不會再改變了 。*放在const之前說明指針是一個常量,也就是不變的是指針本身的值而不是指向的那個值
要想弄清楚聲明的含義最行之有效的辦法是從右向左閱讀
指向常量的指針
指向常量的指針不能用于改變其所指向對象的值。存放常量對象的地址 只能使用指向常量的指針
帶有前綴 const
const double pi = 3.14;// pi是個常量,它的值不能改變
double *ptr = π//錯誤:ptr是一個普通指針const double *cptr = π//正確:cptr可以指向一個雙精度常量
*cptr = 42;//錯誤:不能給*cptr賦值double dval = 3.14;//dval是一個雙精度浮點數,它的值可以改變
cptr = &dval;//正確:但是不能通過cptr改變dval的值
指針類型不一定要與所致的對象一致
和引用一樣,指向常量的指針沒有規定所指向的對象是一個常量,僅僅要求不能通過該指針改變對象的值,沒有規定那個對象的值不能通過其他途徑改變。
常量指針
常量指針必須初始化 一旦初始化, 他的值(也就是存放再指針的地址)不會再改變了
*** 放在const之前說明指針是一個常量,也就是不變的是指針本身的值而不是指向的那個值**
int errNumb = 0;
int *const curErr = &errNumb;// curErr將一直指向errNumb
const double pi = 3.14159;
const double *const pip = π // pip是一個指向常量對象的常量指針
如同2.3.3節(第52頁)所講的,要想弄清楚這些聲明的含義最行之有效的辦法是從右向左閱讀。此例中,離curErr最近的符號是const,意味著curErr本身是一個常量對象,對象的類型由聲明符的其余部分確定。聲明符中的下一個符號是*,意思是 curErr是一個常量指針。最后,該聲明語句的基本數據類型部分確定了常量指針指向的是一個int對象。與之相似,我們也能推斷出,pip是一個常量指針,它指向的對象是一個雙精度浮點型常量。
指針本身是一個常量并不意味著不能通過指針修改其所指對象的值,能否這樣做完全依賴于所指對象的類型。例如,pip是一個指向常量的常量指針,則不論是pip所指的對象值還是pip自己存儲的那個地址都不能改變。相反的,curErr指向的是一個一般的非常量整數,那么就完全可以用curErr去修改errNumb的值:
*pip = 2.72;
//錯誤:pip是一個指向常量的指針
//如果curErr所指的對象(也就是errNumb)的值不為0
if(*curErr) {errorHandler ();*curErr = 0;//正確:把curErr所指的對象的值重置
}
練習
🐋 ?ps 對于不確定的式子 可以代入代碼敲一敲
//練習2.27:下面的哪些初始化是合法的?請說明原因。
(a)int i = -1,&r = 0;
(b) int *const p2 = &i2;
(c) const int i = -1,&r = 0;
(d) const int *const p3 = &i2;
(e) const int *pl = &i2;
(f)const int &const r2;
(g)const int i2= i,&r = i;
//練習2.28:說明下面的這些定義是什么意思,挑出其中不合法的。
(a)int i, *const cp;
(b)int *p1, *const p2;
(C) const int ic,&r = ic;
(d) const int *const p3;
(e)const int *p;
//練習2.29:假設已有上一個練習中定義的那些變量,則下面的哪些語句是合法的?請說明原因。
(a)i = ic;
(b)p1 = p3;
(c)p1 = ⁣
(d)p3 = ⁣
(e)p2= pl;
(f)ic = *p3 ;
🔑 答案答案
//練習2.27:下面的哪些初始化是合法的?請說明原因。
(a) int i = -1,&r = 0;
//可能一些小伙伴看不懂 這其實相當于 int i = -1,int &r = 0;
// 這類引用應初始化一個變量 而不是字面量 不合法(具體看引用篇)
(b) int *const p2 = &i2;
//p2 是一個常量指針 也就是地址不變的指針 如果i2是int類型的則合法, 只能指向int
(c) const int i = -1,&r = 0;
//正確 相當于const int &r=0; 是一個常量引用 可以指向字面量
//在初始化常量引用時允許用任意表達式作為初始值,允許為一個常量引用綁定非常量的對象、字面值,甚至是個一般表達式。
(d) const int *const p3 = &i2;
//指向常量的常量指針 不可以用這個指針更改i2的值 同時這個指針的地址也不會變(e) const int *pl = &i2;
//指向常量的指針 不可以用這個指針更改i2的值(f)const int &const r2;
//這個 應該是語法錯誤(g)const int i2= i,&r = i;
// 相當于 const int &r =i;
//如果i是常量 可以這么做
//練習2.28:說明下面的這些定義是什么意思,挑出其中不合法的。
(a)int i, *const cp;
//int *const cp 常量指針 初始化后不能變地址 也就是地址不能更改,且一定要初始化
(b)int *p1, *const p2;
//int *const p2 常量指針 初始化后不能變地址 也就是地址不能更改,且一定要初始化
(C) const int ic,&r = ic;
//需要初始化 const int &r=ic
(d) const int *const p3;
//需要初始化
(e)const int *p;
//指向常量的指針 需要指向const int 類型的值 不能通過指針改變指向的值的內容
//練習2.29:假設已有上一個練習中定義的那些變量,則下面的哪些語句是合法的?請說明原因。
(a)int i;const int ic;i = ic;
//合法 i只是用了ic的值
(b)int *p1;const int *const p3;p1 = p3;
//int 指針變量 =指向int 的常量指針 類型不一致(c)int *p1;const int icp1 = ⁣
//不合法 必須是const int *p1 才可(d) const int *const p3;const int ic;p3 = ⁣
//不合法 p3不可更改地址(e)int *const p2;int *p1p2= pl;
//常量指針 值不可更改 p2
//pl已經初始化了 (f) const int ic;const int *const p3;ic = *p3 ;
//不能 const類型 是常量 不可更改
完結 歡迎批評指正~ 點個贊點個贊~