Learning Objectives
- 宣告 int、long long、double、float、char、bool 變數,並說明各自適合存放的資料
- 用 sizeof 與 numeric_limits 查看型態的大小與範圍,說明整數溢位
- 說明 char 存的是字元編碼(ASCII),能用編碼做大小寫與數字字元的轉換
- 分辨隱含轉換與 static_cast 明確轉型,避開整數除法造成的錯誤
- 使用 const、auto 與三種初始化寫法,並說明區域變數沒有初始化的風險
為什麼要在意資料型態
變數是記憶體裡一塊有名字的空間,用來存放資料。C++ 的每個變數都要先宣告型態,型態決定三件事:
- 這塊空間有多大(佔幾個位元組)
- 能存放的值的範圍(例如 int 最大約 21 億)
- 能做哪些運算、運算的方式(整數相除和小數相除的結果不一樣)
int age = 19; // 型態 變數名稱 = 初始值;
選錯型態不一定會編譯失敗,卻可能算出錯誤的結果:數字太大裝不下、小數被捨去。這一課看清楚每種型態的特性。
變數名稱的規則:由英文字母、數字、底線組成,不能以數字開頭,不能用 int、return 這類關鍵字,大小寫有別。
習慣上用有意義的英文,例如 studentCount 或 student_count,不要用 a1、tmp2。
基本資料型態
| 型態 | 存放 | 例子 | 常見大小 |
|---|---|---|---|
int |
整數 | 19、-3 |
4 位元組 |
long long |
範圍更大的整數 | 8000000000LL |
8 位元組 |
double |
浮點數(小數),倍精度 | 172.5、3.0 |
8 位元組 |
float |
浮點數,單精度 | 63.2f |
4 位元組 |
char |
一個字元 | 'A'、'7'、' ' |
1 位元組 |
bool |
真假值 | true、false |
1 位元組 |
幾個細節:
- 字元用單引號
'A',字串用雙引號"A";字串是std::string,不是基本型態 - 小數預設是
double;要寫float的常數,在後面加f cout印bool時預設顯示1或0,加上boolalpha才會顯示true、false- 大小由編譯器與平台決定,C++ 標準只規定最小值;表中是 Linux x86-64 上 g++ 的大小。要確定就用
sizeof查
宣告變數與 sizeof
右邊的變數區會在每一行宣告完成後多出一個變數,並顯示它的值。注意 passed 在變數區顯示 true,但第 14 行用 cout 印出來是 1。最後三行用 sizeof 印出各型態佔幾個位元組。
範圍與溢位
4 個位元組有 32 個位元,int 能表示的範圍是 -2,147,483,648 到 2,147,483,647(約正負 21 億)。
long long 有 64 個位元,最大約 9.2 × 10¹⁸。用 <limits> 的 numeric_limits<型態>::max() 可以查到確切的值。
運算結果超出範圍叫做溢位(overflow)。有號整數溢位在 C++ 是未定義行為:常見的結果是「繞回去」變成負數,
但標準不保證,編譯器也不會在執行時提醒你。預期數字會很大(人口、金額以分計、階乘)時,就改用 long long。
浮點數則是精確度有限:float 大約 7 位有效數字、double 大約 15 位。小數在電腦裡多半無法精確表示,
所以一般計算用 double,不要用 == 比較兩個算出來的小數是否相等(第 4 課會再看到)。
溢位與精確度
第 11 行 biggest + 1 超出 int 的範圍,動畫會顯示警告,並用常見的繞回結果繼續執行。第 13~14 行先把值放進 long long 再加 1,就裝得下了。最後兩行用 12 位有效數字印出 1/3,看 float 從第幾位開始不準。
char 與 ASCII
char 存的其實是一個整數編碼,印出時才顯示成對應的字元。英文字母、數字、標點使用 ASCII 編碼:
| 字元 | 編碼 | 字元 | 編碼 |
|---|---|---|---|
'0'~'9' |
48~57 | ' '(空白) |
32 |
'A'~'Z' |
65~90 | 'a'~'z' |
97~122 |
因為編碼是連續的,可以直接用運算處理字元:'A' + 1 是 'B' 的編碼,'a' - 'A' 是 32,
'7' - '0' 是 7。注意 char 和 int 一起運算時,結果是 int,用 cout 印出來會是數字;要印成字元,
先存回 char 變數,或寫 char(…) 轉回字元。
一個 char 只有 1 個位元組,放不下中文字;中文要用 string 存放(UTF-8 編碼下一個中文字通常佔 3 個位元組)。
用編碼處理字元
比較第 10 行和第 11 行:同樣是 letter + 1,存進 char 變數再印,顯示 B;直接印運算結果,顯示的是數字 66。
想一想:型態、範圍與字元
下面的程式會印出什麼?
char c = 'C';
cout << c + 2;
在 Linux x86-64 上用 g++ 編譯,下面的程式會印出什麼?
cout << sizeof(int) + sizeof(double);
要存放全世界的人口數(約 80 億),用哪個型態最合適?
型別轉換
不同型態的值放在一起時,C++ 會自動轉換,叫做隱含轉換:
| 情況 | 結果 |
|---|---|
double price = 30; |
int 轉 double,沒有損失 |
int count = 3.99; |
double 轉 int,小數直接捨去(不是四捨五入),得到 3 |
bool flag = 5; |
0 轉成 false,其他數字都是 true |
int 和 double 一起運算 |
int 先轉成 double,結果是 double |
最常見的錯誤是整數除法:兩個 int 相除,結果還是 int,小數部分被捨去。double avg = 7 / 2; 會得到 3,
因為 7 / 2 在存進 avg 之前就已經算成 3 了。
要明確地轉換型態,用 static_cast<目標型態>(值):
double avg = static_cast<double>(total) / people; // 先把 total 轉成 double,再做小數除法
你可能也會看到 C 語言的寫法 (double)total,效果相同;C++ 建議用 static_cast,比較容易搜尋,意圖也比較清楚。
隱含轉換與 static_cast
注意第 10 行和第 11 行的差別:avg1 的除法發生在轉型之前,只得到 3;avg2 先轉型再除,得到 3.5。
初始化、const 與 auto
初始化的三種寫法,效果相同:
int a = 5;
int b(6);
int c{7}; // 大括號初始化(C++11)
大括號初始化比較嚴格:int c{3.5}; 會編譯錯誤,因為 double 轉 int 會損失資料(稱為縮窄轉換);
int c = 3.5; 則只會悄悄捨去小數。用大括號可以讓編譯器幫你抓出這類錯誤。
const:宣告時給值,之後不能再改,改了就是編譯錯誤。用在不會變的數值(稅率、圓周率、班級人數上限),
名稱常用全大寫,例如 const double TAX_RATE = 0.05;。
auto(C++11):讓編譯器依初始值推斷型態,auto qty = 4; 是 int、auto price = 120.0; 是 double。
auto 變數一定要有初始值;推斷出來的型態宣告後就固定,不會變。型態名稱很長時(例如第 11 課標準模板庫的迭代器)特別好用。
沒有初始化的區域變數:在函式裡寫 int sum;,C++ 不會自動設成 0,裡面是那塊記憶體之前留下的值(垃圾值)。
讀取它是未定義行為。養成習慣:宣告時就給初始值。
初始化、const、auto 與未初始化的變數
sum 宣告時沒有給值,變數區顯示「?」。第 15 行讀取它時,動畫會停下來說明。真正的程式不會停,而是拿垃圾值去算,印出一個看不出道理的數字;每次執行還可能不一樣。
想一想:轉換與初始化
下面的程式會印出什麼?
int a = 7, b = 2;
double r = a / b;
cout << r;
下面的程式執行結果是什麼?
int x{2.5};
cout << x;
下面的程式會印出什麼?
auto p = 9 / 2;
auto q = 9 / 2.0;
cout << p << " " << q;
動手寫:溫度換算
把攝氏 37 度換算成華氏(華氏 = 攝氏 × 9 ÷ 5 + 32),並把單位字元 f 轉成大寫 F。預期輸出:
37 C = 98.6 F
重點回顧
- 變數要先宣告型態:
int、long long存整數,double、float存小數,char存一個字元,bool存 true/false sizeof查大小、numeric_limits<T>::max()查範圍;int 約正負 21 億,超出範圍會溢位(有號整數溢位是未定義行為)char存的是編碼:'A'是 65、'a'是 97、'0'是 48;char 與 int 運算的結果是 int- double 轉 int 會捨去小數;兩個 int 相除是整數除法,要小數結果就先用
static_cast<double>(…)轉型 int c{7};大括號初始化會擋下縮窄轉換;const宣告後不能改;auto依初始值推斷型態- 區域變數不會自動設成 0,宣告時就要給初始值