物件的類別定義了物件能包含的資料和能參與的運算,其中一種運算被大多數類別支持,就是將物件從一種給定的型別轉換(convert)為另一種相關型別。
當在程式的某處我們使用了一種型別而其實物件應該讀取另一種型別時,compiler會自動進行型別轉換,之後對型別轉換做更多介紹-。這裡,我們來說明當給某種型別的物件賦予了另一種型別的值時,會發生什麼?
當我們像下面這樣把一種算數型別的值assign給另一個型別時:
bool b = 42; //b為真 int i = b; //i的值為1 i = 3.14; //i的值為3 double pi = i; //pi的值為3.0 unsigned char c = -1; //假設char佔8-bit,c的值為255 signed char c2 = 256; //假設char佔8-bit,c2的值為"未定義"
型別所能表示的值的範圍決定了轉換的過程:
- 當我們把一個非boolean的算術值assign給bool時,初始值為0則結果為false,否則結果為true。
- 當我們把一個boolean asign給非bool型別時,初始值false為0,初始值為true則結果為1。
- 把一個浮點數assign給整數型別時,進行了近似處理(rounding)。result將僅保留浮點數中小數點前的部分。
- 把整數值assign給浮點數類型時,小數部分記為0。如果該整數所佔的空間超過浮點數型別的容量,精度就會有損失。
- 賦予無號數型別一個超出它範圍的值時,結果是初始值對無號數表示數值總數取module後的餘數。例如:8-bit大小的unsigned char可以表示0至255區間的值,如果我們assign一個區間外的值,則 result = (assigned value) mod 256 即該值對256取餘數。因此,把-1assign給8-bit大小的unsigned char所得的結果是255。
- 賦予有號數型別一個超出它所能表示範圍的值時,結果是未定義的(undefined);此時,程式有可能繼續執行、可能崩潰、可能產生錯誤計算的資料。
當在程式的某一處中使用了一種算術型別的值,但實際上需要的卻是另一種型別的值時,compiler就會執行上述的Type Casting。eg.如果我們使用了一個non-boolean type做為條件,那麼它會自動地轉換成boolean,這一做法和把non-boolean值assign給bool變數時的情況一模一樣。
int i = 42; if (i) //i的值將為true i = 0;
如果i值為0,則條件為假,若i值為其他值(非0),則將使條件為true。以此類推,如果我們把一個布林值用在算術運算式的話,則它的取值非0即1,所以一般不宜用在算術運算式。
含有無號數的運算式
儘管我們不會故意給無號數一個負值,卻可能寫出這樣的code。例如,把一個算術運算式中既有的無號數又有int值時,那個int值就會轉換成無號數。把int轉換成無號數的過程跟把int直接assign給無號數一樣:
unsigned u = 10; int i = -42; std::cout << i + i << std::endl; //輸出-84 std::cout << u + i << std::endl; //如果int占32 bit,輸出4294967264 //原因:-42 mod 2^32
在第一個輸出運算式中,兩個(負)整數相加得到了期望的結果。在第二個輸出運算式中,相加前首先把整數-42轉換成無號數。把負數轉換成無號數類似於直接給無號數一個負值,結果等於這個負數加上無號數的模。
無號數不會小於0這件事至關重要,同樣也關係到迴圈的寫法。例如先前提到的:
for (int i = 10; i >= 0; --i) std::cout << i << std::endl;
可能你會覺得反正也不打算輸出負數,可以用無號數來重寫這個迴圈,便寫出下面這個code:
//錯誤:變數永遠也不會小於0,循環條件一直成立 for (unsigned u = 10; u >= 0; --i) std::cout << u << std::endl;
當u等於0時,疊代輸出0,然後繼續執行for指令的運算式。運算式中u減去1,得到的結果-1被自動轉換成一個合法的無號數4294967295,執行下一圈迴圈,因此產生了無窮迴圈(infinite loop)。
一種解決辦法是用while來代替for:
unsigned u = 11; //確定要輸出的最大數,從比它大1的數開始 while (u > 0){ --u; //先減1,這樣最後一次疊代時就會輸出0 std::cout << u << std::endl; }
運算式優先權:有號數→無號數
字面常數(literal)
請參閱:https://zh.wikipedia.org/wiki/字面常量_(C语言)
我們可以將整數字面值寫作十進位、八進位或十六進位的形式。以0開頭的代表八進制,以0x或0X開頭的為十六進制。例如我們用三種不同方式來表達數值20:
20 |
十進制 |
024 |
八進制 |
0x14 |
十六進制 |
整數字面值具體的data type由它的值和符號決定。默認情況下,十進制字面值是有號數,八進制和十六進制則有可能是有號數亦有可能是無號數。十進制的型別是int、long、和long long中尺寸最小的那個(例如:三者當中最小的是int),當然前提是要容納得下當前的值。八進制和十六進制字面值的型別是能容納其數值的int、unsigned int、long、unsigned long、long long和unsigned long long中的尺寸最小者。如果一個字面值連與之關聯的最大的data type都放不下,將產生錯誤。注意:short沒有對應的字面值。
儘管字面值可以儲存在有號數中,但嚴格來說,十進制字面值不會是負數,例如我們用了一個形如 -42 的負十進制字面值,那個負號並不在字面值內,它的作用僅僅是對字面值取負值而已,即字面值和負號要分成兩個東西來看。
浮點數字面值利用科學記數法(scientific notation)來表達一個小數或所能表達的range中的任一數,我們可以將浮點數看成實數來使用,但受限於硬體,所以它並非真正的實數(不具有稠密性),其中指數部分用E或e標示:
3.14159 | 3.14159E0 | 0. | 0e0 |
.001 |
默認的,浮點數字面值是一個double,除非後綴特別標註F或其他浮點數型。
字元和字串字面值
由單引號括起來的是一個字元稱為char型字面值,雙引號括起來的零個或多個字元值則構成字串。例如:
'a' //字元字面值 "Hello world!" //字串字面值
字串實際上是由字元常值構成的陣列(array)。編譯器在每個字元結尾處添加一個空字元( ‘\0’ ),因此字串字面值的實際長度要比它的內容多1。例如,’A’表示的就是單獨的字元A,而字串"A"則代表了一個字元的陣列,該陣列包含兩個字元:’A’、’\0’。
如果兩個字串字面值緊鄰且僅由空格、縮排和換行符號分隔,則它們實際上是一個整體。
//分多行書寫的字串 std::cout << "a really, really long string literal " "that spans two lines" << std::endl;
也就是這兩對雙引號其實可以看成是同一個字串。
逸出序列(escape sequence)
也稱為轉義序列,請參閱:https://zh.wikipedia.org/wiki/轉義序列
有兩種類型的字元寫程式時不可以直接使用:
- 不可列印(nonprintable)的字元,如倒退字元或其他控制字元,因為他們沒有可視的圖符。
- 在C++中有特殊含意的字元(單引號、雙引號、問號、反斜線)
這種情況下我們就需要使用逸出序列(escape sequence),逸出序列均以反斜線作為開始:
值 |
逸出序列 |
新行字元 |
\n |
水平定位字元 |
\t |
垂直定位字元 |
\v |
退格鍵(backspace) |
\b |
歸位字元(return) |
\r |
換頁字元(form feed) |
\f |
警示(響鈴) |
\a |
反斜線 |
\\ |
問號 |
? 或 \? |
單引號 |
\’ |
雙引號 |
\" |
null 字元(空字元) |
\0 |
八進位 |
\ooo |
十六進位 |
\xhhh |
Unicode (UTF-8) |
\uxxxx |
Unicode (UTF-16) |
\Uxxxxxxxx |
在程式中,上數逸出序列被當作一個字元使用:
std::cout << '\n'; //轉到新的一行 std::cout << "\tHi!\n"; //輸出一個水平tab,輸出"Hi!"然後轉到新的一行
我們也可以使用泛化的逸出序列,其形式是 \x 後緊跟1個或多個十六進制數字,或者 \ 後緊跟1個、2個或3個八進制數字,其中數字部分表示的是字元對應的數值,假設使用的是Latin-1字元集:
\7 |
警報 |
\12 |
換行 |
\40 |
空格 |
\0 |
null |
\155 |
字元’M’ |
\x4d |
字元’M’ |
std::cout << "Hi \x4dO\115!\n"; //輸出Hi!MOM!,轉到新一行
注意:如果反斜線 \ 後面跟著的八進制數字超過3個,只有前三個數字與 \ 構成逸出序列。例如:"\1234″表示兩個字元,看成 ‘\123’ 和 ‘4’。相反地,\x 要用到後面跟著的所有數字,例如:"\x1234″表示一個16位的字元,該字元由這4個十六進制數所對應的bits唯一決定,由於大多數的電腦char型別佔8bits,所以上面這個16bits的例子可能會報錯。
指定字面值的型別
例如:
L’a’ |
寬字元字面值,型別是wchar_t |
u8”Hi!” |
utf-8字串字面值(utf-8用8bits編碼一個Unicode字元) |
42ULL |
無號數超長整數字面值,unsigned long long |
1E-3F |
單精度浮點數,代表1*10^(-3) |
3.14159L |
長雙精度浮點數,long double |
Tips:盡量使用大寫的L,避免使用小寫l以免跟1搞混
前綴(Prefix) |
||
含義 |
型別 |
|
u |
Unicode 16字元 | char16_t |
U | Unicode 32字元 |
char32_t |
L |
寬字元 | wchar_t |
u8 | UTF-8 (僅用於字串字面常值) |
char |
後綴(Suffix) |
|
最小匹配型別 |
|
整數字面值 |
|
u or U |
unsigned |
l or L |
long |
ll or LL |
long long |
浮點數字面值 |
|
f or F |
float |
l or L |
long double |
對一個整數字面值來說,我們能分別指定它是否有號及占用多少空間。如果後綴中有U,則該字面值為無號數類型,也就是說,以U為後綴的十進位數、八進位數或十六進位數都將從unsigned int、unsigned long 和 unsigned long long 中選擇一個能最適匹配的作為其資料型別。如果後綴中有L,則字面值至少是long;如果後綴中有LL,則字面值的型別會是long long 或unsigned long long 。這兩個符號可以混用,例如:以UL為後綴的字面值將會根據具體情況取unsigned long 或 unsigned long long。
布林字面值和指標(Pointer)字面值
true和false是boolean的字面值:
bool test = false;
nullptr是指標字面值,後續將有更多介紹。
此次完成作業:練習2.3-2.8
參考資料: