文章目录
- 一、数组的访问方式
- 1、下标访问
- 2、指针访问
- 1)数组名的含义
- 2)数组地址和元素地址
- 3)通过指针访问数组元素
- i. 二维数组中的“`arr[i]“`
- ii. 二维数组中的“`arr[i][j]“`
- 总结
一、数组的访问方式
1、下标访问
通过数组名和下标访问,这里不赘述。
2、指针访问
int arr[4][5] = { {1, 2, 3, 4, 5},{6, 7, 8, 9, 10},{11,12,13,14,15},{16,17,18,19,20} };
首先,二维数组的本质是数组的数组,即二维数组的每个元素都是一个一维数组。
例如一个二维数组 int arr[4][5]
,那么 arr
就是一个包含了 4 个一维数组的数组,每个一维数组都有 5 个元素。
1)数组名的含义
需要区分:
arr
和arr[0]
在 C/C++中,数组名可以表示数组的首地址,也就是数组的第一个元素的地址。
例如,arr
是二维数组的名字,arr
就表示二维数组arr
的首地址,也就是一维数组 arr[0]
的地址。
同样,arr[0]
是一维数组的名字,arr[0]
就表示一维数组 arr[0]
的首地址,也就是元素 arr[0][0]
的地址。即
arr
等价于&arr[0]
arr[0]
等价于&arr[0][0]
并且,对数组名取地址,就等于数组中第一个元素的值(或对象),即
*arr
等价于arr[0]
*arr[0]
等价于arr[0][0]
简单测试一下:
#include#include // 打印数据类型using namespace std;int main() {int arr[4][5] = { {1, 2, 3, 4, 5},{6, 7, 8, 9, 10},{11,12,13,14,15},{16,17,18,19,20} };cout << "arr = " << arr << endl; cout << "arr[0] = " << arr[0] << endl;cout << "&arr[0][0] = " << &arr[0][0] << endl;cout << endl;cout << "*arr = " << *arr << endl; cout << "*arr[0] = " << *arr[0] << endl;cout << "arr[0][0] = " << arr[0][0] << endl;return 0;}
输出结果如下:
arr = 0x0057f6a0arr[0] = 0x0057f6a0&arr[0][0] = 0x0057f6a0*arr = 0x0057f6a0*arr[0] = 1arr[0][0] = 1
注意:
虽然arr
、arr[0]
和&arr[0][0]
打印出来值相同,但他们含义不同。可以查看他们各自的类型:
C语言中的类型是由编译器根据表达式的语法来确定的,而不是根据表达式的值来确定的。其中arr
、arr[0]
表示的是数组名,而&arr[0][0]
由于&
取地址符是一个指针,并且根据 C/C++规则,数组名的类型是由数组的元素类型和数组的长度决定的,因此就可以解释上述三个变量的类型。
2)数组地址和元素地址
需要区分:
arr
、*arr
和&arr
arr[i]
、*arr[i]
和&arr[i]
arr[i][0]
和&arr[i][0]
arr
、&arr
和*arr
前面讲到,arr
为数组名,表示二维数组首元素地址,则*arr
等价于arr[0]
。
&arr
,对数组名取地址表示 整个二维数组的首地址,虽然在值上和arr
相同,但是对其加减操作上是不同的。&arr+1
代表该二维数组最后一个元素的下个位置的地址,即比arr
大sizeof(arr) = 4*(4*5) = 80
字节,跨过了整个二维数组。arr+1
表示arr[1]
的地址,即比arr
大sizeof(arr[0]) = 4*5 = 20
字节,可参考下一小节 “通过指针访问数组元素”。
&arr+1
从十六进制0x0057f6a0
到0x0057f6f0
,算一下,确实移动了80字节。
&arr+2
从十六进制0x0057f6a0
到0x0057f740
,移动了160字节。
可以理解为
&arr
是一个更大的数组(三维数组),这个数组是以arr这个二维数组作为其中一个元素,那么对&arr
加 1 就是按照其中元素大小来移动指针地址的,可以参考下面 “通过指针访问数组元素” 这一小节。
arr[i]
、&arr[i]
和*arr[i]
同样,arr[i]
作为数组名,表示一维数组的首地址,则*arr[i]
等价于arr[i][0]
。
(下面示例中用arr[0]
举例)
同样,&arr[i]
表示 整个一维数组的首地址,在值上和arr[i]
相同,但是含义不同&arr[i]+1
代表该一维数组最后一个元素的下个位置的地址,即比arr[i]
大sizeof(arr[i]) = 4*5 = 20
字节,跨过了整个一维数组。arr[i]+1
表示arr[i][1]
的地址,即比arr[i]
大sizeof(arr[i][0]) = 4
字节,可参考下一小节 “通过指针访问数组元素”。
&arr[0]+1
和&arr[0]+2
从十六进制0x0057f6a0
分别到0x0057f6b4
和0x0057f6c8
,算一下,分别移动了20字节和40字节。
arr[i][0]
和&arr[i][0]
这个就比较好理解了,二维数组中arr[i][0]
是一个int类型的值,&arr[i][0]
则表示该值的地址。
3)通过指针访问数组元素
i. 二维数组中的arr[i]
结合上一小节,如果我们想要表示二维数组arr
的第 i 个元素,即一维数组arr[i]
,可以用 arr + i
来表示arr[i]
的地址。
这是因为,当我们对 数组名进行加法运算时,实际上是按照 数组元素的大小 来移动地址的。
例如,对于
int arr[4][5]
,如果arr
的地址是 1000,那么arr + 1
的地址就是1000 + 5 * 4 = 1020
,也就是arr[1]
的地址。同理,arr + 2
的地址就是1000 + 2 * 5 * 4 = 1032
,也就是arr[2]
的地址,其中 5*4 表示arr
中一个元素的大小。
通过对以上地址解引用就可以获取相应的对象或者元素。即
arr + i
等价于&arr[i]
。*(arr + i)
等价于arr[i]
。通过对地址解引用可以获得地址所指向的对象,arr[i]
是这个一维数组的数组名,代表的是这个一维数组首元素地址(或称首地址),等价于&arr[i][0]
。
因此,
arr[i]
本质上仍然是个地址,对&arr[i]
解引用并不是得到某个具体类型的值,而是一个对象。
如果不好理解,可以拿一维数组举例,例如
int a[5] = {1,2,3,4,5};
,a
表示数组首地址,也是a[0]
的地址,即a = &a[0]
;a+2
表示a[2]
的地址,即a + 2 = &a[2]
,那么两边同时解引用,则可以获取到a[2]
的值,即*(a + 2) = a[2] = 3
。
这里和二维数组的区别在于,一维数组对a
解引用后,直接获取元素的值,但是在二维数组中,对arr
解引用后,是一个对象,再次解引用才是一个int类型的值。
ii. 二维数组中的arr[i][j]
然后来看看如何用指针来引用 arr[i][j]
的值。我们已经知道了,arr[i]
和 *(arr + i)
都表示一维数组 arr[i]
的值,而arr[i]
本身又是一个数组名,也可以表示数组的首地址,即&arr[i][0]
。因此有:arr[i]
等价于&arr[i][0]
等价于*(arr + i)
,也即arr[i][0]
等价于*(*(arr + i))
。
上面说到:
当对数组名进行加法运算时,实际上是按照 数组元素的大小 来移动地址的。
那么,我们可以用 arr[i] + j
来表示 arr[i][j]
的地址,同样也可以用 *(arr + i) + j
来表示 arr[i][j]
的地址。即:
*(arr + i) + j
等价于&arr[i][j]
。*(*(arr + i) + j)
等价于arr[i][j]
。
总结
总结一下,对于二维数组int arr[4][5]
,有以下的等价关系:
arr
等价于&arr[0]
,*arr
等价于arr[0]
。arr[0]
等价于&arr[0][0]
,*arr[0]
等价于arr[0][0]
。arr + i
等价于&arr[i]
,*(arr + i)
等价于arr[i]
*(arr + i) + j
等价于&arr[i][j]
,*(*(arr + i) + j)
等价于arr[i][j]
。
arr
单独拿出来表示的是首元素的地址,而&arr
是整个数组的地址。
参考:
二维数组的首地址、首行地址和元素地址
C语言学习之:一维数组、二维数组的取值和取地址问题
关于二维数组a[i][j]
为什么C语言中*(a+i)+j能表示a[i][j]的地址