数组与指针深入理解

静态二维数组与指针

我们定义一个二维数组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操作做动态二维数组和静态二维数组所得到的地址值变换不一样,但是所对应到数组的值都一样。所以上面这些计算元素的方法,动态数组和静态数组所得结果都是一样的。

如有错误,还望指正。

发布了33 篇原创文章 · 获赞 148 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_40692109/article/details/102892208