---
# System prepended metadata

title: 第二章 變數和基本型別 練習

---

# 第二章 變數和基本型別 練習

## 練習 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" 即可