把一個(gè)值賦值給一個(gè)無符號(hào) 類型的變量的時(shí)候,如果該值超過了該變量能夠表示的范圍,那么結(jié)果就是該變量獲得了該值經(jīng)過對(duì)表示范圍大小求模運(yùn)算后落在表示范圍內(nèi)的值。比如 1 個(gè)字節(jié)的
unsigned char能夠表示 0 到 255 區(qū)間內(nèi)的值,表示范圍大小為 256,如果你給它賦一個(gè) -3 (顯然不落在 [0,255]),那么該變量拿到的值是 -3 % 256 = -3 + 256 = 253。269 的話就是 269 % 256 = 269 - 256 = 13。相對(duì)地,把一個(gè)值賦值給一個(gè)有符號(hào) 類型的變量的時(shí)候,如果該值超過了該變量能夠表示的范圍,賦值結(jié)果是未定義的,該過程可以正確編譯,但是運(yùn)行了之后會(huì)發(fā)生什么事情是不可預(yù)知的,對(duì)于不同的編譯器可能有不同的結(jié)果,可能是崩潰,可能是不正確的數(shù)據(jù),還有可能是其他的事情,所以不要這么做。
有無符號(hào)數(shù)參與運(yùn)算的時(shí)候,結(jié)果一定是落在無符號(hào)數(shù)的表示范圍值內(nèi)的??焖倏谒愕臅r(shí)候可以先全當(dāng)作有符號(hào),之后把計(jì)算結(jié)果模到無符號(hào)表示范圍就行。但是實(shí)際上計(jì)算機(jī)不是這么計(jì)算的。
例如
int + unsigned int的時(shí)候,會(huì)把int轉(zhuǎn)為unsigned int。-42 + 10,其中-42是int類型,10是unsigned int類型,那么在加法運(yùn)算前,-42會(huì)轉(zhuǎn)為unsigned int的值,也就是 4294967254,然后再加10,變成 4294967264,落在了表示范圍內(nèi),所以結(jié)果就是這個(gè)。如果再多加一點(diǎn),例如給-42加的不是10而是50,運(yùn)算結(jié)果是 4294967304 超過了 2^32-1,那么對(duì)加法結(jié)果依然會(huì)求一次模,得到8。定義于函數(shù)體內(nèi)的內(nèi)置類型對(duì)象如果沒有初始化,則其值未定義;每個(gè)類自行決定起初始化對(duì)象的方式,自己決定是否允許不經(jīng)初始化就定義對(duì)象。
聲明 只規(guī)定變量的類型和名字,但是定義 還申請(qǐng)內(nèi)存空間,還可能做出初始化。因此要區(qū)分這兩者。
C++ 支持分離式編譯,因此一個(gè)文件中的代碼可能需要使用另一個(gè)文件中定義的變量。這種情況下,每個(gè)要使用該變量的文件中都要有對(duì)該變量的聲明,但是該變量的定義 只能在一個(gè)文件中出現(xiàn)。聲明 一個(gè)變量的時(shí)候使用
extern關(guān)鍵字,如extern int i;,這個(gè)時(shí)候不要初始化i,否則extern關(guān)鍵字失效,該語句變成了定義 (想要文件間共享const常量的除外,下面會(huì)有一條這個(gè)解釋)。在函數(shù)體內(nèi)部,如果試圖初始化一個(gè)由
extern關(guān)鍵字標(biāo)記的變量,將引發(fā)錯(cuò)誤。-
聲明空指針的時(shí)候,要顯式使用
// T 代表任意類型 T *p = nullptr;或
T *p = 0;但是不可以把一個(gè)值等于
0的int變量直接賦給指針,比如這樣子:int i = 0; T *p = i; // Cannot initialize a variable of type 'int *' with an lvalue of type 'int' 兩個(gè)合法指針進(jìn)行
==和!=比較的時(shí)候,是比較兩個(gè)指針?biāo)娴牡刂?/em>。當(dāng)兩者的地址都為空時(shí),==返回true;當(dāng)兩者都指向同一個(gè)對(duì)象時(shí),==返回true;當(dāng)兩個(gè)指針都指向同一個(gè)對(duì)象的下一地址時(shí),==也返回true。還有一種比較特殊的情況,當(dāng)一個(gè)指針指向某對(duì)象,另一個(gè)指針指向另外一個(gè)對(duì)象的下一地址,但是因?yàn)閮蓚€(gè)對(duì)象恰好在內(nèi)存中是相鄰 的,所以也會(huì)出現(xiàn) == 返回true的情況,一定不要忘記這種情形,void*指針比較特殊,可以存放任意類型對(duì)象的地址,但也因此失去了被解引用的能力:我們不能確定這個(gè)被指向的對(duì)象是什么類型,自然就不知道解引用后能做什么事情。因此void*指針能做的事情比較有限:去和別的指針做比較 (也就是比較指針?biāo)涗浀牡刂?;作為函數(shù)的輸入或輸出 (這點(diǎn)用的較多);把它的值賦給另外一個(gè)void*指針。int *&r = &i;這句話代表r指向i,但是具體而言r是i的指針的引用。-
常規(guī)的定義
const常量的方法 (編譯時(shí)初始化) 是這樣子的:const int num = 214;在這種定義方式下,編譯器將在編譯時(shí) 在這個(gè)文件中找到在這句代碼后所有用到
num變量的地方,然后把num替換成214(就像宏定義那樣)。 因此如果想要讓不同文件共享這個(gè)const常量,那么在每個(gè)文件中,都要有const int num = 214;這句定義 (不然就沒法替換啦)。然而這樣子的話,違反了前面說過的每個(gè)變量只能被定義一次 這個(gè)限定。因此在默認(rèn)狀態(tài)下,
const常量被設(shè)置成僅在所處的文件內(nèi)部有效,而文件外部就無法讀取。如果想在每個(gè)文件里都能用到同樣的const常量,那么要在每個(gè)文件內(nèi)部均定義同名的對(duì)象,但這個(gè)時(shí)候其實(shí)他們是不同的獨(dú)立的對(duì)象?;蛘撸梢栽谠?const常量聲明和定義的時(shí)候均添加extern關(guān)鍵字。然后在不同文件內(nèi)用extern描述該常量,即可做到只定義一次而能夠多文件間共享。 -
指向常量的引用僅對(duì)該引用可參與的操作進(jìn)行限定,指不能通過對(duì)被引用對(duì)象的引用來修改被引用對(duì)象的值。舉個(gè)例子:
const int a = 3; const int &b = a;第二句的
const的含義是不能通過修改b來修改a的值。由于在確定了引用綁定關(guān)系之后,引用不能易主,所以b是不能再引用別的對(duì)象的,同時(shí)b還不能修改a的值,所以b在整個(gè)生命周期能做的事情只有讀取a的值。 -
對(duì)某一個(gè)常量的引用實(shí)質(zhì)上是對(duì)這個(gè)常量起的別名,因此一般來說 它們類型相同 (也是有例外的),因此都要用
const來限定。企圖用int來引用一個(gè)const int會(huì)發(fā)生編譯時(shí)錯(cuò)誤,因?yàn)榫幾g器發(fā)現(xiàn)這個(gè)引用本身是int類型,那么編譯器會(huì)認(rèn)為寫代碼的人在之后可能會(huì)對(duì)這個(gè)int類型的引用作出修改,而這是被禁止的,所以編譯器要求對(duì)const對(duì)象的引用也要用const限定。但是另一種情況下,一個(gè)const int對(duì)象是可以引用一個(gè)int對(duì)象的,因?yàn)?const限定的是引用關(guān)系本身,你不能通過這個(gè)引用來改變被引用的變量的值,但是被引用的變量可以使用其他方式進(jìn)行改變 (例如通過直接調(diào)用這個(gè)變量的標(biāo)識(shí)符顯式的改變這個(gè)變量)。在這種情況下,你要確保被引用的非常量 (也就是上面栗子中的那個(gè)int) 可以轉(zhuǎn)換成引用的類型 (即const int)。舉個(gè)栗子:int i = 24; double pi = 3.14;在這時(shí)
const int &r1 = i;是可以的,因?yàn)?
24是一個(gè)字面值常量,const int &r2 = i - 3;也是可以的,因?yàn)?
24 - 3得到的也是一個(gè)字面值常量,const int &r3 = pi;還是可以的,這個(gè)時(shí)候編譯器會(huì)先把
pi變成一個(gè)const int temp = 3然后用
r3去引用這個(gè)temp,這個(gè)時(shí)候r3綁定了一個(gè)臨時(shí)量。反正你不可以通過r3來改變temp的值,所以temp就一直在那里,一直都是3,r3的值也一直都是3。但是這樣子是不行的:const int &r4 = "4"; // Reference to type 'const int' could not bind // to an lvalue of type 'const char[2]因?yàn)橐粋€(gè)
string不能轉(zhuǎn)換成一個(gè)const int。 -
和指向常量的引用一樣,指向常量的指針也只對(duì)通過指針對(duì)被指對(duì)象進(jìn)行的操作 進(jìn)行限制。因此,被引用的對(duì)象可以在其他地方通過顯式調(diào)用標(biāo)識(shí)符來修改。但是指向常量的指針本身不一定是常量,例如
const int a = 3; const int *p = &a; //Implicit conversion from 'double' to 'int' changes value from 3.14 to 3 -
中的
p目前指向的是a,接下來可以通過const int b = 4; p = &b;來改變
p指向的對(duì)象。 指針是一個(gè)對(duì)象,所以也可以限定一個(gè)指針為一個(gè)常量,這個(gè)時(shí)候指針本身是不能改變的,也就是說指針?biāo)鶅?chǔ)存的地址是不能改變的,也就是說這個(gè)指針一輩子只能指向那一個(gè)對(duì)象了。這個(gè)時(shí)候
const的位置有所改變,從const int *p變成int *const p,const離p最近,因此首先p是個(gè)常量,然后他還是個(gè)指針。此時(shí)被指向的那個(gè)對(duì)象不僅可以是常量,同時(shí)也可以為變量。-
指針本身是一個(gè)常量,并不意味著不可以借助指針來修改被指向的變量,只要被指向的對(duì)象是個(gè)變量,那就可以通過它的指針來修改它的值,不管這個(gè)指針本身是不是常量。
所謂頂層
const和底層const,頂層代表對(duì)象本身是const,底層代表對(duì)象所引用、指向的對(duì)象是const。所有的引用都是底層const。見下面的例子:int i = 0; int *const pi = &i; // pi 是頂層,因?yàn)?pi 不能改變 const int ci = 0; // ci 是頂層,因?yàn)?ci 不能改變 const int *p0 = &i; // p0 是底層,因?yàn)?p0 可以改變 const int *p1 = &ci; // p1 是底層,因?yàn)?p1 可以改變 const int *const p2 = &ci; // int 右的 const 是頂層,int 左 的 const 是底層 const int &r0 = i; // r0 是底層,用于聲明引用的 const 都是底層 const int &r1 = ci; // r1 是底層,用于聲明引用的 const 都是底層頂層還是底層請(qǐng)看注釋。當(dāng)執(zhí)行對(duì)象的拷貝動(dòng)作時(shí),底層的存在感高于頂層。對(duì)于指針的拷貝,頂層不能接受一切拷貝,底層可以接受一切拷貝,變量可以接受頂層但是不可以接受底層。對(duì)于引用,不能把
int &綁定到const int上,但是可以把const int &綁定到int上。 -
舉一個(gè)例子:
const int r1 = 30; const int r2 = r1 - 3;這么寫的確沒問題,但是還有一種更加推薦的 C++11 提出的寫法,是
constexpr int r1 = 30; constexpr int r2 = r1 - 3;這么寫的主要原因是像
30、r1、r1 - 3這些值不會(huì)改變,并且在編譯時(shí)就能得到計(jì)算結(jié)果的簡單表達(dá)式 被稱作常量表達(dá)式,在實(shí)際應(yīng)用中,幾乎不可能分辨一個(gè)初始值是不是常量表達(dá)式,因此 C++11 規(guī)定允許將變量聲明為constexpr類型以便由編譯器來驗(yàn)證變量的值是不是一個(gè)常量表達(dá)式。如果你在寫代碼時(shí)認(rèn)定一個(gè)變量是一個(gè)常量表達(dá)式,那么你就直接用constexpr限定它。你也可以定義一種constexpr函數(shù),這種函數(shù)應(yīng)該足夠簡單以使得編譯器在編譯時(shí)就可以計(jì)算其結(jié)果。這樣你就可以用constexpr函數(shù)去初始化constexpr變量。 因?yàn)槟阈枰尵幾g器在編譯時(shí)就可以計(jì)算出結(jié)果,所以聲明為
constexpr的時(shí)候所用到的類型要足夠簡單,我們把這些簡單的、顯而易見的、容易得到的類型成為字面值類型,其中包含算數(shù)類型 (內(nèi)置類型中的bool、char、short、int、long、long long、float、double、long double)、引用和指針等類型。自定義的類、IO 庫、string 類則不屬于字面值類型,也就不能被定義成constexpr。事實(shí)上,constexpr函數(shù)的內(nèi)容僅可以有一條return語句,而且return的類型一定是字面值類型,形參的類型也都得是字面值類型。-
constexpr把它所定義的對(duì)象置為了頂層const,所以const int *p = nullptr;和
constexpr int* p = nullptr;有著天壤之別,前者是指
p是一個(gè)指向整形常量的指針,后者是指p是一個(gè)指向整形的常量指針。 -
在使用
typedef或者using語句的時(shí)候要注意,如果你新定義的類型別名指代的是符合類型或者常量,那么在一個(gè)聲明語句中使用新類型別名的時(shí)候,如果加上const修飾符,那么語義會(huì)發(fā)生變化。例如:typedef char *pstring; const pstring cstr = 0; const pstring *ps;當(dāng)理解第二句的
const的作用的時(shí)候,pstring千萬不能直接替換成char *。這個(gè)時(shí)候要把pstring當(dāng)成普通的int來理解,這個(gè)const修飾pstring使得cstr是一個(gè)常量。因此第二句的含義是:聲明一個(gè)常量,他是個(gè)指針,他指向一個(gè)char類型的對(duì)象,并把它定義為空指針。因此第三句話的含義也顯而易見了:聲明一個(gè)指針,它自己并不是常量 (因?yàn)?const在星號(hào)的左邊),但是它指向了一個(gè)常量,他指向的常量的具體類型是pstring,也就是一個(gè)指向一個(gè)char對(duì)象的常量指針。第三句話有點(diǎn)繞,簡單說就是ps是一個(gè)指向指向char的常量指針 的變量指針。 如果在
auto語句中連著聲明定義多個(gè)變量,那么這些變量要是同一個(gè)類型。-
在
auto語句中,如果用一個(gè)引用來初始化一個(gè)變量,那么這個(gè)變量的類型和被引用的對(duì)象的一樣:int i = 0, &r = i; auto a = r;這里的
a的類型是int。 -
auto一般會(huì)忽略掉頂層const,但是會(huì)保留底層const。如果你希望auto可以推斷出頂層const,那么你要明確寫出const限定符:const int ci = i, &cr = ci;// ci 是頂層,cr 是底層 auto b = ci; // b 不是常量,是一個(gè) int auto c = cr; // c 不是常量,是一個(gè) int auto d = &i; // d 不是常量,是一個(gè) int * auto d = &i; // d 不是常量,是一個(gè) const int * const auto f = ci; // f 是一個(gè) const int const auto g = &ci; // g 是一個(gè) const int *const //由于引用本身就是底層 const,所以會(huì)被保留下來 auto &h = ci; // h 是一個(gè) const int & auto &j = 29; // 編譯錯(cuò)誤:不能給非常量引用綁定字面值 const auto &k = 29; // 編譯通過:寫出了 const 才能綁定字面值 decltype() 可以分析表達(dá)式的類型并且返回它。例如: ```C++ decltype(f()) = x; // x 的類型與函數(shù) f() 的返回類型相同 const int ci = 9, &ri = ci; int i = 1, *pi = &i; decltype(ci) x = 0; // x 是 const int decltype(ri) s = x; // s 是 const int &,只傳標(biāo)識(shí)符,那么就返回引用類型 decltype(ri+0) t = s; // s 是 const int,表達(dá)式會(huì)消除掉引用 decltype(i) y = x; // y 是 int decltype(i+3) z = x; // z 是 int decltype((i)) w = x; // w 是 int &,當(dāng)變量外面套著一層括號(hào)的時(shí)候,將被視作表達(dá)式,返回引用 decltype(pi) p = &i; // p 是 int * decltype(*pi) r = i; // r 是 int &,解引用操作返回引用類型總結(jié)一下:對(duì)于引用,直接傳入
decltype會(huì)返回引用類型,普通變量外面套一層括號(hào)傳進(jìn)去也會(huì)返回引用類型,引用放在表達(dá)式里面?zhèn)魅?decltype會(huì)返回被引用對(duì)象的類型。 decltype可以保留頂層 const 和引用類型,但是auto不可以。預(yù)處理器是在編譯之前執(zhí)行的一段程序,可以部分的改變程序內(nèi)容,包括例如
NULL、assert()、#include、#ifdef、#ifndef、#pragma這些東西。當(dāng)預(yù)處理器看到#include這個(gè)標(biāo)記的時(shí)候,就會(huì)把指定的頭文件的內(nèi)容代替這句#include語句。 而像ifdef、endif這些則叫做頭文件保護(hù)符,define用于定義預(yù)處理變量,ifdef、ifndef用于檢查哪個(gè)預(yù)處理變量是否已經(jīng)定義,一旦結(jié)果為真,則執(zhí)行后續(xù)操作直到遇到#endif位置。預(yù)處理變量包括頭文件保護(hù)符無視 C++ 中的作用域的規(guī)則,并且在整個(gè)工程文件中必須唯一。習(xí)慣性的把所有頭文件用頭文件保護(hù)符包含住是值得推薦的。
第 2 章:變量和基本類型
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
相關(guān)閱讀更多精彩內(nèi)容
- #1.基本內(nèi)置類型1.1 算術(shù)類型1.2 類型轉(zhuǎn)換1.3 字面值常量 #2.變量2.1 變量定義2.2 變量定義和...
- 2.1 基本內(nèi)置類型 2.1.1 算數(shù)類型 空類型(void):無值無操作,不能定義void類型變量 字符類型:c...
- 基本內(nèi)置類型 算術(shù)類型字符整型布爾值浮點(diǎn)數(shù) 空類型(void) 算術(shù)類型 帶符號(hào)類型和無符號(hào)類型int、short...
- 初始化與賦值初始化的含義是創(chuàng)建變量的時(shí)候賦予其一個(gè)初始值,賦值的含義是把對(duì)象的當(dāng)前值擦除,而以一個(gè)新值來替代。列表...