# 第二章 變數和基本型別 筆記 ### 原始內建型別(built-in types) 原始內建型別內包含算術型別 (arithmetic types)以及一個特殊的型別:void,而:算術型別可分為兩類:整數值型別(integral types,包含 char 以及 boolean)及浮點數型別(floating-point types),以下為列表: | 型別 | 意義 | 最小尺寸| |---|---|---| | bool | 布林 | 8 bits | | char| 字元 | 8 bits | | wchar_t | 寬字元 | 16 bits | | char16_t | unicode 字元 | 16 bits | | char32_t | unicode 字元 | 32 bits | | short | 短整數 | 16 bits | | int | 整數 | 16 bits(在 32 位元機器中是 32 bits) | | long | 長整數 | 32 bits | | long long | 長整數 | 64 bits(在 C++11 中新定義的) | | float | 單精度浮點數(single-precision) | 6 位有效數字 | | double | 雙精度浮點數(double-precision) | 10 位有效數字 | | long double | 擴充精度浮點數(extended-precision) | 10 位有效數字 | ## 如何選擇型別 - 當你知道值不能為負時,就用一個 unsigned 的型別 - 使用 int 執行整數運算。而在實務上,一般 long 的大小和 int 一樣,而 short 常常顯得太小了。而如果你的資料值超過了 int 的範圍,選擇 long long - 算術運算式(arithmetical expression)中不要使用 char 或 bool,此外 char 是 signed 還是 unsigned 是未定義的,不同的電腦可能會是 signed 或 unsigned - 因為通常 float 的精確度不足,因此浮點運算盡量使用 double ## 型別轉換(type conversions) - 非布林型別指定(assign)、初始化(initialize)給布林型別,初始值為 0 則結果為 false,否則為 true - 布林型別指定(assign)、初始化(initialize)給非布林型別,初始值為 false 結果為 0,初始值為 true 結果為 1 - 如果要將浮點數型別(floating-point type)轉換給整數型別(integral type),浮點數將會被截斷(truncated),也就是指會喪失小數點的部分 ## 字面值(literal) 一個像是 100 的值被稱作 字面值(literal),可分為整數字面值、浮點數字面值、字元字面值、字元字串字面值 - 整數字面值:如 42 - 浮點數字面值:如 100.87 - 字元字面值:使用單引號圍起來,例如:'a' - 字元字串字面值:使用雙引號圍起來,例如:"Hello World" - 轉義序列(escape sequences):\n、\t等 - 可使用 spaces、tabs、newlines 將多字面字串連接,該特性繼承自 C - 字元字串字面值實際上是由字元(chars)所組成連續空間的陣列,並且結尾處以 '\0' 結束,所以字元字串字面值實際上長度比內容多 1 ```cpp std:cout<<"wow, a really, really long string" "literal that spans two lines" <<std::endl; // 兩字串會被連接起來 ``` - 布林字面值:true、false - 指標字面值:nullptr 任何一個字面值的形式和值決定了它的型別,如:42 就代表 int,false 就代表 bool,但其實我們可以使用前綴(prefix)和後綴(suffix)去修改它們的型別 修飾字元與字元字串字面值: | 前綴 | 意義 | 型別 | |---|---|---| | u | Unicode 16 字元 | char16_t | | U | Unicode 32 字元 | char32_t | | L | 寬字元 | wchar_t | | u8 | utf-8 | char | 修飾整數字面值: | 後綴 | 型別 | |---|---| | u、U | unsigned | | l、L | long | | ll、LL | long long | 修飾浮點數字面值: | 後綴 | 型別 | |---|---| | f、F | float | | l、L | long double | 註:_t 修飾的型別代表是使用 typedef 擴充的型別 ## 變數 變數提供一個具名的儲存空間(named-storage),在 C++ 中變數和物件(object)可以互換使用 變數的定義(define): - 定義形式:一個簡單的變數定義由一個型別指定符(type specifier),後面接著由一個變數名,或由逗號(comma)分隔的多個變數名組成的宣告器列表(init-declarator-list),並且以分號(semicolon)結尾,如 int sum = 0, value, units_sold = 0;,其中 int 即為 type specifier,而後續的 sum = 0, value, units_sold = 0,則為宣告器列表(init-declarator-list) - 初始化(initialize):指物件(object)在創建時獲得了一個特定的值 註:初始化(initialize)與指定(assign)是不一樣的東西: - 初始化 = 創建變數 + 賦予初始值 - 指定(assign) = 擦除現在物件(object)的值 + 改用新值複寫 - 列表初始化:使用大括號 {}(curly braces),如 int units_sold{0}; 預設初始化(default initialized): - 定義時不使用初始器(initializers)就會被預設初始化(default initialized) - 此外在函數內部的基本型別(fundmental type)以及指標型別(pointer type)不會被預設初始化,因此會呈現一個未定義的值 建議初始化每一個內建型別(fundmental type 基本型別的同義詞)以及指標型別(pointer type)的變數 ### 變數的宣告(declaration)與定義(define) - 為了讓 C++ 的原始碼能夠個別編譯(separate compilation),C++ 將宣告和定義分開,經過宣告後可使得變數被 compiler 所知,而定義則是負責創建與變數關聯的那個實體 - 只宣告而不定義:在變數名稱前添加關鍵字(keyword)extern,如 extern int i; 指宣告該變數,而變數定義在其他地方。但如果提供了初始器,就變成了定義,如:extern double pi = 3.14; - 變數只能被定義一次,但是可以被多次宣告 - 一個名稱的範疇(namescope)使用大括號(curly braces)來界定 - 第一次使用變數時再定義它 - 巢狀範疇(nested scopes) - 若同時存在於全域範疇(global scope)變數和區塊範疇(block scope)變數時,在已定義區域變數(local variable)的範疇中(scope)中可用 :: 顯式地(explicit)存取全域變數 - 將一個區域變數(local variable)的名稱命名成與全域變數(global variable)一樣肯定是個壞主意 ### 變數名稱的慣例(convention) - 一個識別字(identifier)需能夠表示其意義 - 變數名稱一般都是小寫,也就是 index,而非 Index 或 INDEX - 自定義類別通常用大寫字母開頭,如 Sales_item - 如果變數由多個單字組成,需要有明確的區分方法,使用底線區隔,或是第二個單字後首字使用大寫字母:student_loan 或 studentLoan ## 複合型別(compound type) 在 C++ 中,擁有幾個複合型別,其中兩個(代表其實不只兩個)即參考(reference)和指標(pointer) ### 參考(reference) - 一般說的參考是指的左值參考 - 參考:參考是一個物件(object)或一個函式的別名(alias),參考型別會參考至(refer to)另外一個物件(object),如:int &refVal = val;,此時的 refVal 其實就相當於 val 的別名 - 參考必須要被初始化 - 參考和它的初始值是綁定(bind)在一起的,而不是拷貝,一旦定義就不能更改綁定為其他的物件(object) ### 指標(pointer) - 一個指標是指向(point to)另外一種類別的複合型別(compound type),但不同於參考,參考邏輯上僅是對某個物件的別名(alias),但指標本身就是一個物件,也就是說它擁有自己獨立的地址(address) - 指標內儲存著某個物件(object)的地址(address) - 獲取物件(object)的地址: int i = 42; int *p = &i; 其中 & 是取址符(dereference) - 指標的型別與所指向的物件(object)的型別必須相同(均為同一類型 int、double 等) ## 指標的值可能處於以下四種狀態: - 它可能指向一個物件(object) - 它可能指向緊接在一個物件尾端後的下一個位置 - 它可能是一個空指標(nullptr),代表它沒有指向任何的物件 - 它可能是無效的,除了上述三種狀態以外的值都是無效的 對無效指標的操作極有可能引發錯誤的操作或未定義的行為,而第二種和第三種雖然為有效的,但對它們解參考(dereference)理論上是不被允許的,因為它們兩個代表並未指向任何物件 ```cpp int ival = 42; int *p = &ival; cout<< *p; ``` - 使用指標存取物件:cout << \*p; 輸出指標 p 所指向物件的數據,\* 是解參考運算子(dereference) - 空指標代表該指標不指向任何物件,可使用 int *p = nullptr; 來指向空指標 - 指標和參考的區別:參考本身並非一個物件(object),參考定義後就不能綁定到其他的物件了;指針則沒有此限制,相當於使用一般的物件 - void* 指標可以存放任意物件的地址,但不知道指向那個地址的物件之型別,也因為沒有型別,僅能用來操作記憶體空間,對所存的物件無法解參考(dereference),會造成未定義行為(undefined behavior) - 其他指標類型必須要與所指物件匹配(matching) - 兩個指標相減所產生的結果的型別是 ptrdiff_t,且兩個指標不能相加 - 建議初始化(initialize)所有的指標 ## const 限定詞(const qualifier) 可用於定義一個不能被改變值的變數 ### 初始化(initialize)與 const const 物件必須被初始化,且不能被改變 ```cpp const int a1; // 錯誤,必須要被初始化 const int a2 = 2; // 正確 ``` ### 對 const 的參考 - reference to const(對常數的參考):指向 const 物件的參考,如 const int ival=1; const int &refVal = ival;,可以讀取但是不能修改refVal - 暫存物件(temporary):當編譯器需要一個空間來暫存表達式(expression)的求值結果時,會臨時創建一個未命名的物件,而對暫存物件參考是不合法的行為 ### 指標 和 const - pointer to const(指向常數的指標):不能用於改變其所指物件的值, 如 const double pi = 3.14; const double *cptr = π - const pointer:指標本身是常數,也就是說指標固定指向該物件(存放在指標中的地址不會改變,地址所對應的那個物件是可以修改的)如 int i = 0; int *const ptr = &i; ### 頂層 const - 頂層 const:指標本身是個常數 - 底層 const:指標指向的物件是個常數。複製時嚴格要求相同的底層 const 修飾相同 ### constexpr 和常數運算式(constant expression) - 常數運算式(constant expression):指值不會改變,且在編譯過程中就能得到計算結果的運算式 - C++11 新標準規定,允許將變數宣告為 constexpr 型別以便由編譯器來驗證變數的值是否為一個常數運算式(constant expression),也就是指編譯時期就可以確定的數值 字面值型別: - 能在一個 constexpr 宣告中使用的型別被稱作字面值型別(literal type),因為它簡單到能有字面值 - 算術、參考以及指標型別都是字面值型別 ## 處理型別 隨著程式變得複雜,我們會看到我們所用的型別也會跟著變複雜,因此我們會使用型別別名(alias)以增加可讀性、或是使用 auto 等方式讓程式碼的撰寫更為輕鬆等,這些就是關於處理型別的方式 ### 型別別名(type alias) - 傳統別名:使用 typedef 來定義型別別名(type alias)。如:typedef double wages; - 新標準別名:使用 using 做別名宣告(alias declaration)。如:using SI = Sales_item;(C++11) ```cpp // 註:對於複合型別(compound type)不能直接代回原式理解 typedef char *pstring; // pstring 是 char* 的別名 const pstring cstr = 0; // cstr 是個指向 char 的常數指標 // 如改寫為 const char *cstr = 0; 並不正確,實際上應為 char *const ``` ### auto 型別指定符(type specifier) - auto 型別指定符:可讓編譯器自動推斷型別(auto-deduced type) - 一條含有 auto 的宣告述句(declaration statement)只能有一個型別(type),所以一個 auto 宣告多個變數時只能都是相同的型別,因此如:auto sz = 0, pi = 3.14 是錯誤的,sz 為 int、pi 為 double,型別並不相同 - int i = 0, &r = i; auto a = r; 推斷 a 的型別是 int - 會忽略頂層 const,舉例:const int ci = 1; const auto f = ci; auto 的推斷型別是 int,如果希望是頂層 const 就需要自己加 const - 如果是要對參考型別做自動推導型別,則初始器中的頂層 const 不會被忽略,如:auto &g = ci; ci 是個 const int,則 g 的型別為 const int& ### decltype 型別指定符 - 從運算式(expression)的型別推斷出要定義的型別 - decltype(f()) sum = x; 推斷 sum 的型別是函式 f 的回傳型別 - 如果對變數加括號,編譯器會將其認為是一個運算式,假設定義一個 int i,則 decltype((i)) 得到的結果為 int& - 指定(assign)是會產生參考的一種運算式,參考的型別就是左值的型別。也就是說,如果 i 是 int,則運算式 i = x 的型別是 int& ### struct 盡量不要把類別定義和物件定義放在一起。如:struct Student{} student1, student2; - 類別可以以關鍵字(keyword)struct 開始,後面緊跟著要定義的類別名稱和類別內的內容 - 類別成員:類別體內定義的成員,如成員變數、成員函式(方法 method) - C++11:可以為類別成員提供一個類別內初始值(in-class initializer) ### 編寫自己的標頭檔 - 標頭檔通常包含那些只能被定義一次的實體:如類別、const 和 constexpr 變數 前置處理器概覽: - 前置處理器(preprocessor):確保標頭檔多次被 include 仍然能安全使用 - 當前置處理器看到 #include 巨集(macro)時,會使用指定的標頭檔內容代替 #include - 標頭保護(header guard):標頭保護依賴於前置處理器變數的狀態:已定義和未定義 - #ifdef:已定義時為 true - #ifndef:未定義時為 true - 標頭保護的名稱需要是唯一,並且全部保持大寫。養成良好習慣,不論該標頭檔是否被 #include,都該加上標頭保護(header guard) ```cpp #ifndef SALES_DATA_H // SALES_DATA_H 未定義時為真 #define SALES_DATA_H struct Sale_data { //... } #endif ```
×
Sign in
Email
Password
Forgot password
or
By clicking below, you agree to our
terms of service
.
Sign in via Facebook
Sign in via Twitter
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
New to HackMD?
Sign up