C++ 學習筆記之(2)-變量、類型和限定符
註釋
C++中有兩種註釋
- 單行註釋:雙斜線
//
開始,以換行符結束,可以包含任何文本,比如額外的雙斜線 - 多行註釋:界定符界定,以
/*
開始,以/*
結束,可以包含除*/
外的任意內容,包括換行符。
注意:註釋界定符不能嵌套,否則會發生錯誤
數據類型
C++提供了一組內置數據類型,程序員也可以定義自己的數據類型,即類類型。基本數據類型包括算數類型和控類型。算數類型又分爲兩類:整形和浮點型。
- 基本的字符類型
char
可以存放機器基本字符集中任意字符對應的數值,故char
類型通常一個字節,其他字符類型用於擴展字符集,比如wchar_t
用來確保存放機器最大擴展字符集中的任意字符,比如漢字等。char16_t
和char32_t
爲Unicode
字符集服務。 - 除字符和布爾類型外,其他整型用於表示不同尺寸的整數,
short <= int <= long <= long long
,long long
爲C++11
新特性。 - 浮點型可表示單精度、雙精度和擴展精度值,即
float
,double
和long double
, bool
類型表示真值true
和false
。可以將算術類型的任何值賦給bool
對象。0
值算術類型代表false
,任何非0
的值都代表true
無符號類型和帶符號類型
除去布爾型和擴展字符型外,其他整形可以分爲帶符號
和無符號
類型。
帶符號
類型表示正數、負數和0
,無符號
類型僅能表示大於等於0
的值。類型
int
、short
等爲帶符號類型,前面加unsigned
表示無符號類型。類型unsigned int
可以縮寫爲unsigned
- 執行浮點數運算選用
double
,因爲float
通常精度不夠,且兩者計算代價相差無幾。
類型轉換
對象的類型定義了對象包含的數據和能參與的運算,大多數類型都支持類型轉換,即比如給某類型對象賦值其他類型值,會自動進行類型轉換。
- 若賦給無符號類型一個超出範圍的值時,結果是初始值對五福哈類型表示數值總數取模後的餘數。比如
-1
賦值給unsigned char
類型即表示區間爲0 - 255
, 結果爲255
- 當賦值給帶符號類型一個超出表示範圍的值時,結果是未定義的。
- 切勿混用帶符號類型和無符號類型,因爲帶符號類型會自動的轉換成無符號類型。
字面值常量
形如42
的值被稱爲字面值常量,字面值常量的形式和值決定了它的數據類型
整形和浮點型字面值
- 整型字面值可以是十進制數、八進制數或者十六進制數的形式,以
0
開頭的整數代表八進制數,以0x
或0X
開頭的代表十六進制數。 - 整型字面值的具體類型由值和符號決定。默認十進制字面值爲無符號數,其他不確定。十進制字面值的類型是
int
、long
和long long
中能容納下當前值最小的那個。八進制和十進制字面值的類型是容納其數值的無符號類型的最小者,即int
、unsigned int
等 short
沒有對應字面值- 默認浮點型字面值是一個
double
,表現爲一個小數或以科學計數法表示的指數,其中指數部分用E
或e
標識
字符和字符串字面值
由單引號括起來的一個字符成爲char
型字面值,雙引號括起來的零個或多個字符爲字符串型字面值。
字符串字面值的類型實際上是由常量字符構成的數組,結尾處添加一個空字符
\0
,故實際長度比內容多1
有兩類字符不能直接使用,分別是不可打印字符,比如退格或其他控制字符;另一類是C++中有特殊含義的字符比如單引號、問號等。可以使用轉義序列。
兩個相鄰的字符串字面值若僅由空格、縮進和換行符分割,則他們實際爲一個整體。比如某字符串字面值較長,可分開書寫在多行
std::cout << "a really, really long string literal" "that spans two lines" << std::endl;
布爾字面值和指針字面值
true
和false
是布爾類型的字面值nullptr
是指針類型字面值
通過前綴和後綴可以改變整型、浮點型和字符型字面值的默認類型
變量
變量定義
變量提供一個具名的、可供程序操作的存儲空間。C++中的每個變量都有其數據類型,數據類型決定着變量所佔空間的大小和佈局方式、存儲值的範圍以及變量參與的運算。
變量定義形式:
類型說明符 變量名1, 變量名2;
, 也可以給其賦初值初始花不是賦值,初始化的含義是創建變量時賦予其一個初始值,而賦值的含義是把對象的當前值擦除,以一個新值來替代。
列表初始化用於內置類型變量時,不會轉換類型。
long double ld = 3.1415926536; int a{ld}, b = {ld}; // 錯誤,轉換未執行,因爲存在丟失信息的危險 itn c(ld), d = ld; // 正確,轉換執行,且確定丟失了部分值
默認初始化當定義變量時未指定初值時發生,默認值由變量類型和定義位置決定。任何函數體之外的內置類型被初始化爲
0
;函數體內部的內置類型變量不被初始化,即值未定義
。類的對象若沒有顯示初始化,則其值由類確定。
標識符由字母、數字和下劃線組成,必須以字母或下劃線開頭。長度無限制,但對大小寫敏感
變量聲明和定義的關係
C++ 支持分離式編譯,即分割程序爲多個文件,每個文件獨立編譯
定義:用於爲變量分配存儲空間,還可爲變量指定初始值。程序中,變量有且僅有一個定義。
聲明:用於向程序表明變量的類型和名字。
定義也是聲明,extern聲明不是定義,除非extern顯示初始化
任何包含了顯示初始化的聲明即成爲定義。若extern語句包含初始值,則不是聲明,而是定義。並且extern定義只能在函數體外部,否則會引發錯誤。
extern double pi = 3.1416; //定義
多文件工程中,聲明和定義非常重要。變量的定義必須且只能出現在一個文件中,而其他用到該變量的文件必須對其進行聲明,卻決不能重複定義。
有關聲明和定義的關係,可參見此文章C語言中聲明和定義詳解
複合類型
複合類型是指基於其他類型定義的類型。
引用
引用並非對象,只是爲一個已經存在的對象所起的另外一個名字。
引用必須初始化,因此無法令引用重新綁定到其他對象
引用不是對象,故不能定義引用的引用,也不能定義指向引用的指針。
引用只能綁定在對象上,不能與字面值或某個表達式的計算結果綁定在一起
引用定義
int i = 1024; int &r = i; // r 是一個引用,與 i 綁定在一起
指針
指針是指向另外一種類型的複合類型
- 指針本身就是對象,允許對指針賦值和拷貝,也可以更改指向不同的對象
- 指針無需在定義時賦初值,但和其他內置類型一樣,塊作用域中若沒有被初始化,則值未定義
- 指針的值(即地址)有四種狀態,試圖訪問無效指針的值將引發錯誤
- 指向一個對象
- 執行緊鄰對象所佔空間的下一個位置
- 空指針,意味着指針沒有指向任何對象
- 無效指針,也就是上述情況之外的其他值
- 空指針不指向任何對象,使用指針之前可以檢查是否爲空,C++11推薦使用
nullptr
初始化指針爲空 - void * 指針是一種特殊的指針類型,可以存放任意對象的地址。但無法瞭解改地址中是什麼類型對象。
- 指針是對象,所以可以定義指針類型的引用。若類型比較複雜,可以採取從右到左閱讀變量定義。
const 限定符
const限定符可以對變量的類型加以限定,變成常量,使其不可修改
- const 對象創建後其值不可改變,故必須初始化。
- 默認狀態下,const對象僅在文件內有效。因爲const對象在編譯期間被替換成對應值,故必須在該變量文件中找到其定義。
- 若多文件共享const對象,則必須在const變量定義前加extern關鍵字。
const 引用
對const的引用簡稱常量引用,不能更改引用綁定的對象。
- 引用類型必須與其所引用對象類型一致,但有兩個例外,第一個是初始化常量引用時允許用任意表達式作爲初始值,只要其結果能轉換成引用類型即可。
- 當一個常量引用綁定到其他類型或常量時,其他類型變量會創建一個臨時量來存放轉換或表達式結果,故對該引用的操作就是對該臨時變量的操作。
- 常量引用可以綁定非常量對象、字面值
指針和const
與引用一樣,指針也可以指向常量或非常量,指向常量的指針不能用於改變所指對象值。要想存放常量對象地址,只能使用指向常量的指針。
- 指針的類型必須與其所指對象類型一致,但有兩個例外,第一個就是允許另一個指向常量的指針指向一個非常量對象。
- 指針是對象,而引用不是,故可以把指針本身定爲常量。
- 常量指針必須初始化,一旦初始化完成,則值(即存放的地址)不可改變。
*
在const之前表明指針是常量,即不變的是指針本身,而不是指向的那個值- 頂層const表示指針本身是常量, 底層const表示所指的對象是一個常量
constexpr和常量表達式
常量表達式是指值不會改變並且在編譯過程就能得到計算結果的表達式
字面值是常量表達式,用常量表達式初始化的const對象也是常量表達式
C++ 11規定,允許使用
constexpr
聲明變量爲常量,且必須使用常量表達式初始化。也可以使用constexpr
函數,這種函數足夠簡單到編譯時就能計算結果。constexpr
指針的初始值必須是nullptr
或者0
, 或者是存儲於某個固定地址總的對象。函數體內定義的變量並非存放在固定地址中,故constexpr
指針不能指向此類對象。而定義在函數體外的對象地址固定不變,能用來初始化constexpr
指針constexpr
聲明中若定義了指針,則限定符只對指針有效,與指針所指對象無關const int *p = nullptr; // p 是一個指向整型常量的指針 constexpr int *q = nullptr; // q 是一個指向整數的常量指針。
關於const
的好文推薦
處理類型
類型別名是一個名字,是某種類型的同義詞。有兩種方法可用於定義類型別名
傳統的方法是使用關鍵字
typedef
typedef double wages; // wages是 double 的同義詞 typedef wages base, *p; // base 是double 的同義詞,p 是double * 的同義詞
新方法,使用別名聲明
using SI = Sales_item; // SI 是Sales_item 的同義詞
注意類型別名在聲明中的應用,不要把類型別名替換成原本的樣子來理解聲明語句含義,大錯特錯
typedef char *pstring; const pstring cstr = 0; // cstr 是指向 char 的常量指針 const pstring *ps; // ps 是一個指針,它的對象是指向 char 的常量指針
auto 類型說明符
C++11新標準引入了auto
類型說明符,能讓編譯器分析表達式所述的類型。
使用
auto
語句也能在一條語句聲明多個變量,但因爲一條聲明語句只能有一個基本數據類型,故所有變量的初始基本數據類型必須一樣。auto sz = 0, pi = 3.14; // 錯誤, sz 和 pi 的類型不一致
使用引用就是使用引用的對象,即編譯器使用引用對象的類型作爲
auto
的類型。auto
會忽略頂層const
, 保留底層const
int i = 0, &r = i; auto a = r; // a 是一個整數(r 是 i 的別名,而 i 是一個整數) const int ci = 9, &cr = ci; auto b = ci; // b 是一個整數(ci的頂層const特性被忽略) auto c = cr; // c 是一個整數(cr 是 ci 的別名,ci 本身是一個頂層 const) auto d = &i; // d 是一個整型指針(整數的地址就是指向整數的指針) auto e = &ci; // e 是一個指向整數常量的指針(對常量對象取地址是一種底層 const)
如果希望推斷出的
auto
類型是一個頂層const
,需要明確加上const
在auto
前面。
decltype 類型指示符
C++11 引入了decltype
類型說明符,作用是從表達式的類型推斷出要定義的變量的類型,但是不想用該表達式的值初始化變量,即選擇並返回操作數的數據類型。
decltype(f()) sum = x; // sum 的類型就是函數 f 的返回類型
decltype
使用的表達式是變量時,則decltype
返回該變量的類型(包括頂層const和引用在內), 引用只有在decltype
處使用本身const int ci = 0, &cj = ci; decltype(ci) x= 0; // x 的類型是 const int decltype(cj) y = x; // y 的類型是 const int &, y 綁定到變量 x decltype(cj) z; // 錯誤:z 是一個引用,必須初始化
若
decltype
使用的表達式不是變量,則decltype
返回表達式結果對應的類型。int i = 42, *p = &i, &r = i; decltype(r + 0) b; // 正確:加法的結果是int,因此b是一個未初始化的int decltype(*p) c; // 錯誤:c是int &, 必須初始化,*p 爲解引用,表達式得到的類型爲引用類型,非int
- r 是一個引用,因此
decltype(r)
的結果是引用類型,若想讓結果是r
所指的類型,可以把r
作爲表達式的一部分,如r + 0
,則這個表達式的結果顯然是一個具體值 - 如果表達式的內容是解引用操作,則
decltype
將得到引用類型。
- r 是一個引用,因此
若
decltype
所用的表達式是加了括號的變量,則得到的結果是引用類型decltype((i)) d; // 錯誤:d 是 int&, 必須初始化 decltype(i) e; // 正確:e 是一個(未初始化的)int
decltype((variable))
(注意是雙層括號)的結果永遠是引用,而decltype(variable)
的結果只有當variable
本身就是一個引用時纔是引用。