數組與指針
一、數組的初始化
1、當初始化列表中的值少于數組元素個數時,編譯器會把剩余的元素都初始化為0;
2、如果初始化數組時省略方括號中的數字,編譯器會根據初始化列表中的項數來確定數組的大小;
3、對于數組中個數的多少,有一個比較不容易出錯的方法 - sizeof () 的使用;
e.g. : int nums[] = {31, 28, 31, 30};
int count = sizeof nums / sizeof nums[0]; //計算數組中的元素個數
【sizeof ():給出運算對象的大小,以字節為單位。在上述的語句中,sizeof nums 是整個數組的大小,sizeof nums[0] 是數組中一個元素的大小。】
4、C99 中可以初始化指定的數組元素。
int arr[7] = {1, 2, [4] = 23, 8, [1] = 12}
上述語句的結果為:arr[0] = 1, arr[1] = 12, arr[2] = 0, arr[3] = 0, arr[4] = 23, arr[5] = 8, arr[6] = 0。
5、C 不允許把數組作為一個單元賦給另外一個數組,除初始化以外也不允許使用花括號列表的形式進行賦值。
#define SIZE 5 int main (void) { int oxen[SIZE] = {5, 3, 2, 8}; int yaks[SIZE] ; yaks = oxen; /*不允許*/ yaks[SIZE] = oxen[SIZE]; /*數組下標越界*/ yaks[SIZE] = {5, 3, 2, 8}; /*不起作用*/ }
二、多維數組的理解
1、二維數組的理解
float rain[5][12]; //內含5個數組元素的數組,每個數組元素內含12個float類型的元素
換句話說,rain中每個元素本身都是一個內含12個float 類型值的數組。
2、三維數組的理解
int box[10][20][30];
對于box,其理解方式有:1,box內含10個元素,每個元素是內含20個元素的數組,這20個數組元素中的每個元素是內含30個元素的數組;2,把box想像成有10個二維數組(每個二維數組都是20行30列)堆疊起來。
3、多維數組的理解
與1、2的內容相類似。
三、指針 & 數組
1、指針的值是其指向對象的地址。地址的表示方式依賴于計算機內部的硬件。
2、指針前使用 ‘ * ’ 運算符可以得到該指針所指向對象的值。
3、指針加1,指針的值遞增其所指向類型的大小(以字節為單位)。
四、函數 & 數組 & 指針
1、聲明數組形參
對于函數原型中的數組形參的聲明,下面的幾種原型是等價的。
int sum (int *ar, int n); int sum (int *, int n); int sum (int ar[], int n); int sum (int [], int n);
但是,在函數定義的時候不能省略參數名,下面的幾種函數定義是等價的。
int sum (int *ar, int n){ //函數主體 } int sum (int ar[], int n){ //函數主體 }
2、‘ * ’運算符與‘ ++ ’ / ' -- '運算符的結合
total += *star++; //先‘遞增’后指向 total += (*star)++; //先指向后遞增
對于上述語句,‘ * ’運算符與‘ ++ ’運算符的優先級相同,但是結合律是從右到左,所以先計算start++,之后再計算*start,先‘遞增’后指向。使用后綴形式(start++,而不是++start)意味著先把指針指向位置上的值加到total上,然后再遞增指針;如果使用前綴形式(++start,而不是start++),意味著要先遞增指針,之后把指針指向位置上的值加到total上。
五、指針的幾種操作
int urn[5] = { 100, 200, 300, 400, 500 } int * ptr1, *ptr2, *ptr3;
1、賦值
ptr1 = urn; //將一個地址賦給指針 ptr2 = &urn[2]; //將一個地址賦給指針
2、解引用
printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
解引用 - “ *ptr1”。給出指針指向的地址上存儲的值。
3、取址
printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
取址 - “ &ptr1”。指針也有自己的地址,‘ & ’運算符給出的是指針本身的地址。
4、指針與整數相加
ptr3 = ptr1 + 4; //指針加法
' ptr1 + 4 ' 等價于 &urn[4] 。
5、遞增指針
ptr1++; //指針遞增
‘ ptr1++ ’ 等價于ptr1的值加上4(假設系統中 int 數據為4字節),pte1 指向 urn[1] 。
6、指針減去一個整數
printf("ptr3 - 2 = %p\n", ptr3 - 2);
整數將乘以指針指向類型的大小(以字節為單位),然后用初始地址減去乘積。
7、遞減指針
ptr2--; //指針遞減
8、指針求差
printf("ptr2 - ptr1 = %td\n", ptr2 - ptr1);
通常兩個指針指向同一個數組的不同元素,通過計算求出兩元素之間的距離。差值的單位與數組類型的單位相同。如果 ptr2 - ptr1 = 2,則意味著兩個指針所指向的元素之間相差著兩個 int ,而并不是兩個字節。
9、比較
使用關系運算符進行比較,前提是兩者都是指向相同類型的對象。
六、 const 修飾符的使用
1、對形參使用 const
int sum (const int ar[], int n); /*函數原型*/ int sum (const int ar[], int n){ //函數體 }
上述的 const 修飾符告知編譯器:sum 函數不可以修改 ar 指向的數組中的內容。如果在 sum 函數中使用的類似于 ar[i]++的表達式,編譯器會很容易地檢測到錯誤。
這里只用的 const 修飾符并不是要求原數組是常量,而是該函數在處理數組時將其看做是常量,不可更改。
一般而言,如果函數需要修改數組,在聲明數組形參時不使用 const 修飾符;如果編寫的函數不用修改數組,在聲明數組形參時使用 const 修飾符。
2、const 修飾符的其他內容
【 const 數組】
#define SIZE 3 ... const int data[SIZE] = {2, 4, 7}; ... data[2] = 5; //編譯錯誤
【 const 指針】
double index[3] = {1.1, 2.3, 4.5}; const double * pd = index; ... *pd = 3.8; //不允許 pd[2] = 9.0; //不允許 index[2] = 9.0; //允許,index 未被限定
需要注意的是,可以讓 pd 指向別處:' pd ++ ;' 是允許的。
【其他】
把 const 數據或非 const 數據的地址初始化為指向 const 的指針或為其賦值是合法的。
double rates [4] = {88.99, 100.12, 4.98, 76.45}; const double locked[3] = {1.2, 3.5, 7.9}; const double * pc = rates; //有效 pc = locked; //有效 pc = &rates[2]; //有效
只能把非 const 數據的地址賦給普通指針。
double rates [4] = {88.99, 100.12, 4.98, 76.45}; const double locked[3] = {1.2, 3.5, 7.9}; double * pc = rates; //有效 pc = locked; //無效 pc = &rates[2]; //有效
C 標準規定,使用非 const 修飾符修改 const 數據,導致的結果是未定義的。【不應該把 const 數組名作為實參傳遞給相應形參為非 const 數組名的函數。】
可以聲明并初始化一個不能指向別處的指針,關鍵是 const 修飾符的位置。
double rates [4] = {88.99, 100.12, 4.98, 76.45}; double * const pc = rates; //pc 指向數組的開始 pc = &rates[1]; //無效,pc 不允許指向別處 *pc = 99.9; //有效,更改 rates[0] 的值
可以使用 const 修飾符兩次,使得指針即不能更改其所指向的地址,也不能更改其指向的地址上的值。
double rates [4] = {88.99, 100.12, 4.98, 76.45}; const double * const pc = rates; //pc 指向數組的開始 pc = &rates[1]; //無效,pc 不允許指向別處 *pc = 99.9; //無效,不允許更改 rates[0] 的值
七、指針與多維數組
1、簡單示例
int zippo[4][2]; /* zippo -- 二維數組首元素的地址(每個元素都是內含兩個 int 類型元素的一維數組) zippo + 2 -- 二維數組的第3個元素(即一維數組)的地址 *(zippo + 2) -- 二維數組的第3個元素(即一維數組)的首元素(一個 int 類型的值)的地址 *(zippo + 2) + 1 -- 二維數組的第3個元素(即一維數組)的第2個元素(一個 int 類型的值)的地址 *(*(zippo + 2) + 1) -- 二維數組的第3個一維數組的第2個 int 類型的值,也就是 zippo[2][1] */
2、相關的指針數組
int (* pz) [2]; //pz指向一個內含兩個 int 類型值的數組 int * pzx[2]; //pax是一個內含兩個指針元素的數組,每個元素都指向 int 的指針
3、指針的兼容性
【兩個不同類型的指針之間不可以相互賦值】
4、需要處理二維數組的函數中的形參的聲明
... #define ROWS 3 #defien COLS 4 ... void sum_rows (int ar[][COLS], int rows); //空的方括號表明 ar 是一個指針 void sum_rows (int (*ar) [COLS], int rows); void sum_cols (int [][COLS], int ); //可以,省略形參名 int sum2d(int (*ar) [COLS], int rows); //另一種語法
八、復合字面量【C99 新增】
字面量:除符號常量之外的常量。
1、示例
【pre】5 -- int 類型字面量;2.3 -- double 類型字面量;'Y' -- char 類型字面量;"elephant" -- 字符串字面量。
【新增 - 數組】對于數組,復合字面量類似于數組初始化列表,前面是用括號括起來的類型名。
double diva [2] = {2, 4}; //普通的數組聲明 (int [2]) {2, 4}; //復合字面量
【去掉聲明中的數組名,留下的 int [2] 即是復合字面量的類型名。】
2、相關用法
【與有數組名的數組初始化一樣,同樣可以省略數組大小,編譯器會自動計算數組當前的元素個數。】
(int []) {10, 20, 40} //內含3個元素的復合字面量
【復合字面量是匿名的,必須在創建的同時去使用。常見的用法是指針記錄地址。】
int *pt1; pt1 = (int [2]) {10, 20}; //復合字面量的類型名也代表著首元素的地址,*pt1 = 10,pt[1] = 20
【可以把復合字面量作為實際參數傳遞給帶有匹配形式參數的函數。】
int sum (const int ar[], int n); ... int toal3; total3 = sum((int []) {4, 4, 4, 5, 5, 5}, 6);//第一個實參:內含6個 int 類型值的數組。把信息傳入函數前不必先創建數組,此為復合字面量的典型用法
浙公網安備 33010602011771號