C语言指针疑难杂症

C语言指针疑难杂症
——许多时候,使用C语言编写的代码是难于阅读的,但很多人却热衷于此。
从软件工和的角度出发,代码应该尽可能地易于阅读,无论是多年后的你还是接手项目的别人。但C语言本身的就要比其他语言要难于理解许多,且很多人为了一时的方便,写出了一些只有自己才能理解的代码。本文不是探讨所有的比较难于理解的代码,我们的主题是——指针。
1. C语言指针有一些习惯的用法,它不是疑难杂症,我们应该尽可能去理解它们并且使用它们,如下面的代码:
#include<stdio.h>
int main()
{
	int arr[] = {22,33,44,55,11,18,0};
	int *p;
	for (p = arr; p < arr + 7;)
	{
		printf_s("%d,",*p++);
	}
	return 0;
}


上面代码的输出将会是arr数组内容本身,这里的注意点就是*p++这个语句。这里我不想向大家介绍C语言的运算符的优先顺序和运算符的结合顺序这些内容,因为我也不想记住这些东西。但要理解上面的这个表达式,上面的两个内容又是必须的。但有例外,你只要理解下面对*p++的解释就行了:
在上面这段代码里,*p++等价于*(p++),因此整个表达式的结果将会是数据内容本身而不是指针。又因为是后运算,所以p++立刻返回的结果的值还是之前p的值,p返回后,p的值才发生改变。观看下面代码的输出:
#include<stdio.h>
int main()
{
	int arr[] = {22,33,44,55,11,18,0};
	int *p=arr;
	printf("%p,%p,%p\n", p, p++,arr);
}


输出为: 0038FB70, 0038FB6C, 0038FB6C
为了理解这个输出,首先是要知道printf_s(VC编译器的安全printf函数版本)的运算顺序——右结合的。即从右到左进行运算,然后再对应位置输出:它首先计算arr,然后返回;再计算p++,然后返回;最后计算p,然后返回。p++虽然执行了自加运算,但它的返回值还是它之前的值。因此arr和p++的结果相等,面第一个输出的p是p+1的结果。
这样,上面的*p++就不难理解了,它等价于*(p++),它先自增p,然后返回自增p之前p的值作为结果,然后利用间接寻址运算符取得p++返回值指向的值。又或者将它理解为:它先取得p指向的值,然后再将p进行自增运算。假如p=0038FB6C,那么p++的返回值为0038FB6C,但再次用到p时,p的值是0038FB70(0038FB6C +4)
而同样常见的还有(*p)++,它和++*p是等价的。它是先取得指针指向的值,然后再将这个值加1(如上面当p第一次指向arr时,(*p)++就是取得p指向的值22,然后再加上1(等于23))。这时的自加运算就是一般意义上的加1了,而不是指针运算的加1(实际上是指针的值加上相应的字节数)。三个表达式(*p)++、*p++、++*p的返回值都是指针指向的变量类型,而不是指针类型。


2.请看下面的代码:
#include<stdio.h>
int main()
{
	int arr[5];
	for (int i = 0; i < 5; i++)
	{
		*(arr + i) = i + 5;
	}
	for (int i = 0; i < 5; i++)
	{
		printf_s("%d,", *(arr + i));
	}
	return 0;
}


再看输出:
 
如果你看不明白*(arr+i)表达的意思,那么你就先看一下我的另一篇文章:《C语言指针四——指针与函数》,如果你不想看,我可可告诉你:在这里*(arr+i)和arr[i]是等价的。但很多人仍会感到迷惑,因为上面竟然有一个这样的语句*(arr + i) = i + 5;这里的迷惑来自于,在大家的潜意识里,*(arr+i)返回的是一个值,但怎么可以对一个值进行再赋值呢?其实这种担忧是不必的,上面已经说过*(arr+i)和arr[i]是等价的,既然通过arr[i]可以修改数组元素的值,那么*(arr+i)也可以改变数组相应元素的值。事实上,*(arr+i)返回的是对数组第i位元素的引用,所以用这种方式来修改数组元素是完全合法的,不要担心。


3.C语言数组支持负下标?
请看下面的代码:
#include<stdio.h>
int main()
{
	int arr[10] = {0,1,2,3,4,5,6,7,8,9};
	int *p = &arr[5];
	printf_s("%d,%d",arr[5],p[-3]);
	return 0;
}


输出为:
5 2;

有人认为,上面使用了p[-3]来访问了数组的元素,那么就说明C语言的数组支持负下标。但我认为这种说法是相当错误的,因为p根本就不是一个数组,它只是一个指向整型的指针——整型指针。表达式p[-3]实际上会被转换成*(p+(-3))来进行运算。而arr才是一个真正意义上的数组,如果对arr进行了负下标的运算,如:arr[-3],这必定会发生错误。下面让我们从内存的角度看一下C语言数组不支持负下标的真正原因。必须再次说明,p不是一个数组,它只是一个整型指针。


 
假设数组arr的首地址为0012,它是一个10进制数,那么p的值就是0032(0012+5*4,这里4是int类型变量所点的字节数),如果不明白这一点,请先看我的别一篇文章:《C语言指针三——指针运算本质》。编译器在处理arr[5]时,会将这个表达式先转换为*(arr+5),arr本身是一个数组,但使用arr进行计算时,arr会返回一个特殊的值,这个值是一个指针变量,它相等于arr数组第一个元素的地址(有另一种说法,当arr参与运算时,arr退化为了相应类型的指针),因此arr的值是0012。同时,arr的值是0012是不可改变的,它还有另外一层的意义,arr的值标志了整个数组的开始。因此arr[5]和p[-3]的意义是一样的,它们都被编译器视为:*(arr+5)、*(p-3),arr+5的值为0032,它指向数组第5个元素,而p的值为0032(因int *p = &arr[5];),所以(p-3)的值为0020(0032-3*4),它指向数组的第2个元素。因此最终的输出为:5  2
因为arr才是一个真正的数组,对数组取负下载将会指向数组首元素之前的内存空间,如果arr[-2]:
 
对于arr[-2]指向的内存空间,首先这个内存空间不一定是有效的,再次,即使这是一个有效的内存空间,你也不一定有权访问它,这就是为什么数组不支持负下标的原因——很多时候,它还会出错。而p可以使用负下标的原因是因为经过运算后,得到的结果仍然指向一个合法的内存区域,在这个例子中,如果进行p[-7]求值,它必定也会出错。
因此说,C语言数组支持负下标是一种不负责任的说法,它混淆了数组名和指针的区别,也没有理解到本质的东西。
请记住,指针不是数组,数组名也不是指针,只不过很多时候,它们的使用可以等价的,但毕竟它们不是同一样事物。
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一尺丈量

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值