二维数组
深入理解二维数组
- 首先定义一个二维数组
int a[2][3]={{1,2,3},{4,5,6}}; #or int a[2][2]={1,2,3,4};
新的理解:我们可以这样认为,a可以看作是一个一维数组,包含2个元素,每个元素恰好是包含3个整型的元素,a这个名字的值成了指向一个包含3个整型元素的数组的指针(你学过数组指针就该知道,他们是一个道理,后面我会讲解数组指针)
- 然后开始讨论二维数组和数组名的关系
a表示的是整个数组的首地址,a[0]表示的是第一行的首地址,这两者在数值上是一样的,但含义不同(或者说类型不同),数组名a是对于整个数组,a[0]是对于第一行。
#打印 a a[0] 的值和 a[0][0] 的地址
cout << a << endl;
#0x7ffffffee120
cout << a[0] << endl;
#0x7ffffffee120
cout << &a[0][0] << endl;
#0x7ffffffee120
? 所以
a、a[0]
的值和 a[0][0]
的地址三个表达指向一个地址。- 接着看每一行
cout << a[0] << endl; #0x7ffffffee120 cout << a[1] << endl; #0x7ffffffee12c
输出的结果是每行的首地址,两个地址之间相差12个字节,也就是三个int的大小。
- 下面我们通过
a[0]
和&a[0][0]
这两个地址推导出其他位置的元素。
# 由a[0]推出其他,这里以第1行第1列为例 cout << a[0+1]+1 << endl; #0x7ffffffee130 cout << *(a[0+1]+1) << endl; #5 cout << a[0]+4 << endl; #0x7ffffffee130 cout << *(a[0]+4) << endl; #5# 由&a[0][0]推出其他, 这里以第1行第1列为例 cout << &a[0][0]+4<< endl; #0x7ffffffee130 cout << *(&a[0][0]+4) << endl; #5
前两行通过加a[0]的索引得到其他行的首地址,然后再加偏移量得到元素地址,解引用获得该地址的值。
之后两行是直接计算a[0]的偏移量得到元素地址,解引用获得该元素地址的值。最后两行与直接加偏移量的情况相同。
- 由数组名得到其他元素
a
a是数组的名字,它的类型是“指向包含三个整型元素数组的指针”,默认指向第一行。
文章图片
a+1
现在它指向了第二行,可以理解为当一个int*的指针指向一个数组的某一个int时,++他便指向了后一个int,同理,这里的数组指针指向了一个数组,所以++他就要指向下一个数组。
*(a+1)
若是int*解引用后我们拿到了int类型数据,那数组指针呢?实际上我们对一个数组的指针解引用,拿到的是一个数组。
*(a+1) == a[1]
*(a+1)就是第二行的数组名,如果你觉得这样表达很奇怪,那么a[1]和他意义相同。
我们现在拿到这个二维数组的第二行,第二行实际上就是一个一维数组,所以或许我们所有的处理都可以使用一维数组的方式。
*(a+1)+ 1
让他指向了这个数组中的第2个元素。
文章图片
*(*(a+1)+ 1)
最后一步就可以拿到想要的值。
文章图片
我们来看看一些关于取地址的问题
printf("%d\n",sizeof(a+1)); //8//第二行的地址(指针的大小) printf("%d\n",sizeof(&a[0]+1)); //8 printf("%d\n",sizeof(*(&a[0]+1))); //12
1、a是一个数组指针,加一后指向下一行他还是指针,sizeof中就是指针的大小了。
2、给a[0]取地址?a[0]是一个一维数组的名字,也就是一个指针,再次取地址后的结果就是一个二级指针。而之前我们对数组指针解引用得到数组名,这次刚好相反,所以&arr[0]你可以理解为,我们现在拿到了一个数组指针,对数组指针+1就是让他指向第二行,sizeof中就是指针的大小。(所以数组指针也叫二级指针)
3、对数组指针解引用拿到了第二行数组的名字,sizeof他就是第二行大小。
- 最后进行个小测试
int a[2][3] = {0}; printf("%d\n",sizeof(a)); //24//输出这个二维数组的大小 printf("%d\n",sizeof(a[0][0])); //4//第一个元素的大小(int) printf("%d\n",sizeof(a[0])); //12//第一行的数组的大小 printf("%d\n",sizeof(a[0]+1)); //8//第一行第二个元素的地址(指针的大小) printf("%d\n",sizeof(*(a[0]+1))); //4//第一行第二个元素的大小 printf("%d\n",sizeof(a+1)); //8//第二行的地址(指针的大小) printf("%d\n",sizeof(*(a+1))); //12//第二行的大小 printf("%d\n",sizeof(&a[0]+1)); //8//第二行的地址(指针的大小) printf("%d\n",sizeof(*(&a[0]+1))); //12//第二行的大小 printf("%d\n",sizeof(*a)); //12//第一行的大小 printf("%d\n",sizeof(a[2])); //12//虽然越界了但是sizeof只是一个关键字他不关心这块空间是否真的存在
数组指针
文章图片
int (*p)[3] = a;
括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [3],这正是 a 所包含的每个一维数组的类型。[ ]的优先级高于*,( )是必须要加的,如果赤裸裸地写作int *p[4],那么应该理解为int *(p[4]),p 就成了一个指针数组,而不是二维数组指针。
对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [3],那么p+1就前进 3×4 = 12 个字节,p-1就后退 12 个字节,这正好是数组 a 所包含的每个一维数组的长度。也就是说,p+1会使得指针指向二维数组的下一行,p-1会使得指针指向数组的上一行。
指针数组和二维数组指针有着本质上的区别:指针数组是一个数组,只是每个元素保存的都是指针。二维数组指针是一个指针,它指向一个二维数组,以上面的 a为例,它占用 8个字节的内存。等价关系:
a+i == p+i
a[i] == p[i] == *(a+i) == *(p+i)
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) == *(*(p+i)+j)
- 小练习
int main() { int arr[][3] = { 1, 2, 3, 4, 5, 6 }; int(*p)[3]; p = arr; cout << p[0][0] << " " << *(p[0] + 1) << " " << (*p)[2] << endl; return 0; }
1、p[0][0]
按照下标访问第一行第一个元素,输出1。
2、*(p[0] + 1)
这个让第一行指针指向第二个元素然后解引用,输出2。
3、(*p)[2]
等同于*(*p+2)
,先解引用数组指针拿到第一行数组名,然后加到第3个位置解引用,输出3。
- 使用二维数组的传统定义方法传递参数
如果把数组名作为函数的参数的话,在编译的时候这个数组参数会自动退化为指针,因此以下两种写法虽然不同,但在编译之后是一样的,数组会退化成数组指针。
#include
using namespace std;
/*以下两种写法本质上是一样的*/
int func1(int (*arr)[4]) {
arr[0][2] = 20;
return 0;
}
int func2(int arr[][4]) {
arr[0][2] = 30;
return 0;
}
int main(){
int array[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
func2(array);
cout << array[0][2] << endl;
//fun1: 20 or fun2: 30
return 0;
}
- 使用动态分配内存的方式申请空间,可以使用**传递参数
【C/C++|二维数组与数组指针详解】使用哪种方法定义的就使用哪种方式传参。
#include
using namespace std;
int func1(int **arr) {
cout << arr[0][1]<< endl;
arr[0][2] = 20;
return 0;
}
int main(){
int rows=3 ;
int cols=4 ;
int **arr = new int*[rows];
//先使一个二级指针指向数组指针的地址
for(int i = 0 ;
i < rows ;
++i )
{
arr[i] = new int [cols]();
//为一级指针分配地址
}
arr[0][1]=100;
func1(arr);
cout << arr[0][2]<< endl;
;
//释放空间
for(int i = 0;
i < rows ;
++i)
{
delete [] arr[i];
//先释放二维数组中每个元素指向的数组
arr[i] = NULL;
}
delete [] arr;
//在释放该数组指针;
arr = NULL;
}
推荐阅读
- C语言数组和指针详解
- c/c++|顺序表详解及其c语言代码实现
- #define的用法体会
- C/C++编程|c++类中的临时对象
- c|c 是一种编译型编程语言,我写了一门编程语言,你也可以!
- C/C++|C For Linux之内存访问-内存简介
- java|java c s聊天程序_基于C/S模式的简单聊天程序(附程序源码)
- C++|蓝桥杯之奇妙的数字
- 蓝桥杯|【第六届蓝桥杯】奇妙的数字