0%

C语言关于指针的几道面试题(烧脑预警)

第一题

1
2
3
4
5
6
7
8
9
#include<stdio.h>

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即为首地址元素
\&a则取到一个类型为inr( * )[5]的指针,此时再用(int*)强制转化成int *指针所以此时指针 ptr 指向数组a最后一个元素之后的一个int类型元素的地址。在输出的时候,解引用(a+1)即为a[1] = 2,解引用(ptr-1)即为a[4]=5。

第二题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
};

int main()
{
struct Test* p = (struct Test*)0x100000;
//声明一个指针p,它指向内存中0x100000这个地址,它的类型是(struct Test*)
printf("%p\n", p + 0x1);
printf("%p\n", (unsigned long)p + 0x1);
printf("%p\n", (unsigned int*)p + 0x1);
return 0;
}

在上面这道题中需要注意的是结构体struct Test大小为20字节
第一个输出中p + 0x1即为p指向的地址跳过一个结构体struct Test的字节大小(20个字节)而\%p是打印当前指针指向的地址,即按照16位进制输出,所以结果为00100014
第二个输出中(unsigned long)则将p由指针强制转换成了一个整数,此时再加0x1并按16位进制输出,结果为00100001
第三个输出中(unsigned int* )将p强制转换成unsigned int* 类型的指针,此时加0x1,跳过四个字节,所以输出为00100004

第三题

1
2
3
4
5
6
7
8
9
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);
//%x是按照16进制输出
return 0;
}

在上面这道题中ptr[-1]即为*(ptr - 1),\&a取到一个类型为int(*)[4]的指针,随后强制转换成int *指针此时减一再解引用即为数组a[3]处的元素,为4。
(int *)((int)a + 1)的理解比较有难度,我们知道 a 在此处表示的是数组元素的首地址,在上式中已经隐式转换成一个int * 类型的指针,此时强制转换成int类型再加一,相当于整数加一,接着再强制转换成int *类型,所以 ptr2 实际指向的位置是数组第一个元素的后三个字节加上第二个元素的第一个字节(int类型数组中一个元素四个字节,转换成整数加一后只跳过了一个字节)假设机器为32为系统,小端字节序,那么数组前两个元素在内存中的排布为 a [0]:01 00 00 00、a[1]:02 00 00 00,那么ptr2实际指向的元素在内存中的排列是 00 00 00 02,解引用后按照16进制输出即为
2 00 00 00。

第四题

1
2
3
4
5
6
7
8
9
int main()
{
int a[3][2] = { (0, 1), (2, 3), (4, 5) };
//由于逗号表达式的作用,实际上数组只输入了1,3,5三个数字
int *p;
p = a[0];
printf("%d", p[0]);
return 0;
}

对于二维数组a[ 3 ][ 2 ]来说,a[ 0 ]取到了包含两个元素的一维数组的首地址它包含的元素是1,3。另指针p指向它,再输出p[ 0 ],即为输出一维数组a[0]的首元素也就是二维数组a的首元素,也就是数字 1。

第五题

1
2
3
4
5
6
7
8
9
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]);
//%p用来打印地址,此处意为输出16进制数字
return 0;
}

p是一个int(*)[4]类型的指针,p = a之后,每一个p指针指向数组a中的四个元素,可以看成两个每一个格子都错开,但整体上看又对齐的尺子

a[0][0] a[0][1] a[0][2] a[0][3] a[0][4]
p[0][0] p[0][1] p[0][2] p[0][3] p[1][0]

类似以上表格形式
那么p[4][2]指向内存中的元素就是a[ 3 ] [ 2 ],在输出中,二者同时取地址再相减,实际上算出来的是他们之间相隔几个int 类型元素(此时二者已经隐式转换成了int* 类型的指针),二者之间相隔-4个int类型元素,所以最终输出结果是FFFFFFFC,-4

第六题

1
2
3
4
5
6
7
8
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(*)[2][5]类型的数组指针,指向整个数组,此时加一,跳过整个数组,再(int *)强制转换成int *类型指针,它此时指向数组a的最后一个元素的后边四个字节。输出的时候减去一再解引用,也就是输出数组a的最后一个元素,10 。
*(aa + 1)等价于aa[ 1 ],此处被隐式转换成一个int( * )[5]的指针,接着(int *)将它强制转换成一个int *类型指针,其指向数组中下标为【1】【0】的元素,也就是6,输出的时候减一再解引用,输出的就是6之前的一个元素,也就是5。

第七题

1
2
3
4
5
6
7
8
9
int main()
{
char* a[] = { "work", "at", "alibaba" };
//表示有一个数组,其中的元素都是指且指向char类型的数据
char** pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}

pa是一个二级指针,它指向了一个指针数组a的首元素(这个首元素是一个指针,它指向位于常量区的“work\\0”的首地址),在pa进行自加运算后,pa指向了数组a中的第二个元素,而第二个元素也是指针,指向位于常量区的字符串“at\\0”的首地址,对pa解引用之后,得到了字符串“at\\0”的首元素地址,用\%s进行输出(\%s从当前地址开始输出字符直到遇到\\0),所以最终输出结果是 at

最终题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main()
{
char* c[] = { "ENTER", "NEW", "POINT", "FIRST" };
//c是一个数组,其中每个元素都是指针,分别指向四个字符串的首地址
char** cp[] = { c + 3, c + 2, c + 1, c };
//cp是一个数组,它的首元素(每个元素都是二级指针)指向数组c中指向字符
//串“FIRST”首地址的指针元素,它的最后一个元素则指向数组c中的首元素
//依此类推(也就是颠倒数组c)
char*** cpp = cp;
//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本来指向cp中的第一个元素,再进行自加之后指向的是cp中的第二个元素(指针自加直接跳过一个元素),第一次解引用得到 c + 2,它是一个指针指向数组c中的第三个元素,而数组c中的第三个元素是一个指向位于常量区字符串“POINT\\0”的指针,再次解引用之后进行输出,即输出了该字符串,最终输出结果是 POINT。

第二个输出

++ 在上一个输出中cpp已经自加了一次,此时再进行自加,它指向了数组cp中的第三个元素,也就是 c + 1

第一个 * 在cpp解引用之后得到了指针 c + 1,它指向了数组c中的第二个元素,也就是指向字符串“NEW\\0”的那个指针

在自减运算之后,此刻得到了一个指向数组c中第一个元素的指针,这里不用管数组cp是怎么排列的,上一步运算中我们得到了指向数组c中第二个元素地址的指针,自减后自然得到的是数组c中指向第一个元素的指针。

第二次解引用之后,得到了数组c中第一个元素的内容,是一个指向字符串“ENTER\\0”的指针

+3 此时的加三意味着跳过三个char类型的数据,也就是“ER\\0”,所以最后的输出结果是ER。

第三个输出

cpp[-2]即为*(cpp - 2),在这一步操作之后,得到了数组cp中的第一个元素(此前cpp指向数组cp中的第三个元素),它是一个指针,指向数组c中的最后一个元素

再对cpp[-2]解引用操作之后,得到了数组c中的最后一个元素,这个元素是一个指针,指向字符串“FIRST\\0”的首地址

+3 之后,跳过三个char类型元素,进行输出,最后结果是 ST

第四个输出

cpp[ -1 ][ -1 ]即为*(*(cpp -1)-1)

cpp - 1再解引用得到了数组cp中的第二个元素c+2(此前cpp指向数组cp中的第三个元素),他是一个指针,指向数组 c 中的第三个元素

再次减一并解引用 上一步操作中已经得到了指向数组 c 第三个元素的指针,在减一之后,得到了指向数组c中第二个元素的指针,解引用之后得到了数组c中第二个元素的内容

加一即跳过一个char型元素,所以最终输出结果是EW

总结

指针是一个比较容易搞混淆的概念,在感到困惑的时候,不妨画张图来表示内存与指针,搞清楚指针的指向和内容,这样结果会更加清晰明了

-------------本文结束感谢您的阅读-------------