📍指针笔试题
🚀笔试题1:
int main() { int a[5] = { 1, 2, 3, 4, 5 }; int *ptr = (int *)(&a + 1); printf( "%d,%d\n", *(a + 1), *(ptr - 1)); return 0; } //程序的结果是什么?
输出结果如下:
【解析】:
int a[5] = { 1, 2, 3, 4, 5 };
: 这行代码定义了一个包含5个整数的整型数组,初始化为从1到5的连续整数。- int* ptr = (int*)(&a + 1);:&a表示的是整个数组的地址,+1跳过整个数组,指向数组末尾后的地址(此时是int()a[5]类型),(int)表示的是将此地址强制转换成 int* 类型,所以这行代码将一个指向整型的指针ptr指向数组a之后的内存位置,也就是数组的末尾之后的位置。
*(a + 1)
:a
表示数组首元素的地址,+1表示下标为1的元素的地址(即第二个元素的地址),对其解引用,*(a+1)
表示第二个元素,即2。*(ptr - 1)
:ptr
表示的是5后面的地址,-1就表示在该地址的条件下向前移动一个位置,即指向5的地址,对其解引用就得到元素5。
🚀笔试题2:
//由于还没学习结构体,这里告知结构体的大小是20个字节 struct Test { int Num; char *pcName; short sDate; char cha[2]; short sBa[4]; }*p; //假设p 的值为0x100000。 如下表表达式的值分别为多少? //已知,结构体Test类型的变量大小是20个字节 int main() { 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
是一个指向这种结构体类型的指针,并被赋值为0x100000。
根据C语言中的指针算术规则,指针在增加时会根据所指向类型的大小进行适当的偏移。
- printf("%p\n", p + 0x1); 因为p是结构体指针类型,所以这里的表达式p + 0x1会将指针p增加一个结构体的大小,即20个字节(20的16进制为0x14)。故结果将是:0x100014。
- printf("%p\n", (unsigned long)p + 0x1); 在这个表达式中,将指针p先转换为unsigned long类型,这时原来的结构体指针p就变成了unsigned long类型的数组,加上0x1,就是单纯的将0x100000加上0x1。所以结果将是:0x100001。
- printf("%p\n", (unsigned int*)p + 0x1); 这个表达式中,将指针p转换为unsigned int*类型,然后增加0x1。因为unsigned int*大小是4个字节,所以增加1个字节相当于增加0x4。故结果将是: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\n", ptr1[-1], *ptr2); return 0; }
输出结果如下:
【解析】:
1.int a[4] = { 1, 2, 3, 4 }; 这行代码定义了一个包含4个整数的数组,初始化为 1, 2, 3, 和 4。
2.int* ptr1 = (int*)(&a + 1); 这里取数组a的地址,表示整个数组的地址,加1,跳过了4个整型的大小,然后将结果转换为int*类型的指针。这使得ptr1指向数组a之后的内存位置(即4后面的地址)。
3.int* ptr2 = (int*)((int)a + 1); 这里将数组a的值(即数组的第一个元素的地址)转换为int类型,然后加1(注意如果没有转换为int类型,就是指针+1,实际上加了4个字节),最后将结果转换为int*类型的指针。这使得ptr2指向数组a的第一个元素之后的内存位置。
4.printf("%x,%x\n", ptr1[-1], *ptr2); 这里使用指针ptr1访问了其前一个位置的值(即数组a的最后一个元素),并将它以十六进制格式打印出来。然后,使用指针ptr2访问了其所指向位置的值(即数组a的第二个元素),并同样以十六进制格式打印出来。
由于ptr1
指向数组a
之后的位置,而ptr2
指向数组a
的第一个元素之后的位置,它们访问的内存位置是不同的。然而,数组的元素在内存中是连续存储的,因此最终输出的结果将受到内存布局的影响。
🚀笔试题4:
#include <stdio.h> int main() { int a[3][2] = { (0, 1), (2, 3), (4, 5) }; int *p; p = a[0]; printf( "%d\n", p[0]); return 0; }
输出结果如下:
【解析】:
在这段代码中,数组a是一个3x2的二维数组,每个元素都是一个包含两个整数的小数组。
1.int a[3][2] = { (0, 1), (2, 3), (4, 5) }; 这里使用逗号运算符初始化二维数组a的元素。逗号运算符的结果是它的最后一个表达式的值,因此实际上是初始化了数组a的元素为 {1,3, 5},而0, 2, 4被忽略了。所以,a的内容实际上是 {1, 3}, {5, 0}, {2, 4}。
2.int* p; 这里声明了一个指向整数的指针p。
3.p = a[0]; 这里将指针p指向数组a的第一个元素,即{1, 3}的首地址。
4.printf("%d\n", p[0]); 这里通过指针p访问了其第一个元素,即数组a的第一个元素的第一个整数元素,即1。然后将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; }
输出结果如下:
【解析】:
int a[5][5];
这行代码定义了一个5x5的二维整数数组。int(*p)[4];
这里定义了一个指向包含4个整数的数组的指针。p = a;
这里将指针p
指向数组a
的第一个子数组,即a[0]
(二维数组的第一行地址)。printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
这行代码计算了两个地址之间的偏移量,并打印出结果。
现在我们来计算偏移量:
&p[4][2]
表示指针p
指向的数组的第5个子数组(即第五行的元素),然后取其中的第3个整数元素的地址。&a[4][2]
表示数组a
的最后一行的第3个元素的地址。
因此,表达式 &p[4][2] - &a[4][2]
会计算两个指针之间的偏移量,这是一个指针运算,计算的结果将是两个指针相差的数组元素个数。在这个特定的情况下,由于p
指向的数组每行有4个元素,所以&p[4][2]
会在&a[4][2]
前面,如图,&p[4][2]
和&a[4][2]
之间有四个元素,因此这个计算结果将是-4
。 而%p是以16进制的形式打印其补码(因为数据在内存中存储是以补码的形式存储的,所以打印的时候也是打印补码),-4
的原、反、补码如下;:
原 10000000 00000000 00000000 00000100
反 111111111 111111111 111111111 111111011
补 111111111 111111111 111111111 111111100
故其补码的16进制形式为 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\n", *(ptr1 - 1), *(ptr2 - 1)); return 0; }
输出结果如下:
【解析】:
1.int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; 这行代码定义了一个2x5的二维整数数组,并用初始化值填充它。
2.int* ptr1 = (int*)(&aa + 1); 这里取数组aa的地址,去出的是整个二维数组的地址,加1,跳过整个二维数组,指向二维数组末尾后面的地址,然后将结果转换为int*类型的指针。这将使得ptr1指向数组aa之后的内存位置(即10后面的地址)。
3.int* ptr2 = (int*)(*(aa + 1)); 这里aa表示的是二维数组首元素的地址(即第一行的地址),+1跳过一行元素,指向第二行的地址,然后对其解引用得到第二行第一个元素的地址,最后将结果转换为int*类型的指针。这将使得ptr2指向第二行的起始位置。
4.printf("%d,%d\n", *(ptr1 - 1), *(ptr2 - 1)); 这里使用指针算术访问了指针ptr1的前一个位置的值(即数组aa的最后一个元素),并将它打印出来。然后,使用指针算术访问了指针ptr2的前一个位置的值(即第二行的最后一个元素),并将它打印出来。
注意:ptr1
指向整个二维数组 aa
之后的位置,而 ptr2
指向第二行的起始位置,它们访问的内存位置是不同的。
🚀笔试题7:
#include <stdio.h> int main() { char *a[] = {"work","at","alibaba"}; char**pa = a; pa++; printf("%s\n", *pa); return 0; }
输出结果如下:
【解析】:
1.char *a[] = {"work","at","alibaba"};
这行代码定义了一个字符指针数组 a,其中每个元素是一个指向字符串常量的指针。数组中有三个元素,分别指向字符串 “work”、“at” 和 “alibaba”。
2.char**pa = a;
这里定义了一个指向字符指针的指针 pa,将其指向数组 a 的第一个元素,即指向字符串 “work” 的指针。
3.pa++;
这里将指针 pa 增加了一次,使其指向数组 a 的下一个元素,即指向字符串 “at” 的指针。
4.printf("%s\n", *pa);
这里通过指针 pa 访问了其所指向的字符串(这里*pa表示的是字符串"at"的地址),然后使用 %s 格式化输出该字符串。
🚀笔试题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; }
输出结果如下:
【解析】:
这段代码涉及指针和指针数组的操作,有点复杂,让我们逐行解释:
1.char* c[] = { "ENTER","NEW","POINT","FIRST" };
这行代码定义了一个字符指针数组 c,其中每个元素是一个指向字符串常量的指针。数组中有四个元素,分别指向字符串 “ENTER”、“NEW”、“POINT” 和 “FIRST”。
2.char** cp[] = { c + 3,c + 2,c + 1,c };
这里定义了一个指向字符指针的指针数组 cp,其中每个元素是一个指向数组 c 中的某个元素的指针。c + 3 表示指向 “FIRST” 的指针,c + 2 表示指向 “POINT” 的指针,以此类推。
3.char*** cpp = cp;
这里定义了一个指向指针数组 cp 的指针 cpp。
4.printf("%s\n", **++cpp);
这里首先将指针 cpp
增加了一次,然后通过 **
解引用两次得到 “POINT” 的字符串,然后输出。
5.printf("%s\n", *-- * ++cpp + 3);
这里先将指针 cpp 增加一次,然后将其解引用得到指向 “NEW” 的指针。接着,*-- * ++cpp 操作会将 *++cpp 减少一次,然后将其解引用,得到字符串"ENTER" 的地址。最终, + 3 操作将 “ENTER” 的字符串地址向后偏移3,得到字符串 “ER”,然后输出。
6.printf("%s\n", *cpp[-2] + 3);
这里使用指针数组的索引操作,首先cpp[-2] 得到指向字符串 “FIRST” 的指针,然后对其解引用,得到字符串"FIRST"的地址,最终+ 3 将 “FIRST” 的字符串地址向后偏移3,得到字符串 “ST”,然后输出。
7.printf("%s\n", cpp[-1][-1] + 1);
这里 cpp[-1]
得到指向 “POINT” 的指针,然后 cpp[-1][-1]
得到 “NEW” 的字符串指针(地址),然后 + 1
将字符串地址向后偏移1,得到字符串 “EW”,然后输出。
💥小结:
理解和掌握指针和数组的概念是编程中的关键。通过练习和实践,你将能够更好地处理与指针和数组相关的问题。遇到困难并不是失败,而是在通向成功的道路上的一部分。每次犯错,你都在学习如何做得更好。我们一起加油!!✨☄
🔥今天的分享就到这里了, 如果觉得博主的文章还不错的话, 请👍三连支持一下博主哦🤞