變量
數據類型
C++ 的類型系統由如下幾部分組成:
- 基礎類型(括號內為代表關鍵詞/代表類型)
- 無類型/
void型 (void) - (C++11 起)空指針類型 (
std::nullptr_t) - 算術類型
- 整數類型 (
int) - 布爾類型/
bool型 (bool) - 字符類型 (
char) - 浮點類型 (
float,double)
- 整數類型 (
- 無類型/
- 複合類型2
布爾類型
一個 bool 類型的變量取值只可能為兩種:true 和 false。
一般情況下,一個 bool 類型變量佔有 \(1\) 字節(一般情況下,\(1\) 字節 =\(8\) 位)的空間。
C 語言的布爾類型
C 語言最初是沒有布爾類型的,直到 C99 時才引入 _Bool 關鍵詞作為布爾類型,其被視作無符號整數類型。
Note
C 語言的 bool 類型從 C23 起不再使用整型的零與非零值定義,而是定義為足夠儲存 true 和 false 兩個常量的類型。
為方便使用,stdbool.h 中提供了 bool,true,false 三個宏,定義如下:
1 2 3 | |
這些宏於 C23 中移除,並且 C23 起引入 true,false 和 bool 作為關鍵字,同時保留 _Bool 作為替代拼寫形式1。
整數類型
用於存儲整數。最基礎的整數類型是 int.
注意
由於歷史原因,C++ 中布爾類型和字符類型會被視作特殊的整型。
在幾乎所有的情況下都 不應該 將除 signed char 和 unsigned char 之外的字符類型作為整型使用。
整數類型一般按位寬有 5 個梯度:char,short,int,long,long long.
C++ 標準保證 1 == sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)
由於歷史原因,整數類型的位寬有多種流行模型,為解決這一問題,C99/C++11 引入了 定寬整數類型。
int 類型的大小
在 C++ 標準中,規定 int 的位數 至少 為 \(16\) 位。
事實上在現在的絕大多數平台,int 的位數均為 \(32\) 位。
對於 int 關鍵字,可以使用如下修飾關鍵字進行修飾:
符號性:
signed:表示帶符號整數(默認);unsigned:表示無符號整數。
大小:
short:表示 至少 \(16\) 位整數;long:表示 至少 \(32\) 位整數;- (C++11 起)
long long:表示 至少 \(64\) 位整數。
下表給出在 一般情況下,各整數類型的位寬和表示範圍大小(少數平台上一些類型的表示範圍可能與下表不同):
| 類型名 | 等價類型 | 位寬(C++ 標準) | 位寬(常見) | 位寬(較罕見) |
|---|---|---|---|---|
signed char |
signed char |
\(8\) | - | - |
unsigned char |
unsigned char |
\(8\) | - | - |
short,short int,signed short,signed short int |
short int |
\(\geq 16\) | \(16\) | - |
unsigned short,unsigned short int |
unsigned short int |
\(\geq 16\) | \(16\) | - |
int,signed,signed int |
int |
\(\geq 16\) | \(32\) | \(16\)(常見於 Win16 API) |
unsigned,unsigned int |
unsigned int |
\(\geq 16\) | \(32\) | \(16\)(常見於 Win16 API) |
long,long int,signed long,signed long int |
long int |
\(\geq 32\) | \(32\) | \(64\)(常見於 64 位 Linux、macOS) |
unsigned long,unsigned long int |
unsigned long int |
\(\geq 32\) | \(32\) | \(64\)(常見於 64 位 Linux、macOS) |
long long,long long int,signed long long,signed long long int |
long long int |
\(\geq 64\) | \(64\) | - |
unsigned long long,unsigned long long int |
unsigned long long int |
\(\geq 64\) | \(64\) | - |
當位寬為 \(x\) 時,有符號類型的表示範圍為 \(-2^{x-1}\sim 2^{x-1}-1\), 無符號類型的表示範圍為 \(0 \sim 2^x-1\). 具體而言,有下表:
| 位寬 | 表示範圍 |
|---|---|
| \(8\) | 有符號:\(-2^{7}\sim 2^{7}-1\), 無符號:\(0 \sim 2^{8}-1\) |
| \(16\) | 有符號:\(-2^{15}\sim 2^{15}-1\), 無符號:\(0 \sim 2^{16}-1\) |
| \(32\) | 有符號:\(-2^{31}\sim 2^{31}-1\), 無符號:\(0 \sim 2^{32}-1\) |
| \(64\) | 有符號:\(-2^{63}\sim 2^{63}-1\), 無符號:\(0 \sim 2^{64}-1\) |
等價的類型表述
在不引發歧義的情況下,允許省略部分修飾關鍵字,或調整修飾關鍵字的順序。這意味着同一類型會存在多種等價表述。
例如 int,signed,int signed,signed int 表示同一類型,而 unsigned long 和 unsigned long int 表示同一類型。
另外,一些編譯器實現了擴展整數類型,如 GCC 實現了 128 位整數:有符號版的 __int128_t 和無符號版的 __uint128_t,如果您在比賽時想使用這些類型,請仔細閲讀比賽規則 以確定是否允許或支持使用擴展整數類型。
注意
STL 不一定對擴展整數類型有足夠的支持,故使用擴展整數類型時需格外小心。
示例代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
以上示例代碼存在如下問題:
__int128_t f3(__int128_t)中使用的是 C 風格的絕對值函數,其簽名為int abs(int),故n首先會強制轉換為int,然後才會調用abs函數。__int128_t f4(__int128_t)中使用的是 C++ 風格的絕對值函數,其並沒有簽名為__int128_t std::abs(__int128_t)的函數重載,所以無法通過編譯。- C++ 的流式輸出不支持
__int128_t與__uint128_t。
以下是一種解決方案:
修正後的代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | |
字符類型
分為「窄字符類型」和「寬字符類型」,由於算法競賽幾乎不會用到寬字符類型,故此處僅介紹窄字符類型。
窄字符型位數一般為 \(8\) 位,實際上底層存儲方式仍然是整數,一般通過 ASCII 編碼 實現字符與整數的一一對應,有如下三種:
signed char:有符號字符表示的類型,表示範圍在 \(-128 \sim 127\) 之間。unsigned char:無符號字符表示的類型,表示範圍在 \(0 \sim 255\) 之間。-
char擁有與signed char或unsigned char之一相同的表示和對齊,但始終是獨立的類型。char的符號性取決於編譯器和目標平台:ARM 和 PowerPC 的默認設置通常沒有符號,而 x86 與 x64 的默認設置通常有符號。GCC 可以在編譯參數中添加
-fsigned-char或-funsigned-char指定將char視作signed char或unsigned char,其他編譯器請參照文檔。需要注意指定與架構默認值不同的符號有可能會破壞 ABI,造成程序無法正常工作。
注意
與其他整型不同,char、signed char、unsigned char 是 三種不同的類型。
一般來説 signed char,unsigned char 不應用來存儲字符,絕大多數情況下,這兩種類型均被視作整數類型。
浮點類型
用於存儲「實數」(注意並不是嚴格意義上的實數,而是實數在一定規則下的近似),包括以下三種:
float:單精度浮點類型。如果支持就會匹配 IEEE-754 binary32 格式。double:雙精度浮點類型。如果支持就會匹配 IEEE-754 binary64 格式。long double:擴展精度浮點類型。如果支持就會匹配 IEEE-754 binary128 格式,否則如果支持就會匹配 IEEE-754 binary64 擴展格式,否則匹配某種精度優於 binary64 而值域至少和 binary64 一樣好的非 IEEE-754 擴展浮點格式,否則匹配 IEEE-754 binary64 格式。
| 浮點格式 | 位寬 | 最大正數 | 精度位數 |
|---|---|---|---|
| IEEE-754 binary32 格式 | \(32\) | \(3.4\times 10^{38}\) | \(6\sim 9\) |
| IEEE-754 binary64 格式 | \(64\) | \(1.8\times 10^{308}\) | \(15\sim 17\) |
| IEEE-754 binary64 擴展格式 | \(\geq 80\) | \(\geq 1.2\times 10^{4932}\) | \(\geq 18\sim 21\) |
| IEEE-754 binary128 格式 | \(128\) | \(1.2\times 10^{4932}\) | \(33\sim 36\) |
IEEE-754 浮點格式的最小負數是最大正數的相反數。
因為 float 類型表示範圍較小,且精度不高,實際應用中常使用 double 類型表示浮點數。
另外,浮點類型可以支持一些特殊值:
- 無窮(正或負):
INFINITY. - 負零:
-0.0,例如1.0 / 0.0 == INFINITY,1.0 / -0.0 == -INFINITY. - 非數(NaN):
std::nan,NAN,一般可以由0.0 / 0.0之類的運算產生。它與任何值(包括自身)比較都不相等,C++11 後可以 使用std::isnan判斷一個浮點數是不是 NaN.
無類型
void 類型為無類型,與上面幾種類型不同的是,不能將一個變量聲明為 void 類型。但是函數的返回值允許為 void 類型,表示該函數無返回值。
空指針類型
請參閲指針的 對應章節
定寬整數類型
C++11 起提供了定寬整數的支持,具體如下:
<cstdint>:提供了若干定寬整數的類型和各定寬整數類型最大值、最小值等的宏常量。<cinttypes>:為定寬整數類型提供了用於std::fprintf系列函數和std::fscanf系列函數的格式宏常量。
定寬整數有如下幾種:
intN_t: 寬度 恰為 \(N\) 位的有符號整數類型,如int32_t.int_fastN_t: 寬度 至少 有 \(N\) 位的 最快的 有符號整數類型,如int_fast32_t.int_leastN_t: 寬度 至少 有 \(N\) 位的 最小的 有符號整數類型,如int_least32_t.
無符號版本只需在有符號版本前加一個字母 u 即可,如 uint32_t,uint_least8_t.
標準規定必須實現如下 16 種類型:
int_fast8_t,int_fast16_t,int_fast32_t,int_fast64_t,
int_least8_t,int_least16_t,int_least32_t,int_least64_t,
uint_fast8_t,uint_fast16_t,uint_fast32_t,uint_fast64_t,
uint_least8_t,uint_least16_t,uint_least32_t,uint_least64_t.
絕大多數編譯器在此基礎上都實現瞭如下 8 種類型:
int8_t,int16_t,int32_t,int64_t,
uint8_t,uint16_t,uint32_t,uint64_t.
在實現了對應類型的情況下,C++ 標準規定必須實現表示對應類型的最大值、最小值、位寬的宏常量,格式為將類型名末尾的 _t 去掉後轉大寫並添加後綴:
_MAX表示最大值,如INT32_MAX即為int32_t的最大值。_MIN表示最小值,如INT32_MIN即為int32_t的最小值。
注意
定寬整數類型本質上是普通整數類型的類型別名,所以混用定寬整數類型和普通整數類型可能會影響跨平台編譯,例如:
示例代碼
1 2 3 4 5 6 7 8 9 10 11 | |
int64_t 在 64 位 Windows 下一般為 long long int, 而在 64 位 Linux 下一般為 long int, 所以這段代碼在使用 64 位 Linux 下的 GCC 時不能通過編譯,而使用 64 位 Windows 下的 MSVC 時可以通過編譯,因為 std::max 要求輸入的兩個參數類型必須相同。
此外,C++17 起在 <limits> 中提供了 std::numeric_limits 類模板,用於查詢各種算數類型的屬性,如最大值、最小值、是否是整形、是否有符號等。
1 2 3 4 5 6 7 8 9 | |
類型轉換
在一些時候(比如某個函數接受 int 類型的參數,但傳入了 double 類型的變量),我們需要將某種類型,轉換成另外一種類型。
C++ 中類型的轉換機制較為複雜,這裏主要介紹對於基礎數據類型的兩種轉換:數值提升和數值轉換。
數值提升
數值提升過程中,值本身保持不變。
Note
C 風格的可變參數域在傳值過程中會進行默認參數提升。如:
示例代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | |
在調用 test 時,f 提升為 double,從而底層存儲內容和 fd 相同,輸出為
1 2 3 | |
若將 double xx = va_arg(valist, double); 改為 float xx = va_arg(valist, float);,GCC 應該給出一條類似下文的警告:
1 2 3 4 5 6 7 | |
此時的程序將會在輸出前終止。
這一點也能解釋為什麼 printf 的 %f 既能匹配 float 也能匹配 double。
整數提升
小整數類型(如 char)的純右值可轉換成較大整數類型(如 int)的純右值。
具體而言,算術運算符不接受小於 int 的類型作為它的實參,而在左值到右值轉換後,如果適用就會自動實施整數提升。
具體地,有如下規則:
- 源類型為
signed char、signed short / short時,可提升為int。 - 源類型為
unsigned char、unsigned short時,若int能保有源類型的值範圍,則可提升為int,否則可提升為unsigned int。(C++20起char8_t也適用本規則) char的提升規則取決於其底層類型是signed char還是unsigned char。bool類型可轉換到int:false變為0,true變為1。- 若目標類型的值範圍包含源類型,且源類型的值範圍不能被
int和unsigned int包含,則源類型可提升為目標類型。3
注意
char->short 不是數值提升,因為 char 要優先提升為 int / unsigned int,之後是 int / unsigned int->short,不滿足數值提升的條件。
如(以下假定 int 為 32 位,unsigned short 為 16 位,signed char 和 unsigned char 為 8 位,bool 為 1 位)
(signed char)'\0' - (signed char)'\xff'會先將(signed char)'\0'提升為(int)0、將(signed char)'\xff'提升為(int)-1, 再進行int間的運算,最終結果為(int)1。(unsigned char)'\0' - (unsigned char)'\xff'會先將(unsigned char)'\0'提升為(int)0、將(unsigned char)'\xff'提升為(int)255, 再進行int間的運算,最終結果為(int)-255。false - (unsigned short)12會先將false提升為(int)0、將(unsigned short)12提升為(int)12, 再進行int間的運算,最終結果為(int)-12。
浮點提升
位寬較小的浮點數可以提升為位寬較大的浮點數(例如 float 類型的變量和 double 類型的變量進行算術運算時,會將 float 類型變量提升為 double 類型變量),其值不變。
數值轉換
數值轉換過程中,值可能會發生改變。
注意
數值提升優先於數值轉換。如 bool->int 時是數值提升而非數值轉換。
整數轉換
-
如果目標類型為位寬為 \(x\) 的無符號整數類型,則轉換結果是原值 \(\bmod 2^x\) 後的結果。
-
若目標類型位寬大於源類型位寬:
-
若源類型為有符號類型,一般情況下需先進行符號位擴展再轉換。
如
- 將
(short)-1((short)0b1111'1111'1111'1111)轉換為unsigned int類型時,先進行符號位擴展,得到0b1111'1111'1111'1111'1111'1111'1111'1111,再進行整數轉換,結果為(unsigned int)4'294'967'295((unsigned int)0b1111'1111'1111'1111'1111'1111'1111'1111)。 - 將
(short)32'767((short)0b0111'1111'1111'1111)轉換為unsigned int類型時,先進行符號位擴展,得到0b0000'0000'0000'0000'0111'1111'1111'1111,再進行整數轉換,結果為(unsigned int)32'767((unsigned int)0b0000'0000'0000'0000'0111'1111'1111'1111)。
- 將
-
若源類型為無符號類型,則需先進行零擴展再轉換。
如將
(unsigned short)65'535((unsigned short)0b1111'1111'1111'1111)轉換為unsigned int類型時,先進行零擴展,得到0b0000'0000'0000'0000'1111'1111'1111'1111,再進行整數轉換,結果為(unsigned int)65'535((unsigned int)0b0000'0000'0000'0000'1111'1111'1111'1111)。
-
-
若目標類型位寬不大於源類型位寬,則需先截斷再轉換。
如將
(unsigned int)4'294'967'295((unsigned int)0b1111'1111'1111'1111'1111'1111'1111'1111)轉換為unsigned short類型時,先進行截斷,得到0b1111'1111'1111'1111,再進行整數轉換,結果為(unsigned short)65'535((unsigned short)0b1111'1111'1111'1111)。
-
-
如果目標類型為位寬為 \(x\) 的帶符號整數類型,則 一般情況下,轉換結果可以認為是原值 \(\bmod 2^x\) 後的結果。4
例如將
(unsigned int)4'294'967'295((unsigned int)0b1111'1111'1111'1111'1111'1111'1111'1111)轉換為short類型時,結果為(short)-1((short)0b1111'1111'1111'1111)。 -
如果目標類型是
bool,則是 布爾轉換。 -
如果源類型是
bool,則false轉為對應類型的 0,true轉為對應類型的 1。
浮點轉換
位寬較大的浮點數轉換為位寬較小的浮點數,會將該數舍入到目標類型下最接近的值。
浮點整數轉換
-
浮點數轉換為整數時,會捨棄浮點數的全部小數部分。
如果目標類型是
bool,則是 布爾轉換。 -
整數轉換為浮點數時,會舍入到目標類型下最接近的值。
如果該值不能適應到目標類型中,那麼行為未定義。
如果源類型是
bool,那麼false轉換為零,而true轉換為一。
布爾轉換
將其他類型轉換為 bool 類型時,零值轉換為 false,非零值轉換為 true。
定義變量
簡單地説5,定義一個變量,需要包含類型説明符(指明變量的類型),以及要定義的變量名。
例如,下面這幾條語句都是變量定義語句。
1 2 3 | |
在目前我們所接觸到的程序段中,定義在花括號包裹的地方的變量是局部變量,而定義在沒有花括號包裹的地方的變量是全局變量。實際有例外,但是現在不必瞭解。
定義時沒有初始化值的全局變量會被初始化為 \(0\)。而局部變量沒有這種特性,需要手動賦初始值,否則可能引起難以發現的 bug。
變量作用域
作用域是變量可以發揮作用的代碼塊。
全局變量的作用域,自其定義之處開始6,至文件結束位置為止。
局部變量的作用域,自其定義之處開始,至代碼塊結束位置為止。
由一對大括號括起來的若干語句構成一個代碼塊。
1 2 3 4 5 6 7 | |
如果一個代碼塊的內嵌塊中定義了相同變量名的變量,則內層塊中將無法訪問外層塊中相同變量名的變量。
例如上面的代碼中,輸出的 \(g\) 的值將是 \(10\)。因此為了防止出現意料之外的錯誤,請儘量避免局部變量與全局變量重名的情況。
常量
常量是固定值,在程序執行期間不會改變。
常量的值在定義後不能被修改。定義時加一個 const 關鍵字即可。
1 2 | |
如果修改了常量的值,在編譯環節就會報錯:error: assignment of read-only variable‘a’。
參考資料與註釋
- Working Draft, Standard for Programming Language C++
- 類型 - cppreference.com
- C 語言的 算術類型 - cppreference.com
- 基礎類型 - cppreference.com
- 定寬整數類型(C++11 起)- cppreference.com
- William Kahan (1 October 1997)."Lecture Notes on the Status of IEEE Standard 754 for Binary Floating-Point Arithmetic".
- 隱式轉換 - cppreference.com
- 聲明 - cppreference
- 作用域 - cppreference.com
-
參見 https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3054.pdf ↩
-
包括數組類型、引用類型、指針類型、類類型、函數類型等。由於本篇文章是面向初學者的,故不在本文做具體介紹。具體請參閲 類型 - cppreference.com ↩
-
不包含寬字符類型、位域和枚舉類型,詳見 整型轉換 - cppreference。 ↩
-
自 C++20 起生效。C++20 前結果是實現定義的。詳見 整型轉換 - cppreference。 ↩
-
定義一個變量時,除了類型説明符之外,還可以包含其他説明符。詳見 聲明 - cppreference。 ↩
本页面最近更新:,更新历史
发现错误?想一起完善? 在 GitHub 上编辑此页!
本页面贡献者:OI-wiki
本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供,附加条款亦可能应用