C Primer Plus (第五版)中文版——第 10 章 陣列和指標
10.1 陣列
陣列(array)由一系列型別相同的元素構成。陣列宣告(array declaration)中包括陣列元素的數目和元素的型別。如:
int month[12]; /* 12個整數的陣列 */
/* int 是陣列中元素的型別;month 是陣列名,也是陣列首元素的地址;
方括號 [] 表示 month 是一個數組;方括號中的數字 12 指明瞭陣列大小(所包含的元素數目) */
10.1.1 初始化
可以使用花括號括起來的一系列數值來初始化陣列,數值之間用逗號隔開。
int month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
如果需要使用只讀陣列,建議在宣告並初始化陣列時,使用關鍵字 const。如:
const int month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
如果不初始化陣列,陣列元素儲存的是無用的數值;如果部分初始化陣列,未初始化的元素則被設定為零;如果初始化列表中專案個數大於陣列大小,編譯器將報錯。
10.1.2 指定初始化專案(C99)
C99新增了=一種新特定:指定初始化專案(designated initializer)。該特性允許選擇對某些元素進行初始化。如:
int month[12] = {31, 28, [4] = 31, 30, [1] = 29};/* 在初始化列表中使用帶有方括號的元素下標可以指定某個特定的元素 */
該語句將31賦給 month[0],將28賦給 month[1],將31賦給 month[4],將30賦給 month[5],再將29賦給 month[1]。其他未初始化的元素被設定為0。若多次對一個元素進行初始化,則最後一次的有效,因次 month[1] 的數值最終為29。
10.1.3 為陣列賦值
宣告完陣列後,可以藉助陣列的索引對陣列元素進行賦值。注意:C 不支援把陣列作為一個整體來進行賦值,也不支援用花括號括起來的列表形式進行賦值(初始化的時候除外)。
/* 為陣列賦值 */ #include <stdio.h> #define SIZE 5 /* 可採用識別符號常量代替陣列大小 */ int main(void) { int toes[SIZE] = {1, 2, 3, 4, 5}; /* 這裡是可以的 */ int year[SIZE]; year[0] = 5; /* 允許 */ year = toes; /* 不允許 */ year = {1,3,5,7,9}; /* 不起作用 */ }
10.4.1 陣列邊界
陣列索引不能超出陣列邊界。陣列計數是從0開始。陣列大小為20的陣列,其索引為0~19。
10.1.5 指定陣列大小
C99標準引入了一種新陣列:變長陣列(variable-length array),簡稱 VLA。
10.2 多維陣列
陣列的陣列,稱之為二維陣列。如:
float rain[5][12]; /* 5個由12個浮點陣列成的陣列的陣列 */
/* rain 是一個包含5個元素的陣列,並且每個元素都是包含12個 float 數值的陣列 */
10.2.1 初始化二維陣列
可以用逗號分隔的5個數值列表來初始化像 rain 這樣的二維陣列。初始化的時候也可以省略內部的花括號,只保留外部花括號。
float rain[5][12] =
{
{4.3,4.3,4.3,3.0,2.0,1.2,0.2,0.2,0.4,2.4,3.5,6.6},
{8.5,8.2,1.2,1.6,2.4,0.0,5.2,0.9,0.3,0.9,1.4,7.3},
{9.1,8.5,6.7,4.3,2.1,0.8,0.2,0.2,1.1,2.3,6.1,8.4},
{7.2,9.9,8.4,3.3,1.2,0.8,0.4,0.0,0.6,1.7,4.3,6.2},
{7.6,5.6,3.8,2.8,3.8,0.2,0.0,0.0,0.0,1.3,2.6,5.2}
};
10.2.2 更多維數的陣列
三維陣列:
int box[10][20][30]; /* 陣列box是由10個二維陣列(每個二維陣列都是20行,30列)堆放起來構成的立方體*/
一維陣列是排成一排的資料;二維陣列是放在一個平面的資料;三維陣列把平面資料一層一層地壘起來。處理陣列通常需要使用迴圈:處理一維陣列使用一重迴圈;處理二維陣列使用二重巢狀迴圈;處理三維陣列使用三重巢狀迴圈……
10.3 指標和陣列
- 陣列名同時是陣列首元素的地址。
- 指標的數值就是它所指的物件的地址。
- 在指標前運用取值運算子 * 就可以得到該指標所指的物件的數值。
- 對指標加1,等價於對指標的值加上它所指的物件的位元組大小。
month == &month[0]; /* 相同的地址 */
*month == month[0]; /* 相同的值 */
month + 2 == &month[2]; /* 相同的地址 */
*(month + 2) == month[2]; /* 相同的值 */
從本質上說,對同一個物件有兩種不同的符號表示方法。
10.4 函式、陣列和指標
指標最基本的功能在於同函式交換資訊,另一基本功能是用在處理陣列的函式中。當函式的實際引數是一個數組名時,形式參量必須是與之相匹配的指標。在且僅在這種場合中, C 對 int ar[ ] 和 int *ar 做出同樣解釋,即 ar 是指向 int 的指標。因此,以下四種函式原型等價:
int sum(int *ar);
int sum(int *); /* 原型允許省略變數名*/
int sum(int ar[]);
int sum(int []); /* 原型允許省略變數名*/
以下兩種函式定義等價:
int sum(int *ar)
{
//statement
}
int sum(int ar[])
{
//statement
}
處理陣列的函式實際上是使用指標作為引數的,但在編寫和處理陣列的函式時,陣列符號和指標符號都是可以選用的。C 保證在為陣列分配儲存空間的時候,指向陣列之後的第一個位置的指標是合法的(month+12合法),但不能該指標進行取值運算。
10.5 指標操作
int *ptr;
int urn[5] = {1, 2, 3, 4, 5};
ptr1 = urn; /* 把陣列 urn 首地址賦給指標 ptr1 */
ptr2 = &urn[3]; /* 把 urn[3] 的地址賦給指標 ptr2 */
- 賦值(assignment):可以把一個地址賦值給指標。通常使用陣列名或地址運算子&來進行地址賦值。
- 取值(dereferencing):取值運算子 * 可以取出指標所指向地址中儲存的數值。*ptr1 的結果為1.
- 取指標地址:使用運算子 & 可以得到儲存指標本身的地址。
- 將一個整數加給指標:使用運算子 + 可以把一個整數加給一個指標,或把一個指標加給一個整數。這個整數會和指標所指型別的位元組數相乘,然後將所得結果加到初始地址上。ptr1+4 的結果等同於&urn[4],*ptr1 的結果為5。
- 增加指標的值:可通過一般的加法或增量運算子來增加一個指標的值。ptr1++ 的結果等同於&urn[1],*ptr1 的結果為2。
- 從指標中減去一個整數:使用運算子 - 可以從指標中減去一個整數。指標必須是第一個運算元,或是一個指向整數的指標。這個整數會和指標所指型別的位元組數相乘,然後將所得結果從初始地址中減去。ptr2-3 的結果等同於&urn[0],*ptr2 的結果為1。
- 減小指標的值:可通過一般的減法或減量運算子來減小一個指標的值。ptr2-- 的結果等同於&urn[2],*ptr2 的結果為3。
- 求差值(differencing):可以求出兩個指標的差值。差值的單位是相應型別的大小。ptr2-ptr1的結果為3,表示指標所指向物件之間的距離是3個 int 數值的大小。有效指標差值運算的前提是參加運算的兩個指標是指向同一陣列(或其中之一是指向陣列後的第一個地址)。
- 比較:可以使用關係運算符來比較兩個指標的值,前提是兩個指標具有相同的型別。
注意:嚴禁對未初始化的指標取值。
10.6 保護陣列內容
10.6.1 對形式參量使用 const
若函式想修改陣列,那麼在宣告陣列參量時就不要使用 const;若函式不需要修改陣列,那麼在宣告陣列參量時最好使用 const。
int sum(const int ar[], int n); /* 原型 */
int sum(const int ar[], int n) /* 定義 */
{
int i;
int tatal = 0;
for(i = 0; i<n ; i++)
{
total += ar[i];
}
}
10.6.2 有關 const 的其他內容
- const 可以用來建立符號常量:
const int SIZE = 5;
- 在函式宣告參量時使用 const,不僅可以保護陣列(10.6.1程式),而且使函式可以使用宣告為 const 的陣列。
- 可以用 const 建立陣列常量:
const int month[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
/* 如果隨後的程式碼試圖改變陣列,編譯器將報錯 */
- 可以用 const 宣告指向常量的指標:
int years[5] = {2015, 2016, 2017, 2018, 2019};
const int * pc = years; //pc宣告為指向常量(const int)的指標
*pc = 2000; //不合法,指向常量的指標不能用於修改資料
pd++; //合法,可以讓pd指向其他地址
rates[0] = 10; //合法,因為rates不是常量
/* 可以將常量或非常量資料的地址賦給指向常量的指標 */
int years[5] = {2015, 2016, 2017, 2018, 2019};
const int score[3] = {100, 85, 90};
const int * pc = years; //合法
pc = score; //合法
pc = &years[3]; //合法
/* 只有非常量資料的地址才可以賦給普通的指標 */
int years[5] = {2015, 2016, 2017, 2018, 2019};
const int score[3] = {100, 85, 90};
int * pnc = years; //合法
pnc = score; //不合法
pnc = &years[3]; //合法
- 可以用 const 宣告常量指標:
int years[5] = {2015, 2016, 2017, 2018, 2019};
int * const pc = years; //pc指向陣列首地址
pc = &years[2] //不合法,不能更改指標指向的地址
*pc = 2000; //合法,可以更改指標所指向地址的資料
- 可以用兩個 const 來建立指向常量的常量指標,這個指標既不能更改所指向的地址,也不能修改所指向地址的資料:
int years[5] = {2015, 2016, 2017, 2018, 2019};
const int * const pc = years; //pc指向陣列首地址
pc = &years[2] //不合法,不能更改所指向的地址
*pc = 2000; //不合法,不能更改所指向地址的資料
10.7 指標和多維陣列
int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5, 7} }; /* 二維陣列有四個元素,每個元素是包含兩個int的陣列*/
zippo == &zippo[0]; /* 陣列名同時是陣列首元素的地址 */
*zippo == zippo[0]; /* 對zippo取值得到二維陣列的首元素,該元素是一個一維陣列 */
//
//
//
zippo[0] == &zippo[0][0]; /* zippo[0]本身是一維陣列的陣列名,同時也是其首元素zippo[0][0]的地址 */
*zippo[0] == zippo[0][0]; /* 對zippo[0]取值得到其首元素的數值 */
**zippo == zippo[0][0]; /* 對zippo進行兩次取值即可得到通常的數值zippo[0][0] */
zippo++ == &zippo[1]; /* 指向該二維陣列的下一個元素(一個包含兩個int的陣列) */
zippo[0]++ == &zippo[0][1];/* 指向該一維陣列的下一個元素(一個int數值) */
10.7.1 指向多維陣列的指標
int zippo[4][2] = { {2,4}, {6,8}, {1,3}, {5, 7} }; /* 二維陣列有四個元素,每個元素是包含兩個int的陣列*/
int (* pz) [2]; /* pz指向一個包含兩個int值的陣列,即pz指向int[2]*/
pz = zippo; /* 將zippo首地址賦給指標pz */
zippo[m][n] == *(*(zippo + m) + n);
pz[m][n] == *(*(pz + m) + n); //合法
10.7.2 指標相容性
- 指標之間的賦值規則比數值型別的賦值更嚴格。指標賦值的必要前提是:左值和右值都指向同一型別。
int *pt; // pt是指向int的指標
int (*pa) [3]; // pa是指向int[3]的指標
int **pz; // pz是指向int的指標的指標
int ar1[2][3];
int ar2[3][2];
pt = &ar1[0][0]; // 合法,都指向int
pt = ar1[0]; // 合法,都指向int
pa = ar1; // 合法,都指向int[3]
pa = ar2; // 不合法,ar2指向int[2]
pz = ar2; // 不合法,pz指向int的指標
pz = &pt; // 合法,都指向int的指標
*pz = pt; // 合法,都指向int
*pz = ar2[0]; // 合法,都指向int
- 把 const 指標賦給非 const 指標是錯誤的。把非 const 指標賦給 const 指標是允許的(前提是隻進行一層間接運算)。
int * p1; // 非const指標
const int * p2; // const指標
const int ** pp2; // const指標
p1 = p2; // 不合法,把const指標賦給非const指標
p2 = p1; // 合法,把非const指標賦給const指標
pp2 = &p1; // 不合法,把非const指標賦給const指標,但進行了多次間接運算
10.7.3 函式和多維陣列
如果要編寫處理二維陣列的函式,可以使用如下的函式宣告:
int somefunction(int (*ar) [4]);
int somefunction(int ar[][4]); //當且僅當ar是函式的形式參量時合法
宣告N維陣列的指標時,可以參看如下的函式宣告:
int somefunction(int (*ar) [4][5][6]); //ar是一個指標
int somefunction(int ar[][4][5][6]);
函式宣告中,最左邊的方括號表示這是一個指標,可以留空;而其他方括號描述的是所指物件的資料型別,不允許留空。
10.8 變長陣列(VLA)
C99引入了變長陣列,它允許使用變數定義陣列各維。如:
int rows = 5;
int cols = 6;
double sales[rows][cols]; //一個變長陣列
變長陣列必須是自動儲存類的,它們必須在函式內部或作為函式參量宣告,且宣告時不可以進行初始化。
如何宣告一個帶有二維變長陣列引數的函式:
int sum(int rows, int cols, int ar[rows][cols]); //ar是一個變長陣列,維數的宣告必須先於陣列
int sum(int, int, int ar[*][*]); //可以省略變數名,但需要用星號*來代替省略的維數
10.9 複合文字
C99新增了複合文字(compound literal)。文字是非符號常量。對於陣列來說,複合文字看起來像是在陣列的初始化列表前面加上用圓括號括起來的型別名。如:
int diva[2] = {10, 20};
(int [2]) {10, 20}; //一個複合文字,型別名就是 int[2]
初始化一個複合文字時也可以省略陣列大小,如:
(int []) {100, 200, 300}; //有3個元素的複合文字
複合文字必須在建立的同時以某種方式使用它們,如使用指標儲存其位置:
int * pt; // 一個指向int的指標
pt = (int [2]) {10, 20}; // 這個文字常量被標識為一個int陣列,同時也代表首元素的地址,因此可以賦給一個指向int的指標
// 隨後就可以使用這個指標。*pt值為10,pt[1]值為20
複合文字也可以作為實際引數被傳遞給帶有型別與之匹配的形式參量的函式,如:
int sun(int ar[], int n); //函式宣告
...
int total;
total = sum((int []) {1, 2, 3, 4, 5}, int 5); //第一個引數是包含5個元素的int陣列,同時也是首元素地址