C++Primer 第二章學習筆記

變量和基本類型

2.1 基本內置類型

2.1.1 算數類型

  • 基本的字符類型是 char ,一個 char 的空間應確保可以存放機器基本字符集中任意字符對應的數字值。也就是說,一個 char 的大小和一個機器字節一樣。

    • char 隨編譯器的不同表現爲 unsigned charsigned char
  • C++語言規定,一個 int 至少和一個 short 一樣大, long 至少和 int 一樣大, long long 至少和 long 一樣大。 long long 是在C++11中新定義的。

  • 如何選擇類型

    • 當已明確數值不可能爲負值時,選用無符號類型數。
    • 使用 int 執行整數運算。因爲在實際應用中, short 常常顯得太小, long 一般和 int 有一樣的尺寸。如果數值超過 int 的範圍,選用 long long
    • 在算數表達式中不要使用 charbool,只有在存放字符或布爾值時纔是用它們。因爲 char在一些機器上是有符號的,而在另一些機器上又是無符號的,所以 char進行運算特別容易出問題。如果需要使用一個不大的整數,那麼明確指明它的類型是 signed char或者 unsigned char
    • 浮點運算用 double,因爲 float又是精度不夠而且兩者的計算代價相差無幾。事實上,對於某些機器來說, double甚至快於 floatlong double提供的精度一般是沒必要的,而且其運算時的消耗也不容忽視。

2.1.2 類型轉換

    在這裏插入圖片描述

  • 當把一個非布爾的算數值賦給 bool時, 0false,其餘爲 true 即爲1。

  • 當把一個布爾賦給一個非布爾時, false0true1

  • 浮點轉整形只保留整數部分。

  • 整形轉浮點小數部分即爲0。如果該整數所佔的空間超過了浮點類型的容量,精度可能有損失。

  • 當賦給一個無符號類型超出其範圍的值時,結果爲該值對無符號類型表示數值總數取模後的餘數。8比特表示0至255,因此-1賦給8比特的 unsigned char 所得結果爲255.

  • 當賦給一個帶符號類型超出其範圍的值時,結果爲 未定義的(undefined 。此時,程序可能繼續工作、可能崩潰,也可能產生垃圾數據。

  • 當一個表達式中既有 int 又有 unsigned 時,若 int 爲負值時,會強轉成正數,造成結果錯誤。

2.1.3 字面值常量

  • 整形字面值可寫作十進制數、八進制數或十六進制數。以 0 開頭的整數代表八進制數,以 0x0X 開頭的代表十六進制數。

  • 字面值 ‘A’ 表示的就是單獨的字符 A ,而字符串 “A” 則代表了一個字符的數組,該數組包含兩個字符:一個是字母 A 、另一個是空字符。

  • 需要注意

    std::cout << "asdf" "asdf " << std::endl;
    

    這樣的書寫方式是正確的。

  • 轉義序列雖然有兩個字符,如 \n 等,但使用時當做一個字符處理,用單引號引起,書寫爲

    std::cout << '\n';
    
  • 泛化的轉義序列,其形式爲 \x 後加十六進制數或 \ 後加八進制數,其數字爲字符對應的數值。假設使用的是Latin-1字符集(Latin1是ISO-8859-1的別名,有些環境下寫作Latin-1。ISO-8859-1編碼是單字節編碼,向下兼容ASCII,其編碼範圍是0x00-0xFF,0x00-0x7F之間完全和ASCII一致,0x80-0x9F之間是控制字符,0xA0-0xFF之間是文字符號。) ,以下是一些示例:
    \7 (響鈴), \12 (換行符) , \115 (字符M,115爲8進制數), \x4d (同爲字符M,4d爲十六進制數)。

  •     
    還可以將U與L或LL合在一起使用,例如以UL爲後綴的將字面值的數據類型將根據具體數值情況取 unsigned longunsigned long long

2.2 變量

  • 初始化不是賦值,初始化的含義是在創建變量時賦予其一個初始值,而賦值的含義是把對象的當前值擦除,而以一個新值來替代。

  • 對於一個 int 的初始化,以下的方式都是正確的:

    int num1 = 0;
    int num2 = (0); 
    int num3 = { 0 };
    int num4(0);
    int num5{ 0 };
    
  • 作爲 C++11 新標準的一部分,用花括號來初始化變量得到了全面應用,這種初始化的形式被稱爲列表初始化。現在,無論是初始化對象還是某些時候爲對象賦新值,都可以使用一組花括號括起來的初始化了。
    但需注意,當用於內置類型的變量時,這種初始化有一個重要特點:如果我們使用列表初始化且初始值存在丟失信息的風險,則編譯器將報錯:

    long double ld = 3.1415926;
    int a{ ld }, b{ ld };       //錯誤:轉換未執行,因爲存在丟失信息的危險
    int c(ld), d = ld;        //正確:轉換執行,且確實丟失了部分值
    

    編譯器報錯,前兩句爲error,後兩句爲warning在這裏插入圖片描述

2.2.2 變量聲明和定義的關係

  • 變量只能被定義一次,但是可以被多次聲明。
  • 如果給 extern 關鍵字標記的變量賦初始值,將抵消 extern 的作用。extern 語句如果包含初始值就不再是聲明,而變成定義了。在函數體內部,如果試圖初始化一個由 extern 關鍵字標記的變量,將引發錯誤。

名字的作用域

  • 嵌套作用域中,內層作用域能訪問外層作用域。同時, 允許在內層作用域中重新定義外層作用域已有的名字
  • 函數內部不宜定義與全局變量同名的形變量。
  • ::加變量名可以顯式地訪問全局變量。
  • 如果有函數可能用到某全局變量,則不宜再定義一個同名的全局變量。

2.3.1 引用

  • 引用就是爲對象起了另一個名字。一般在初始化變量時,會產生一個新對象,將初始值拷貝到新對象中。然而定義引用時,程序把引用和它的初始值綁定在一起,而不是將初始值拷貝給引用。一旦初始化完成,引用將和它的初始值對象一直綁定在一起。因爲無法令引用重新綁定到另外一個對象,因此引用必須初始化
  • 需要注意:引用並非對象,相反的,它只是爲一個已存在的對象所起的另外一個名字,因爲引用本身不是一個對象,所以不能定義引用的引用

2.3.2 指針

  • 指針本身是對象,允許對指針賦值和拷貝,並且可以先後指向幾個不同的對象。

  • 指針可以無需在定義時賦值,指針的類型要和它所指向的對象嚴格匹配。

  • 解引用操作僅適用於那些確實指向了某個對象的有效指針。

  • 初始化指針時:

    int *p1 = nullptr;
    int *p2 = 0;
    int *p3 = NULL;
    

    等價,都能生成空指針。用nullptr初始化指針,是C++11新標準引入的一種方法。NULL是一個預處理變量,其值就是0。把 int 變量直接賦給指針是不行的,即使 int 變量的值爲 0 。

  • 不能直接操作 void* 指針所指的對象,因爲我們並不知道這個對象到底是什麼類型,也無法確定能在該對象上做什麼操作。

  •     在這裏插入圖片描述

2.3.3 理解複合類型的聲明

  •     在這裏插入圖片描述
  • 面對一條比較複雜的指針或引用的聲明語句時,從左往右閱讀有助於弄清楚它的真正含義。
    int i = 42;
    int* p = &i;
    int*& r = p;
    
    r 最近的符號是 & ,因此r是一個引用。* 說明r引用的是一個指針。 int 指出 r 引用的是一個 int 指針。

2.4 const 限定符

  • const 對象一旦創建後其值就不能再改變,所以 const 對象必須初始化。
  • 默認狀態下, const 對象僅在文件內有效。當多個文件中出現同名的 const 變量,其實就等同於在不同文件中分別定義了獨立的變量。但可以通過 extern 關鍵字來引用其他文件中的 const 變量。
  • 如果想在多個文件之間共享 const 對象,必須在變量的定義之前添加 extern 關鍵字。

2.4.1 const 的引用

  • const 變量的引用需要常量引用,需要用 const 引用。

  • 允許 const int& 直接引用一個常數或表達式,這對於普通的 int& 是不允許的。
    在這裏插入圖片描述

  •     在這裏插入圖片描述
    像上圖這樣即使改變 dval 的值也無法改變 ri 的值,但如果 dvalint 類型,咋改變 dval 的值則 ri 隨之改變。

  • 我所理解的 const 引用,例如 int i= 10; const int& a = i; 只是不允許通過 a 來改變 i 的值,但仍可以通過改變 i 的值來使 a 發生改變,其餘地方與普通引用無異。

2.4.2 指針和 const

  • 指向常量的指針不能用於改變所指對象的值。要想存放常量對象的地址,只能使用指向常量的指針。

  • 和常量引用一樣,指向常量的指針也可以指向一個非常量。所謂指向常量的指針僅僅要求不能通過該指針改變對象的值,而沒有規定那個對象的值不能通過其他途徑改變。

  • 試試這樣想吧:所謂指向常量的指針或引用,不過是指針或引用“自以爲是”罷了,他們覺得自己指向了常量,所以自覺地不去改變所指對象的值。

const 指針

  • 指針也是對象,因此允許把指針本身定爲常量。常量指針必須初始化,而且一旦初始化,則它的值(也就是存放在指針中的那個地址)就不能再改變了。

  • const int *a = &i; 意味着不能通過 *a 來改變 i 的值; int *const a = &i; 意味着從定義開始,指針 a 就要一直指向變量 i 的地址,不能改變,但這種情況允許通過改變 *a 的值來改變 i 的值,即可以改變 a 所指向的地址的內容,但不可改變其所指的地址;而 const int *const a = i; 則說明 a 是一個指向常量對象的常量指針,既不能改變所指地址的值,也不能通過改變 *a 的值來改變其所指向的地址的內容。

2.4.3 頂層 const

  • 指針本身是一個對象,它又可以指向另一個對象。因此,指針本身是不是常量以及指針所指的是不是一個常量就是兩個相互獨立的文藝。用名詞 頂層 const (top-level const) 表示指針本身是個常量, 而用名詞 底層 const(low-level) 表示指針所指的對象是一個常量。指針類型既可以是頂層 const 也可以是底層 const。例如 const int *const a = &i; 靠右的 const 是頂層 const ,靠左的是底層 const在這裏插入圖片描述

2.4.4 constexpr 和常量表達式

  • 常量表達式(const expression) 是指值不會改變並且在編譯過程就能得到計算結果的表達式。例如: const int sz = get_size(); // sz 不是常量表達式 因爲儘管 sz 本身是一個常量,但它的具體值知道運行時才能獲取到,所以也不是常量表達式。

  • C++11 新標準規定,允許將變量聲明爲 constexpr 類型以便由編譯器來驗證變量的值是否是一個常量表達式。聲明爲 constexpr 的變量一定是一個常量,而且必須用常量表達式初始化。

  • 一般來說,如果你認定變量是一個常量表達式,那就把它聲明成 constexpr 類型。

指針和 constexpr

  • constexpr 聲明中如果定義了一個指針,限定符 constexpr 僅對指針有效,與指針所指的對象無關;

    const int *p = nullptr; //p 是一個指向整形常量的指針
    constexpr int *q = nullptr; //q 是一個指向整數的常量指針
    

    pq 的類型相差甚遠,其中的關鍵在於 constexpr 把它所定義的對象只爲了頂層 const

  • constexpr 指針既可以指向常量也可以指向一個非常量。

2.5 處理類型

2.5.1 類型別名

  • 傳統方法是使用關鍵字 typedef

    typedef double wages;
    typedef wages base, *p; // p 是 double* 的同義詞
    
  • 新標準規定了一種新的方法,使用別名聲明來定義類型別名:

    using SI = Sales_item; //SI 是 Sales_item 的同義詞
    

    這種方法用關鍵字 using 作爲別名聲明的開始,其後緊跟別名和等號,其作用是把等號左側的名字規定成等號右側類型的別名。
    在這裏插入圖片描述

  • const char *cstr = 0; //是對 const pstring cstr 的錯誤理解

2.5.2 auto 類型說明符

  • auto 的定義的變量必須有初始值。

  • 使用 auto 也能在一條語句中聲明多個變量。因爲一條聲明語句只能有一個基本數據類型,所以該語句中所有變量的初始基本數據類型都必須一樣:

    auto i = 0, *p = &i; // 正確
    auto sz = 0, pi = 3.14; // 錯誤
    
  • auto 一般會忽略掉 頂層const ,同時 底層const 則會保留下來。在這裏插入圖片描述
    如果希望推斷出的 auto 類型是一個 頂層const ,則需明確指出:

    const auto f = ci; //ci 的推演類型是 int,f 是 const int
    

    還可以將引用的類型設爲 auto ,此時原來的初始化規則任然適用在這裏插入圖片描述

2.5.3 decltype 類型指示符

  • 當我們希望從表達式的類型推斷出要定義的變量的類型,但是不想用該表達式的值初始化變量。可以用 C++11 引入的第二種類型說明符 decltype ,它的作用是選擇並返回操作數的數據類型。在此過程中,編譯器分析表達式並得到它的類型,卻不實際計算表達式的值:

    decltype(f()) sum = 0; // sum 的類型就是函數 f 的返回類型 
    

    在這個過程中編譯器並不實際調用函數 f , 而是使用當調用發生時 f 的返回值類型作爲 sum 的類型。換句話說,編譯器爲 sum 指定的類型是什麼呢?就是假如 f 被調用的話將會返回的那個類型。

    在這裏插入圖片描述

  • 解引用指針可以得到指針所指的對象,而且還能給這個對象賦值。因此,解指針操作得到的是引用,正如下面 decltype(*p) 的結果類型就是 int& ,而非 int
    在這裏插入圖片描述

  • decltype 使用的變量如果加上了一層或多層括號,會得到引用在這裏插入圖片描述

  • 賦值時會產生引用的一類典型表達式,引用的類型就是左值的類型。也就是說,如果 iint ,則表達式 i=x 的類型是 int&

2.6.1 定義 Sales_data 類型

  • struct 右側的表示結束的花括號後必須寫一個分號,這是因爲類題後面可以緊跟變量名以示對該類型對象的定義,所以分號必不可少。
    在這裏插入圖片描述

預處理器概述

在這裏插入圖片描述

頂層 const 是一個 const ,規定某對象的值不能改變。

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