# 練習21:高級數據類型和控制結構
> 原文:[Exercise 21: Advanced Data Types And Flow Control](http://c.learncodethehardway.org/book/ex21.html)
> 譯者:[飛龍](https://github.com/wizardforcel)
這個練習是C語言中所有可用的數據類型和控制結構的摘要。它也可以作為一份參考在補完你的知識,并且不含有任何代碼。我會通過創建教學卡片的方式,讓你記住一些信息,所以你會在腦子里記住所有重要的概念。
這個練習非常有用,你應該花至少一周的時間來鞏固內容并且補全這里所沒有的元素。你應學出每個元素是什么意思,以及編寫程序來驗證你得出的結論。
## 可用的數據類型
`int`
儲存普通的整數,默認為32位大小。
> 譯者注:`int`在32或64位環境下為32位,但它不應該被看作平臺無關的。如果需要用到平臺無關的定長整數,請使用`int(n)_t`。
`double`
儲存稍大的浮點數。
`float`
儲存稍小的浮點數。
`char`
儲存單字節字符。
`void`
表示“無類型”,用于聲明不返回任何東西的函數,或者所指類型不明的指針,例如`void *thing`。
`enum`
枚舉類型,類似于整數,也可轉換為整數,但是通過符號化的名稱訪問或設置。當`switch`語句中沒有覆蓋到所有枚舉的元素時,一些編譯器會發出警告。
## 類型修飾符
`unsigned`
修改類型,使它不包含任何負數,同時上界變高。
`signed`
可以儲存正數和負數,但是上界會變為(大約)一半,下界變為和上界(大約)等長。
> 譯者注:符號修飾符只對`char`和`*** int`有效。`*** int`默認為`signed`,而`char`根據具體實現,可以默認為`signed`,也可以為`unsigned`。
`long`
對該類型使用較大的空間,使它能存下更大的數,通常使當前大小加倍。
`short`
對該類型使用較小的空間,使它儲存能力變小,但是占據空間也變成一半。
## 類型限定符
`const`
表示變量在初始化后不能改變。
`volatile`
表示會做最壞的打算,編譯器不會對它做任何優化。通常僅在對變量做一些奇怪的事情時,才會用到它。
`register`
強制讓編譯器將這個變量保存在寄存器中,并且也可以無視它。目前的編譯器更善于處理在哪里存放變量,所以應該只在確定這樣會提升性能時使用它。
## 類型轉換
C使用了一種“階梯形類型提升”的機制,它會觀察運算符兩邊的變量,并且在運算之前將較小邊的變量轉換為較大邊。這個過程按照如下順序:
+ long double
+ double
+ float
+ long long
+ long
+ int (short, char)
> 譯者注:`short`和`char`會在運算之前轉換成`int`。同種類型的`unsigned`和`signed`運算,`signed`保持字節不變轉換成`unsigned`。
## 類型大小
`stdint.h`為定長的整數類型定義了一些`typedef`,同時也有一些用于這些類型的宏。這比老的`limits.h`更加易于使用,因為它是不變的。這些類型如下:
`int8_t`
8位符號整數。
`uint8_t`
8位無符號整數。
`int16_t`
16位符號整數。
`uint16_t`
16位無符號整數。
`int32_t`
32位符號整數。
`uint32_t`
32位無符號整數。
`int64_t`
64位符號整數。
`uint64_t`
64位無符號整數。
> 譯者注:當用于對類型大小有要求的特定平臺時,可以使用這些類型。如果你怕麻煩,不想處理平臺相關類型的今后潛在的擴展的話,也可以使用這些類型。
下面的模式串為`(u)int(BITS)_t`,其中前面的`u`代表`unsigned`,`BITS`是所占位數的大小。這些模式串返回了這些類型的最大(或最小)值。
`INT(N)_MAX`
`N`位符號整數的最大正值,例如`INT16_MAX`。
`INT(N)_MIN`
`N`位符號整數的最小負值。
`UINT(N)_MAX`
`N`位無符號整數的最大正值。為什么不定義其最小值,是因為最小值是0,不可能出現負值。
> 警告
> 要注意,不要從字面上在任何頭文件中去找`INT(N)_MAX`的定義。這里的`N`應該為特定整數,比如8、16、32、64,甚至可能是128。我在這個練習中使用了這個記法,就不需要顯式寫出每一個不同的組合了。
在`stdint.h`中,對于`size_t`類型和足夠存放指針的整數也有一些宏定義,以及其它便捷類型的宏定義。編譯器至少要保證它們為某一大小,并允許它們為更大的大小。
`int_least(N)_t`
至少`N`位的整數。
`uint_least(N)_t`
至少`N`位的無符號整數。
`INT_LEAST(N)_MAX`
`int_least(N)_t`類型的最大值。
`INT_LEAST(N)_MIN`
`int_least(N)_t`類型的最小值。
`UINT_LEAST(N)_MAX`
`uint_least(N)_t`的最大值。
`int_fast(N)_t`
與`int_least(N)_t`相似,但是是至少`N`位的“最快”整數。
`uint_fast(N)_t`
至少`N`位的“最快”無符號整數。
`INT_FAST(N)_MAX`
`int_fast(N)_t`的最大值。
`INT_FAST(N)_MIN`
`int_fast(N)_t`的最小值。
`UINT_FAST(N)_MAX`
`uint_fast(N)_t`的最大值。
`intptr_t`
足夠存放指針的符號整數。
`uintptr_t`
足夠存放指針的無符號整數。
`INTPTR_MAX`
`intptr_t`的最大值。
`INTPTR_MIN`
`intptr_t`的最小值。
`UINTPTR_MAX`
`uintptr_t`的最大值。
`intmax_t`
系統中可能的最大尺寸的整數類型。
`uintmax_t`
系統中可能的最大尺寸的無符號整數類型。
`INTMAX_MAX`
`intmax_t`的最大值。
`INTMAX_MIN`
`intmax_t`的最小值。
`UINTMAX_MAX`
`uintmax_t`的最大值。
`PTRDIFF_MIN`
`ptrdiff_t`的最小值。
`PTRDIFF_MAX`
`ptrdiff_t`的最大值。
`SIZE_MAX`
`size_t`的最大值。
## 可用的運算符
這是一個全面的列表,關于你可以在C中使用的全部運算符。這個列表中我會標明一些東西:
二元
該運算符有左右兩個操作數:`X + Y`。
一元
該運算符作用于操作數本身`-X`。
前綴
該運算符出現在操作數之前:`++X`。
后綴
通常和前綴版本相似,但是出現在操作數之后,并且意義不同:`X++`。
三元
只有一個三元運算符,意思是“三個操作數”:`X ? Y : Z`。
## 算數運算符
下面是基本的算數運算符,我將函數調用`()`放入其中因為它更接近“算數”運算。
`()`
函數調用。
二元 `*`
乘法。
`/`
除法。
二元 `+`
加法。
一元 `+`
無變化。
后綴 `++`
讀取變量然后自增。
前綴 `++`
自增變量然后讀取。
后綴 `--`
讀取變量然后自減。
前綴 `--`
自減變量然后讀取。
二元 `-`
減法。
一元 `-`
取反,可用于表示負數。
## 數據運算
它們用于以不同方式和形式訪問數據。
`->`
結構體指針的成員訪問。一元`*`和`.`運算符的復合。
`.`
結構體值的成員訪問。
`[]`
取數組下標。二元`+`和一元`*`運算符的復合。
`sizeof`
取類型或變量大小。
一元 `&`
取地址。
一元 `*`
取值(提領地址)。
## 邏輯運算符
它們用于測試變量的等性和不等性。
`!=`
不等于。
`<`
小于。
`<=`
小于等于。
`==`
等于(并不是賦值)。
`>`
大于。
`>=`
大于等于。
## 位運算符
它們更加高級,用于修改整數的原始位。
二元 `&`
位與。
`<<`
左移。
`>>`
右移。
`^`
位異或。
`|`
位或。
`~`
取補(翻轉所有位)。
## 布爾運算符。
用于真值測試,仔細學習三元運算符,它非常有用。
`!`
取非。
`&&`
與。
`||`
或。
`?:`
三元真值測試,`X ? Y : Z`讀作“若X則Y否則Z”。
## 賦值運算符
復合賦值運算符在賦值同時執行運算。大多數上面的運算符都可以組成復合賦值運算符。
`=`
賦值。
`%=`
取余賦值。
`&=`
位與賦值。
`*=`
乘法賦值。
`+=`
加法賦值。
`-=`
減法賦值。
`/=`
除法賦值。
`<<=`
左移賦值。
`>>=`
右移賦值。
`^=`
位異或賦值。
`|=`
位或賦值。
## 可用的控制結構
下面是一些你沒有接觸過的控制結構:
`do-while`
`do { ... } while(X);`首先執行花括號中的代碼,之后再跳出前測試`X`表達式。
`break`
放在循環中用于跳出循環。
`continue`
跳到循環尾。
`goto`
跳到你已經放置`label`的位置,你已經在`dbg.h`中看到它了,用于跳到`error`標簽。
## 附加題
+ 閱讀`stdint.h`或它的描述,寫出所有可能出現的大小定義。
+ 查詢本練習的每一項,寫出它在代碼中的作用。上網瀏覽資料來研究它如何正確使用。
+ 將這些信息做成教學卡片,每天看上15分鐘來記住它們。
+ 創建一個程序,打印出每個類型的示例,并驗證你的研究結果是否正確。
- 笨辦法學C 中文版
- 前言
- 導言:C的笛卡爾之夢
- 練習0:準備
- 練習1:啟用編譯器
- 練習2:用Make來代替Python
- 練習3:格式化輸出
- 練習4:Valgrind 介紹
- 練習5:一個C程序的結構
- 練習6:變量類型
- 練習7:更多變量和一些算術
- 練習8:大小和數組
- 練習9:數組和字符串
- 練習10:字符串數組和循環
- 練習11:While循環和布爾表達式
- 練習12:If,Else If,Else
- 練習13:Switch語句
- 練習14:編寫并使用函數
- 練習15:指針,可怕的指針
- 練習16:結構體和指向它們的指針
- 練習17:堆和棧的內存分配
- 練習18:函數指針
- 練習19:一個簡單的對象系統
- 練習20:Zed的強大的調試宏
- 練習21:高級數據類型和控制結構
- 練習22:棧、作用域和全局
- 練習23:認識達夫設備
- 練習24:輸入輸出和文件
- 練習25:變參函數
- 練習26:編寫第一個真正的程序
- 練習27:創造性和防御性編程
- 練習28:Makefile 進階
- 練習29:庫和鏈接
- 練習30:自動化測試
- 練習31:代碼調試
- 練習32:雙向鏈表
- 練習33:鏈表算法
- 練習34:動態數組
- 練習35:排序和搜索
- 練習36:更安全的字符串
- 練習37:哈希表
- 練習38:哈希算法
- 練習39:字符串算法
- 練習40:二叉搜索樹
- 練習41:將 Cachegrind 和 Callgrind 用于性能調優
- 練習42:棧和隊列
- 練習43:一個簡單的統計引擎
- 練習44:環形緩沖區
- 練習45:一個簡單的TCP/IP客戶端
- 練習46:三叉搜索樹
- 練習47:一個快速的URL路由
- 后記:“解構 K&R C” 已死