上一篇介绍了指针和数组的区别和联系,相信很多同学都已经明白了指针和数组的用法,那么如果指针和数组混合起来,你还会用吗?
指针数组
指针数组是一个数组,它里面存储的是指针变量。比如说 int *p[5]
,数组里面有五个元素,里面存储的是指针。由于[]
比*
的优先级要高,故 p 先与[]
结合,也就是说 p 是一个数组。
数组指针
数组指针是一个指针,它在32位机器下占四个字节大小,64位机器下占据八个字节。比如int (*p)[5]
,表示指向拥有5个元素的数组,示例:
int main()
{
int a[3][5] = {0};
int (*p)[5] = a;
}
需要注意的是,数组指针后面括号里面的数字必须与二维数组的列数相同才可以。
地址偏移量
地址偏移量的概念不太好理解,为了便于理解,我们引入房间地址
和楼层地址
这两个概念。
在一维数组中,比如int a[5];
a 代表的是一个房间地址
,那么 a+1 就会偏移一个房间地址
的距离(以数组中的一个元素所占内存字节为单位进行地址偏移),也就是偏移到 a[1]
的地方。
&a
代表的是一个楼层地址
,&a+1
会偏移整个数组的长度(以整个数组所占内存字节为单位进行偏移),也就是偏移到了整个数组后面的地方。
指针数组操作二维数组
int main()
{
//指针数组操作二维数组
int a[4][5]; //定义一个二维数组
int i, j;
int *p[4]; //定义一个指针数组(是个数组),和二维数组的行数相同。
//初始化二维数组
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
a[i][j] = 10 * i + j;
}
}
//输出二维数组内容
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("a[%d][%d]= %d\t", i, j, a[i][j]);
}
}
printf("\n\n");
p[0] = *a; //指针数组(数组),存储的是"房间"的地址,而不是"楼层"的地址,如果这样写p[0] = a;这样在c++中编译不通过的
p[1] = *(a + 1); //给第二个元素赋值
p[2] = *(a + 2); //给第三个元素赋值
p[3] = *(a + 3); //给第四个元素赋值
//第一种方式输出二维数组内容
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("p[%d][%d]= %d\t", i, j, p[i][j]);
}
}
printf("\n\n");
//第二种方式输出二维数组内容
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("*(*(p+%d)+%d)= %d\t",i ,j, *(*(p + i) + j));
}
}
printf("\n\n");
//第三种方式输出二维数组内容
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("*(p[%d]+%d)= %d\t", i, j, *(p[i] + j));
}
}
return 0;
}
输出结果:
解释:
p 数组里面存放的是一级指针,p[0] 保存的是二维数组第一层楼
的第一个房间地址
,p[1] 保存的是二维数组第二层楼的第一个房间地址,p[2] 保存的是第三层楼的第一个房间地址,p[3] 保存的是二维数组第四层楼的第一个房间地址;之后,p[0][0] 表示:(p[0])[0] == (p 数组的第一个元素)[0] == (*a)[0] == (a[0])[0] == a[0][0],其他的以此类推,得出结果。
数组指针操作二维数组
int main()
{
//数组指针操作二维数组
int a[4][5]; //定义一个二维数组
int i, j;
int (*p)[5]; //定义一个数组指针(是个指针),和二维数组的行数相同
//初始化二维数组
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
a[i][j] = 10 * i + j;
}
}
//输出二维数组内容
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("a[%d][%d]= %d\t", i, j, a[i][j]);
}
}
printf("\n\n");
p = a; //让指针指向二维数组
// p = *a; //虽然a和*a的值都是一样的,但是这样无法赋值(a是"楼层地址",*a是"房间地址")
//第一种方式输出二维数组内容
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("p[%d][%d]= %d\t", i, j, p[i][j]);
}
}
printf("\n\n");
//第二种方式输出二维数组内容
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("*(*(p+%d)+%d)= %d\t",i, j, *(*(p + i) + j));
}
}
printf("\n\n");
//第三种方式输出二维数组内容
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("*(p[%d]+%d)= %d\t",i, j, *(p[i] + j));
}
}
return 0;
}
输出结果:
解释:
p 里面存放的是一维数组的地址(注意不是数组首元素的地址,是整个数组的地址),在程序中 a 表示的就是第一层楼的地址,也就是 &a[0] 。由于 p 指向的是楼层地址,则 p+1 则表示第二层楼的地址,p+2 表示第三层楼的地址...当我们用 p[0][0] 的时候,其实表示的就是第一层楼的第一个房间内容(p 表示的第一层楼的地址,p[0] 表示的第一层楼的第一个房间的地址,p[0][0] 表示的第一层楼的第一个房间的内容),其他的以此类推。
总结
- 指针数组是数组,数组指针是指针;它们符合各自数组或指针的特性;
- 数组和指针都是操作地址的运算;
- 地址也是有级别的,例如上面所说的
楼层地址
和房间地址
(本质是地址操作时候的偏移单位不同); - 很重要的一点,数组的地址是连续的!!上面的操作都是基于这点才可以实现的,如果换成链表,则上面程序的输出结果将无法判断;
- 上面提到的指针数组和数组指针都属于
二级指针
范畴,如果是int ****p[5]
,你还能操作二维数组吗?换成三维数组呢?抓住本质,无论多少级都一样操作。
补充
与指针数组和数组指针类似的还有一个:二级指针
,示例:
int main()
{
int a[4][5];
int i, j;
int *q[4];
int **p;
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
a[i][j] = 10 * i + j;
}
}
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("a[%d][%d]= %d\t", i, j, a[i][j]);
}
}
printf("\n\n");
q[0] = *a;
q[1] = *(a + 1);
q[2] = *(a + 2);
q[3] = *(a + 3);
p = q;
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("p[%d][%d]= %d\t", i, j, p[i][j]);
}
}
printf("\n\n");
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("*(*(p+%d)+%d)= %d\t",i ,j, *(*(p + i) + j));
}
}
printf("\n\n");
for(i = 0; i < 4; i++)
{
for(j = 0; j < 5; j++)
{
printf("*(p[%d]+%d)= %d\t", i, j, *(p[i] + j));
}
}
return 0;
}
自己输出一下结果,尝试理解一下指针的工作原理,相信你可以征服它!!!
练习
int a[] = {0, 2,4,6,8};
int *p[5]={a, a+1, a+2, a+3, a+4};
int ** pp = p;
int main()
{
printf("%d\n",*pp++);
printf("%d\n", *pp-a);
printf("%d\n", ** ++ pp);
printf("%d\n", pp - p);
return 0;
}
输出结果是什么呢?
#include <stdio.h>
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int *ptr = (int *)(&a + 1);
printf("%d %d\n", *(a+1), *(ptr-1));
return 0;
}
输出结果是什么呢?