静态二维数组与指针
我们定义一个二维数组int a[3][4]。
二维数组实际是由多个一维数组组成,在这里,a[3][4]就是由3个长度为4的一维数组组成的二维数组。并且它们在空间上是连续的,相当于一个长为12的一维数组。
数组类型说明
a的类型为int(*)[4],数组指针(后面动态数组还会出现指针数组,注意区别),是指向第一个一维数组的指针,即a所指向的类型为一个一维数组。
a[0]的类型为int*,是指向第一个一维数组第一个元素的指针,即a[0]指向的类型为一个元素,这里是int。
使用下面语句获得类型:
std::cout << typeid(a).name() << std::endl;
std::cout << typeid(a[0]).name() << std::endl;
std::cout << typeid(a[0][0]).name() << std::endl;
结果看起来好像和我们之前说的不一样,其实是一样的,只是vs喜欢这样显示。int [3][4]就是int(*)[4]类型,int [4]就是int *类型。
不信可以用下面这种方法试一试。
这里显示a的类型就是int(*)[4]。
数组与指针的关系
要弄清楚数组与指针的关系,我们需要弄清楚*a,a,&a,a[0],&a[0],&a[0][0],a[0][0]之间的关系。
首先我们来看一下它们的值。a[0][0]自然不用说,就是数组第一个值。
剩下的我们来看一看值:
std::cout << *a << std::endl;
std::cout << &a << std::endl;
std::cout << a << std::endl;
std::cout << a[0] << std::endl;
std::cout << &a[0] << std::endl;
std::cout << &a[0][0] << std::endl;
结构如下:
可以看到它们的值都是一样的。那它们之间有什么差异呢。这里我们使用+1来判断。
首先我们要知道,一个指针+1就是指针向后移动它所指向类型的字节数。假如指向的是int类型,就会向后移动4位(不同环境int字节可能不同),如果指向的是一个长为4的int类型的数组,将会后移4×4=16位。这里的位是指地址的位,地址是以字节位单位编号的,所以地址里的的每一个数就对应一个字节的数据。
我们从新重新使用以下代码:
std::cout <<"a:\t\t" <<a << std::endl;//没+1前都一样所以其他的省略
std::cout << "*a+1:\t\t"<<*a+1 << std::endl;
std::cout << "&a+1:\t\t"<<&a + 1 << std::endl;
std::cout << "a+1:\t\t"<<a + 1 << std::endl;
std::cout << "a[0]+1:\t\t"<<a[0] + 1 << std::endl;
std::cout << "&a[0] + 1:\t"<<&a[0] + 1 << std::endl;
std::cout << "&a[0][0] + 1:\t"<<&a[0][0] + 1 << std::endl;
结果如下:
我们发现
*a+1和a[0]+1,&a[0][0]一样,移动了4位(变大了4)。因为它们本质上来说是一样的。a指向第一个一维数组,也就指向a[0],a[0]指向第一个数组的第一个元素,也就是指向a[0][0]。
&a+1指针后移了48位,因为它指向的是a[3][4]这个数组,这里int型数据占4个字节,+1则后移3×4×4位。
a+1 指针后移了16位,因为它指向的是第一个[4]的一维数组,即a[0],+1后移4×4位。
&a[0]等同于a,因为a就是a[3][4]中第一个数组的地址。
元素的获取
要获得数组的元素,m行n列元素,可以用三种方法。
1.直接用下标访问:a[m][n];
2.用第m行指针位移:*(a[m]+n);
3.用第一个元素位移:*(*(a+m)+n);
测试代码如下:
int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
std::cout << *a << std::endl;
std::cout << a[0] << std::endl;
std::cout << *a[0] << std::endl;
std::cout << *a[0] + 1 << std::endl;
std::cout << *(*(a + 1)+1) << std::endl;
结果如下:
长度计算
从上面我们可以知道,a,a[0]这些都是地址,那么它们的长度是多少呢。
int a[3][4];
std::cout << sizeof(a[0]) << std::endl;
std::cout << sizeof(a) << std::endl;
结果为 16 48。分别为一维数组长度和二维数组长度。但是这里就有疑问了,它们不是指针吗,它们的长度不该是指针本身的长度吗,这是因为编译器在将 C 代码转换成汇编代码时,自动将其替换成了实际的数值。如果运行以下代码:
void test(int b[3][4]) {
std::cout << sizeof(b) << std::endl;
}
int main()
{
int a[3][4];
test(a);
}
将会得到下面结果:
这个时候指针b的长度就为指针本身的长度。因为b本来就是指针,我们经常在数组作参数传入函数时,会多加一个参数记录其长度一样,因为数组指针并没有整个数组的长度信息,只能将首地址传入函数。
如果没搞清楚可以看我的另一篇博客:点这里。
动态二维数组与指针
这里我们只讨论new这种方式创建的二维数组。
int** c = new int*[3];//创建3×4的数组
for (int i = 0; i < 3; i++)
{
c[i] = new int[4];
}
其存储结构如下:
这里可以看到new创建的动态二维数组也是由多个一维数组组成的。不同的是,动态二维数组的中这些一维数组并没有相连,而是每个数组对应一个指向其第一个数的指针。再将这些指针组合成数组(静态数组没有这个指针数组)。c就是指向这些数组指针组成的数组的第一个数的地址。
我们用之前的代码测试:
std::cout << c << std::endl;
std::cout << c[0] << std::endl;
std::cout << &c[0] << std::endl;
std::cout << &c[0][0] << std::endl;
得到如下结构:
从结果中我们发现,和静态二维数组不一样,c和c[0]的值不同。因为c[0]指向第一个一维数组的指针,c为指向指针构成的数组的第一个元素的指针。c的类型为int**,c[0]的类型为int*。这里c和c[0]都是指向的一维数组,只不过c[0]指向的元素是int,c指向的元素是指针。
+1后结果如下:
可以看到,无论是c还是c[0],+1后都是移动4位,因为它们都是指向一维数组第一个元素的指针,元素类型分别为指针类型和int类型,int类型占4个字节,指针类型也占4个字节。*c=c[0]因为c指向的是指针数组的第一个位置。
知道地址关系之后再来看看对值的影响,首先我们对动态二维数组赋值:
int k=0;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
c[i][j] = k;
k++;
}
}
测试结果如下:
虽然在地址+1操作做动态二维数组和静态二维数组所得到的地址值变换不一样,但是所对应到数组的值都一样。所以上面这些计算元素的方法,动态数组和静态数组所得结果都是一样的。
如有错误,还望指正。