指针与数组经典面试题

在前面讲解完毕指针相关内容后,下面便开始进行答题模拟


再进行做题以前,我建议自己先行快速浏览一下指针内容:

指针基础

指针进阶




数组笔试题讲解

一维数组

整型数组

请问下面的答案依次是什么???

int a[] = {1,2,3,4};
printf("%d\n",sizeof(a));//      1
printf("%d\n",sizeof(a+0));//    2
printf("%d\n",sizeof(*a));//     3
printf("%d\n",sizeof(a+1));//    4
printf("%d\n",sizeof(a[1]));//   5
printf("%d\n",sizeof(&a));//     6
printf("%d\n",sizeof(*&a));//    7
printf("%d\n",sizeof(&a+1));//   8
printf("%d\n",sizeof(&a[0]));//  9
printf("%d\n",sizeof(&a[0]+1));//10

/*
1. 16
2. 4   or  8
3. 4
4. 4   or  8
5. 4
6. 4   or  8
7. 16
8. 4   or  8
9. 4   or  8
10.4   or  8
*/

解析:

  1. 当只有数组名和sizeof结合时候,表示求整个数组大小. 所以结果是 4 * 4 = 16
  2. a+0是一个表达式,sizeof(a+0)就认为a是地址,0是地址,地址加地址还是地址,地址(指针)的大小只能是4或者8
  3. *a是数组a第一个元素,是一个整型,整型的大小是4个字节,所以结果是 4
  4. a+1和第二个一样,测的地址,地址(指针)大小只能是4或者8
  5. a[1]是一个整数,同理,整型大小是4个字节
  6. &a是一个地址,同理只能是4或者8
  7. *&a中*与&是互相抵消的,所以*&a等于a,而只有数组名和sizeof结合时,就代表求整个数组大小,所以是4 * 4=16
  8. 9.10都是地址相加减,所以只能是4或者8
字符数组
sizeof求字符大括号数组

下面会出现三组题,有一定的坑洞;

char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", sizeof(arr));// 1
printf("%d\n", sizeof(arr+0));// 2
printf("%d\n", sizeof(*arr));// 3
printf("%d\n", sizeof(arr[1]));// 4
printf("%d\n", sizeof(&arr));// 5
printf("%d\n", sizeof(&arr+1));// 6
printf("%d\n", sizeof(&arr[0]+1));// 7
/*
答案:
1. 6
2. 4 or 8
3. 1
4. 1
5. 4 or 8
6. 4 or 8
7. 4 or 8
*/

解析:

  1. 当数组名与sizeof结合时候,表示求整个数组大小,所以arr数组的大小是6;

  2. sizeof中不是单独的数组名,所以arr+0这时候表示arr是一个地址,arr+0还是等于arr,一个地址的大小就是4 or 8

  3. *arr表示首元素,而首元素是字符'a',一个字符的大小是一个字节,所以答案是1;

  4. arr[1]代表的是第二个元素,第二个元素是'b',所以一个字符的大小就是一个字节,所以答案是1;

    1. 题目中 &arr表示的是数组地址,所以后面无论怎么加减都是地址,地址的大小是4 or 8;
  5. &arr[0] + 1还是一个地址,只是第二个元素的地址,而地址的大小是 4 or ;

strlen求字符数组标准写法
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n", strlen(arr));// 1
printf("%d\n", strlen(arr+0));// 2
printf("%d\n", strlen(*arr));// 3
printf("%d\n", strlen(arr[1]));// 4
printf("%d\n", strlen(&arr));// 5
printf("%d\n", strlen(&arr+1));// 6
printf("%d\n", strlen(&arr[0]+1));// 7

/*
答案:
1. 随机值
2. 随机值
3. 报错
4. 报错
5. 随机值
6. 在第一题的随机值上减6
7. 在第一题的随机值上减1
*/

解析:

注意: 这里是strlen,不再是sizeof,strlen接收的参数是地址,并从该地址后面进行查找\0,直到找到为止

  1. arr是首元素地址,所以strlen变开始从a进行往后找寻\0,但是arr数组是没有的\0的,所以他会越过数组界限,直到找到为止,

因此说最后返回的是一个随机值.

  1. arr+0还是arr,因此照样从a开始往后面进行查询,与第一天一样,还是一个随机值

  2. 4.题都是传送的一个字符,即传送了一个整数(字符本质还是整数),但是要求传送地址,所以报错

  3. &arr是数组地址,但是&arr的值和 arr一模一样,所以还是会返回一个随机值.

  4. &arr+1,表示跨越整个数组,最后指向数组末尾,于是从数组末尾开始向后查找\0的值,因此返回的随机值是相比于第一题减去6

  5. &arr[0]+1首元素地址加一,代表第二个元素地址,因此从此开始向后查询\0,因此最后返回的随机值相比第一题减去1;

下面是除去3.4.两个报错题其他题的结果:完全与解析温吻合

sizeof求取标准写法数组
char arr[] = "abcdef";
printf("%d\n", sizeof(arr));// 1
printf("%d\n", sizeof(arr+0));// 2
printf("%d\n", sizeof(*arr));// 3
printf("%d\n", sizeof(arr[1]));// 4 
printf("%d\n", sizeof(&arr));// 5
printf("%d\n", sizeof(&arr+1));// 6
printf("%d\n", sizeof(&arr[0]+1));// 7
/*
答案:
1. 7
2. 4 or 8
3. 1
4. 1
5. 4 or 8
6. 4 or 8
7. 4 or 8
*/

解析:

  1. 当只有数组名与sizeof结合时候,表示求得是整个数组大小,所以整个数组大小是 7(包括\0)

  2. arr+0不是单独的数组名与sizeof结合在一起.所以arr是一个地址,地址的大小是 4 or 8

  3. 4.题目都是一个确定的字符,一个字符的大小是 1直字节,所以答案是 1字节

  4. 6.7.都是求取的地址,地址的大小是 4 or 8;

strlen求取标准字符串
char arr[] = "abcdef";
printf("%d\n", strlen(arr));//1
printf("%d\n", strlen(arr+0));//2
printf("%d\n", strlen(*arr));//3
printf("%d\n", strlen(arr[1]));//4
printf("%d\n", strlen(&arr));//5
printf("%d\n", strlen(&arr+1));//6
printf("%d\n", strlen(&arr[0]+1));//7

/*
答案:
1. 6
2. 6
3. 报错
4. 报错
5. 6
6. 随机值
7. 5
*/

解析: strlen是接收地址并且往后查找\0,然后停止

  1. arr是首元素地址,往后查找,可以找到\0,所以是 6

  2. arr+0还是首元素地址,往后查找,可以找到\0,所以还是 6

  3. 4.之前我们讲过的类似,他们传的是整型(字符本质),不是地址,所以报错

  4. &arr是数组地址,但是&arrarr的值一样,都是从第一个元素开始查找,所以答案还是6

  5. &arr[0] + 1是第二个元素的地址,所以从他开始往后查找们就会少一个首元素,所以值 是 6-1 = 5

下面还是除去报错的3.4.题之外的结果:

image-20210421235853389

常量字符指针

注意这里的区别,很多人会容易搞错

char *p = "abcdef";   //这种写法代表"abcdef"是常量字符串,只给p存放了一个a的地址,且*p路不可修改
printf("%d\n", sizeof(p));// 1
printf("%d\n", sizeof(p+1));// 2
printf("%d\n", sizeof(*p));// 3
printf("%d\n", sizeof(p[0]));// 4
printf("%d\n", sizeof(&p));// 5
printf("%d\n", sizeof(&p+1));// 6
printf("%d\n", sizeof(&p[0]+1));// 7
/*
答案:
1. 4 or 8
2. 4 or 8
3. 1
4. 1
5. 4 or 8
6. 4 or 8
7. 4 or 8
*/

解析:

p是指针,即字符a的地址,所以在上面中除了 3 4小题之外,其余的所有表达式都是求取指针(地址)大小,所以指针大小只能是4 或者 8

而2 3小题中 *p 等于a,而p[0]等于*(p + 0)也是字符a (不明白这个关系的请看这里数组名与地址关系) 所以3 4小题都是 1

关于strlen求常量字符串长度
char* p = "abcdef";   //这种写法代表"abcdef"是常量字符串,只给p存放了一个a的地址;
printf("%d\n", strlen(p)); //1
printf("%d\n", strlen(p + 1));//2
printf("%d\n", strlen(*p));//3
printf("%d\n", strlen(p[0]));//4
printf("%d\n", strlen(&p));//5
printf("%d\n", strlen(&p + 1));//6
printf("%d\n", strlen(&p[0] + 1));//7
/*
答案:
1. 6
2. 5
3. error
4. error
5. 随机值
6. 随机值
7. 随机值
*/

解析:

  1. p是a的地址,所以向a的地址后面查找\0,所以 答案是 6

  2. p+1是地址向后移动一位,然后从该地址处向后找\0,所以答案是 5

  3. 4…这两个题和之前的一样,strlen要求接收地址,但是*pp[0]都是确切的数值,所以报错

    5.6.7.题都是在 p的地址上操作,而p后面的地址所指向元素是否为0,不可知,所以都是随机值.

二维数组

这里要求对数组名关系掌握准确,可以看这篇文章数组名与地址

并且一个二维数组是多个一维数组串联在一起的.

int a[3][4] = {0};
printf("%d\n",sizeof(a)); // 1
printf("%d\n",sizeof(a[0][0]));// 2
printf("%d\n",sizeof(a[0]));// 3
printf("%d\n",sizeof(a[0]+1));// 4
printf("%d\n",sizeof(*(a[0]+1)));// 5
printf("%d\n",sizeof(a+1));// 6
printf("%d\n",sizeof(*(a+1)));// 7
printf("%d\n",sizeof(&a[0]+1));// 8
printf("%d\n",sizeof(*(&a[0]+1)));// 9
printf("%d\n",sizeof(*a));// 10

/*
答案:
1. 48
2. 4
3. 16
4. 4 or 8
5. 4
6. 4 or 8
7. 16
8. 4 or 8
9. 16
10.16
*/

解析:

  1. a是数组名,单独与sizeof在一起表示求整个数组大小,所以大小是 3*4*4=48
  2. a[0][0]是一个确切的数字,即第一行第一列的数字,整型的大小是4个字节
  3. a[0]代表的是第一行数组的数组名,而数组名单独与sizeof在一起表示求的是整个数组的大小,所以大小是4 * 4 = 16
  4. a[0]是第一行数组名,即地址,地址加1还是地址,所以地址的大小是 4 or 8
  5. a[0] + 1代表第一行第二列的元素地址,前面有*,所以是求第一行第二列元素的大小,所以是 4个字节
  6. a是地址,a+1还是地址,地址的大小是 4 or 8
  7. a是首元素地址,即第一行的数组地址,a+1代表是第二行的数组地址,前面有个*,所以*(a+1)是第二行的数组名,数组名单独与sizeof在一起表示求的是整个数组大小,所以第二行的数组大小是 4 * 4 = 16
  8. &a[0]是地址,地址加一还是地址,地址的大小是 4 or 8
  9. &a[0]+1代表第二行的数组地址,前面有个*,则变成了第二行的数组名 ,数组名单独与sizeof结合在一起,表示整个数组大小,所以第二行的数组大小是4 * 4 = 16
  10. a是第一行数组地址,前面有个*,表示第一行数组的数组名,数组名单独与sizeof结合在一起.所以大小是4 * 4 = 16

下面是32位与64位机器下的运行结果:

image-20210423002226777

总结 :数组名的意义:

  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));
    return 0; 
}
/*
结果:
2 5
*/

解析:

&a是数组地址,&a+1是指针指向数组a末尾,前面有一个强制性整型指针转换,此时的ptr则指向数组末尾,且是一个整型指针

所以ptr-1就是5的地址,所以解引用就是 5

a是数组第一个元素地址,加1,就是第二个元素地址,解引用就是 2

下面是图解

image-20210423095112288

笔试题2

//由于还没学习结构体,这里告知结构体的大小是20个字节
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
struct Test
{
 int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p;

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

/*
答案:
0x00100014
0x00100001
0x00100004
*/

解析:

p是结构体指针,可以跨越20个字节,所以p+1就是地址跨越20个,所以在0x00100000上增加0x00000014(16进制的20),

所以答案是 0x00100014

p是结构体指针,但是前面有强制性无符号整型类型转化,所以把p变成了一个长整型,即一个**数字,**那么数字加一,就是在0x00100000上加0x00000001,所以结果是 0x00100001

p是结构体指针,但是前面有强制性无符号整型指针类型转化,所以p是一个整型指针,所以整型指针加1,地址跳跃4个字节,所以在原来的基础上加0x00000004;, 所以结果是 0x00100004

笔试题3

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

/*答案:
0x00000005
0x02000000
*/

解析:

&a+1是数组地址加一,则指向了数组末尾,然后前面有强制整型指针类型转换,所以此时ptr1是整型指针,且指向数组的末尾

又因为ptr1[-1]等于*(ptr1 -1 ),代表指针位置向前移动一位,即指向4,所以答案是 0x00000004

a是数组的首元素1的地址,前面又强制类型转换为整数,然后在整数的基础上加一,最后再换成整型指针,我们以图说明

image-20210423130227279

*可以看到经过小端存储以后,随着地址的变化,就可以知道值,第二问中 (int )((int)a + 1)指向了00解引用即向后访问4个字节

即得到 00 00 00 02,由于这是小端存储,所以真正的数字是 02 00 00 00

笔试题4

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    int *p;
    p = a[0];
    printf( "%d", p[0]);
 return 0;
}
/*
答案:
1
*/

解析:

(0,1)这种是逗号表达式,所以真正存进数组a的是1 ,3 ,5

由于这是一个二维数组,所以a[0]是第一行的数组名,即第一行第一列的元素的地址.

所以p[0]等价于*(a[0] + 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]);
    return 0; 
}
10000000 00000000 00000000 00000100
11111111 11111111 11111111 11111011
1111 1111 1111 1111 1111 1111 1111 1100
FFFFFFFC
/*
答案:
FFFFFFFC,-4
*/

image-20210423140640426

解析:

由图中所画可以知道 &p[4][2] - &a[4][2]的结果是-4(地址相减代表中间元素个数)

-4的补码是 11111111 11111111 11111111 11111100

因此上式化成%p形式是 FFFFFFFC

​ 化成%d形式是-4

笔试题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; 
}

/*
答案:
10 , 5
*/

解析:

首先这是一个二维数组.

  1. 所以&aa+1代表跨越整个二维数组.即指向10的末尾.然后前面强制类型转换为int*,所以ptr1此时是整型指针.指向10的末尾.

​ 所以*(ptr1 - 1)就是10

  1. aa是第一行的数组地址,aa+1就是5的末尾.*(aa+1)即是第二行的数组名,即元素6的地址前面又强制类型转换为int*,所以此时ptr2是整型指针.*(ptr-1)就是 5

笔试题7

#include <stdio.h>
int main()
{
 char* a[] = {"work","at","alibaba"};
 char** pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0; 
}

/*
答案:
at
*/

在这里插入图片描述

由此可以看出,pa++以后指向了数组a的第二块空间,*pa即得到了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);
 printf("%s\n", *--*++cpp+3);
 printf("%s\n", *cpp[-2]+3);
 printf("%s\n", cpp[-1][-1]+1);
 return 0; }

/*
答案:
POINT
ER
ST
EW
*/

在这里插入图片描述

  1. 同理,char** * cpp代表cpp可以跳过char**大小,因此++cpp,即指向了数组cp[1]的位置,第一次解引用,即得到c+2,而c+2则是指向数组c[2],所以第二次解引用,即得到c[2]里面的内容(p的地址),所以打印就是 POINT

注意:第一题以后,cpp的值就是加1过后了,即指向cp[1].所以第二题注意细节

  1. *++cpp就如上一题说的一样,即得到c+1(因为cpp已经在第一题加了一次),然而c+1前面有个--,所以c+1变成了c,而c指向的是数组c[0],所以再次解引用就得到了c[0]里面的内容(E的地址),然而,E却加3,即得到第二个E的地址,所以打印出来就是 ER

注意:同理注意经过两次变化的cpp的值.

  1. 因为cpp[-2]等价于*(cpp-2)所以cpp-2便指向了cp[0],所以cpp[-2]的内容便是c+3, 然后*cpp[-2]就是解引用c+3,而c+3指向的是c[3]位置,解引用因此得到了F的地址,而F又加3,所以得到了S地址,所以最后打印 ST
  2. cpp[-1][-1] 相当于*(*(cpp-1) -1),而cpp-1指向的是cp[1]位置,解引用的到时c+2然后c+2又减1.所以得到c+1,又因为c+1指向的是c[1]位置,所以*(c+1)便得到N的地址,而N又加一,所以得到E的地址,所以打印 EW
  • 39
    点赞
  • 56
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

捕获一只小肚皮

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

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

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

打赏作者

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

抵扣说明:

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

余额充值