C++ primer 第二章筆記及習題

chapter2 變量和基本類型

筆記

2.1基本內置類型

2.1.1算術類型

  • 算術類型:整型(包括字符和bool),浮點型。
  • 空類型:不對應具體值,僅用於某些特殊場合。如函數不返回任何值時使用空類型作爲返回類型。
  • 無符號類型中的所有比特都用來存儲值。
  • C++標準中約定,有符號數在表示範圍內正值和負值的量應該平衡。

2.1.2類別轉換

  • 非布爾型賦值給布爾型:非0爲Ture,0爲False。
  • 布爾賦型值給非布爾型:Ture爲1,False爲0。
  • 浮點型賦值整型:近似處理,結果值僅保留浮點數的整數部分。
  • 整型轉浮點型:小數部分取0。
  • 當賦一個超出其範圍的值給無符號類型時,結果爲初始值對無符號類型表示數字總數取模後的餘數。
  • 當給帶符號類型賦超過其表示範圍的數值時,其結果是未定義的,可能導致程序崩潰。
  • 切勿混用帶符號類型和無符號類型。當表達式中既有帶符號類型又有無符號類型時,帶符號數會自動轉換成無符號數,程序會正確運行,但不會得到我們期望的結果。

2.1.3字面值常量

  • 整型字面值
    • 形式
      • 八進制:以0開頭
      • 十進制:正常表示
      • 十六進制:以0x開頭
  • 浮點數字面值(默認double)
    • 形式
      • 小數:3.141590..001
      • 科學計數法:3.14159E00e0 指數部分用E或e標識。
  • 字符和字符串字面值
    • 形式
      • 字符字面值:用單引號括起來,‘a’
      • 字符串字面值:用雙引號括起來,“abc”
      • 注意:字符串字面值實際上是由常量字符構成的數組,在每個字符串結尾處添加空字符’\0’,字符串字面值的實際長度比它的內容多1
  • 轉義序列
    • 有兩類字符無法直接使用
      • 不可打印的字符
      • 在C++中有特殊含義的字符(引號雙引號等)
      • 這些情況下需要用到轉義序列(\t\n等)
    • 泛化轉義類型
      • \x後緊跟一個或多個十六進制數字
      • \後緊跟1個、2個、3個八進制數字。數字部分表示字符對應的數值。
  • 指定字面值的類型
    • 通過添加前綴和後綴,我們可以改變字面值的默認類型。
      • 整型字面值:u或U表示unsigned,l或L表示有long,ll或LL表示longlong。
      • 浮點數字面值:f或F表示float,l或L表示double。
      • 字符和字符串:u表示Unicode16,U表示Unicode32,L表示寬字符,u8表示UTF-8(僅用於字符串字面常量)
  • 布爾字面值和指針字面值
    • Ture和False是布爾類型的字面值。
    • nullptr是指針字面值。

2.2變量

變量提供一個具名的,可供程序操作的存儲空間。

2.2.1變量定義

  • 定義形式:類型說明符後接一個或多個以逗號分割的變量名組成的列表,最後以分號結束。定義時可以爲一個或多個變量賦值。
    • 例子:int a;
    • int a,b,c=5;
  • 初始值
    • 對象在創建時獲得了一個值,我們稱其爲初始化
    • 定義完成的變量立刻就可以使用,因此在同一條定義語句中,可以用先定義的變量值去初始化後定義的其它變量。
      • 例子:double price = 109.99, discount = price * 0.16;
    • 初始化與賦值:初始化不是賦值,初始化的含義是創建變量時賦予其一個初始值,而賦值的含義是把對象的當前值擦除,而以一個新值來替代。
  • 列表初始化
    • 花括號來來初始化變量的形式被稱爲列表初始化。
    • 例子:int units_sold = {0};以及int units_sold{0};
  • 默認初始化
    • 在定義變量時沒有指定初值,變量被默認初始化。**若內置類型的變量未被顯示初始化,則它的值是由定義的位置決定的。定義於任何函數體之外的變量初始化爲0;在函數體內部的變量不被初始化(即值未定義),在拷貝或訪問此類值時將引發錯誤。**因此,建議初始化每一個內置類型的變量

2.2.2變量聲明和定義的關係

  • 聲明:使名字爲程序所知,一個文件如果想使用別處定義的名字,則必須包含對那個名字的聲明。
  • 定義:負責創建與名字關聯的實體。
  • 變量的聲明和定義都規定了變量的類型和名字,但是定義還可以申請存儲空間,也可能爲變量賦初值
  • 聲明:extern int i;如果想聲明而非定義一個變量,就在變量名前添加關鍵字extern,而不要顯式地初始化變量。
  • 顯式化的聲明即爲定義:extern double pi = 3.14159;
  • 變量能且只能定義一次,但是可以被多次聲明
  • 在變量第一次被使用的附近定義它。

2.2.3標識符

  • 由字母、數字和下劃線組成,必須以字母或下劃線開頭。對長度沒有限制,對大小寫敏感。
  • 自定義標識符不能有連續兩個下劃線,不能以下劃線加大些字母開頭。定義在函數體外的標識符不能以下劃線開頭。
  • 變量命名規範(用於提高可讀性):
    • 標識符要能體現實際含義。
    • 變量名一般用小寫字母。
    • 自定義類名一般以大些字母開頭。
    • 多個單詞組成應採用下劃線分隔,或採用駝峯法。
  • 不能使用C++已有的關鍵字。

2.2.4名字的作用域

  • 同一個名字在不同的作用域可能指向不同的實體。作用域通常以花括號{}分隔。
  • 外層作用域的變量內層可見,全局作用域的變量全局可見。
  • 如果函數有可能用到某全局變量或外層作用域變量,則不宜再定義一個同名的局部變量

2.3複合類型

2.3.1引用

  • 引用爲對象起了另外一個名字,定義引用時,程序把引用和它的初始值綁定在一起。一旦初始化完成,引用和它的初始化對象一直綁定在一起,因此引用必須初始化
  • 例子:int i = 1024; int &r = i;。一般以&d的形式進行,d爲被引用的變量名。
  • 引用類型要與之綁定的對象嚴格匹配。

2.3.2指針

  • 指針是指向另一種類型的符合類型。指針內存放着某個對象的地址。
    • 例子:int *ip1, *ip2
  • 指針和引用的區別(補充)
    • 指針本身是一個對象,引用只是對象的別名
    • 指針在其生命週期內可以先後指向不同的對象,引用在初始化完成後,即和它的初始化對象綁定在一起。
    • 指針無需在定義時賦初值,引用必須初始化。
  • 獲取對象的地址
    • 獲取指針內存放着的某個對象的地址需要使用取址符:&
    • 例子:int ival = 42; int *p2 = &ival;
    • 指針類型要與之指向的對象嚴格匹配。
  • 指針值有四種情況:
    • 1.指向一個對象。
    • 2.指向緊鄰對象所佔空間的下一個位置。
    • 3.空指針,意味着指針沒有指向任何對象。
    • 4.無效指針,上述情況之外的其他值。
    • 訪問無效指針將印發錯誤,使用第二第三種指針也是不被允許的。
  • 利用指針訪問對象
    • 解引用符*可以訪問指針所指向的對象。
    • 例子:int ival = 42; int *p = &ival; cout << *p;
  • 空指針
    • 空指針不指向任何對象。
    • 初始化空指針的方式:int *p1 = nullptr; int *p2 = 0;
    • 建議初始化所有指針

2.3.3理解複合類型的聲明

  • 定義多個變量
    • int* p1, p2;應理解爲int(* p1), p2;p1是指針,p2是int。*僅僅修飾了p1而已。
  • 指向指針的引用
    • int *p; int *&r= p; r爲對指針p的引用
  • 面對複雜的指針或聲明語句,從右向左閱讀是比較好弄清楚的方式

2.4 const限定符

  • const的特點是const對象的值無法被改變,因此const對象也必須被初始化。
  • 任何對const對象賦值的行爲都會報錯。只要不改變const對象的內容,其餘操作基本都能夠被進行。
  • 例子:
    • int const i = 42; //編譯時初始化
    • int const j = get_size(); //運行時初始化

2.4.1 const的引用

  • 對const的引用簡稱常量引用,在初始化常量引用時允許用任意表達式作爲初始值,允許爲一個常量引用綁定非常量的對象、字面值、一般表達式。
  • 常量引用不允許通過引用修改其綁定對象的值,但可以通過其他途徑修改其綁定對象的值,如果被綁定的對象不是const對象的話。(直接修改綁定對象的值,通過其他非常量引用修改等)。

2.4.2 指針和const

  • 指向常量的指針不能用於改變其所指對象的值,但可以指向一個非常量對象。
  • 可以這樣理解,常量引用和指向常量的指針,不過是自己認爲自己指向了一個常量,所以無法更改其值而已。
  • const指針
    • 常量指針必須被初始化,且初始化完成後其值就不能被改變。

2.4.3 頂層const

  • 定義
    • 頂層const(top-level const):指針本身是個常量
    • 底層const(low-level const):指針所指的對象是個常量
  • 執行對象的拷貝操作時:
    • 1)頂層const不受影響
    • 2)對於底層const而言:
      • 拷入和拷出的對象必須具有相同的底層const資格
      • 兩個對象的數據類型必須能夠轉換。一般來說,非常量可以轉換爲常量,反之則不行。
  • 用於聲明引用的const都是底層const

2.4.4 constexpr 和常量表達式

  • 常量表達式:值不會改變並且在編譯過程就能得到計算結果的表達式。
  • constexpr變量:將變量聲明爲constexpr類型, 由編譯器驗證其值是否爲常量表達式。
  • constexpr定義的指針爲頂層const。

2.5 處理類型

2.5.1 類型別名

  • 類型別名是一個名字,用於指代某種類型
    • 定義:typedef: typedef double wages; wages等同於double
    • 別名聲明(alias declaration):using SI = Sales_item; 等同於Sales_item
  • 在複合類型或常量中,在定義了別名後不要試圖替換爲本來的樣子進行理解會造成錯誤。

2.5.2 auto類型說明符

  • auto:讓編譯器代替分析表達式所屬的類型。通過初始值推算變量類型
    • auto會忽略引用和頂層const,保留底層const
    • 在一條語句內定義多個變量時,初始值必須爲同一類型

2.5.3 decltype類型指示符

  • decltype:在不用表達式的值初始化變量的情況下,從表達式的類型推斷出要定義的變量的類型。編譯器分析表達式並得到他的類型,卻不實際計算表達式的值
  • 如果表達式的內容是解引用操作,的decltype將得到引用類型。
  • decltype的表達式如果有雙層括號,則一定是個引用。

練習

2.1.1節練習

練習2.1

  • 類型int、long、long long和short的區別是什麼?無符號類型和帶符號類型的區別是什麼?float和double的區別是什麼?

1)int佔2個字節。long佔4個字節。long long佔8個字節。short佔1個字節。其表示範圍相應地有所不同。

2)無符號類型表示值大於等於0。帶符號類型能表示負數。

3)float佔32字節。double佔64字節。double表示範圍比float大。


練習2.2

  • 計算按揭貸款時,對於利率、本金和付款分別應該選擇何種數據類型?說明你的理由。

利率float。本金和付款爲double,因爲可能需要更多有效位數。付款方式char。

2.1.2節練習

練習2.3

  • 讀程序寫結果。

    unsigned u = 10, u2 = 42;
    std::cout << u2 - u << std::endl;    // 輸出32
    std::cout << u - u2 << std::endl;    //輸出-32對int佔位數得值的模
    
    int i = 10, i2 = 42;
    std::cout << i2 - i << std::endl;    //輸出32
    std::cout << i - i2 << std::endl;    //輸出-32
    std::cout << i - u << std::endl;    //輸出0
    std::cout << u - i << std::endl;    //輸出0
    

練習2.4

  • 編寫程序檢查你的估計是否正確,如果不正確,請仔細研讀本節知道弄明白問題所在。

2.1.3節練習

練習2.5

  • 指出下述字面值的數據類型並說明每一組內幾種字面值的區別。
(a)'a', L'a', "a", L"a" 
(b)10, 10u, 10L, 10uL, 012, 0xC
(c)3.14, 3.14f, 3.14L
(d)10, 10u, 10., 10e-2

(a)'a'     //char字符型,字面值爲a
   L'a'    //long char 寬字符型,字面值爲a
   "a"     //string字符串型,字面值爲a\0
   L"a"    //long string寬字符串型,字面值爲a\0
(b)10      //int整型,10
   10u     //unsigned int無符號整型,10
   10L     //long長整型,10
   10uL    //unsigned long無符號長整型,10
   012     //八進制int,字面值爲八進制的12,十進制的10
   0xC     //十六進制int,字面值爲十六進制的C,十進制的10
(c)3.14    //double浮點數,3.14
   3.14f   //float浮點數,3.14
   3.14L   //long double拓展精度浮點數,3.14
(d)10      //int整型,10
   10u     //unsigned int無符號整型,10
   10.     //double浮點數,10.
   10e-2   //double浮點數,科學計數法表示,10e-2(0.01)

練習2.6

  • 下面兩組定義是否有區別,如果有,請敘述之:
int month = 9, day = 7;
int month = 09, day = 07;
//有區別,前一組定義的是day是十進制,後一組day定義的是八進制。

練習2.7

  • 下述字面值表達何種含義?它們各自的數據類型是什麼?
(a)"Who goes with F\145rgus?\012"
(b)3.14e1L            (c)1024f        (d)3.14L

(a)"Who goes with Fergus?"加一個換行  string字符串  //\145爲八進制下的e,\12爲換行符
(b)31.4 long double 拓展精度浮點數
(c)1024 float 浮點數
(d)3.14 long double 拓展精度浮點數

練習2.8

  • 請利用轉義序列編寫一段程序,要求先輸出2M,然後轉到新一行。修改程序使其先輸出2,然後輸出製表符,再輸出M,最後轉到新一行。
int main()
{
	std::cout << "2\115\n" << std::endl;
	std::cout << "2\t\115\n" << std::endl;
	return 0;
}

2.2.1節練習

練習2.9

  • 解釋下列定義的含義。對於非法的定義,請說明在何處並將其改正。
(a) std::cin >> int input_value;                    (b) int i = { 3.14 };
(c) double salary = wage = 9999.99;                 (d) int i = 3.14;

(a)獲取int輸入。非法。變量需先定義,後使用。 改爲:
    int input_value{0};
    std::cin >> input_value;
(b)令整型數i初始化爲 3.14 。非法。使用float進行列表初始化int會丟失數據,因此會被編譯器拒絕。改爲:
    int i = 3.14;
(c)初始化salary wage 爲9999.99。非法。這段代碼應解釋爲,將9999.99賦值給wage,並將wage的值作爲初值,對salary進行初始化。而wage並未被定義,未被初始化。改爲:
    double wage = 0;
    double salary = wage = 9999.99;
(d)令整型數i初始化爲 3.14 。合法。但會自動轉換,丟棄小數點後的信息。

練習2.10

  • 下列變量的初值分別是什麼?
std::string global_str;
int global_int;
int main()
{
    int local_int;
    std::string local_str;
}
global_str的初值爲空串。global_int的初值爲0。local_int和local_str在main函數內,則不被初始化,初值爲未定義。

2.2.2節練習

練習2.11

  • 指出下面的語句是聲明還是定義:
(a) extern int ix = 1024;
(b) int iy;
(c) extern int iz;

(a)顯示的聲明即爲定義,賦初始值的行爲抵消了extern的聲明作用。
(b)聲明並定義。
(c)聲明。

2.2.3節練習

練習2.12

  • 請指出下面的名字中哪些是非法的?
(a) int double = 3.14;    //非法。使用了C++已有的關鍵字
(b) int _;                //合法
(c) int catch-22;         //非法。標識符只能由數字,字母,下劃線組成
(d) int 1_or_2 = 1;       //非法。標識符必須以下劃線或字母開頭
(e) double Double = 3.14; //合法

2.2.4節練習

練習2.13

  • 下面程序中j的值是多少?
int i = 42;
int main()
{
    int i = 100;
    int j = i;
}

100。局部變量i覆蓋了全局變量i

練習2.14

  • 下面的程序合法嗎?如果合法,它將輸出什麼?
int i = 100, sum = 0;
for (int i = 0; i != 10; i++)
    sum += i;
std::cout << i << " " << sum << std::endl;

合法,輸出100 45

2.3.1節練習

練習2.15

  • 下面的哪個定義是不合法的?爲什麼?
(a) int ival = 1.01;      //合法,但小數點部分會被捨棄
(b) int &rvall = 1.01;    //不合法,引用類型的初始值必須是一個對象
(c) int &rval2 = ival;    //合法
(d) int &rval3;           //不合法,引用類型必須有初始值

練習2.16

  • 考查下面的所有賦值然後回答:哪些賦值是不合法的?爲什麼?哪些賦值是合法的?它們執行了什麼樣的操做?
int i = 0, &r1 = i;
double d = 0, &r2 = d;
(a) r2 = 3.14159;    //合法,給r2(d)賦值爲3.14159
(b) r2 =r1;          //合法,給r2(d)賦值r1(i)的值0
(c) i =r2;           //合法,給i賦值r2(d)的值0
(d) r1 = d;          //合法,給r1(i)賦值d的值0

練習2.17

  • 執行下面的代碼段將輸出什麼結果?
int i, &ri = i;
i = 5; ri = 10;
std::cout << i << " " << ri << std::endl;

10 10

2.3.2節練習

練習2.18

  • 編寫代碼分別更改指針的值以及指針所指向對象的值。
int main()
{
	int i = 100, j = 200, *p = &i;
	p = &j;    //更改指針的值
	*p = 100;  //更改指針所指向對象的值
	return 0;
}

練習2.19

  • 說明指針和引用的主要區別。
- 指針本身是一個對象,引用只是對象的別名
- 指針在其生命週期內可以先後指向不同的對象,引用在初始化完成後,即和它的初始化對象綁定在一起。
- 指針無需在定義時賦初值,引用必須初始化。

練習2.20

  • 請敘述下面這段代碼的作用。
int i = 42;
int *p1 = &i;     // 初始化指針p1指向i
*p1 = *p1 * *p1;  // 將指針p1指向的對象值變爲它的平方,即p1現在所指的對象值爲1764。

練習2.21

  • 請解釋下述定義。在這些定義中有非法的嗎?如果有,爲什麼?
int i = 0;
(a) double* dp = &i;    //非法。類型不匹配
(b) int *ip = i;        //非法。指針指向的是變量的地址,不是變量本身。需用取址符&取其地址。
(c) int *p = &i;        //合法

練習2.22

  • 假設p是一個int型指針,請說明下述代碼的含義。
if (p)    // 判斷指針是否爲空指針
if (*p)   // 判斷指針指向的對象的值是否爲0

練習2.23

  • 給定指針p,你能知道它是否指向了一個合法的對象嗎?如果能,敘述判斷的思路;如果不能,也請說明原因。

不能知道。因爲指針可以任意指向一個地址,即使有時候可能看似正確,但依然不能保證它是無害的,指針的意義就是它的值是內存地址,它將按它指向的類型的信息來解釋那片空間。而那片空間是否合法,是由程序員保證,而不是指針本身。

練習2.24

  • 在下面這段代碼中爲什麼p合法而lp不合法?
int i = 42;         void *p = &i;         long *lp = &i;

void*指針可以指向任何值,因此p合法。longlong int類型,與iint類型不匹配。故非法。

2.3.3練習

練習2.25

  • 說明下列變量的類型和值。
(a) int* ip,i, &r = i;    //ip,int指針,值不確定。i,int指針,值不確定。r,int引用,值不確定。
(b) int i, *ip = 0;       //i int,值不確定。 ip,int指針,空指針。
(c) int* ip, ip2;         //ip int指針,值不確定。ip2,int,值不確定。

2.4節練習

練習2.26

  • 下面哪些句子是合法的?如果有不合法的句子,請說明爲什麼?
(a) const int buf;         //不合法,未初始化
(b) int cnt = 0;           //合法
(c) const int sz = cnt;    //合法
(d) ++cnt; ++sz;           //後一個不合法,const不允許被賦值(++內含賦值)

2.4.2節練習

練習2.27

  • 下面的哪些初始化是合法的?請說明原因。
(a) int i = -1, &r =0;            //不合法,引用必須綁定對象
(b) int *const p2 = &i2;          //如果i2是int則合法
(c) const int i = -1, &r =0;      //合法。常量引用允許綁定任何值
(d) const int *const p3 = &i2;    //如果i2是const int則合法
(e) const int *p1 = &i2;          //如果i2是int則合法
(f) const int &const r2;          //不合法,引用必須先初始化
(g) const int i2 = i, &r = i;     //如果i是int則合法

練習2.28

  • 說明下面的這些定義是什麼意思,挑出其中不合法的。
(a) int i, *const cp;         //定義int i,後半句不合法,常量必須被初始化
(b) int *p1, *const p2;       //定義int指針 p1,後半句不合法,常量必須被初始化
(c) const int ic, &r = ic;    //不合法,常量必須被初始化。後半句合法,對int常量ic的引用
(d) const int *const p3;      //不合法,常量必須被初始化
(e) const int *p;             //合法,定義指向int常量的指針p

練習2.29

  • 假設已有上一個練習中定義的那些變量,則下面的哪些語句是合法的?請說明原因。
(a) i = ic;      //合法,將int常量ic的值賦值給int i
(b) p1 = p3;     //不合法,指針類型不同
(c) p1 = &ic;    //不合法,普通指針p1無法指向常量
(d) p3 = &ic;    //不合法,p3是常量指針,無法改變其值
(e) p2 = p1;     //不合法,p2是常量指針,無法改變其值
(f) ic = *p3;    //不非法,ic是常量int,無法改變其值

2.4.3節練習

練習2.30

  • 對於下面的這些語句,請說明對象被申明成了頂層const還是底層const?
const int v2 = 0;        int v1 = v2;             //v2頂層const
int *p1 = &v1, &r1 = v1;                          
const int *p2 = &v2, *const p3 = &i, &r2 = v2;    //p2底層const,p3兩者都有,r2底層const

練習2.31

  • 假設已有上一個練習中所做的那些聲明,則下面的哪些語句是合法的?請說明頂層const和底層const在每個例子中有何體現。
r1 = v2;    //合法,拷貝時頂層const無影響
p1 = p2;    //不合法,底層const只能拷貝給擁有同樣底層const
p2 = p1;    //合法,int*可以轉換成const int*
p1 = p3;    //不合法,底層const只能拷貝給擁有同樣底層const
p2 =p3;     //合法,擁有同樣底層const

2.4.4節練習

練習2.32

  • 下面的代碼是否合法?如果非法,請設法將其修改正確。
int null = 0, *p = null;

不合法,null是個整型,p是個int 指針,二者雖然字面值是一樣的,均爲0,但類型不同,不可混用。改爲:

int null = 0, *p = nullptr;

2.5.2節練習

練習2.33

  • 利用本節定義的變量,判斷下列語句的運行結果。
a = 42; b = 42; c = 42;    //成功賦值
d = 42; e = 42; g = 42;    //賦值失敗

練習2.34

  • 基於上一個練習中的變量和語句編寫一段程序,輸出賦值前後變量的內容,你剛纔的推斷正確嗎?如果不對,請反覆研讀本節的示例知道你明白錯在何處爲止。
int i = 0, &r = i;
auto a = r;
cout << a <<endl;
// r是i的別名,i是一個整數,故a爲一個整數
a = 42; //accept
cout << a << endl;

const int ci = i, &cr = ci;
auto b = ci; //ci爲一個常量,但是auto會忽略const,故b爲一個int變量
cout << b << endl;
b = 42;
cout << b << endl;

auto c = cr; //cr是ci的別名,ci爲常量,auto忽略const,故b爲一個int變量
cout << c << endl;
c = 42;
cout << c << endl;

auto d = &i; //&i爲i的地址,故d爲一個指向int變量的指針
cout << d << endl;
//d = 42; //賦值失敗,d內存儲的爲一個int變量的地址

auto e = &ci; //&ci爲常量ci的地址,故e本來應該爲指向常量的指針,但是auto忽略頂層const,故e爲一個指向int變量的指針
cout << e << endl;

auto &g = ci; //ci爲一個常量, 故g爲一個常量引用
cout << g << endl;
// 等式右邊爲常量引用,左邊也必須定義爲常量形式

練習2.35

  • 判斷下列定義推斷出的類型是什麼,然後編寫程序進行驗證。
const int i  = 42;             //const int整型常量
auto j = i;                    //int整型
const auto &k = i;             //int整型的常量引用
auto *p = &i;                  //指向常量整型的指針
const auto j2 = i, &k2 = i;    //j2 int整型常量,k2 int整型的常量引用

Code:
const int i = 42;
auto j = i;//i爲常量,auto判斷會忽略頂層const,故j爲int變量
j = 0; //賦值成功,j爲int型

const auto &k = i; // 
// k = 0; //賦值失敗,因爲k爲const int&

auto *p = &i;
// *p = 0; //賦值失敗,*p不可修改,因爲p爲const int *

const auto j2 = i, &k2 = i; 
// j2 = 0; //賦值失敗,j2爲const int,j2值不可修改
// k2 = 0; //賦值失敗,k2爲const int&, k2值不可修改

2.5.3節練習

練習2.36

  • 關於下面的代碼,請指出每一個變量的類型以及程序結束時它們各自的值。
int a =3, b =4;
decltype(a) c = a;
decltype((b)) d =a;
++c;
++d;    //d是對a的引用,自增使得a和d均加1
//a,4,int b,4,int c,4,int, d,4,int&

練習2.37

  • 賦值時會產生引用的一類典型表達式,引用的類型就是左值的類型。也就是說,如果iint,這表達式i=x的類型是int&。根據這一特點,請指出下面的代碼中的每一個變量的類型和值。
int a = 3, b = 4;
decltype(a) c = a;
decltype(a = b) d =a; //decltype只推斷類型,沒有進行賦值操作

//a,int,3 b,int,4 c,int,3 d,int&,3

練習2.38

  • 說明由decltype指定類型和由auto指定類型那個有何區別。請舉出一個例子,decltype指定的類型和auto指定的類型一樣;再舉一個例子,decltype指定的類型與auto指定的類型不一樣。
//auto必須初始化,decltype不用。auto的結果與表達形式無關,decltype結果類型與表達式形式有關
 
//decltype指定的類型與auto指定的類型一樣:auto (x)=a; decltype(x)=a;
 
//decltype指定的類型與auto指定的類型不一樣:auto (x)=a; decltype((x))=a;
 
int i = 0, &r = i;
// same
auto a = i;
decltype(i) b = i;
// different
auto c = r;
decltype(r) d = r;

2.6.1節練習

練習2.39

  • 編譯下面的程序觀察其運行結果,注意,如果忘記寫類定義體後面的分號會發生什麼情況?記錄下相關信息,以後可能會用。
struct Foo { /* 此處爲空 */} //注意:沒有分號
int main()
{
    return 0;
}
//error C2628: “Foo”後面接“int”是非法的(是否忘記了“;”?)

練習2.40

  • 根據自己的理解寫出Sales_data類,最好與書中的例子區別。
struct Sale_data {
	std::string bookno;
	std::string bookname;
	double price = 0.0;
	int num = 1;
};
int main() {
	return 0;
}

2.6.2節練習

練習2.41

  • 使用你自己的Sales_item類重寫1.5.1節、1.5.2節和1.6節的練習。眼下先把Sales_data類的定義和main函數放在同一個文件裏。

1.5.1

#include <iostream>
#include <string>
 
struct Sale_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

int main()
{
    Sale_data book;
    double price;
    std::cin >> book.bookNo >> book.units_sold >> price;
    book.revenue = book.units_sold * price;
    std::cout << book.bookNo << " " << book.units_sold << " " << book.revenue << " " << price;
 
    return 0;
}

1.5.2

#include <iostream>
#include <string>
 
struct Sale_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};
 
int main()
{
    Sale_data book1, book2;
    double price1, price2;
    std::cin >> book1.bookNo >> book1.units_sold >> price1;
    std::cin >> book2.bookNo >> book2.units_sold >> price2;
    book1.revenue = book1.units_sold * price1;
    book2.revenue = book2.units_sold * price2;
 
    if (book1.bookNo == book2.bookNo) {
        unsigned totalCnt = book1.units_sold + book2.units_sold;
        double totalRevenue = book1.revenue + book2.revenue;
        std::cout << book1.bookNo << " " << totalCnt << " " << totalRevenue << " ";
        if (totalCnt != 0)
            std::cout << totalRevenue / totalCnt << std::endl;
        else
            std::cout << "(no sales)" << std::endl;
 
        return 0;
    }
    else {
        std::cerr << "Data must refer to same ISBN" << std::endl;
        return -1;  // indicate failure
    }
}

1.6

#include <iostream>
#include <string>
 
struct Sale_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};
 
int main()
{
    Sale_data total;
    double totalPrice;
    if (std::cin >> total.bookNo >> total.units_sold >> totalPrice) {
        total.revenue = total.units_sold * totalPrice;
 
        Sale_data trans;
        double transPrice;
        while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice) {
            trans.revenue = trans.units_sold * transPrice;
 
            if (total.bookNo == trans.bookNo) {
                total.units_sold += trans.units_sold;
                total.revenue += trans.revenue;
            }
            else {
                std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " ";
                if (total.units_sold != 0)
                    std::cout << total.revenue / total.units_sold << std::endl;
                else
                    std::cout << "(no sales)" << std::endl;
 
                total.bookNo = trans.bookNo;
                total.units_sold = trans.units_sold;
                total.revenue = trans.revenue;
            }
        }
 
        std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " ";
        if (total.units_sold != 0)
            std::cout << total.revenue / total.units_sold << std::endl;
        else
            std::cout << "(no sales)" << std::endl;
 
        return 0;
    }
    else {
        std::cerr << "No data?!" << std::endl;
        return -1;  // indicate failure
    }
}

2.6.3節練習

練習2.42

  • 根據你自己的理解重寫一個Sales_data.h頭文件,並以此爲基礎重做2.6.2節練習。

Sales_data.h

#include <iostream>
#include <string>
 
// own Sales_data
struct Sales_data {
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
 
    void CalcRevenue(double price);
    double CalcAveragePrice();
    void SetData(Sales_data data);
    void AddData(Sales_data data);
    void Print();
};
 
void Sales_data::CalcRevenue(double price)
{
    revenue = units_sold * price;
}
 
void Sales_data::SetData(Sales_data data)
{
    bookNo = data.bookNo;
    units_sold = data.units_sold;
    revenue = data.revenue;
}
 
void Sales_data::AddData(Sales_data data)
{
    if (bookNo != data.bookNo) return;
    units_sold += data.units_sold;
    revenue += data.revenue;
}
 
double Sales_data::CalcAveragePrice()
{
    if (units_sold != 0)
        return revenue / units_sold;
    else
        return 0.0;
}
 
void Sales_data::Print()
{
    std::cout << bookNo << " " << units_sold << " " << revenue << " ";
    double averagePrice = CalcAveragePrice();
    if (averagePrice != 0.0)
        std::cout << averagePrice << std::endl;
    else
        std::cout << "(no sales)" << std::endl;
}

1.5.1

#include <iostream>
 
#include "2.42-Sales_data.h"
 
int main()
{
    Sales_data book;
    double price;
    std::cin >> book.bookNo >> book.units_sold >> price;
    book.CalcRevenue(price);
    book.Print();
 
    return 0;
}

1.5.2

#include <iostream>
 
#include "2.42-Sales_data.h"
 
int main()
{
    Sales_data total;
    double totalPrice;
    if (std::cin >> total.bookNo >> total.units_sold >> totalPrice) {
        total.CalcRevenue(totalPrice);
 
        Sales_data trans;
        double transPrice;
        while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice) {
            trans.CalcRevenue(transPrice);
 
            if (total.bookNo == trans.bookNo) {
                total.AddData(trans);
            }
            else {
                total.Print();
                total.SetData(trans);
            }
        }
 
        total.Print();
 
        return 0;
    }
    else {
        std::cerr << "No data?!" << std::endl;
        return -1; // indicate failure
    }
}

1.6

#include <iostream>
 
#include "2.42-Sales_data.h"
 
int main()
{
    Sales_data total;
    double totalPrice;
    if (std::cin >> total.bookNo >> total.units_sold >> totalPrice) {
        total.CalcRevenue(totalPrice);
 
        Sales_data trans;
        double transPrice;
        while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice) {
            trans.CalcRevenue(transPrice);
 
            if (total.bookNo == trans.bookNo) {
                total.AddData(trans);
            }
            else {
                total.Print();
                total.SetData(trans);
            }
        }
 
        total.Print();
 
        return 0;
    }
    else {
        std::cerr << "No data?!" << std::endl;
        return -1; // indicate failure
    }
}

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