指针和数组是 C 语言中比较重要的知识点,尤其是指针和数组结合之后,很多同学就不清楚是怎么一回事了。在这里结合几个例子,介绍一下我所理解的指针和数组,希望对大家有所帮助。
指针
指针在 C 语言中是一种类型,就和其他基本数据类型一样。通常在32位机器上面占四个字节,在64位机器上占八个字节。指针用来保存其他变量的地址,请看下面整型指针与整型变量的对比:
int main()
{
int a = 4; //定义整型变量 a
int b = 10; //定义整形变量 b
int *p = &b; //定义整型指针 p ,保存变量 b 的地址
//a 变量和 p 变量的值
printf("a 变量的值为: %d\n", a);
printf("p 变量的值为: %p\n", p);
//指针变量的特殊操作
printf("指针所指向变量的值: %d\n", *p);
*p = 15;
printf("指针所指向变量的值: %d\n", *p);
printf("b 变量的值为: %d\n", b);
return 0;
}
解释:
在上面的程序中,定义了整型变量 a、整型变量 b 和 整型指针变量p,让指针变量 p 保存变量 b 的内存地址,也就是说 p 指针"指向"变量 b 。程序接着打印 a 变量的值和p 变量的值,分别代表整型变量 a 保存的整数值和指针变量 p 保存的变量 b 的地址值(%p表示用十六进制数字表示地址值)。指针还有特殊的操作:*操作
,通过*操作
可以访问指针所指向的变量,在程序中也就是说 *p
与变量 b
是等价的,操作变量 *p
就是操作变量 b
。
类型 | 变量 | 值 | 特殊操作 |
---|---|---|---|
int | a | a (使用 %d 打印) | 无 |
int * | p | p (使用 %p 打印) | *p (代表指向的变量) |
数组
数组是一种特殊的数据类型,它把具有相同类型的若干变量按照有序的顺序组织起来,极大的方便了我们的开发。数组名代表数组的首地址,即数组第一个元素的地址,通过下标 []
来访问数组里面的元素。
int main()
{
int a[5] = {1, 2, 3, 4, 5};
printf("%d\n", a[0]);
return 0;
}
上面代码中的 a[0]
代表数组中的第一个元素,输出的结果为1;
数组和指针的联系
其实数组的下标 []
操作也是一种特殊操作,与指针的 *
操作十分相似。数组名是一个指针:指针常量。它无法改变指向,只能指向数组的首地址,所以a++、a--之类的操作都是错误的(a是数组名)。既然数组名是一个指针,可以使用下标 []
操作,那么普通的指针变量是不是也可以使用呢?反过来,普通指针的 *
操作是不是也适合数组呢?
int main()
{
int a[4] = {5, 6, 7, 8};
int *p = a;
int i;
printf("输出数组里面的内容:\n");
for(i = 0; i < 4; i++)
printf("a[%d] = %d\t", i, a[i]);
printf("\n");
for(i = 0; i < 4; i++)
printf("p[%d] = %d\t", i, p[i]);
printf("\n");
for(i = 0; i < 4; i++)
printf("*(a+%d)=%d\t", i, *(a+i));
printf("\n");
for(i = 0; i < 4; i++)
printf("*(p+%d)=%d\t", i, *(p+i));
printf("\n");
return 0;
}
运行结果:
从结果可以看出,[]
和 *
操作都适用于指针变量,数组也是通过操作指针来实现对元素的查询、修改操作。让我们再深入一点,数组是如何通过下标操作找到对应的元素的呢?
int main()
{
int a[5] = {1, 2, 3, 4, 5};
int *p = a;
printf("%d\n", p[2]);
printf("%d\n", *(p+2));
p = &a[2];
printf("%d\n", p[0]);
printf("%d\n", *p);
return 0;
}
运行一下代码发现,输出的数据都是3,也就是数组中的第三个元素。怎么会这样?
解释:
程序中当指针 p 指向数组的第三个元素的时候,这时候 p[0]
输出的是3,也就是a[2]
的值,那么 p[0]
的操作结果与 *p
的操作结果一致;另外,大家试一试
输出 p[1]
的值,不出所料操作结果应与 *(p+1)
的操作结果一致。发现规律没?[]
操作其实就是 *
操作啊!!!发现新大陆了啊,有没有?
让我们冷静的分析一下: []
操作和 *
操作实际上都是偏移操作(内存地址偏移),通过当前指针指向的内存地址以及常量来进行地址偏移,最后通过指针的类型解析所指向地址的内容。
那么偏移的单位是多少?这个与指针或者数组的类型有关,比如 int *
类型的指针一次偏移 sizeof(int)
的地址,double *
类型的指针一次偏移 sizeof(double)
的地址。
指针访问数组的方式
使用 [] 访问。例如
int a; int *p = a;
用 p 表示输出 a[2] 的值:p[2] ;如果是这样int a[5]; int *p = &a[2];
用 p 表示输出a[3] 的值:p[1]。使用 * 访问。例如
int a[5]; int *p = a;
用 p 表示输出 a[2] 的值:*(p+2);如果是这样:int a[5]; int *p = &a[2];
用 p 表示输出 a[3] 的值:*(p+1)。
思考
知道指针和数组的操作原理后,下面的题你会做吗?
第一题:
int main()
{
int a[5] = {1, 2, 3, 4, 5};
p = &a[2];
printf("%d\n", p[-2]);
printf("%d\n", *(p-2));
return 0;
}
第二题:
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;
}
第二题注意指针的类型变化哦,自己编写运行一下,看结果与自己想的一样吗?
ps: 竟然还能 p[-2],坑爹有没有...
总结
- 指针和数组都可以使用下标或者 * 操作。
- 下标操作或者 * 操作的本质都是地址偏移,通过指针的类型来解析内存地址里面存储的数据。
- void * 指针可以指向任何地址,但是无法使用 * 操作,原因就是编译器不知道如何解析void * 指针所指向的内存地址的内容。