第 2 章:變量和基本類型

  1. 把一個(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。

  2. 相對(duì)地,把一個(gè)值賦值給一個(gè)有符號(hào) 類型的變量的時(shí)候,如果該值超過了該變量能夠表示的范圍,賦值結(jié)果是未定義的,該過程可以正確編譯,但是運(yùn)行了之后會(huì)發(fā)生什么事情是不可預(yù)知的,對(duì)于不同的編譯器可能有不同的結(jié)果,可能是崩潰,可能是不正確的數(shù)據(jù),還有可能是其他的事情,所以不要這么做。

  3. 有無符號(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ì)算的。

  4. 例如 int + unsigned int 的時(shí)候,會(huì)把 int 轉(zhuǎn)為 unsigned int。-42 + 10,其中 -42int 類型,10unsigned 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。

  5. 定義于函數(shù)體內(nèi)的內(nèi)置類型對(duì)象如果沒有初始化,則其值未定義;每個(gè)類自行決定起初始化對(duì)象的方式,自己決定是否允許不經(jīng)初始化就定義對(duì)象。

  6. 聲明 只規(guī)定變量的類型和名字,但是定義 還申請(qǐng)內(nèi)存空間,還可能做出初始化。因此要區(qū)分這兩者。

  7. 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è)解釋)。

  8. 在函數(shù)體內(nèi)部,如果試圖初始化一個(gè)由 extern 關(guān)鍵字標(biāo)記的變量,將引發(fā)錯(cuò)誤。

  9. 聲明空指針的時(shí)候,要顯式使用

    // T 代表任意類型
    T *p = nullptr;
    

    T *p = 0;
    

    但是不可以把一個(gè)值等于 0int 變量直接賦給指針,比如這樣子:

    int i = 0;
    T *p = i; 
    // Cannot initialize a variable of type 'int *' with an lvalue of type 'int'
    
  10. 兩個(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 的情況,一定不要忘記這種情形,

  11. void* 指針比較特殊,可以存放任意類型對(duì)象的地址,但也因此失去了被解引用的能力:我們不能確定這個(gè)被指向的對(duì)象是什么類型,自然就不知道解引用后能做什么事情。因此 void* 指針能做的事情比較有限:去和別的指針做比較 (也就是比較指針?biāo)涗浀牡刂?;作為函數(shù)的輸入或輸出 (這點(diǎn)用的較多);把它的值賦給另外一個(gè) void* 指針。

  12. int *&r = &i; 這句話代表 r 指向 i,但是具體而言 ri 的指針的引用。

  13. 常規(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 描述該常量,即可做到只定義一次而能夠多文件間共享。

  14. 指向常量的引用僅對(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 的值。

  15. 對(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。

  16. 和指向常量的引用一樣,指向常量的指針也只對(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
    
  17. 中的 p 目前指向的是 a,接下來可以通過

    const int b = 4;
    p = &b;
    

    來改變 p 指向的對(duì)象。

  18. 指針是一個(gè)對(duì)象,所以也可以限定一個(gè)指針為一個(gè)常量,這個(gè)時(shí)候指針本身是不能改變的,也就是說指針?biāo)鶅?chǔ)存的地址是不能改變的,也就是說這個(gè)指針一輩子只能指向那一個(gè)對(duì)象了。這個(gè)時(shí)候 const 的位置有所改變,從 const int *p 變成 int *const p,constp 最近,因此首先 p 是個(gè)常量,然后他還是個(gè)指針。此時(shí)被指向的那個(gè)對(duì)象不僅可以是常量,同時(shí)也可以為變量。

  19. 指針本身是一個(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 上。

  20. 舉一個(gè)例子:

    const int r1 = 30;
    const int r2 = r1 - 3;
    

    這么寫的確沒問題,但是還有一種更加推薦的 C++11 提出的寫法,是

    constexpr int r1 = 30;
    constexpr int r2 = r1 - 3;
    

    這么寫的主要原因是像 30r1、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 變量。

  21. 因?yàn)槟阈枰尵幾g器在編譯時(shí)就可以計(jì)算出結(jié)果,所以聲明為 constexpr 的時(shí)候所用到的類型要足夠簡單,我們把這些簡單的、顯而易見的、容易得到的類型成為字面值類型,其中包含算數(shù)類型 (內(nèi)置類型中的 boolchar、shortint、long、long longfloat、double、long double)、引用和指針等類型。自定義的類、IO 庫、string 類則不屬于字面值類型,也就不能被定義成 constexpr。事實(shí)上,constexpr 函數(shù)的內(nèi)容僅可以有一條 return 語句,而且 return 的類型一定是字面值類型,形參的類型也都得是字面值類型。

  22. constexpr 把它所定義的對(duì)象置為了頂層 const,所以

    const int *p = nullptr;
    

    constexpr int* p = nullptr;
    

    有著天壤之別,前者是指 p 是一個(gè)指向整形常量的指針,后者是指 p 是一個(gè)指向整形的常量指針。

  23. 在使用 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 的常量指針 的變量指針。

  24. 如果在 auto 語句中連著聲明定義多個(gè)變量,那么這些變量要是同一個(gè)類型。

  25. auto 語句中,如果用一個(gè)引用來初始化一個(gè)變量,那么這個(gè)變量的類型和被引用的對(duì)象的一樣:

    int i = 0, &r = i;
    auto a = r;
    

    這里的 a 的類型是 int

  26. 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ì)象的類型。

  27. decltype 可以保留頂層 const 和引用類型,但是 auto 不可以。

  28. 預(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ù)符包含住是值得推薦的。

最后編輯于
?著作權(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ù)。

相關(guān)閱讀更多精彩內(nèi)容

  • #1.基本內(nèi)置類型1.1 算術(shù)類型1.2 類型轉(zhuǎn)換1.3 字面值常量 #2.變量2.1 變量定義2.2 變量定義和...
    MrDecoder閱讀 609評(píng)論 0 2
  • 2.1 基本內(nèi)置類型 2.1.1 算數(shù)類型 空類型(void):無值無操作,不能定義void類型變量 字符類型:c...
    咸魚翻身ing閱讀 285評(píng)論 0 0
  • 基本內(nèi)置類型 算術(shù)類型字符整型布爾值浮點(diǎn)數(shù) 空類型(void) 算術(shù)類型 帶符號(hào)類型和無符號(hào)類型int、short...
    2625K閱讀 3,627評(píng)論 0 1
  • 初始化與賦值初始化的含義是創(chuàng)建變量的時(shí)候賦予其一個(gè)初始值,賦值的含義是把對(duì)象的當(dāng)前值擦除,而以一個(gè)新值來替代。列表...
    KevinCool閱讀 373評(píng)論 0 0
  • Dear, 2018年5月4日,在得知自己被心儀單位差掉的時(shí)候,再一次感受無法把握的無奈感,其實(shí)真的還是挺難過的...
    靜默行走閱讀 307評(píng)論 0 0

友情鏈接更多精彩內(nèi)容