C语言指针水平检测题,答对大部分证明对指针就有一定的了解了!!!


注:以下分析均在32位环境下运行

1.小题

第一题:

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(a+0));
printf("%d\n",sizeof(*a));
printf("%d\n",sizeof(a+1));
printf("%d\n",sizeof(a[1]));
printf("%d\n",sizeof(&a));
printf("%d\n",sizeof(*&a));
printf("%d\n",sizeof(&a+1));
printf("%d\n",sizeof(&a[0]));
printf("%d\n",sizeof(&a[0]+1));

答案:
在这里插入图片描述
分析:

1.a是数组名,表示首元素地址。但在sizeof内部单独出现(不单独出现仍然表示首元素地址,这里的单独出现指的是不进行运算) 的时候表示整个数组,因此整个数组大小是16.

2.a没有单独出现在sizeof内部,因此(a+0)此时代表首元素地址。因此答案是4

3.a是首元素地址,*a是第一个元素1,1是整型,因此大小是4

4.a没有单独出现在sizeof内部,此时表示首元素地址。a+1表示跳过了一个整型,此时a+1是第二个元素的地址,因此大小是4.

5.a[1]等价于*(a+1),相当于在例(4)的基础上解引用,代表第二个元素,大小是4.

6.&a代表整个数组的地址(区分开首元素地址),&a的类型是数组指针,是int(*)[4]类型的。是指针的话大小也是4

对数组指针不了解的看:
https://blog.csdn.net/m0_51641706/article/details/119968733

7.解引用和取地址符号抵消了,剩下a,单独出现在sizeof内部。表示整个数组,因此大小是16

8.&a表示整个数组的地址,由于类型是int(*)[4],对&a+1则表示跳过整个数组。越界了,虽然越界,但sizeof里并不会真的去访问这段空间,它只看sizeof里面的操作数是什么类型的。此时仍然是数组指针。因此大小仍然是4.

对于sizeof不会访问对应空间可以看这两个例子

sizeof(int) ---------4
sizeof(i++) ------------i并不会加1

9.代表首元素地址,大小是4

10.代表第二个元素地址,大小是4

第二题

	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));


	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr+0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr+1));
	printf("%d\n", strlen(&arr[0]+1));

答案:

仔细看这里并不是全部的答案,10和11两条语句是会使程序崩溃的。(运行时把它注释了)
在这里插入图片描述

分析:
1.arr单独出现在sizeof内表示整个数组,大小是6

2.arr没有单独出现在sizeof内部,arr表示的是首元素地址,大小是4

3.*arr表示第一个元素字符a,大小是1

4.和3同理

5.&arr表示整个数组的地址,是一个数组指针,大小是4

6.&arr+1表示一个数组指针,大小是4.(和第一题的(8)同理)

7.&arr[0]表示第一个元素地址,加一表示第二个元素地址。大小是4.

从第八题开始是strlen,别看错了。

8.strlen的原理是找NUL,由于arr里面没有,因此它会一直往数组后越界访问,直到它找到NUL. 因此长度是随机值

9.和8同理,也是随机值

10.第十题是有点让人困惑的。表达式的意思是strlen(‘a’),这是什么意思呢?计算机认为这是把a的ASCII码值当作地址传入strlen了。由于操作系统不允许访问地址为97的空间,因此程序会崩溃。

11.和10同理,相当于访问地址为98的空间,这是不允许的。程序会崩溃

12.&arr是整个数组的地址,类型是char(*)[6],在传入strlen时会被强制转换为char*类型。&arr的地址和arr是一样的,因此答案应该和第8小题一样,也是随机值。

13.&arr+1是跳过整个数组,然而它仍是一个地址,strlen会从那个地址继续往后找NUL,长度仍是随机值。有趣的是,它比第12小题的随机值少6。

14.相当于从第二个元素开始出发找NUL,长度仍然是随机值。有趣的是它比第12小题的随机值少1.

第三题

	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));


	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));

答案:有两条语句会导致程序崩溃哦。这是注释后的运行结果
在这里插入图片描述**分析:**1.大小是7,别忘了这种初始化会自动在末尾加上NUL

2.arr没有单独出现在sizeof内部,因此a这里是首元素的地址,大小是4

3.对arr解引用是字符a,因此大小是1

4.和小题3同理,大小是1

5.&arr是整个数组的地址,类型是char(*)[7],是地址大小就是4

6.和5同理,大小是4

7.是第二个元素的地址,大小是4

下面的是strlen了

8.终于到常规的strlen的使用了,长度是6

9.和小题8同理,长度是6

10.*a是第一个字符’a’,相当于strlen(97),程序会崩溃

11.和第10小题同理

12.&arr的地址和arr相同,虽然类型是数组指针但在传入strlen的时候,会强制转换成char*。长度也是6

13.&arr+1相当于跳过一整个数组,因此NUL的位置就是未知的了。长度是随机值

14.相当于第二个元素的地址,因此长度是5

第四题

	char* p = "abcdef";
	printf("%d\n", sizeof(p));
	printf("%d\n", sizeof(p + 1));
	printf("%d\n", sizeof(*p));
	printf("%d\n", sizeof(p[0]));
	printf("%d\n", sizeof(&p));
	printf("%d\n", sizeof(&p + 1));
	printf("%d\n", sizeof(&p[0] + 1));


	printf("%d\n", strlen(p));
	printf("%d\n", strlen(p + 1));
	printf("%d\n", strlen(*p));
	printf("%d\n", strlen(p[0]));
	printf("%d\n", strlen(&p));
	printf("%d\n", strlen(&p + 1));
	printf("%d\n", strlen(&p[0] + 1));

答案:
在这里插入图片描述

分析:
1.注意看,这里p是字符指针了。因此大小是4

2.p+1是第二个字符的地址,大小是4

3.*p是一个字符,大小是1

4.p[0]是一个字符,大小是1

5.&p的类型是char**,仍然是指针,大小是4

6.&p+1的类型仍然是char**,虽然越界了然无所谓,sizeof不会真的访问那段空间,大小是4

7.第二个字符地址,大小是4

下面是strlen了

8.长度是6,这个超简单

9.长度是5,这个也简单对吧

10.相当于strlen(97),老套路了。不解释了。

11.和小题10同理

12这道题有点不同,&p的类型是char**,它里面存的是p的地址,可它自己的地址可是未知的。因此长度是随机值。

13.char**+1的地址也是未知的,长度是随机值

14.长度是5

有没有发现,只要原理懂了其实套路都是一样的!
但接下来这道题可能就稍微难一点了。

第四题

	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a[0][0]));
	printf("%d\n", sizeof(a[0]));
	printf("%d\n", sizeof(a[0] + 1));
	printf("%d\n", sizeof(*(a[0] + 1)));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(*(a + 1)));
	printf("%d\n", sizeof(&a[0] + 1));
	printf("%d\n", sizeof(*(&a[0] + 1)));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a[3]));

答案:
在这里插入图片描述

分析:
1.a是二维数组的数组名,单独出现在sizeof内部,代表的是整个数组。因此大小是12×4等于48

2.a[0][0]是第一个元素,大小是4

3.a[0]是第一行的数组名。它单独出现在sizeof内部,因此代表整个第一行数组,大小是4×4等于16

注:为什么a[0]是第一行数组名?

我们要知道数组名本身的意义是首元素的地址。a[0]等价于*(a+0)
相当于对二维数组数组名a解引用了,而二维数组数组名代表的含义是
第一行数组的地址,因此解引用后代表的是第一行第一列元素的地址,这
正是一维数组数组名的含义。

由此类推a[1],a[2],a[3]...都是二维数组里面的一维数组名。
分别是第二行,第三行的,...

4.a[0]是第一行数组名,但它没有单独出现在sizeof内,因此它代表的是第一行第一列的元素的地址,加1后变成第一行第二列的元素的地址。因此大小是4

5.在小题4的基础上解引用得到了第一行第二列的元素,因此大小是4

6.a是二维数组数组名,但没有单独出现在sizeof内部,因此a代表第一行数组的地址,+1得到第二行数组的地址。类型是int(*)[4]的数组指针。大小是4

7.在小题6的基础上解引用得到了第二行首元素的地址,即第二行的数组名。它单独出现在sizeof内部。因此大小是16

8.a[0]是第一行首元素地址,&a[0]是第一行地址,&a[0]+1是第二行数组地址,类型是数组指针,大小是4

9.在8的基础上解引用,得到了第二行数组的首元素地址,它是第二行数组的数组名,因此大小是16、

10.a是第一行数组的地址,*a是第一行首元素地址,它单独出现在sizeof内部,因此大小是16.

11.a[3]仍然是第四行数组名,虽然它越界了。但我们知道sizeof并不会去访问这段空间,它只在乎里面的操作数的类型是什么。因此大小是16

2.大题(最后一题最难)

第一题

int a[5] = { 1, 2, 3, 4, 5 };
int* ptr = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));

答案:2,5

分析:
由图就很容易看出来了,不难
在这里插入图片描述
第二题

//已知该结构体大小为20字节
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;

int main()
{
	p = 0x100000;
 	printf("%x\n", p + 0x1);
 	printf("%x\n", (unsigned long)p + 0x1);
 	printf("%x\n", (unsigned int*)p + 0x1);
 	return 0;
}

答案:
在这里插入图片描述

分析:
1.p是一个结构体指针,对它加一即跳过一个对应结构体大小的空间。因此p+0x1等于100014(这是16进制的表达方式,其实就是加了20个字节)

2.把p强制转换成unsigned long类型,由于这不是指针了,就变成普通的加法,答案是100001

3.强制转换成int*,加一跳过四个字节。因此为100004

第三题

int a[4] = { 1, 2, 3, 4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x,%x", ptr1[-1], *ptr2);
return 0;

答案:
4
2000000

分析:
1.下标是负数其实是访问该地址前面的元素。
ptr1相当于跳过了一个数组,访问ptr[-1]相等于*(ptr-1),答案就是4.

2.ptr2有趣很多。它先把a强制转换成int,再加1,相当于对地址加1。1在内存中以大端形式存储。此时ptr2指向的值如图:
在这里插入图片描述
由于ptr2是int*类型,因此解引用的作用范围是4个字节,*ptr2访问的内容如下:
在这里插入图片描述

从内存中拿出来后变成02 00 00 00

注:这里写的都是16进制

第四题

int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int* p;
    p = a[0];
    printf("%d", p[0]);
    return 0;
}

**分析:**这道题有坑。二维数组的初识化是用大括号的。形式如下:

int a[3][2] = { {0,1},{2,3},{4,5} };

而这里用的是逗号表达式。实际上这个二维数组被初始化成了如下的样子:
在这里插入图片描述
根据这个图,答案也很明显了。就是输出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个元素。因此p+1是跳过4个元素。
而a是二维数组数组名,加1跳过5个元素。
在这里插入图片描述

p[4][2] 等价于*(*(p+4)+2),实际上计算机就是这么算的.

因此我们可以知道它们两对应的位置在哪。如图:
在这里插入图片描述
由于指针减指针得到的是两个指针之间的元素个数。(注:不是它们两相差的字节数)因此答案是4。

多提一句:对int类型的数组指针解引用后可以理解成类型int*。原先对数组指针加1是跳过一个数组,解引用后加1就是跳过一个整型

第六题:

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", *(ptr1 - 1), *(ptr2 - 1));
    return 0;
}

分析:
这道题挺常规的。
aa是二维数组名,类型是int(*)[5],&aa的类型是int(*)[2][5],对它加1代表跳过一整个二维数组。由于此时的类型仍然是int(*)[2][5],需要强制转换成int*才能赋值给ptr1.

aa+1再解引用的类型本来就是int*了,这里的强制转换其实没必要。

答案是5,10

第七题:

int main()
{
	 char *a[] = {"work","at","alibaba"};
	 char**pa = a;
	 pa++;
	 printf("%s\n", *pa);
	 return 0;
}

分析:
字符指针数组里每一个元素并不是字符串(C语言也没有字符串),而是每个字符串的第一个字符的地址
在这里插入图片描述
pa++,代表了pa现在指向a的下一个字节的地址。
因此解引用之后得到的是at字符串的首字符地址。所以打印出来是at。

第八题:(压轴题)

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;
}

分析:
画图即可解决。
初始状态:
在这里插入图片描述
第一条式子

cpp++指向c+2,而c+2指向POINT,因此第一个打印出来的是POINT

第二条式子(易错)

cpp++指向c+1(由于第一条式子cpp已经自增1了)
解引用后得到c+1.c+1再减减得到c(重点,易错点)
注:这里不是让c+1变成c+2。而是让c+1变成c
c指向的是ENTER。
此时再解引用得到ENTER的首字符地址。
再加3得到ER的首字符地址。因此输出ER。

第三条式子

CPP此时指向的是cp的第三个元素
cpp[-2]等效于*(cpp-2),得到了c+3。
解引用得到了FIRST的首字符地址
再加3,得到了ST

第四条式子

cpp[-1][-1]+1等效于*(*(cpp-1)-1)+1
cpp-1指向c+2的地址,解引用得到c+2.
c+2减1得到c+1再解引用得到NEW的首字符地址
NEW再加1得到EW的首字符地址
因此输出EW

在这里插入图片描述

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值