# 第二章 變數和基本型別 練習
## 練習 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 = ⁣ // 不合法,普通指標不能指向常數
p3 = ⁣ // 合法,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" 即可