在前面幾次我們接觸的資料類型都是簡單資料類型,使用一個資料個體表示一個元素。C語言中還提供了多種複雜資料類型,其中最簡單的一種就是數組。數組這一結構使用記憶體中一段連續的記憶體空間儲存一組相同類型的變量,這些變量通過數組的下标/索引的不同互相區分。數組與指針有着十分緊密的聯系,通常使用數組下标能實作的操作都可以使用指針完成,而且使用指針的程式通常效率更高。但是指針和數組也存在着一些明顯的差别,如果誤用将導緻錯誤。另外,C語言中還定義了一種極為常用的特殊的數組——字元串,其本質與字元數組類似,但是又有一些特殊的特性,并且可以使用一些特殊的操作提高對字元串操作的效率。
數組的定義方法同定義變量類似,都是采用資料類型+變量名的方法定義。例如如果希望定義一個包含5個整型資料,數組名為array的數組,則通過以下方式定義:
數組定義完成後,可以進行初始化操作。數組的初始化有多重方式,可以對數組中所有資料初始化,也可以隻初始化前面的幾個資料。如果賦初值的個數是确定的,甚至還可以通過初始化類指定數組的大小。例如:
數組定義和初始化完成後,可以使用數組名和下标來索引數組的元素。數組中起始的元素下标為0,通常稱作第0個元素,一個包含n個元素的數組下标最大值為n-1。如對上文定義的nArray,5個元素分别可以用nArray[0]、nArray[1]、nArray[2]、nArray[3]、nArray[4]表示。使用循環來輸出每個元素的方法如:
需要注意的是,數組的讀寫絕對不可以超過定義的邊界。如果數組讀寫越界,則會導緻擷取資料錯誤甚至程式崩潰。
幾乎所有的數組資料都可以用對應類型的指針進行操作,甚至在很多場合,指針和數組本質上起的作用就是一緻的。究其根本原因在于,一個數組的數組名,本身就是一個指針常量,其值為數組第一個元素的位址。
例如我們定義一個數組,并定義一個指針指向該數組:
這樣,由于數組名nArray實際上儲存了該數組的首位址,指針變量pArray便指向了數組中第一個元素0。第二條語句實際上等價于:
數組和指針變量的關系如圖所示:

當指針變量指向數組的一個元素後,使用間接運算符就可以擷取數組中的元素:
前面我們使用的指針變量,隻是研究了其指向位址、擷取目标記憶體單元的資料等最簡單的功能,指針變量本身并沒有考慮移動的問題。實際上,指針變量也是變量的一種,可以進行相應的運算,隻要在指向一串連續的有效記憶體位址,指針變量可以進行前移和後移運算來周遊記憶體中的資料,這種特性使得指針變量在程式設計開發中擁有強大的功能。
如果我們已經将指針變量pArray指向了數組中某一個特定元素,那麼指針pArray+1将指向數組的下一個元素,而pArray-1将指向指針的上一個元素。如下圖所示:
例如,我們将指針變量指向數組中的第4個元素,并将指針變量前後移動:
這樣,周遊數組中的元素除了使用數組下标之外又多了一種選擇——使用指針運算。首先将指針指向數組首位址,随後不斷擷取指向下一個位置的指針直至數組結束,并通過間接運算符擷取指向的資料,即可以實作數組的周遊功能。
或者可以使指針變量持續向後移動1位來周遊整個數組:
實際上,從擷取連續記憶體位址中的某個資料這一功能考慮,數組下标法nArray[n]和指針解引用法*(pArray+n)是等價的,二者大部分時間可以無條件互換。不僅如此,當作為函數的參數時,二者也可以互相調換使用。如以下兩個函數的聲明,實際上沒有任何差別:
需要注意的一點是,使用數組作為參數時,編譯器隻關注數組名(也就是指向數組的指針)而并不關心數組的長度。隻是如果形參數組聲明的長度超過了實際值,在運作時可能出現問題,是以應避免。實際上,如果使用數組名作為參數,我們更推薦直接省略數組的長度:
雖然指針和數組有着諸多相同之處,但是我們決不能忽略二者的本質差別。指針是一個變量,可以進行上文中提到的各種運算(隻支援前移、後移等運算;如果兩個指針指向同一段數組,還可以根據內插補點計算距離;對指針進行乘除等運算非法);而數組名是一個常量,自定義之後便不可以改變,更不能進行移動等運算。另外,對數組名利用sizeof運算符求大小得到的是整個數組占記憶體的大小,而對指針求sizeof得到的是變量本身所占據的長度,通常為4。另外,如果将數組名作為函數參數,子函數内部對這個參數計算sizeof得到的依然是4,這是因為子函數已經把實參數組名轉化為了一個指針,是以計算結果與sizeof其他指針變量相同。
字元串是C語言的一種資料類型,由多個字元型資料組成。在C語言中,字元串原本都是常量,并且在程式運作時儲存在專門的字元串常量區。在我們的代碼中可以定義字元型的指針指向一個字元串常量,而後我們就可以在程式中使用該指針表示字元串常量。
在這段代碼中,如果視圖對指針指向的内容進行修改,那麼雖然編譯可以順利通過,但是運作時程式一定會崩潰,因為我們視圖向非法的位置寫入資料:
如果我們希望獲得一個我們可以修改的字元串變量,那麼我們就必須将字元串賦給一個字元型數組中。然而字元串這一資料類型有其自身的特殊之處,所有字元串的末尾都自動會添加一個\0作為結束符。是以,在為字元串數組賦初值時需格外注意不要遺漏:
在将字元串儲存到我們自己定義的數組中之後,處理字元串有多種方法。最基本的方法就是同其他類型的數組一樣循環周遊處理,但是這樣不但繁瑣而且效率較低。對于字元串通常有其他更為友善快捷的方法。
字元串的輸入和輸出:
第一種方法是使用标準輸入輸出函數printf和scanf,将字元串整體輸入和輸出。表示字元串的格式說明符為%s:
使用這種方法輸入字元串時,必須注意要配置設定足夠的記憶體空間儲存輸入的資料。另外,空格、回車都會被作為字元串結束符處理。
第二種方法是使用字元串專用的輸入和輸出函數puts和gets:
這種方法通常比格式化輸入和輸出更為友善,而且輸入資料時隻認回車作為結束符,可以讀入空格。
其他字元串處理:
C語言中并沒有對字元串這一類型定義整體的操作符,但是提供了多種字元串操作的庫函數:
strcpy:字元串拷貝函數
strcmp:字元串比較函數
strlen:計算字元串長度函數
strcat:字元串拼接函數
值得注意的是,strlen和sizeof的功能不同。sizeof用于計算某一個量在記憶體中占據的大小,而strlen用于計算字元串的長度。如果将二者對一字元串計算,得到的值可能完全不同。