两道题题引发的思考
两道题题引发的思考
写在前面
下周的讨论课又到我讲论文了,这几天都在看论文、准备ppt,选了一篇好难的论文啊,看了这么久了,还是不够清晰。。。所以决定先换下思路,做点题。
题1
下面的代码输出是什么
void add(int *p)
{
(*p)++;
printf("%d",*p);
if (*p>5)
{
return;
}
add(p);
}
int main()
{
int i=0;
add(&i);
system("pause");
return 0;
}
分析
这个题目看起来还是比较友好的。首先我们来分析一下add()函数。其实看名字就能知道它是在做加法啦,不要更明显好吗=。=
这个函数没有返回值,函数参数是一个int型的指针,函数体中首先对指针p指向的内容做自增操作,然后打印出自增后的值,接着判断自增后的值与5的关系,如果大于5,则返回。
接下来看看main函数在干啥。main中首先声明了一个int型的变量i,并初始化为0,然后把i的地址传入add函数中。
最后看看函数的执行过程:
第一步:i = 0,执行add(0),则*p = 0,执行(*p)++后,*p = 1,因此打印出1, 此时 *p >5不成立,因此执行add(1)
第二步:add(1), 则 *p = 1, 执行(*p )++后,*p = 2,因此打印出2, 此时 *p >5不成立,因此执行add(2)
。。。
第五步:add(4), 则 *p = 4, 执行(*p)++后,*p = 5,因此打印出5,此时 *p >5仍然不成立,因此执行add(5)
第六步:add(5),则 *p = 5, 执行(*p)++后,*p = 6,因此打印出6, 此时 *p >5成立,因此函数返回
所以最后的输出是:123456,运行截图如下:
题2
下面的代码输出是什么
int main()
{
int a[5]={1,2,3,4,5};
int *p=(int *)(&a+1);
printf("%d",*(p-1));
system("pause");
return 0;
}
这个题目看起来就不如第一题那么容易了。在没有运行代码之前,我是这么理解的:&a就是数组的首地址,也就是a[0]的地址,那么&a+1应该就是a[1]的地址了,也就是说p指向a[1],最后输出*(p-1),那么应该输出的是a[0],结果应该是1。竟然已经分析到这了,那我们先看看结果吧:
突然有种被打脸了的感觉。。。这和我预期的不太一样啊。为什么会这样呢?
为了更好的理解这个结果,我们将上述代码进行拆分,并把中间结果也输出来观察
首先,输出&a+1和&a以及&a[0],它们是内存中的三个地址,分别如下:
从输出中可以看出,&a和&a[0]是同一个地址,而&a+1是&a上进行了偏移。为了计算这个偏移量,我手动用计算器计算了两个地址之间的偏移量,得出的结果是14。到这里的时候我直接懵了,14是个什么鬼?从哪里冒出来的?为啥不是4?就算不是4,那也应该是4的倍数的吧?这个14真的是让我困扰了好久。一开始我以为是自己在计算器上输入错了,但是测试了几遍,都是这个结果。然后我真的好懵。。师兄过来说,你用的是十六进制啊。。。那十六进制的14不就是20么,20?不就刚好是数组的大小么?这个时候似乎有些豁然开朗了。。
事实上,尽管&a和&a[0]是相同的,但本质上是有所区别的,a是一个数组起始地址,而a[0]只是数据第一个元素的地址,&a+1是在&a的基础上偏移了“一个单位”,而这“一个单位”,是a指向的内容占的空间,也就是整个数组的空间,因此这个时候&a+1并不是指向a[1], 而是下图中紫色的位置。因此很容易看出*(p-1) = 5.
而&a[0]+1是在a[0]的基础上偏移“一个单位”,此时的“一个单位”是以a[0]为参考的,因此是4个字节。
我们可以再添加几句打印语句:
cout<<(&a+1)-&a<<endl;
cout<<(int *)(&a+1)-(int *)&a<<endl;
cout<<(char *)(&a+1)-(char *)&a<<endl;
看到上图中的结果了吗?对于第一句,a是一个单位,因此&a+1只比&a多了1
而第二句中,我们把这个单位转换为int *了,a数据里面有几个整型数据呢?当然是5啦
对于第三句,把单位转换成char *,那么一个包含5个整型的数组的大小自然是20了,这时的单位就是字节了
把数组类型改成double也是一样的道理,只是最后一句话打印的结果是40。换成char数组也是相似的,只是要注意char数组中,默认最后一个字符是\0。
总结
又是关于指针的,看来是该找个时间总结一下指针的一些常见用法了,今天先到这里了,滚去看论文了。。