# 第二章 變數和基本型別 練習 ## 練習 2.1 int、long、long long 與 short 之間的差異是什麼?無號型別與有號型別之間的差異呢?float 與 double 之間呢? 解: C++ 規定 short 和 int 至少要 16 位元,long 至少要 32 位元,long long 則至少要 64 位元。 有號型別能夠表示正、負數以及 0,而無號型別只能夠表示 0 和正整數 ## 練習 2.2 若要計算抵押貸款的付款金額(mortgage payment),你分別會用何種型別表示利率(rate)、本金(principal)和償還金額(payment)?解釋你選擇該型別的原因 解: 使用 double,因為需要進行浮點運算 ## 練習 2.3 下列程式碼會產生什麼輸出呢? ```cpp unsigned u = 10, u2 = 42; std::cout << u2 - u << std::endl; std::cout << u - u2 << std::endl; int i = 10, i2 = 42; std::cout << i2 - i << std::endl; std::cout << i - i2 << std::endl; std::cout << i - u << std::endl; std::cout << u - i << std::endl; ``` 解: ``` 32 4294967264 32 -32 0 0 ``` ## 練習 2.4 寫一個程式來檢查你的預測是否正確。若不是,就重複研讀本節,直到你了解問題出在哪裡為止 解: 同上題 ## 練習 2.5 判斷下列每個字面值之型別。解釋四個範例中每組自面值之間的差異 ```cpp (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):字元字面值、寬字元字面值、字元字串字面值、寬字元字串字面值 - (b):十進位有號整數、十進位無號整數、十進位有號長整數、十進位無號長整數、八進位有號整數、十六進位有號整數 - (c):雙精度浮點數、單精度浮點數、擴充精度浮點數 - (d):十進位有號整數、十進位無號整數、雙精度浮點數、雙精度浮點數(科學記號表示法) ## 練習 2.6 下列定義之間的差異是什麼(如果有的話)? ```cpp int month = 9, day = 7; int month = 09, day = 07; ``` 解: 第一行定義的是十進位有號整數,而第二行定義的是八進位的有號整數,且第二行的 month 變數有誤,八進位之中不能有 9 ## 練習 2.7 這些字面值代表什麼值呢?它們各有什麼型別呢? ```cpp (a)"Who goes with F\145rgus?\012" (b)3.14e1L (c)1024f (d)3.14L ``` 解: - (a)Who goes with Fergus?(換行),為字元字串字面值,型別為 char[23] - (b)擴充精度浮點數(e1 代表科學記號) - (c)無效,因為後綴(suffix)只能用於浮點數字面值,而 1024 是有號整數 - (d)擴充精度浮點數 ## 練習 2.8 使用轉義序列(escape sequences),寫出一個程式印出 2M 後面接著一個 newline。修改這個程式,印出 2,然後一個 tab,然後一個 M,後面接著一個 newline 解: ```cpp #include <iostream> int main() { std::cout << 2 << "\115\012"; std::cout << 2 << "\t\115\012"; return 0; } ``` ## 練習 2.9 解釋下列定義。對那些不合法的定義,請解說何處出錯了,以及如何更正之 ```cpp (a)std::cin >> int input_value; (b)int i = { 3.14 }; (c)double salary = wage = 9999.99; (d)int i = 3.14; ``` 解: - (a):應先定義再使用,不可寫在同一行 ```cpp int input_value = 0; std::cin >> input_value; ``` - (b):用串列初始化(list initialization)內建型別的變數時,如果存在會丟失資訊的轉型(casting),則編譯器將會報錯 ```cpp double i = { 3.14 }; ``` - (c):在這裡的 wage 是未定義的,應該在此之前先將其定義 ```cpp double wage; double salary = wage = 9999.99; ``` - (d):不報錯,但是小數點部分會被截斷(truncate) ```cpp double i = 3.14; ``` ## 練習 2.10 下列每個變數各有什麼初始值呢(如果有的話)? ```cpp std::string global_str; int global_int; int main() { int local_int; std::string local_str; } ``` 解: global_str 和 global_int 是全域變數(global variable),所以初始值分别為空字串和 0 local_int 是區域變數(local variable)並且沒有被初始化,因此它的初始值是未定義的 local_str 是 string 類別的物件,它的值由類別決定,在此為空字串 ## 練習 2.11 指出下面的每一個是宣告還是定義: ```cpp (a)extern int ix = 1024; (b)int iy; (c)extern int iz; ``` 解: - (a):定義 - (b):定義 - (c):宣告 ## 練習 2.12 下列哪一個名稱無效(如果有的話)? ```cpp (a)int double = 3.14; (b)int _; (c)int catch-22; (d)int 1_or_2 = 1; (e)double Double = 3.14; ``` 解: (a)、(c)、(d)不合法 - (a):缺少 identifier - (c):identifier 中不可有 - 號 - (d):identifier 不可以數字開頭 ## 練習 2.13 下面程式中 j 的值為何? ```cpp int i = 42; int main() { int i = 100; int j = i; } ``` 解: j 的值是 100,區域變數(local variable) i 覆盖了全局變數(global variables) i ## 練習 2.14 下列程式合法嗎?若是,印出的會是什麼值呢? ```cpp int i = 100, sum = 0; for (int i = 0; i != 10; ++i) sum += i; std::cout << i << " " << sum << std::endl; ``` 解: 合法,輸出為 100 45 ## 練習 2.15 下列哪個定義是無效的(如果有的話)?為什麼呢? ```cpp (a)int ival = 1.01; (b)int &rval1 = 1.01; (c)int &rval2 = ival; (d)int &rval3; ``` 解: (b、(d)無效 - (b):參考必須綁定在物件或函式上 - (d):參考必須要被初始化 ## 練習 2.16 如果有的話,下列哪個指定(assign)是無效的?如果是有效的,請解釋原因 ```cpp int i = 0, &r1 = i; double d = 0, &r2 = d; ``` ```cpp (a)r2 = 3.14159; (b)r2 = r1; (c)i = r2; (d)r1 = d; ``` 解: - (a):合法,d 被指定為 3.14159 - (b):合法,會發生隱性轉型(int -> double) - (c):合法,但會發生截斷(truncate) - (d):合法,但會發生截斷(truncate) ## 練習 2.17 下列程式碼會印出什麼呢? ```cpp int i, &ri = i; i = 5; ri = 10; std::cout << i << " " << ri << std::endl; ``` 解: 輸出:10 10 ## 練習 2.18 寫出程式碼來變更一個指標的值。寫出程式碼來變更該指標所指的值 解: ```cpp int a = 0, b = 1; int *p1 = &a, *p2 = p1; // 更改指標的值 p1 = &b; // 更改指標所指向的物件的值 *p2 = b; ``` ## 練習 2.19 解釋指標與參考之間的關鍵差異 解: - 參考是另一個物件的別名(alias),而指標本身就是一個物件(object) - 參考必須被初始化(initialize),並且一旦定義了參考就無法再綁定到其他物件(object),而指標無須在定義時被初始化(initialize),並且也可以重新被指定讓其指向其他物件(object) ## 練習 2.20 下列程式會做什麼事呢? ```cpp int i = 42; int *p1 = &i; *p1 = *p1 * *p1; ``` 解: 讓指標 pi 指向 i,然後將 i 的值重新指定為 42 * 42 ## 練習 2.21 解釋下列的每個定義。指出哪些是不合法的(如果有的話),並說明原因 ```cpp int i = 0; (a)double *dp = &i; (b)int *ip = i; (c)int *p = &i; ``` 解: - (a):非法,不能將一個指向 double 的指針指向 int - (b):非法,不能將 int 變數拿來初始化(initialize)給指標 - (c):合法 ## 練習 2.22 假設 p 是對 int 的一個指標,請解說下列程式碼: ```cpp if (p) // ... if (*p) // ... ``` 解: - 第一句判斷 p 是不是一個空指標(null pointer) - 第二句判斷 p 所指向的物件的值是不是為 0 ## 練習 2.23 給定一個指標 p,你能夠判斷 p 指向的是否為一個有效的物件嗎?若是,為什麼呢?若不是,原因為何? 解: 不能,因為首先要確定這個指標是不是有效的,才能判斷它所指向的物件是不是也是有效的 ## 練習 2.24 為什麼 p 的初始化是合法的,但 lp 的不合法呢? ```cpp int i = 42; void *p = &i; long *lp = &i; ``` 解: void* 是從 C 語言那裡繼承過來的,可以指向任何型別的物件(型別為指向任意型別的指標都可以被轉型為 void\*,而 void\* 也可以被轉型為指向任意型別的指標),而其他指標型別必須要與所指向的物件的型別要嚴格匹配(matching) ## 練習 2.25 判斷下列每個變數的型別和值 ```cpp (a)int *ip, i, &r = i; (b)int i, *ip = 0; (c)int *ip, ip2; ``` 解: - (a):ip 是一個指向 int 的指標,i 是一個 int,r 是 i 的參考 - (b):i 是 int,ip 是一個空指標 - (c):ip 是一個指向 int 的指標,ip2 是一個 int ## 練習 2.26 下列何者是合法?如果不合法,請解釋原因 解: ```cpp const int buf; // 不合法,const 物件必須被初始化 int cnt = 0; // 合法 const int sz = cnt; // 合法 ++cnt; ++sz; // 不合法,const 物件不能被改變 ``` ## 練習 2.27 下列哪個初始化是合法的?請解釋原因 解: ```cpp int i = -1, &r = 0; // 不合法,r 必須參考一個物件 int *const p2 = &i2; // 合法,常數指標 const int i = -1, &r = 0; // 合法 const int *const p3 = &i2; // 合法 const int *p1 = &i2; // 合法 const int &const r2; // 不合法,r2 是參考,參考沒有頂層 const const int i2 = i, &r = i; // 合法 ``` ## 練習 2.28 解釋下列定義。指出其中非法的定義 解: ```cpp int i, *const cp; // 不合法,const 指標必須被初始化 int *p1, *const p2; // 不合法,const 指標必須被初始化 const int ic, &r = ic; // 不合法,const int 必須被初始化 const int *const p3; // 不合法,const 指標必須被初始化 const int *p; // 合法,為一個指向 const int 的指標 ``` ## 練習 2.29 使用前一個練習中的變數,請問下列哪個指定是合法的?請解釋原因 解: ```cpp i = ic; // 合法,常數變數可以被指定給普通變數 p1 = p3; // 不合法,p3 是 const 指標,不能被指定給普通指標 p1 = &ic; // 不合法,普通指標不能指向常數 p3 = &ic; // 合法,p3 是常數指標且指向常數 p2 = p1; // 合法,可以將普通指標賦值給常數指標 ic = *p3; // 合法,對 p3 解參考(dereference)後是一個 int 然後指定給 ic ``` ## 練習 2.30 指出下列宣告所宣告的物件是否有頂層或低層的 const ```cpp const int v2 = 0; int v1 = v2; int *p1 = &v1, &r1 = v1; const int *p2 = &v2, *const p3 = &i, &r2 = v2; ``` 解: - v2 是頂層 const - p2 是底層 const - p3 同時有頂層 const 與底層 const - r2 是底層 const ## 練習 2.31 給定上一個練習的宣告,請判斷下列的指定是否合法。解釋每個例子中頂層或低層 const 是如何套用的 解: ```cpp r1 = v2; // 合法,頂層 const 在拷貝時不受影響 p1 = p2; // 不合法,p2 是底層 const,如果要拷貝必須要求 p1 也有相匹配的底層 const p2 = p1; // 合法,int* 可以轉換成 const int* p1 = p3; // 不合法,p3 是一個底層 const,而 p1 不是 p2 = p3; // 合法,p2 和 p3 都是底層 const,拷貝時忽略頂層 const ``` ## 練習 2.32 下列程式碼是否合法?若非,你要如何讓它變得合法? ```cpp int null = 0, *p = null; ``` 解: 合法,指標可以初始化為 0,表示為空指標,但較推薦使用 nullptr ## 練習 2.33 使用本節的變數定義,判斷下列這些指定式各會發生什麼事 解: ```cpp a = 42; // a 是一個 int b = 42; // b 是一個 int(ci 的頂層 const 在拷貝時會被忽略) c = 42; // c 也是一個 int d = 42; // d 是一個 int*,所以此述句(statement)非法 e = 42; // e 是一個 const int*,所以此述句(statement)非法 g = 42; // g 是一個 const int 的參考,此參考都是底層 const,所以不能被指定 ``` ## 練習 2.34 寫一個包含前一個練習中的變數和指定的程式。在指定前後印出那些變數,以檢查你在前面練習的預測是否正確。如果不是,就研讀範例,直到你認為你知道是什麼引導你走向錯誤結論為止 ## 練習 2.35 判斷從下列各個定義推導出來的型別。一旦你找出型別,就寫個程式來看看你是否正確 ```cpp const int i = 42; auto j = i; const auto &k = i; auto *p = &i; const auto j2 = i, &k2 = i; ``` 解: - j 是 int - k 是 const int& - p 是 const int* - j2 是 const int - k2 是 const int& ## 練習 2.36 在下列程式碼中,判斷每個變數的型別,以及程式碼執行完畢之後每個變數的值 ```cpp int a = 3, b = 4; decltype(a) c = a; decltype((b)) d = a; ++c; ++d; ``` 解: - c 是 int,值為 4 - d 是 int&,绑定到 a,a 的值為 4 ## 練習 2.37 指定(assignment)是會產出參考型別的運算式(expression)實例。型別會是指涉左邊運算元型別的一種參考,也就是說,如果 i 是一個 int,那麼運算式 i = x 的型別就會是 int&。藉由這個知識,判斷這段程式碼中每個變數的型別與值 ```cpp int a = 3, b = 4; decltype(a) c = a; decltype(a = b) d = a; ``` 解: - c 是 int,值為 3 - d 是 int&,绑定到 a ## 練習 2.38 描述 decltype 和 auto 之間型別推導的差異。給出一個運算式(expression)實例,讓 auto 和 decltype 都會推導出相同的型別,以及一個會推導出不同型別的運算式實例 解: decltype 處理頂層 const 和參考的方式與 auto 不同,decltype 會將頂層 const 和參考保留 ```cpp int i = 0, &r = i; // a 與 b 所推導出的型別相同 auto a = i; decltype(i) b = i; // c 與 d 所推導出的型別不相同,c 為 int,d 為 int& auto c = r; decltype(r) d = r; ``` ## 練習 2.39 編譯下列程式,看看你如果忘了類別定義後的分號,會發生什麼事。記住這種訊息以便未來參考 ```cpp struct Foo { /* empty */ } // 注意:沒有分號 int main() { return 0; } ``` 解: g++ error: expected ';' after struct definition ## 練習 2.40 寫出你自己的版本的 Sales_data 類別 ```cpp struct Sales_data { // 全部皆為 public std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; double price = 0.0; }; ``` ## 練習 2.41 使用你自己的 Sales_data 類別來改寫 1.5.1、1.5.2 和 1.6 的練習。就現在來說,你應該將你的 Sales_data 類別定義在跟 main 函式相同的檔案中 ```cpp // 1.5.1 #include <iostream> #include <string> struct Sales_data { // 全部皆為 public std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; double price = 0.0; }; int main() { // 讀取或寫入 Sales_data Sales_data book1; std::cin >> book1.bookNo >> book1.units_sold >> book1.price; book1.revenue = book1.units_sold * book1.price; std::cout << book1.bookNo << " " << book1.units_sold << " " << book1.revenue << " " << book1.price << std::endl; return 0; } ``` ```cpp // 1.5.2 #include <iostream> #include <string> struct Sales_data { // 全部皆為 public std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; double price = 0.0; }; int main() { // 相加兩個 Sales_data Sales_data book1, book2; double price1, price2; std::cin >> book1.bookNo >> book1.units_sold >> book1.price; std::cin >> book2.bookNo >> book2.units_sold >> book2.price; book1.revenue = book1.units_sold * book1.price; book2.revenue = book2.units_sold * book2.price; Sales_data book3; if (book1.bookNo == book2.bookNo) { book3.bookNo = book1.bookNo; book3.units_sold = book1.units_sold + book2.units_sold; book3.revenue = book1.revenue + book2.revenue; std::cout << book3.bookNo << " " << book3.units_sold << " " << book3.revenue << " "; if (book3.units_sold != 0) { book3.price = book3.revenue / book3.units_sold; std::cout << book3.price << std::endl; } else { std::cout << "(no sales)" << std::endl; return 0; } } else { std::cerr << "Data must refer to same ISBN" << std::endl; return -1; } } ``` ```cpp // 1.6 #include <iostream> #include <string> struct Sales_data { // 全部皆為 public std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; double price = 0.0; }; int main() { // Bookstore 程式(可加總多筆資料) Sales_data total; if (std::cin >> total.bookNo >> total.units_sold >> total.price) { total.revenue = total.units_sold * total.price; Sales_data trans; while (std::cin >> trans.bookNo >> trans.units_sold >> trans.price) { trans.revenue = trans.units_sold * trans.price; 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) { total.price = total.revenue / total.units_sold; std::cout << total.price << 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) { total.price = total.revenue / total.units_sold; std::cout << total.price << std::endl; } else std::cout << "(no sales)" << std::endl; return 0; } else { std::cerr << "No data" << std::endl; return -1; // indicate failure } } ``` ## 練習 2.42 寫出你自己版本的 Sales_data.h 標頭(header file),並且用它來改寫 2.6.2 的練習 ```cpp // Sales_data.h #ifndef SALES_DATA_H #define SALES_DATA_H #include <string> struct Sales_data { // 全部皆為 public std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; double price = 0.0; }; #endif ``` 將上述練習中定義 Sales_data struct 的部分刪除,並且改為 #include "Sales_data.h" 即可