【C语言】——指针八:指针运算笔试题解析
-
- 一、题一
- 二、题二
- 三、题三
- 四、题四
- 五、题五
- 六、题六
- 七、题七
一、题一
//程序输出结果是什么
int main()
{
int a[5] = { 1,2,3,4,5 };
int* ptr = (int*)(&a + 1);
printf("%d, %d", *(a + 1), *(ptr - 1));
return 0;
}
这道题 * (a + 1)
相信大家都没问题,它表示的是数组的第二个元素。
我们来重点讲一下*(ptr - 1)
。
- 先来看
&a
。对数组名进行取地址操作, 取出的是整个数组的地址,虽然数值上与数组首元素相等,但他们是不同的类型。在这里,它类型为 。
- 再看
&a + 1
,这里取出的是整个数组的地址,+1 即跳过整个数组。最后,再强制类型转换成 * 类型
ptr - 1
此时,指针已经跳过了整个数组,因为此时的 已经是 * 类型, – 1,后退 4 个字节,即指向数组最后一个元素。
*(ptr - 1)
最后再进行解引用,取出数组最后一个元素
运行结果:
二、题二
//在x86环境下
//假设结构体大小20字节
//程序输出结果是啥
struct Test
{
int Num;
char* pcName;
short sDate;
char cha[3];
short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}
- 首先我们来理解
(定义的结构体类型)*p = (struct Test*)0x100000;
什么意思呢?即定义了一个结构体类型,创建了该类型的指针变量 。同时,将0x100000这个十六进制数强制类型转换成该结构体指针类型(整数默认 类型)后,将其赋值指针变量 。
printf("%p\n", p + 0x1);
指针类型 +1,跳过的是该指针所指向元素的类型的大小个字节。这里定义的结构体大小为 20 字节,即跳过 20 字节,因为是十六进制表示,即:0x100000 + 0x14,答案:0x100014
printf("%p\n", (unsigned long)p + 0x1);
第二个 语句,先将 强制类型转换成 类型,此时0x100000 仅仅是 一个普通的数字而已,再加 0x1,是单纯的数学上的相加。答案:0x100001
printf("%p\n", (unsigned int*)p + 0x1);
第三个 语句,先将 强制类型转换成 *类型, 大小为 4 个字节,所以 +1 跳过 4 个字节,十六进制表示。答案:0x100004
运行结果:
三、题三
int main()
{
int a[3][2] = { (0,1),(2,3),(4,5) };
int* p;
p = a[0];
printf("%d\n", p[0]);
return 0;
}
int a[3][2] = { (0,1),(2,3),(4,5) };
先来看对数组的初始化。这里有个小坑点: { (0,1 ), (2,3 ), (4,5 ) } 大括号内是三个小括号,表示的是逗号表达式,并不是三个大括号,按行初始化。逗号表达式,从左到右一次执行,整个表达式的结果是最后一个表达式的结果。
因此数组中存放的数据是:
p = a[0];
a [ 0 ] 可以看做是二维数组中,第一行数组的数组名,数组名表示的是数组首元素的地址,即 中存放的是 1 的地址
printf("%d\n", p[0]);
p [ 0 ] 相当于*(p + 0) == *p,即打印 1。
运行结果:
四、题四
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
- 理解这题,关键的是理解
&p[4][2]
和&a[4][2]
分别指向哪个元素。
- 首先来看
&a[4][2]
, -
- &
等价于 & * ( * (a + 4)+ 2),& 和 * 相互抵消,即等价于 * (a + 4)+ 2。这里 是什么呢? 是第一行这个一维数组的地址,类型为 。 + 4,指向第五行。再对其进行解引用,得到的是第五行这个数组,即第五行的数组名,即第五行数组首元素的地址,对其 +2,指向第五行第三个元素。
- &
- 接着我们来看
&p[4][2]
, -
的类型为 ,题目中将 的地址给 ,其实会报警告,因为 是 类型,类型不匹配,但程序能运行。
-
&p[4][2]
等价于* (p + 4)+ 2。首先,我们要理解 + 1 跳过几个字节。因为 的类型为 ,所以在 眼里,二维数组 是一个每行有 4 个元素的数组,+1 跳过的是 4 个整型,即 16 个字节。而 + 4 则是过了 16 个整型,即 64 个字节
* (p + 4) + 2
:对 进行解引用,再 +2,这个过程与* (a + 4)+ 2
相同,即指针移动了两个整型大小的字节,指向第三个元素。
-
可以看到,
&p[4][2]
与&a[4][2]
之间相差了 4 个元素,因为&p[4][2]
是小地址,所以 &p[4][2] – &a[4][2] = -4
-
printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
&p[4][2] – &a[4][2]的结果是 – 4,因此以%d
的形式打印没问题。那以%p
形式打印呢?%p是打印地址,地址肯定是无符号类型。- 4 的补码是11111111 11111111 11111111 11111100,转为十六进制为 FF FF FF FC。
运行结果:
五、题五
int main()
{
int aa[2][5] = { 1,2,3,4,5,6,7,8,9,10 };
int* ptr1 = (int*)(&aa + 1);
int* ptr2 = (int*)(*(aa + 1));
printf("%d %d\n", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
- 这道题的数组初始化没有坑啊,不用担心。
int* ptr1 = (int*)(&aa + 1);
& :& + 数组名,取出的是整个数组的地址,+1 跳过整个数组,再将其强制类型转换成 * 类型。
int* ptr2 = (int*)(*(aa + 1));
是数组首元素的地址,即第一行的地址,+1 跳过第一行,指向第二行。对其进行解引用,得到的是第二行的数组名,即第二行首元素的地址。其实这里强制类型转换成 *是多余的,第二行首元素的地址本来就是 *类型。
printf("%d %d", *(ptr1 - 1), *(ptr2 - 1));
此时 * 与 * 都是 *类型,- 1 后退 4 个字节,即一个整型元素。
运行结果:
六、题六
int main()
{
char* a[] = { "work","at","alibaba" };
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
char* a[ ] = { "work","at","alibaba" };
这是一个指针数组,数组中每个元素都放着指向对应字符串的首元素的地址。
char** pa = a;
这里取出数组首元素的地址,因为数组首元素原本类型为指针类型,所以这里 为二级指针。
pa++;
= + 1,指向数组第二个元素的地址。
printf("%s\n", *pa);
对 解引用,得到指向 “ ” 的地址,打印 。
运行结果:
这里我们可以把各个地址打印出来观察
int main()
{
char* a[] = { "work","at","alibaba" };
printf("%p\n", &a[0]);
printf("%p\n", &a[1]);
printf("%p\n", &a[2]);
printf("\n");
printf("%p\n", a[0]);
printf("%p\n", a[1]);
printf("%p\n", a[2]);
return 0;
}
运行结果:
七、题七
int main()
{
char* c[] = { "ENTER","NEW","POINT","FIRST" };
char** cp[] = { c + 3,c + 2,c + 1,c };
char*** cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *-- * ++cpp + 3);
printf("%s\n", *cpp[-2] + 3);
printf("%s\n", cpp[-1][-1] + 1);
return 0;
}
要理解这道题,画图是必不可少的,我们先把图画出来
printf("%s\n", **++cpp);
- 前置++ 与解引用操作符
的优先级一致,根据结合性从右往左运算 - 先是
自增,自增后指向 - 第一次解引用,找到
,第二次解引用找到 - 最终打印:POINT
printf("%s\n", *-- * ++cpp + 3);
- 首先 + 运算符优先级最小,因此 +3 最后才算
- 其他四个操作符优先级一样,根据结合性从左到右计算
- 先是
自增,自增后指向 解引用,找到 自减, + 1 自减为 ,原本指向 ,自减后指向 解引用,找到 指向的是 “ENTER” 字符串的首元素 ‘E’ ,+3 则指向 ‘E’ - 答案:ER
printf("%s\n", *cpp[-2] + 3);
- 首先来看
,它等价于 * , – 2 后再解引用(注: 本身值没变),找到 - 接着,再解引用,找到
指向 “FIRST” 字符串的首元素 ‘F’,+3 则指向 ‘S’ - 答案:ST
printf("%s\n", cpp[-1][-1] + 1);
等价于 * ( * ( – 1 ) – 1),先来看里面的 *( – 1),将其解引用,找到 - 接着,再看 *
, ,原本指向 ,– 1 后指向 。 指向 “NEW” 字符串的首元素 ‘N’,+1 则指向 ‘E’ - 答案:EW
版权声明:本文为博主作者:9毫米的幻想原创文章,版权归属原作者,如果侵权,请联系我们删除!
原文链接:https://blog.csdn.net/yusjushd/article/details/137194288