# 練習8:大小和數組
> 原文:[Exercise 8: Sizes And Arrays](http://c.learncodethehardway.org/book/ex8.html)
> 譯者:[飛龍](https://github.com/wizardforcel)
在上一個練習中你做了一些算術運算,不過帶有`'\0'`(空)字符。這對于其它語言來說非常奇怪,因為它們把“字符串”和“字節數組”看做不同的東西。但是C中的字符串就是字節數組,并且只有不同的打印函數才知道它們的不同。
在我真正解釋其重要性之前,我先要介紹一些概念:`sizeof`和數組。下面是我們將要討論的一段代碼:
```c
#include <stdio.h>
int main(int argc, char *argv[])
{
int areas[] = {10, 12, 13, 14, 20};
char name[] = "Zed";
char full_name[] = {
'Z', 'e', 'd',
' ', 'A', '.', ' ',
'S', 'h', 'a', 'w', '\0'
};
// WARNING: On some systems you may have to change the
// %ld in this code to a %u since it will use unsigned ints
printf("The size of an int: %ld\n", sizeof(int));
printf("The size of areas (int[]): %ld\n",
sizeof(areas));
printf("The number of ints in areas: %ld\n",
sizeof(areas) / sizeof(int));
printf("The first area is %d, the 2nd %d.\n",
areas[0], areas[1]);
printf("The size of a char: %ld\n", sizeof(char));
printf("The size of name (char[]): %ld\n",
sizeof(name));
printf("The number of chars: %ld\n",
sizeof(name) / sizeof(char));
printf("The size of full_name (char[]): %ld\n",
sizeof(full_name));
printf("The number of chars: %ld\n",
sizeof(full_name) / sizeof(char));
printf("name=\"%s\" and full_name=\"%s\"\n",
name, full_name);
return 0;
}
```
這段代碼中我們創建了一些不同數據類型的數組。由于數組是C語言工作機制的核心,有大量的方法可以用來創建數組。我們暫且使用`type name[] = {initializer};`語法,之后我們會深入研究。這個語法的意思是,“我想要那個類型的數組并且初始化為{..}”。C語言看到它時,會做這些事情:
+ 查看它的類型,以第一個數組為例,它是`int`。
+ 查看`[]`,看到了沒有提供長度。
+ 查看初始化表達式`{10, 12, 13, 14, 20}`,并且了解你想在數組中存放這5個整數。
+ 在電腦中開辟出一塊空間,可以依次存放這5個整數。
+ 將數組命名為`areas`,也就是你想要的名字,并且在當前位置給元素賦值。
在`areas`的例子中,我們創建了一個含有5個整數的數組來存放那些數字。當它看到`char name[] = "Zed";`時,它會執行相同的步驟。我們先假設它創建了一個含有3個字符的數組,并且把字符賦值給`name`。我們創建的最后一個數組是`full_name`,但是我們用了一個比較麻煩的語法,每次用一個字符將其拼寫出來。對C來說,`name`和`full_name`的方法都可以創建字符數組。
在文件的剩余部分,我們使用了`sizeof`關鍵字來問C語言這些東西占多少個字節。C語言無非是內存塊的大小和地址以及在上面執行的操作。它向你提供了`sizeof`便于你理解它們,所以你在使用一個東西之前可以先詢問它占多少空間。
這是比較麻煩的地方,所以我們先運行它,之后再解釋。
## 你會看到什么
```sh
$ make ex8
cc -Wall -g ex8.c -o ex8
$ ./ex8
The size of an int: 4
The size of areas (int[]): 20
The number of ints in areas: 5
The first area is 10, the 2nd 12.
The size of a char: 1
The size of name (char[]): 4
The number of chars: 4
The size of full_name (char[]): 12
The number of chars: 12
name="Zed" and full_name="Zed A. Shaw"
$
```
現在你可以看到這些不同`printf`調用的輸出,并且瞥見C語言是如何工作的。你的輸出實際上可能會跟我的完全不同,因為你電腦上的整數大小可能會不一樣。下面我會過一遍我的輸出:
> 譯者注:16位機器上的`int`是16位的,不過現在16位機很少見了吧。
5
我的電腦認為`int`的大小是4個字節。你的電腦上根據位數不同可能會使用不同的大小。
6
`areas`中含有5個整數,所以我的電腦自然就需要20個字節來儲存它。
7
如果我們把`areas`的大小與`int`的大小相除,我們就會得到元素數量為5。這也符合我們在初始化語句中所寫的東西。
8
接著我們訪問了數組,讀出`areas[0]`和`areas[1]`,這也意味著C語言的數組下標是0開頭的,像Python和Ruby一樣。
9~11
我們對`name`數組執行同樣的操作,但是注意到數組的大小有些奇怪,它占4個字節,但是我們用了三個字符來打出"Zed"。那么第四個字符是哪兒來的呢?
12~13
我們對`full_name`數組執行了相同的操作,但它是正常的。
13
最后我們打印出`name`和`full_name`,根據`printf`證明它們實際上就是“字符串”。
確保你理解了上面這些東西,并且知道這些輸出對應哪些創建的變量。后面我們會在它的基礎上探索更多關于數組和存儲空間的事情。
## 如何使它崩潰
使這個程序崩潰非常容易,只需要嘗試下面這些事情:
+ 將`full_name`最后的`'\0'`去掉,并重新運行它,在`valgrind`下再運行一遍。現在將`full_name`的定義從`main`函數中移到它的上面,嘗試在`Valgrind`下運行它來看看是否能得到一些新的錯誤。有些情況下,你會足夠幸運,不會得到任何錯誤。
+ 將`areas[0]`改為`areas[10]`并打印,來看看`Valgrind`會輸出什么。
+ 嘗試上述操作的不同變式,也對`name`和`full_name`執行一遍。
## 附加題
+ 嘗試使用`areas[0] = 100;`以及相似的操作對`areas`的元素賦值。
+ 嘗試對`name`和`full_name`的元素賦值。
+ 嘗試將`areas`的一個元素賦值為`name`中的字符。
+ 上網搜索在不同的CPU上整數所占的不同大小。
- 笨辦法學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” 已死