指针——C的灵魂
一、二维数组的地址认知
我们都知道一维数组,但是关于二维数组,我们容易搞蒙。有一个特殊的说法:二维数组就是父子数组,为了研究清楚地址的概念,我们把二维数组回归到一维数组去讲解。看这段代码:
int a[3][4] = {{1,3,5,7}, {9,11,13,15}, {17,19,21,23}};
通过对指针和数组的学习,我们明白了数组名就是地址,所以我们可以把二维数组 a [ 3 ] [ 4 ] a[3][4] a[3][4] 中的 a [ 3 ] a[3] a[3] 看作是父数组,那么它的地址就是数组名 a a a ,也叫行地址,父数组中包含三个元素: a [ 0 ] a[0] a[0]、 a [ 1 ] a[1] a[1]、 a [ 2 ] a[2] a[2] 。再看上面一幅图片,在每个父数组 a [ 0 ] a[0] a[0]、 a [ 1 ] a[1] a[1]、 a [ 2 ] a[2] a[2] 中,又包含四个元素,就是上图对应的三行,我们把这三行看作是子数组,那么 a [ 0 ] a[0] a[0]、 a [ 1 ] a[1] a[1]、 a [ 2 ] a[2] a[2] 就变成了子数组的名字,所以就是子数组的地址,也叫列地址。
既然明白了以上的道理,那么我们就来讨论以下父数组和子数组的偏移问题,即 a + 1 a+1 a+1 和 a [ 0 ] + 1 a[0]+1 a[0]+1 的问题。通过上面我们对父子数组地址的认知,我们可以清楚的知道 a + 1 a+1 a+1 是父数组的偏移,那么它偏移多少个呢?由于每个父数组中有4个元素,所以它每偏移1个单位,就会偏移4个元素的长度,如上图,所以当 a + 1 a+1 a+1 的时候,单个元素就会从 a [ 0 ] [ 0 ] a[0][0] a[0][0] 直接跳到 a [ 1 ] [ 0 ] a[1][0] a[1][0]。子数组发生 a [ 0 ] + 1 a[0]+1 a[0]+1 的时候呢?由于子数组是由一个一个连续的数组元素组成的,所以子数组每次偏移1个单位只会引起一个元素的偏移,如上图第一行子数组,当 a [ 0 ] + 1 a[0]+1 a[0]+1 的时候,单个元素就会从 a [ 0 ] [ 0 ] a[0][0] a[0][0] 跳到 a [ 0 ] [ 1 ] a[0][1] a[0][1] 。以上就是我们对二维数组地址的认知。
认知到这一步,我们就来接着看一下在二维数组
a
[
3
]
[
4
]
a[3][4]
a[3][4] 中,
∗
a
*a
∗a 和
∗
a
[
0
]
*a[0]
∗a[0] 分别表示什么?在一维数组
a
[
3
]
a[3]
a[3] 中,
∗
a
*a
∗a 就是把一维数组中第一个元素的值
a
[
0
]
a[0]
a[0] 取出来,而在二维数组中,
∗
a
*a
∗a 表示的则是将二维数组第一行首个元素
a
[
0
]
[
0
]
a[0][0]
a[0][0] 的地址取出来,同时也是第一个子数组的首元素的地址,注意,二维数组没有一行一行取值的概念,这里取出来的是第一行首个元素的地址,而不是值。那么,由于
a
[
0
]
a[0]
a[0] 是子数组第一个元素的首地址,所以
∗
a
[
0
]
*a[0]
∗a[0] 是将子数组第一个元素即
a
[
0
]
[
0
]
a[0][0]
a[0][0] 的值取出来。
1.通过编程验证认知
下面我们对以上几种情况进行代码验证,看以下代码:
#include<stdio.h>
int main()
{
int arr[3][4] = {{11,22,33,44},{12,13,14,15},{55,66,77,88}};
printf("arr是父亲地址:%p,偏移1后是:%p\n",arr,arr+1);
printf("arr[0]是子数组地址:%p,偏移1后是:%p\n",arr[0],arr[0]+1);
printf("*(arr+0)是父亲地址:%p,偏移1后是:%p\n",*(arr+0),*(arr+0)+1);
return 0;
}
运行结果:
第一行打印的是父数组的偏移,偏移1以后偏移整个子数组的长度,每个子数组中有4个整型元素,所以一共偏移了16个字节,所以两个地址相差F,第二行是子数组的偏移,子数组偏移1偏移1个元素的长度,所以打印出来的两个地址相差4个字节,第三行是用对父数组名取地址的形式来提取子数组第一个元素的地址进行打印,和第二行代码等效,同样也是偏移了4个字节。
2.二维数组地址写法认知,见怪不怪
关于二维数组地址的写法有很多种,甚至这些写法再C语言面试的过程中很容易把人搞懵,下面我们就来通过编程总结以下:
#include<stdio.h>
int main()
{
int arr[3][4] = {{11,22,33,44},{12,13,14,15},{55,66,77,88}};
int i,j;
for(i = 0;i<3;i++){
for(j = 0;j<4;j++){
printf("add:0x%p,data:%d \n",&arr[i][j],arr[i][j]);
printf("add:0x%p,data:%d \n",arr[i]+j,*(arr[i]+j));
printf("add:0x%p,data:%d \n",*(arr+i)+j,*(*(arr+i)+j));
printf("============================================\n");
}
}
return 0;
}
其中,第一行输出语句中的
a
[
i
]
[
j
]
a[i][j]
a[i][j] 是第i行第j列元素的值,在前面加上个 & ,就是这个元素的地址,第二行输出语句中的
a
r
r
[
i
]
arr[i]
arr[i] 是二维数组第i行子数组元素的首地址,所以
a
r
r
[
i
]
+
j
arr[i]+j
arr[i]+j 就是第i行第j列元素的地址,
∗
(
a
r
r
[
i
]
+
j
)
*(arr[i]+j)
∗(arr[i]+j)就是它的值,第三行输出语句中的
(
a
r
r
+
i
)
(arr+i)
(arr+i) 是父数组第i行的首地址,
∗
(
a
r
r
+
i
)
*(arr+i)
∗(arr+i) 就是第i行子数组首个元素的地址,在这个基础上加j就是第i行第j列元素的地址,
∗
(
(
a
r
r
+
i
)
+
j
)
*((arr+i)+j)
∗((arr+i)+j) 就是这个元素的值。
3.二维数组地址写法总结(面试)
二、数组指针
数组指针,顾名思义就是指向数组的指针,既然指针能指向一个整型变量,那么它就一定能够指向数组变量,但是能不能定义一个指针,让指针偏移的时候,也偏移对应大小的数组呢?这就是数组指针,定义一个指针,指向数组。首先我们看一组错误的写法:
#include<stdio.h>
int main()
{
int arr[3][4] = {{11,22,33,44},{12,13,14,15},{55,66,77,88}};
int i,j;
int *p;
p = arr;
for(i = 0;i<3;i++){
for(j = 0;j<4;j++){
printf("%d \n",*(*(p+i)+j));
}
}
return 0;
}
运行结果:
可以看出来,编译警告。为什么呢?因为我们定义的指针指向了二维数组名 a r r arr arr, 二维数组名表示父数组,它是以行为偏移单位的,所以一次偏移一个子数组,一共4个元素16个字节,而我们定义的指针 p p p 是按照单个元素一个一个进行偏移的,所以两者在偏移的时候就会不匹配,产生警告。那如何正确定义一个数组指针呢?正确的方式应该是这样:
int (*p2)[4];
p2 = arr;
方括号中的4表示这个指针跨度为4个单位长度,这样就与我们父数组也就是数组名表示的地址相匹配了。我们看一下实际应用:
#include<stdio.h>
int main()
{
int arr[3][4] = {{11,22,33,44},{12,13,14,15},{55,66,77,88}};
int (*p2)[4];
p2 = arr;
printf("p2 = %p\n",p2);
printf("++p2 = %p\n",++p2);
return 0;
}
运行代码:
指针指向数组以后,对指针的操作就和之前我们对数组名操作是一样的,都是对地址进行操作。这样写的话就不会产生警告了。跨度为一个子数组的长度,两个地址相差16。指针每次自增指向的是每个子数组的首地址。
二、维数组和数组指针的配合应用
#include<stdio.h>
void tipsInputRowLine(int *pm,int *pn)
{
printf("输入行列值:\n");
scanf("%d%d",pm,pn);
puts("done!\n");
}
int getTheData(int (*p)[4],int row,int line)
{
int data;
data = *(*(p+row)+line);
return data;
}
int main()
{
int arr[3][4] = {{11,22,33,44},{12,13,14,15},{55,66,77,88}};
int row,line;
int data;
tipsInputRowLine(&row,&line);
data = getTheData(arr,row,line);
printf("第%d行%d列的元素的值为:%d\n",row,line,data);
return 0;
}