文章目录
- 1 数组名的理解
- 2 使用指针访问数组
- 3 一维数组传参的本质
- 4 指针数组
- 5 指针数组的使用
1 数组名的理解
当我们运行以下代码:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n", &arr[0]);
printf("%p\n", arr);
return 0;
}
会发现,arr[0]的地址和数组名arr的地址是一样的。
所以我们大致可以猜测一下:
数组名是数组首元素的地址
但是按照这样的理解,以下代码的结果应该是4才对
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%zd\n", sizeof(arr));
return 0;
}
但是当我们运行就会发现,结果是40.
那说明之前的猜测有一些问题。
实际上的结论是:
数组名是数组首元素的地址,
但是有两个例外:
1.sizeof(数组名),这里的数组名表示整个数组,sizeof计算的是整个数组的大小,单位是字节。
2.&数组名,这里的数组名也表示整个数组,取出的是整个数组的地址
当我们运行代码,肯定会疑惑,根据结论的第二个例外,&数组名取出整个数组的地址,为什么得到的arr的地址和数组首元素的是一样的呢?
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n", &arr); //取出整个数组的地址
printf("%p\n", &arr[0]);
printf("%p\n", arr);
return 0;
}
其实也不难理解,取出首元素的地址其实就相当于取出了整个元素的地址,如果我们得到了首元素的地址,就可以顺藤摸瓜得到剩下所有元素的地址。
我们可以通过以下的代码观察它们之间的区别;
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr+1);
printf("\n");
printf("&arr[0] = %p\n", &arr[0]);
printf("&arr[0]+1 = %p\n", &arr[0]+1);
printf("\n");
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr+1);
printf("\n");
return 0;
}
可以发现,三种方式得到的地址都是一样的,但是由于arr和&arr[0]取出的都是首元素的地址,把它们地址加1,加的是一个整形元素,4个字节,但是如果是**&arr,加1后地址加了40个字节**(70到98,加了28,这是16进制表示,转换成十进制就是40),所以这个例子能很好的证明&arr其实取出的是整个数组的地址,但是由于我们可以通过首地址找到所以元素的地址,为了方便显示,我们观察到的地址还是首元素的地址。
2 使用指针访问数组
如果我们想要实现数组的输入和输出时,可以这样写代码:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* p = arr;
//向数组输入数
for (i = 0; i < sz; i++)
{
scanf("%d", p + i);
//p最开始指向数组首元素的地址,每次输入完后要执行后面的地址进行下一次的输入
}
//输出数组的内容
for (i = 0; i < sz; i++)
{
printf("%d", *(p + i));
}
return 0;
}
程序能够很好的运行,但是如果我们稍作修改呢?
假如我们把scanf函数中的p+i修改成p++,结果又会是什么呢?
代码如下:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
int* p = arr;
//向数组输入数
for (i = 0; i < sz; i++)
{
scanf("%d", p++);
}
//输出数组的内容
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
运行后我们会发现,程序出现了问题,输出随机值,这是为什么呢?
仔细想想,最开始i=0的时候,指针指向数组首元素的地址,之后每输入一个值指针指向后一个元素的地址,但是当i=9,输入完第10个数后,p++,此时p++指向的是数组最后一个元素的后面的地址,当我们用printf输出数组时,我们自以为指针最开始还是指向数组首元素的地址,但是实际上指针指向的位置已经没有元素了,所以导致没有输出结果,如果我们想输出,就需要在输出数组之前把指针重新指向数组的首元素地址。
我们也可以通过调试来观察问题:
在调试时我们可以设置条件断点,避免无意义的重复输入,从i=8开始执行,按fn+f5开始调试后直接输入10个数组元素,然后打开监视,之后按fn+f10一直往下执行,直到执行到输入最后一个数,也就是i=9的时候,观察p所指向的地址
可以发现,当i等于9,并且输入完第10个数之后,p指向了arr[9]的后面一个元素(arr[10])的地址,但是我们创建的数组为arr[0]~arr[9],p最后指向越界了,所以在输出的时候会出现随机值。
我们可以对代码稍微进行一下修改,在打印数组内容之前使p重新指向数组的第一个元素地址,就加一句p=arr;
修改后结果如图:
3 一维数组传参的本质
当我们运行下面代码:分别在主函数和函数中求数组的大小
#include <stdio.h>
void test_len(int* arr)
{
int sz1 = sizeof(arr) / sizeof(arr[0]);
printf("sz1 = %d\n", sz1);
return 0;
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
test_len(arr);
int sz2 = sizeof(arr) / sizeof(arr[0]);
printf("sz2 = %d\n", sz2);
return 0;
}
可以发现在函数中,数组的大小竟然是1,这是为什么呢?
我们可以尝试求出函数中arr和arr[0]的大小分别是多少
可以发现,传递的数组arr大小竟然也是4,所以之前求sz1的大小得到的是4/4=1.
这是因为数组传参的本质传递的是数组首元素的地址。
当我们想得到数组的大小并在函数中使用时,最好先在主函数中先计算结果,再通过参数的形式传递给函数。
4 指针数组
我们可以进行类比:
整形数组:存放整形的数组,例如int arr[5] = { 1, 2, 3, 4, 5}; //数组里的每一个元素都是int类型
字符数组:存放字符的数组,例如char ch[5] = { ‘a’, ‘b’, ‘c’, ‘d’, ‘e’}; //数组每个元素都是char类型
因此我们可以推断:
指针数组:存放指针的数组,数组里的每一个元素都是存放指针(地址)的。
例如假设我们已知a,b,c,d,e里面存放的都是整数。
指针数组里面的元素依次存放a,b,c,d,e的地址,我们可以这样表示:int * arr[5] = { &a, &b, &c, &d, &e};
如图所示:
5 指针数组的使用
我们可以使用指针来模拟二维数组。
代码如下:
#include <stdio.h>
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int* ptr[3] = { arr1,arr2,arr3 };
int i;
for (i = 0; i < 3; i++)
{
int j;
for (j = 0; j < 5; j++)
{
printf("%d ", ptr[i][j]);
}
printf("\n");
}
return 0;
}
首先我们创建指针数组ptr,用来存放三个数组的数组名(本质存放的是数组首元素的地址),再通过双重循环找到对应的值,外层循环用来实现我们找的是哪一个数组的首元素的地址,内存循环我们可以顺着数组首元素的地址找到数组后面的所以元素并打印出来。
指向关系如图:
可以发现,三个一维数组通过指针数组的方法,模拟出了二维数组的样子,但是不等同二维数组,因为我们知道二维数组的n行在内存中是连续存放的,但是我们创建的3个一维数组并不一定在同一块空间内,我们可以得到三个数组首元素的地址,通过地址分别找到三块空间再顺藤摸瓜找到数组所有元素并打印出来。
我们知道ptr[i]在运行时会被编译器转换成指针的形式*(ptr+i),ptr[i][j]也类似,我们可以把ptr[i]当做ptr,把[j]当做[i],替换之前的指针形式就能得到ptr[i][j] = * (ptr[i]+j) = *( *(ptr+i)+j) ,所以我们在打印的时候也可以使用指针的形式,也能得到正确结果.
代码如下:
#include <stdio.h>
int main()
{
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int* ptr[3] = { arr1,arr2,arr3 };
int i;
for (i = 0; i < 3; i++)
{
int j;
for (j = 0; j < 5; j++)
{
printf("%d ", *(*(ptr+i)+j));
}
printf("\n");
}
return 0;
}