C語言數組名
一維數組名
在
C
語言中,幾乎所有使用數組的表達式中,數組名的值就是一個指針常量,不能作為左值。它是數組第一個元素的地址,它的類型取決於數組元素的類型。
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[3] = {1, 2, 3};
/**
* 數組名array是一個指針常量,它的類型是int *
*/
int *p_array = array;
printf("array[0] = %d\narray[1] = %d\narray[2] = %d\n", p_array[0], p_array[1], p_array[2]);
/**
* 一維數組
* 數組名(array): 是數組首元素的首地址,一般是一個地址常量,和&array[0]的值相同
* &數組名(&array): 是整個數組array的首地址(指向數組的指針)
* 它們的值相同,但是意義不同
*/
printf("array addr = %p\n&array addr = %p\n", array, &array);
return 0;
}
array
和 &array
的值是相同的:
在以下的兩種場合下,數組名並不是使用指針常量來表示:
- 數組名作為"
sizeof
"關鍵字的操作數時,返回整個數組的長度,而不是指向數組的指針的長度 - 數組名作為單目運算符"
&
"的操作數時,取一個數組名的地址所產生的是一個指向數組的指針,而不是指向某個指針常量的指針。
普通二維數組
二維數組就是一維數組的每個元素存放的是一個一維數組。
通常以行和列在邏輯上來理解它,但它實際上在內存中是一維的。
格式:
類型 二維數組名[第一維大小][第二維大小]
如:
int array[3][2]; /* 可以推廣到 int array[m][n] */
表示有 3 個一維數組,每個一維數組里面都存放的是由 2 個 int
類型元素組成的一維數組。
元素初始化
- 給每個元素初始化
int array[3][2] = {
{1, 3},
{2, 4},
{5, 7}
};
- 其它的初始化形式
由於二維數組的元素在內存中實際上是按一維進行連續存放的,因此初始化列表可以寫成一維形式。
int array[3][2] = {1, 3, 2, 4, 5, 7};
如果提供了初始化列表,則可以省略第一維的長度,如:
int array[][2] = {
{1, 3},
{2, 4},
{5, 7}
};
注: 如果初始化列表是一維形式,則不能省略第一維的長度
如圖是一個二維數組在內存中的大致描述,為幫助更好的理解二維數組(不考慮大小端):
二維數組訪問元素
使用下標訪問
如:
a[0][0]
, a[0][1]
可以理解為:a[0]
是把二維數組看成一個一維數組后的第一個元素,它也是一個一維數組,a[0][0]
就是這個一維數組里面的第一個元素,a[0][1]
就是這個一維數組的第二個元素。
二維數組的數組名
我們知道,一維數組的數組名,它是數組的第一個元素的首地址。
二維數組的數組名是把二維數組看成一維數組后的第一個元素的首地址。
如:
int array[3][2] = {1, 3, 2, 4, 5, 7};
看成一維數組:
array[0]
, array[1]
, array[2]
每個一維數組都存放的是一個由 2 個 int
類型組成的一維數組,因此看成一維數組后的第一個元素是:array[0]
array[0]
的類型是 int [2]
,那么它的地址的類型為 int (*) [2]
,它的地址是一個指向 int [2]
類型的一維數組的指針,因此數組名 array
實際上是一個指向一維數組的數組指針,數組名 和 &array[0]
是等價的。
/* 下面兩語句效果一樣 */
int (*p_array) [2] = array;
int (*p_array) [2] = &array[0];
將二維數組和每一維元素賦值給指針
/**
* array是int (*) [2]類型,不難理解應該用數組指針
*/
int (*p_array)[2] = array;
/**
* array[0] 是int [2]類型,為什么要用int * ?
*/
int *p_one_array = array[0];
/**
* &array[0][0]是對一個int的元素取地址,不難理解應該用int *類型指針
*/
int *p_element = &array[0][0];
個人理解
array[0]
看成是一個一維數組,它有 2 個元素,方便理解,把array[0]
看成一個整體,並將其替換為ary
,即:ary[2]
,不難看出它的數組名其實就是array[0]
,和一維數組的數組名有異曲同工之妙。所以ary
要用int *
,即:array[0]
賦值給int *
類型的指針變量。
常見的幾種賦值
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[3][2] = {1, 3, 2, 4, 5, 7};
/**
* 二維數組的數組名是把二維數組看成一維數組后的第一個元素的首地址。
* 二維數組名本質就是一個數組指針
*/
int (*p_array)[2] = array;
/**
* 對一維數組取地址,即整個一維數組的地址,指向數組的指針,指針類型為 int (*) [2]
*/
int (*p_ary)[2] = &array[0];
int *p_ary_one = array[0];
int *p_ary_element = &array[0][0];
/* 它們的值是一樣的 */
printf("array addr = %p\n&array[0] addr = %p\narray[0] addr = %p\n&array[0][0] = %p\n",
array, &array[0], array[0], &array[0][0]);
return 0;
}
幾種寫法的值都是一樣的:
二維數組作為函數形參
將一個二維數組傳遞給函數時,函數的形式參數也需要定義為二維數組形式。
如:
void test_fun(int array[3][2])
{
//int a = array[0][1];
}
由於編譯器在處理數組形參時,會轉換為指針,形參中第一維長度會被忽略,編譯器不會檢查實參和形參的第一維長度是否匹配,因此通常省略第一維長度。
如:
void test_fun(int array[][2])
{
//int a = array[0][1];
}
為什么不能將普通二維數組賦值給一個二級指針?
將二維數組作為實參傳遞給形參二級指針
void test(int **pary)
{
...
}
void caller()
{
int ary[3][2] = {1, 2, 3, 4, 5, 6};
test((int **)ary); /* 錯誤做法 */
}
解引用操作符:
根據指針當前的地址值,以及所指向的數據類型,訪問一塊連續的內存空間(大小由指針所指向的數據類型決定),將這塊空間的內容轉換成相應的數據類型,並返回左值。
如下代碼:
將二維數組強制轉換后賦值給二級指針,當對該二級指針解引用時,就得到了數組第一個元素 array[0][0]
的值 99
,如果再次解引用就會把 99
當成地址來取int
類型大小的值,這樣會導致非法訪問內存。
#include <stdio.h>
int main(int argc, char *argv[])
{
int array[3][2] = {99, 3, 2, 4, 5, 7};
int **p_test = (int **)array;
int (*p_ary)[2] = array;
/**
* 對二維數組解引用,得到一維數組,因此可以繼續用[]進行下標訪問
*/
//array <==> &array[0]
printf("*array[1] = %d <==> %d\n", *array[1], *p_ary[1]);
printf("(*array)[1] = %d <==> %d\n", (*array)[1], (*p_ary)[1]);
/**
* 此處對二級指針解引用,得到的一級指針是數組的第一個元素值(99)
* 把這個值(99)當成地址再次解引用會非法訪問內存,因此不能直接將一個二維數組名強制轉換后賦值給二級指針。
*/
printf("*p_test = %d\n", *p_test);
/**
* array + 1 偏移一個一維數組大小,*array + 1 偏移int類型大小
*/
printf("**array = %d *(*array + 1) = %d\n", **array, *(*array + 1));
return 0;
}
結果如圖:
因此直接將普通二維數組賦值給二級指針或者作為實參傳遞給形參是二級指針的函數也是不正確的做法。
二維數組與二級指針的參數匹配關系
數組名被改寫成指針的規則不是遞歸定義的,
數組的數組會被改寫成“數組指針”,而不是“指針的指針”。
實參 | 所匹配的形式參數 |
---|---|
數組的數組 char ary[3][2] |
數組指針 char (*pary)[2] |
指針數組 char *pstr[3] |
指針的指針 char **ptr |
數組指針(行指針) char (*pary)[3] |
不改變 char (*pary)[3] |
指針的指針 char **ptr |
不改變 char **ptr |