指针和数组笔试题解析

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

上面的结果分别是多少?
首先,我们先讲一下知识点:
数组名是数组首元素的地址
这里有2个例外:
1.sizeof(数组名),这里的数组名是表示整个数组的,计算的是整个数组的大小,单位是字节。
2.&数组名,这里的数组名也表示整个数组,取出的是数组的地址。
除上面2中特殊情况外,所有的数组名都是数组首元素的地址

第一个,数组名a单独放在sizeof内部,计算的整个数组的大小,单位是字节,4*4 = 16。

第二个,a不是单独放在sizeof里面,所以a表示的是数组首元素的地址,a+0还是数组首元素的地址,是地址,在32位机器上是4个字节,64位上是8个字节。

第三个,a不是单独放在sizeof里面,所以a表示的是数组首元素的地址,*a就是对首元素的地址的解引用,就是首元素,大小是4个字节。

第四个,a不是单独放在sizeof里面,所以a表示的是数组首元素的地址,a+1是第二个元素的地址,是地址,大小就4/8个字节。

第五个,a[1]是数组的第二个元素,大小是4个字节。

第六个,&a 表示是数组的地址,数组的地址也是地址,地址大小就是4/8字节。

第七个,有两种解释:
1.可以理解为* 和&抵消效果,*&a相当于a,sizeof(a)是16
2.&a是数组的地址,它的类型是int( * )[4]数组指针,如果解引用,访问的就是4个int的数组,大小是16个字节

第八个,&a是数组的地址,&a+1 跳过整个数组后的地址,是地址就是4/8个字节。

第九个,&a[0]取出数组第一个元素的地址,是地址大小就是4/8个字节。

第十个,&a[0]+1就是第二个元素的地址,是地址大小就是4/8个字节。

2. 字符数组

//第一部分
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));

首先,回顾一下知识点:
sizeof只关注占用空间的大小,单位是字节
sizeof不关注类型
sizeof是操作符
strlen关注的字符串中\0,计算的是\0之前出现了多少个字符
strlen指针对字符串
strlen是库函数

第一部分,第一个,arr作为数组名单独放在sizeof内部,计算的整个数组的大小,单位是字节,6。

第一部分,第二个,arr就是首元素的地址,arr+0还是首元素的地址,地址大小就是4/8个字节。

第一部分,第三个,arr就是首元素的地址,*arr就是首元素,是一个字符,大小是一个字节,1。

第一部分,第四个,arr[1]就是数组的第二个元素,是一个字符,大小是1个字节。

第一部分,第五个,&arr取出的是数组的地址,数组的地址也是地址,地址就是4/8个字节。

第一部分,第六个,&arr取出的是数组的地址,&arr+1,跳过了整个数组,&arr+1还是地址,地址就是4/8个字节。

第一部分,第七个,&arr[0]是第一个元素的地址,&arr[0]+1就是第二个元素的地址,地址就是4/8个字节。

第一部分,第八个,arr是首元素的地址,但是arr数组中没有\0,计算的时候就不知道什么时候停止,结果是:随机值。

第一部分,第九个,arr是首元素的地址,arr+0还是首元素的地址,结果是:随机值。

第一部分,第十个,strlen需要的是一个地址,从这个地址开始向后找字符,直到\0,统计字符的个数。但是*arr是数组的首元素,也就是’a’,这是传给strlen的就是’a’的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突。

第一部分,第十一个,arr[1]是第二个元素,是’b’,和上一个一样,内存访问冲突。

第一部分,第十二个,&arr是arr数组的地址,虽然类型和strlen的参数类型有所差异,但是传参过去后,还是从第一个字符的位置向后数字符,结果还是随机值。

第一部分,第十三个,&arr + 1跳过一个数组,来到f后面的地址,结果还是随机值。

第一部分,第十四个,&arr[0] + 1是字符b的地址,后面不知道,所以结果是随机值。

//第二部分
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));

首先,存放的是字符串,所以f后面隐藏了一个’\0’。
第二部分,第一个,arr作为数组名单独放在sizeof内部,计算的整个数组的大小,单位是字节,7。

第二部分,第二个,arr就是首元素的地址,arr+0还是首元素的地址,地址大小就是4/8个字节。

第二部分,第三个,arr就是首元素的地址,*arr就是首元素,是一个字符,大小是一个字节,1。

第二部分,第四个,arr[1]就是数组的第二个元素,是一个字符,大小是1个字节。

第二部分,第五个,&arr取出的是数组的地址,数组的地址也是地址,地址就是4/8个字节。

第二部分,第六个,&arr取出的是数组的地址,&arr+1,跳过了整个数组,&arr+1还是地址,地址就是4/8个字节。

第二部分,第七个,&arr[0]是第一个元素的地址,&arr[0]+1就是第二个元素的地址,地址就是4/8个字节。

第二部分,第八个,arr是首元素的地址,从首元素a开始数到f停止,所以是6个。

第二部分,第九个,arr是首元素的地址,arr+0还是首元素地址,所以也是6个。

第二部分,第十个,*arr是数组的首元素,也就是’a’,这是传给strlen的就是’a’的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突。

第二部分,第十一个,arr[1]是第二个元素,是’b’,和上一个一样,内存访问冲突。

第二部分,第十二个,&arr是arr数组的地址,虽然类型和strlen的参数类型有所差异,但是传参过去后,还是从第一个字符的位置向后数字符,所以结果是6。

第二部分,第十三个,&arr + 1跳过一个数组,来到’\0’后面的地址,后面是什么,我们不知道,结果是随机值。

第二部分,第十四个,&arr[0] + 1是字符b的地址,b到f是5个,所以是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));

先看一下局内图:
在这里插入图片描述
第三部分,第一个,p是一个指针变量,sizeof§计算的就是指针变量的大小,就是4/8个字节。

第三部分,第二个,p是指针变量,是存放地址的,p+1也是地址,地址的大小就是4/8个字节。

第三部分,第三个,p是char* 的指针,解引用访问1个字节,sizeof(*p)是1个字节。

第三部分,第四个,p[0]->*(p+0)->*p,所以是1个字节。

第三部分,第五个,p是指针变量,&p是二级指针,也是地址,是地址就是4/8个字节。

第三部分,第六个,&p是地址, + 1后还是地址,是地址就是4 / 8字节。
&p + 1,是p的地址+1,在内存中跳过p变量后的地址。

第三部分,第七个,p[0]就是a,&p[0]就是a的地址,&p[0]+1就是b的地址,是地址就是4/8字节。

第三部分,第八个,p中存放的是’a’的地址,strlen§就是从’a’的位置向后求字符串的长度,长度是6。

第三部分,第九个,p+1是’b’的地址,从b的位置开始求字符串长度是5。

第三部分,第十个,*p也就是’a’,这是传给strlen的就是’a’的ascii码值97,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突。

第三部分,第十一个,p[0]也是’a’,strlen函数会把97作为起始地址,统计字符串,会形成内存访问冲突。

第三部分,第十二个,&p是取出指针变量p的地址,p在另一个内存空间,后面的内容不知道,所以结果是随机值。
在这里插入图片描述

第三部分,第十三个,&p+1跳过一个指针变量,指向p后面的位置,后面是什么不知道,结果是随机值。
在这里插入图片描述
第三部分,第十四个,p[0] -> *(p+0) -> *p ->‘a’ ,&p[0]就是首字符的地址,&p[0]+1就是第二个字符的地址从第2 字符的位置向后数字符串,长度是5。

3. 二维数组

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]));

第一个,数组名单独放在sizeof内部,计算的是整个数组的大小。所以是48个字节。

第二个,a[0][0]表示的是数组第一个元素,所以是4个字节。

第三个,a[0]表示第一行的数组名,a[0]作为数组名单独放在sizeof内部,计算的是第一行的大小。

第四个,a[0]作为第一行的数组名,没有&,没有单独放在sizeof内部,所以a[0]表示的就是首元素的地址,即a[0][0]的地址,a[0]+1就是第一行第二个元素的地址,是地址就是4/8个字节。

第五个,a[0]+1是第一行第二个元素的地址,解引用就是第二个元素,所以是4个字节。

第六个,a是二维数组的数组名,没有&,没有单独放在sizeof内部,a表示首元素的地址,即第一行的地址,a+1就是第二行地址。是类型为int(*)[4]的数组指针,是地址就是4/8个字节。

第七个,* (a+1)就是第二行,相当于第二行的数组名,* (a+1)->a[1],sizeof(*(a+1))计算的是第二行大小,16个字节。

第八个,&a[0]是第一行的地址,&a[0]+1是第二行的地址,是地址就是4/8个字节。

第九个,*(&a[0]+1)就相当于第二行,也就是a[1],sizeof(a[1]),大小是16个字节。

第十个,a是二维数组的数组名,没有&,没有单独放在sizeof内部,a表示首元素的地址,*a就是二维数组的首元素,也就是第一行。

第十一个,感觉a[3]越界了,但是没关系。它会推导它的类型是int [4],所以是16个字节。

4. 指针笔试题

笔试题1:

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跳过整个数组。所以&a+1的位置如下图所示:
在这里插入图片描述
a+1,a是首元素的地址,+1就是第二个元素的地址,解引用是第二个元素。所以第一个是2。
因为&a+1强制类型转换int*,所以-1跳了一个整形,所以指向第五个元素。
在这里插入图片描述
解引用访问4个字节(1个整型),所以结果是5。

笔试题2:

struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;

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

已知,结构体Test类型的变量大小是20个字节。
第一个,我们知道+1跳过一个指针类型的大小,所以p+0x1跳过一个结构体大小,一个结构体大小为20个字节。所以跳过20个字节,结果为0x100014。

第二个,我们将p强制类型转换成unsigned long类型,p就被看作为一个十进制数字了。所以+1就为0x100001。

第三个,我们将p强制类型转换成unsigned int*类型,+1跳过4个字节,所以结果是0x100004。

笔试题3:

int main()
{
    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; 
}

第一个,ptr1[-1]其实就是*(ptr1-1),所以结果就是4。

第二个,a是数组首元素的地址,强制类型转换成int,就是一个十进制数字,+1就在地址上+1,相当于加了一个字节。
我们将数组的内容用一个字节一个字节展示出来:
在这里插入图片描述
所以ptr2指向第一个元素的第二个字节:
在这里插入图片描述
然后,*ptr2访问的是4个字节,我们假设的是小端存储,拿出来时是0x02000000,按照%x打印就是2000000。

笔试题4:

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) };这是一个逗号表达式。 结果是这样的 int a[3][2] = {1,3,5};
所以二维数组里放的数字是这样的:
在这里插入图片描述
a[0]没有&,也没有单独放在sizeof内部,所以表示的是第一行第一个元素的地址,p[0]->*(p+0),就是解引用p。所以结果就是第一行第一个元素。结果为1。

笔试题5:

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

首先,我们找到a[4][2]的位置:
在这里插入图片描述
图中红色区域是a[4][2]的位置。
那么p[4][2]的位置在哪里呢?
首先,我们知道p是一个数组指针,p能够指向的数组是4个元素。所以p+1就会跳过4个int元素的数组,也就是16个字节。p[4][2]->* (*(p+4)+2)。
a是数组首元素的地址,所以p指向起点。
在这里插入图片描述
然后p+4跳过64个字节,也就是16个int,解引用会访问4个int。
在这里插入图片描述
然后 * ( * (p+4)+2)向后移动2个int。
在这里插入图片描述
所以&p[4][2] - &a[4][2]是由低地址减去高地址,按照%d打印结果为-4。
那按照%p打印呢?
将-4先转换为补码:1111 1111 1111 1111 1111 1111 1111 1100
按16进制打印结果为:FF FF FF FC

笔试题6:

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 + 1跳过一个二维数组的大小,来到10的后面,强制类型转换为int*。
ptr1-1就会跳过一个int*大小,来到10的前面,解引用访问一个int *大小,结果为10。

aa+1,aa不是单独放在sizeof内部,也没有&。所以aa代表的是首元素地址,也就是第一行地址,+1就是第二行地址,解引用就会得到第二行数组名,也就会得到第二行第一个元素的地址,强制类型转换为int*,-1就会跳过4个字节,就是第一行最后一个地址,解引用结果就为5。

笔试题7:

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

我们先把数组a和pa的内存图画一下:
在这里插入图片描述
pa指向数组的首元素。
当pa++后,指向数组的第二个元素,解引用打印就是at。

笔试题8:

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,cpp的地址就会到第二个元素:
在这里插入图片描述
解引用一次,我们就访问了c+2的地址,也就是C数组第三个元素地址,第二次解引用我们就访问了P的地址,然后打印就是POINT。

第二个,我们要知道+的优先级是最低的,所以首先算++,++cpp,cpp向后移动一位,来到第三个元素。
在这里插入图片描述
解引用访问了c+1,–后,c+1变为c。再解引用访问的就是数组c的首元素,也就是E的地址。然后+3,向后移动三位来到后面E的地址,打印就是ER。

第三个,*cpp[-2]+3-> **(cpp-2)+3
cpp-2所表示的是cp数组首元素地址,两次解引用找到了F的地址,+3就找到了S的地址,打印就是ST。
在这里插入图片描述

第四个,cpp[-1][-1]+1->* (*(cpp-1)-1)+1
cpp-1我们可以拿到cp第二个元素的地址,解引用访问了第二个元素,再-1,就会变为c+1,解引用就会拿到c数组中第二个元素,N的地址,再+1就会来到E,打印结果为EW。

小结:
++,–,会改变cpp的位置,cpp-1,cpp-2,不会改变cpp位置,我们要清楚这一点。

5. 总结:

到这里,我们讲解了不少指针的练习题,大家一定要清楚理解掌握指针的内容,才能去做这些题目,希望大家都能够把指针学好。如果大家认为我有哪些不足之处或者知识上的错误都可以告诉我,我会在之后的文章中不断改正,也请大家多多包涵。如果大家觉得这篇文章有用的话,也希望大家可以给我关注点赞,你们的支持就是对我最大的鼓励,我们下一篇文章再见。
在这里插入图片描述

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

学代码的咸鱼

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

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

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

打赏作者

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

抵扣说明:

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

余额充值