掌握指针和数组:经典笔试题攻略(万字详解)(下)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 掌握指针和数组:经典笔试题攻略(万字详解)(下)

📍指针笔试题



🚀笔试题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;
}
//程序的结果是什么?


输出结果如下:

5b7d3109631446de8a39a1c38206c6ed.png


【解析】:

  1. int a[5] = { 1, 2, 3, 4, 5 };: 这行代码定义了一个包含5个整数的整型数组,初始化为从1到5的连续整数。
  2. int* ptr = (int*)(&a + 1);:&a表示的是整个数组的地址,+1跳过整个数组,指向数组末尾后的地址(此时是int()a[5]类型),(int)表示的是将此地址强制转换成 int* 类型,所以这行代码将一个指向整型的指针ptr指向数组a之后的内存位置,也就是数组的末尾之后的位置。
  3. *(a + 1)a表示数组首元素的地址,+1表示下标为1的元素的地址(即第二个元素的地址),对其解引用,*(a+1)表示第二个元素,即2。
  4. *(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;
}


输出结果如下:

823629fd30014cecaba30aafdd95286f.png


【解析】:

在给定的代码中,有一个名为struct Test的结构体类型,大小为20个字节。变量p是一个指向这种结构体类型的指针,并被赋值为0x100000。

根据C语言中的指针算术规则,指针在增加时会根据所指向类型的大小进行适当的偏移。


  1. printf("%p\n", p + 0x1); 因为p是结构体指针类型,所以这里的表达式p + 0x1会将指针p增加一个结构体的大小,即20个字节(20的16进制为0x14)。故结果将是:0x100014。
  2. printf("%p\n", (unsigned long)p + 0x1); 在这个表达式中,将指针p先转换为unsigned long类型,这时原来的结构体指针p就变成了unsigned long类型的数组,加上0x1,就是单纯的将0x100000加上0x1。所以结果将是:0x100001。
  3. 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;
}


输出结果如下:

1ae733e1a5f14191a1330dede6cc1ae9.png


【解析】:


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的第一个元素之后的位置,它们访问的内存位置是不同的。然而,数组的元素在内存中是连续存储的,因此最终输出的结果将受到内存布局的影响。

482e5b2ae7ae4122a40d1871e1ee534e.png


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


输出结果如下:

923481c2a8624e7a91620265be5853d7.png


【解析】:


在这段代码中,数组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以十进制格式打印出来。

dfeeea5381a44a1ea27660191650f1e6.png

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


输出结果如下:

92af9cc33984481484e83072a74fac49.png


【解析】:

  1. int a[5][5]; 这行代码定义了一个5x5的二维整数数组。
  2. int(*p)[4]; 这里定义了一个指向包含4个整数的数组的指针。
  3. p = a; 这里将指针p指向数组a的第一个子数组,即a[0](二维数组的第一行地址)。
  4. 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

24a8e57e9e8d41f2bf74dc2074d8068c.png

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


输出结果如下:

1344605eb87448999e477b63375e9420.png


【解析】:


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 指向第二行的起始位置,它们访问的内存位置是不同的。

62b4fd5521c04ad0b8c7ad22fe1c7bc6.png


🚀笔试题7:


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


输出结果如下:

f48e9f2ee8504aedab48fbc6c923dde0.png


【解析】:


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


输出结果如下:

f2c6827e1aa548739a7b6fb543213880.png


【解析】:

这段代码涉及指针和指针数组的操作,有点复杂,让我们逐行解释:


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。


d9b3db52e7084cbcacfa8ef495031626.png


4.printf("%s\n", **++cpp);

这里首先将指针 cpp 增加了一次,然后通过 ** 解引用两次得到 “POINT” 的字符串,然后输出。

1c38f272a599499ea51c295facc15e8b.png

5.printf("%s\n", *-- * ++cpp + 3);

这里先将指针 cpp 增加一次,然后将其解引用得到指向 “NEW” 的指针。接着,*-- * ++cpp 操作会将 *++cpp 减少一次,然后将其解引用,得到字符串"ENTER" 的地址。最终, + 3 操作将 “ENTER” 的字符串地址向后偏移3,得到字符串 “ER”,然后输出。

9af532e006724eb2a0da682263632d69.png


6.printf("%s\n", *cpp[-2] + 3);

这里使用指针数组的索引操作,首先cpp[-2] 得到指向字符串 “FIRST” 的指针,然后对其解引用,得到字符串"FIRST"的地址,最终+ 3 将 “FIRST” 的字符串地址向后偏移3,得到字符串 “ST”,然后输出。

9f8bc8d91cd14fa39d6d61e231879c10.png

7.printf("%s\n", cpp[-1][-1] + 1);

这里 cpp[-1] 得到指向 “POINT” 的指针,然后 cpp[-1][-1] 得到 “NEW” 的字符串指针(地址),然后 + 1 将字符串地址向后偏移1,得到字符串 “EW”,然后输出。

c7d781505c7548589a9c24f0318b82e1.png


💥小结:



理解和掌握指针和数组的概念是编程中的关键。通过练习和实践,你将能够更好地处理与指针和数组相关的问题。遇到困难并不是失败,而是在通向成功的道路上的一部分。每次犯错,你都在学习如何做得更好。我们一起加油!!✨☄


🔥今天的分享就到这里了, 如果觉得博主的文章还不错的话, 请👍三连支持一下博主哦🤞


8be35d7ae93446bfb27deecb11d01ccd.gif

目录
相关文章
|
17天前
使用指针访问数组元素
【10月更文挑战第30天】使用指针访问数组元素。
30 3
|
16天前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
28 2
|
25天前
|
算法 索引
单链表题+数组题(快慢指针和左右指针)
单链表题+数组题(快慢指针和左右指针)
29 1
|
1月前
|
存储
如何使用指针数组来实现动态二维数组
指针数组可以用来实现动态二维数组。首先,定义一个指向指针的指针变量,并使用 `malloc` 为它分配内存,然后为每个子数组分配内存。通过这种方式,可以灵活地创建和管理不同大小的二维数组。
|
1月前
|
存储
如何通过指针数组来实现二维数组?
介绍了二维数组和指针数组的概念及其区别,详细讲解了如何使用指针数组模拟二维数组,包括定义与分配内存、访问和赋值元素、以及正确释放内存的步骤,适用于需要动态处理二维数据的场景。
|
1月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
1月前
魔法指针 之 二级指针 指针数组
魔法指针 之 二级指针 指针数组
19 1
|
1月前
|
存储
一篇文章了解区分指针数组,数组指针,函数指针,链表。
一篇文章了解区分指针数组,数组指针,函数指针,链表。
18 0
|
1月前
|
编译器 C语言
【C语言】指针篇-深入探索数组名和指针数组- 必读指南(2/5)
【C语言】指针篇-深入探索数组名和指针数组- 必读指南(2/5)
|
3月前
|
搜索推荐 C语言
指针与数组
指针与数组
59 9
下一篇
无影云桌面