0. 前言
------家人们,答应你们的指针下一篇来啦,这期我们就来详细讲讲指针与数组之间的爱恨情仇!!!
一、数组名的理解
首先在深入探讨他们之间的关系前,我们先来重新认识一下数组这就老朋友。
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int* p = &arr[0];
看过我的深入探寻指针奥秘的老朋友们肯定对上面这段代码不陌生,新朋友也不要担心,可以点开看看,说不定会给你带来对指针的新理解。
那么我们今天重新来讲解一下这段代码,我们通过&arr[0]来获取数组第一位元素的地址,但是数组名本身就是首元素的地址,不信我们来做个测试。
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
return 0;
}
我们数组名和数组首元素的地址打印出来结果是一模一样的,由此不难得出 数组名就是数组首元素的地址,但是它是有特殊情况的!
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
printf("%d\n", sizeof(arr));
return 0;
}
输出结果为四十,如果是首元素地址的话,应该要输出4/8才对呀。没错这就是特殊情况中的一种。
- sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
- &数组名,这里的数组名同样也是表示整个数组,取出来的是整个数组的地址。
除了这两个特例以外,任何地方使用数组名,数组名都表示首元素的地址。
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
printf("&arr = %p\n", &arr);
return 0;
}
哈哈哈哈不要高兴的太早来看看这段代码呢。
这就很奇怪了,不是说&数组名取出的是整个数组的地址吗?!那这样的话arr又和&arr有啥区别呢?上代码!!!
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0] + 1);
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr + 1);
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr + 1);
return 0;
}
这里我把编译器换成了x64位,看到这个结果是不是更迷惑了,这里我们发现&arr[0]和&arr[0]+1相差4个字节,arr和arr+1也是相差4个字节,但是&arr和&arr+1相差40个字节,这是因为&arr[0]和arr都是首元素地址,+1就是跳过一个元素。但是&arr是数组的地址,==+1是跳过整个数组。==现在对数组名的理解加深了很多吧。
二、指针访问数组
我们正式开始讨论指针和数组之间的联系。有了前面这些知识的铺垫,我们就能很好的来使用指针访问数组啦。
int main()
{
int arr[10] = { 0 };
int n = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (int i = 0; i < n; i++)
{
scanf("%d", p + i);//也可以写scanf("%d",arr+i);
}
for (int i = 0; i < n; i++)
{
printf("%d ", p[i]);
}
return 0;
}
在代码中注释提及的地方,因为这里arr是首元素的地址可以赋值给p,所以这里的arr和p是等价的,那么我们可以使用arr[i]访问数组元素,p[i]同样也可以,将代码中的p[i]换成*(p+i)也是一样的效果,本质上这两个是等价的。
同理arr[i]也应该等价于*(arr+i),数组遍历在编译器处理时,也是转换成首元素的地址+偏移量求出元素地址, 然后解引用访问的。
三、一维数组的传参本质
我们之前都是在函数外部计算数组的元素个数,那我们可以把函数传给一个函数后,函数内部求数组的元素个数嘛?
void test(int arr[])
{
int n2 = sizeof(arr) / sizeof(arr[0]);
printf("n2 = %d\n", n2);
}
int main()
{
int arr[10] = { 0 };
int n1 = sizeof(arr) / sizeof(arr[0]);
printf("n1 = %d\n", n1);
test(arr);
return 0;
}
我们发现函数内部没有正确的获取到数组的元素个数。在数组传参时,传递的是数组名,本质上数组传递的是数组首元素的地址,所以理论上应该使用指针变量来接收首元素地址。正是因为函数的参数部分本质是指针,所以函数内部是没办法求数组元数个数的。那么在函数内部我们用sizeof(arr)计算一个地址的大小,而不是数组的大小。
void test(int arr[])
{
printf("arr[] = %d\n",sizeof(arr));
}
void test1(int* arr)
{
printf("*arr = %d\n", sizeof(arr));//计算一个指针变量的大小
}
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
test(arr);
test1(arr);
return 0;
}
一维数组传参,形参可以用数组形式,也可以是指针形式
四、二级指针
指针变量也是变量,是变量它就会有地址,那指针的地址又存放在什么地方呢?这就不得不说到二级指针。
int main()
{
int a = 10;
int * pa = &a;
int ** ppa = &pa;
return 0;
地址都是我假设的,看了这个图是不是稍微对下面这段代码好理解一点了。
- *ppa: 对ppa进行解引用,找到的是&pa,其实就是访问pa。
- *ppa: 先通过ppa找到pa,然后对pa解引用(*pa),找到a。
五、指针数组
小问答:请问指针数组是指针还是数组?
整型数组,是存放整型的数组,字符数组是存放字符的数组,那么。。。指针数组就是存放指针的数组啦!!!指针数组的每个元素是地址,又可以指向一块区域。
我们来用指针数组模拟一下二维数组呢?
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* parr[3] = { arr1,arr2,arr3 };
int i, j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d ", parr[i][j]);
}
printf("\n");
}
return 0;
}
parr[i]是访问parr数组的元素,找到的数组元素指向整型一维数组,parr[i][j]就是整型一维数组中的元素。但是其实上述代码并不完全是二维数组,因为每一行并非连续。
本期的内容就暂时讲到这里啦,下一篇文章我们会进一步探讨指针,数组,函数等等之间的关系。