指针?数组?来几道笔试题!


这次的讲解是按照32位平台来讲解的。

指针和数组笔试题

一维数组

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

1、printf("%d\n",sizeof(a)); 因为 sizeof(数组名) 计算的是整个数组的大小。&数组名,得到的是整个数组的大小。除了这两种情况,数组名都是表示首元素的地址。所以算的是数组的大小,就是 16 。
2、printf("%d\n",sizeof(a+0)); 因为 a 表示首元素 1 的地址,所以 +1 还是表示首元素的地址,所以是 4 个字节。
3、printf("%d\n",sizeof(*a)); 因为 *a 就是对数组名解引用,得到的就是数组首元素 a[0] 也就是 1 所以是整型,是 4 。
4、printf("%d\n",sizeof(a+1)); 因为 a 是首元素的地址,所以 +1 就是第二个元素 2 的地址,是地址,所以就是 4 。
5、printf("%d\n",sizeof(a[1])); 因为 a[1] 表示第二个元素,所以是 4 。
6、printf("%d\n",sizeof(&a)); 因为是取地址,拿到的是地址,所以也是 4 。
7、printf("%d\n",sizeof(*&a)); 因为是对 a 取地址,然后解引用,拿到的就是数组 a 所以算的是数组的大小,就是 16 。
8、printf("%d\n",sizeof(&a+1)); 因为对 &数组名,拿到的是整个数组的地址,所以 +1 就是跳过整个数组的地址。
9、printf("%d\n",sizeof(&a[0])); 因为是取第一个元素的地址,所以还是地址,所以是 4 。
10、printf("%d\n",sizeof(&a[0]+1)); 因为是取第一个元素的地址,然后加 1 ,就是第二个元素的地址,因为还是地址,所以是 4 。

字符数组

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));			//6
printf("%d\n", sizeof(arr+0));			//4
printf("%d\n", sizeof(*arr));			//1
printf("%d\n", sizeof(arr[1]));			//1
printf("%d\n", sizeof(&arr));			//4
printf("%d\n", sizeof(&arr+1));			//4
printf("%d\n", sizeof(&arr[0]+1));		//4
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));			//随机值 - 6
printf("%d\n", strlen(&arr[0]+1));		//随机值 - 1

1、printf("%d\n", sizeof(arr)); 因为sizeof 算的是大小,数组有 6 个元素,每个元素是 char 类型,所以是 6 。
2、printf("%d\n", sizeof(arr+0)); 因为数组名是首元素地址,+0 还是首元素地址,所以这里是地址。所以是 4 。
3、printf("%d\n", sizeof(*arr)); 因为对数组名解引用,得到的是首元素,是 a ,是 char 类型,所以是 1 。
4、printf("%d\n", sizeof(arr[1])); 因为 arr[1] 是数组的第二个元素,所以是 b 是 1 。
5、printf("%d\n", sizeof(&arr)); 因为是 &数组名 所以是地址,所以是 4 。
6、printf("%d\n", sizeof(&arr+1)); 因为是取地址 +1 所以还是地址,是 4 。
7、printf("%d\n", sizeof(&arr[0]+1)); 因为是取 a 的地址,然后 +1 就是 b 的地址,所以还是地址,是 4 。
8、printf("%d\n", strlen(arr)); 因为数组名是首元素地址,所以是从首元素开始求,因为没有 ‘\0’ 所以一直往后找,所以是随机值。
9、printf("%d\n", strlen(arr+0)); 和上面这个一样,因为数组名是首元素地址,首元素 +0 还是首元素,所以是随机值。
10、printf("%d\n", strlen(*arr)); 因为是对数组名解引用,得到的是 a ,a 的 ACSII 码值是 97 ,所以这里是从 97 往后找,会导致程序崩溃,所以是野指针。
11、printf("%d\n", strlen(arr[1])); 和上面那个一样,因为 arr[1] 是 b ,b 的 ASCII 码值是 98 ,所以是从 98 往后找,会导致程序崩溃,所以是野指针。
12、printf("%d\n", strlen(&arr)); 因为取数组的地址,还是字符串的起始位置,所以还是随机值。
13、printf("%d\n", strlen(&arr+1)); 因为取数组名 + 1 跳过的是整个数组,所以长度少了 6 。所以是之前的随机值 - 6 。
14、printf("%d\n", strlen(&arr[0]+1)); 因为是取 arr[0] 的地址,就是 a 的地址,+1 就是 b 。所以是从 b 开始计算长度,所以是随机值 - 1。

char arr[] = "abcdef";
printf("%d\n", sizeof(arr));			//7
printf("%d\n", sizeof(arr+0));			//4
printf("%d\n", sizeof(*arr));			//1
printf("%d\n", sizeof(arr[1]));			//1
printf("%d\n", sizeof(&arr));			//4
printf("%d\n", sizeof(&arr+1));			//4
printf("%d\n", sizeof(&arr[0]+1));		//4
printf("%d\n", strlen(arr));			//6
printf("%d\n", strlen(arr+0));			//6
printf("%d\n", strlen(*arr));			//野指针
printf("%d\n", strlen(arr[1]));			//野指针
printf("%d\n", strlen(&arr));			//6
printf("%d\n", strlen(&arr+1));			//随机值
printf("%d\n", strlen(&arr[0]+1));		//5

1、printf("%d\n", sizeof(arr)); 因为是字符串,字符串的结束标志是 ‘\0’ 所以算大小的时候也要算 ‘\0’ 。所以是 7 。
2、printf("%d\n", sizeof(arr+0)); 因为sizeof(数组名) 计算的是整个数组的大小。&数组名,得到的是整个数组的大小。除了这两种情况,数组名都是表示首元素的地址。所以算的是数组的大小,就是 16 。所以这里是地址,是 4 。
3、printf("%d\n", sizeof(*arr)); 因为是 *arr 就是对数组解引用,所以拿到的是第一个元素 a 。是 char 类型,所以是 1 。
4、printf("%d\n", sizeof(arr[1])); 因为下标是 1 ,所以是 b ,是 char 类型,所以是 1 。
5、printf("%d\n", sizeof(&arr)); 因为是对数组取地址,得到的是地址,所以是 4 。
6、printf("%d\n", sizeof(&arr+1)); 因为是取数组地址 +1 ,跳过的是整个数组,所以还是地址。所以是 4 。
7、printf("%d\n", sizeof(&arr[0]+1)); 以为是取第一个元素的地址,然后+1,得到的就是第二个元素的地址,就是 b 的地址,是地址,所以是 4 。
8、printf("%d\n", strlen(arr)); 因为 strlen 是统计字符串的长度,遇到 ‘\0’ 结束,所以是 6 。
9、printf("%d\n", strlen(arr+0)); 因为数组名表示首元素,所以 +0 还是首元素,所以是从 a 开始统计,还是 6 。
10、printf("%d\n", strlen(*arr)); 因为是对数组名解引用, 拿到的是首元素的地址,也就是 a 的 ASCII 码值 97 开始找,会导致程序崩溃,所以是野指针。
11、printf("%d\n", strlen(arr[1])); 和上面这个一样,是第二个元素 b 的 ASCII 码值,所以也会导致程序崩溃,是野指针。
12、printf("%d\n", strlen(&arr)); 因为是取数组的地址,所以得到的也是首元素 a 所以还是 6 。
13、printf("%d\n", strlen(&arr+1)); 因为是对数组取地址 +1 ,所以跳过的是整个数组,然后再统计,就是随机值。
14、printf("%d\n", strlen(&arr[0]+1)); 因为是对 a 取地址 +1,所以拿到的就是 b 的地址,然后从 b 开始统计,就是 5 。

char *p = "abcdef";
printf("%d\n", sizeof(p));				//4
printf("%d\n", sizeof(p+1));			//4
printf("%d\n", sizeof(*p));				//1
printf("%d\n", sizeof(p[0]));			//1
printf("%d\n", sizeof(&p));				//4
printf("%d\n", sizeof(&p+1));			//4
printf("%d\n", sizeof(&p[0]+1));		//4
printf("%d\n", strlen(p));				//6
printf("%d\n", strlen(p+1));			//5
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));		//5

1、printf("%d\n", sizeof§); 因为 *p 是指针,指向的是常量字符串。所以 p 是指针,所以是 4 。
2、printf("%d\n", sizeof(p+1)); 因为 p 是指针变量,指针变量指向的是地址,是地址,所以是 4 。
3、printf("%d\n", sizeof(*p)); 因为对指针变量解引用拿到的是第一个元素,第一个元素是 a ,是 char 类型,所以是 1 。
4、printf("%d\n", sizeof(p[0])); 因为 p[0] 是第一个元素,[] 相当于一次解引用,所以拿到的是第一个元素,第一个元素是 a ,char 类型,所以是 4 。
5、printf("%d\n", sizeof(&p)); 因为是取地址,是地址,所以是 4 。
6、printf("%d\n", sizeof(&p+1)); 还是取地址,所以是 4 。
7、printf("%d\n", sizeof(&p[0]+1)); 因为 &p[0] 是第一个元素的地址,+1 是第二个元素的地址。是地址,所以是 4 。
8、printf("%d\n", strlen§); 因为 strlen 求的是字符串的长度,字符串的结束标志是 ‘\0’ ,strlen 统计的也是 ‘\0’ 前面的字符,所以是 6 。
9、printf("%d\n", strlen(p + 1)); 因为 p 是首元素,所以 p + 1 就是第二个元素,就是 b ,所以从 b 开始统计,就是 5 。
10、printf("%d\n", strlen(*p)); 因为 *p 是首元素,就是 a,拿到的是 97,会导致程序崩溃,是野指针。
11、printf("%d\n", strlen(p[0])); 和上面一样 [] 就是一次解引用,拿到的是 a ,就是 97 所以会导致程序崩溃,是野指针。
12、printf("%d\n", strlen(&p)); 对指针取地址,所以是随机值。
13、printf("%d\n", strlen(&p+1)); 因为对指针取地址 + 1,跳过的是指针,指向了下一个地方,因为不能确定这指针的 4 个字节里面有没有 ‘\0’ 所以是一个新的随机值。
14、printf("%d\n", strlen(&p[0]+1)); 因为是取 a 的地址,然后 + 1,跳过了 a ,指向了 b ,所以是从 b 开始数,就是 5 。

二维数组

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

1、printf("%d\n",sizeof(a)); 因为 sizeof(数组名) 计算的是整个数组的大小。&数组名,得到的是整个数组的大小。除了这两种情况,数组名都是表示首元素的地址。所以算的是数组的大小。所以是 48 。
2、printf("%d\n",sizeof(a[0][0])); 因为是第一行第一个元素,元素类型是整型,所以是 4 。
3、printf("%d\n",sizeof(a[0])); 因为是二维数组,所以 a[0] 表示第一行的元素,第一行有 4 个元素,每个元素是整型,所以是 16 。
4、printf("%d\n",sizeof(a[0]+1)); 因为 a[0] 是第一行第一个元素的地址,+1 表示 第一行第二个元素的地址,所以是 4 。
5、printf("%d\n",sizeof((a[0]+1))); 因为是拿到的是第一行的第二个元素,所以是 4 。
6、printf("%d\n",sizeof(a+1)); 因为 a 表示第一行的地址,+1 就是第二行的地址,是地址,所以是 4 。
7、printf("%d\n",sizeof(*(a+1))); 因为是对第二行解引,拿到的是第二行的元素,有 4 个元素,所以是 16 。
8、printf("%d\n",sizeof(&a[0]+1)); 因为 &a[0] 拿到的是第一行的地址,+1 就是第二行的地址。所以是地址,是 4 。
9、printf("%d\n",sizeof(
(&a[0]+1))); 因为是对第二行的地址解引用,所以拿到的就是第二行的元素。有 4 个元素,所以是 16 。
10、printf("%d\n",sizeof(*a)); 因为数组名表示第一行的地址,所以对数组名解引用,拿到的就是第一行的元素,所以是 16 。
11、printf("%d\n",sizeof(a[3])); 这个是因为 sizeof() 内部的表达式时不参与运算的,所以算的是第四行的大小,第四行是假想出来的,因为是根据类型来算的,是假设存在,所以就是 16 。就类似于 sizeof(int) 是根据类型来算的,并不会真实运算。

总结:

数组名的意义:

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表示首元素的地址。

指针笔试题

笔试题1:

程序的结果是什么

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

运行结果是 2、5 因为指针变量 ptr 是指向数组 a 后面的地址。
在这里插入图片描述
数组名 + 1,指向的是下一个元素,因为数组名表示首元素,所以解引用就拿到了第二个元素,就是 2 。因为上面图片画的那样,ptr 指向数组后面的地址,所以 ptr - 1 就是拿到了 ptr 前面的元素。也就是数组的最后一个元素,就是 5 。

笔试题2:

给定结构体的大小是 20 。假设 p 的值为0x100000。 如下表表达式的值分别为多少

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

int main()
{
 printf("%p\n", p + 0x1);					//00100014
 printf("%p\n", (unsigned long)p + 0x1);	//00100001
 printf("%p\n", (unsigned int*)p + 0x1);	//00100004
 return 0;
}

printf("%p\n", p + 0x1); 因为 p 是结构体指针,+1 就是跳过了一个结构体,结构体的大小是 20 。因为是 16 进制表示,所以是 14 。
printf("%p\n", (unsigned long)p + 0x1); 因为将 p 进行了强制类型转化,转化为了长整型,所以 +1 就是 +1,
printf("%p\n", (unsigned int*)p + 0x1); 因为强制转化为指针,指针大小是 4 个字节(32位平台),所以加的也是指针,结果就是 +4 。

笔试题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);	//4 2000000
    return 0;
}

这里涉及到大小端的问题,因为我的编译器是小端存储,就是低地址在高位,高地址在低位。1 的二进制位是 00 00 00 01 因为是小端存储,所以内存当中就是按照 01 00 00 00 存放的。
结果是 4 2000000 因为 ptr[-1] 就是 ptr 指向的位置往前移一个,所以就指向了 4 。ptr2 是先将 a 转化为整型,然后再 +1 然后再转化为地址。转化为地址之后,因为多加了 1 ,所以就是差了一个字节,所以 ptr2 的指向就是 01 00 00 00 的 00 。
在这里插入图片描述
因为内存当中这 4 个元素是这样存放的 01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 。就是这样:
在这里插入图片描述
因为 *ptr2 是四个字节,所以就是从 ptr2 的指向往后数 4 个字节,就是 00 00 00 02 。因为是小端存储,所以实际的值就是 02 00 00 00 因为 2 前面的 0 不打印,所以打印出来就是 2000000 。

笔试题4:

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

答案是 1 。因为是三行两列的数组,数组初始化的时候是逗号表达式,逗号表达式的结果是最后一个逗号的结果。所以数组当中存放的就是 1,3,5,0,0,0 。这 6 个元素。就是这样:{{1,3},{5,0},{0,0}} 。所以 p[0] 就是第一行的第一个元素,就是 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]);
    //FFFFFFFC   -4
    return 0;
}

因为二维数组 a 是由五行五列组成的。而 p 是一个指针数组,指向每行 4 个元素的数组。所以 p[4][2] 和 a[4][2] 的关系就是这样。
在这里插入图片描述
二者的关系就是这样,第一个打印的是地址,第二个打印的是相差的元素个数。因为指针 - 指针,得到的是两个指针之间的元素个数。所以是 - 4 。而地址是越来越大的,所以相差的是 -4 ,因为打印地址,是没有负数的,所以是直接把 -4 的补码按照16进制打印了。原码按位取反得到反码,反码 +1 得到补码。
在这里插入图片描述
每 4 个 1 是一个 F ,最后 1100 是 12 ,就是 C 。所以结果就是 FFFFFFFC 。

笔试题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));	//10 5
    return 0;
}

因为第一个是对数组名取地址,然后 +1 跳过的是整个数组,打印的时候先 -1 然后再解引用,就指向了10,第二个是数组名 +1 ,数组名是第一行第一个元素的地址,先 +1 后解引用,就是拿到了第二行第一个元素。然后再 -1 ,就拿到了 5 。因为二维数组在内存当中也是连续存储的。

笔试题7:

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

因为这是三个字符串,三个元素,每个元素都是 char* 。第一个元素是 work 第二个是 at 第三个是 alibaba 都是指向第一个字母。 pa 是二级指针,说明指向了第一个字符串,当 pa++ 的时候,就指向了第二个字符串,也就是 at ,解引用拿到了 a 的地址。所以打印出来就是 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);			//POINT
 printf("%s\n", *--*++cpp+3);		//ER
 printf("%s\n", *cpp[-2]+3);		//ST
 printf("%s\n", cpp[-1][-1]+1);		//EW
 return 0;
}

先画出内存分布
在这里插入图片描述
printf("%s\n", **++cpp); 因为cpp 存的是第一个元素的地址,就是指向 c + 3 然后 cpp 前置 ++ 。cpp 就指向了 c + 2 。然后先一次解引用,就找到 c + 2 了。然后再解一次引用,就拿到了 POINT 的 P 的地址,然后打印就是 POINT 。
在这里插入图片描述

printf("%s\n", ++cpp+3); 因为上次 ++ 了,所以现在的 cpp 就指向了 c + 2 。cpp 又一次前置 ++ 所以 cpp 就指向了 c + 1 。然后解引用,就拿到了 c + 1 然后再 – 就指向了 c 因为 c + 1 然后 - 1 就是 c 。就指向了 ENTER 的 E ,然后再 +3 就指向了 ER 的 E 。然后再解引用,就从 E 开始打印,就输出了 ER 。
在这里插入图片描述
printf("%s\n", *cpp[-2]+3); 因为是先 -2 然后在解引用,然后再 +3 。此时的 cpp 指向 c + 1 。所以 -2 之后就指向了 c + 3 。就是指向了 FIRST 的 F ,然后再 + 3 就指向了 S 然后再解引用,就输出了 ST 。[] 就相当于一次解引用。
在这里插入图片描述
printf("%s\n", cpp[-1][-1]+1); 因为先 [-1] 所以 cpp 就指向了 c + 2 。然后再 -1 就指向了 c + 1 ,就指向了 NEW 的 N ,然后再 +1 就指向了 E 的地址,然后再打印,就输出了 EW 。

  • 10
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Lockey-s

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

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

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

打赏作者

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

抵扣说明:

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

余额充值