关于数组和指针的笔试题解析(详解)(下)

简介: 关于数组和指针的笔试题解析(详解)

📄练习6:

code:

#include<stdio.h>
#include<string.h>
int main()
{
  char* p = "abcdef";
  printf("%zd\n", strlen(p));
  printf("%zd\n", strlen(p + 1));
  //printf("%zd\n", strlen(*p));
  //printf("%zd\n", strlen(p[0]));
  printf("%zd\n", strlen(&p));
  printf("%zd\n", strlen(&p + 1));
  printf("%zd\n", strlen(&p[0] + 1));
  return 0;
}

运行结果:

6
5
3
11
5

💡解析

printf("%zdd\n", strlen(p)); //6

字符串中有\0

p中存放的是a的地址

从a的地址开始向后访问

⭕故,长度是6


printf("%zd\n", strlen(p + 1)); //5

p指向a

p+1指向b

从b开始往后数,直到遇到\0为止

⭕故,长度是5


//printf("%zd\n", strlen(*p));//err

p指向a

*p就是a

将p的值传递给strlen

⭕故,非法访问


//printf("%zd\n", strlen(p[0]));//err

p[0]=>*(p+0)=>*p

⭕故,非法访问


printf("%zd\n", strlen(&p)); //随机值

&p是p的地址

从p所占空间的起始位置开始查找的

完全不可知的

⭕故,随机值


printf("%zd\n", strlen(&p + 1));//随机值

&p+1指向的是p的地址后面

从&p后面的地址开始读取

也是不可知的

⭕故,随机值


printf("%zd\n", strlen(&p[0] + 1)); //5

&p[0] + 1是‘b’的地址

从’b’后面开始数

⭕故,长度是5


💻二维数组

📄练习:

code:

#include<stdio.h>
int main()
{
  int a[3][4] = { 0 };
  printf("%zd\n", sizeof(a));
  printf("%zd\n", sizeof(a[0][0]));
  printf("%zd\n", sizeof(a[0]));
  printf("%zd\n", sizeof(a[0] + 1));
  printf("%zd\n", sizeof(*(a[0] + 1)));
  printf("%zd\n", sizeof(a + 1));
  printf("%zd\n", sizeof(*(a + 1)));
  printf("%zd\n", sizeof(&a[0] + 1));
  printf("%zd\n", sizeof(*(&a[0] + 1)));
  printf("%zd\n", sizeof(*a));
  printf("%zd\n", sizeof(a[3]));
  return 0;
}

运行结果:

48
4
16
4
4
4
16
4
16
16
16

💡解析

printf("%zd\n", sizeof(a));  //48

计算的是整个二维数组的大小,单位是字节

数组里共有3*4=12个元素

每个元素都是int类型

则344

⭕故,大小是48个字节


printf("%zd\n", sizeof(a[0][0]));  //4

a[0][0]表示第一行第一个元素

⭕故,大小是4个字节


printf("%zd\n", sizeof(a[0]));  //16

a[0]表示第一行数组名

将第一行数组名单独放在sizeof内部

计算的是第一行大小

⭕故,大小是16个字节


printf("%zd\n", sizeof(a[0] + 1));  //4

a[0]是第一行数组名,但是没有把a[0]单独放在sizeof中

这里的a[0]表示第一行第一个元素的地址

a[0] + 1表示第一行第一个元素地址+1,即第一行第二个元素地址==>a[0][1]的地址

是地址,大小是4或者8个字节

⭕故,大小是4或者8个字节


printf("%zd\n", sizeof(*(a[0] + 1))); //4

a[0] + 1是第一行第二个元素的地址

*(a[0] + 1)解引用,访问的就是第一行第二个元素

⭕故,大小是4个字节


printf("%zd\n", sizeof(a + 1)); //4

这里的a是二维数组的数组名

没有将a单独放在sizeof中

那么,a就是数组首元素地址,也就是第一行的地址😏

a+1跳过一行的地址,就是第二行地址

既然是地址,那么…

⭕故,大小是4或者8个字节


printf("%zd\n", sizeof(*(a + 1))); //16

a+1是第二行地址

*(a + 1)解引用,表示第二行

*(a + 1)==>a[1]

printf("%zd\n", sizeof(*(a + 1)));
<==>
printf("%zd\n", sizeof(a[1]);

两种表示方法,你get到了嘛??😏

⭕故,大小是16个字节


printf("%zd\n", sizeof(&a[0] + 1)); //4

a[0]是第一行数组名

&a[0]取出第一行的地址

&a[0] + 1第一行的地址+1,表示第二行地址

是地址…

⭕故,大小是4或者8个字节


printf("%zd\n", sizeof(*(&a[0] + 1))); //16

&a[0] + 1是第二行的地址

*(&a[0] + 1))对第二行地址解引用,访问第二行数组

⭕故,大小是16个字节


printf("%zd\n", sizeof(*a)); //16

a表示首元素地址,也就是第一行地址

*a对一行地址解引用,就是第一行

⭕故,大小是16个字节


printf("%zd\n", sizeof(a[3])); //16

a[3]是第四行,但是刚开始我们开的数组没有第四行

我们知道,sizeof()是根据类型计算的

实际上不会访问a[3]

a[3]和前面a[0]类型是一样的

⭕故,大小是16个字节


🗞️小结

  1. sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。
  2. &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
  3. 除此之外所有的数组名都表⽰⾸元素的地址。

关于sizeof和strlen的介绍,可以看小编的文章《sizeof和strlen的对比》,里面有详细解释!!!

🚩指针运算笔试题

📄练习1:

code:

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

运行结果:

2,5

💡解析

1️⃣对*(a + 1)的分析:

  • a表示数组首元素地址
  • a+1首元素地址+1,表示第二个元素地址
  • *(a + 1)解引用,表示第二个元素,即2

2️⃣对*(ptr - 1)的分析:

  • 首先分析int* ptr = (int*)(&a + 1);
  • &a表示整个数组地址
  • &a+1表示跳过整个数组地址
  • &a的类型是int(*)[5] ,+1后的类型还是这个类型
  • 现在要将(&a + 1)赋值给ptr
  • ptr是int* 类型,所以需要强制类型转换

  • 分析*(ptr - 1)
  • ptr-1表示前移动,即5的地址:

  • *(ptr-1)解引用,表示5

📄练习2:

在X86环境下

假设结构体的⼤⼩是20个字节

code:

#include <stdio.h>
struct Test
{
  int Num;
  char* pcName;
  short sDate;
  char cha[2];
  short sBa[4];
}*p = (struct Test*)0x100000;
int main()
{
  printf("%p\n", p + 0x1);
  printf("%p\n", (unsigned long)p + 0x1);
  printf("%p\n", (unsigned int*)p + 0x1);
  return 0;
}

运行结果:

00100014
00100001
00100004

💡解析

printf("%p\n", p + 0x1);  //00100014

0x1表示1(在16进制中)

p+0x1即p+1

那么,指针+1到底加几呢??😥

这个取决于指针类型,现在是一个结构体指针

那么加1就是跳过一个结构体大小

结构体大小是20个字节,加的是0x100014

⭕所以,大小变成00100014(16进制中)


printf("%p\n", (unsigned long)p + 0x1); //00100001

p的类型被强制转换成unsigned long,无符号整型

此时就不是指针类型啦

那么就是:整型+1

不再是整型指针+1,小小的细节,要清楚

0x100000+1=0x100001

⭕所以,输出结果:00100001


printf("%p\n", (unsigned int*)p + 0x1);  //00100004

p被强制转换成整型指针(unsigned int)

整型指针+1就是+4,即0x100004

⭕所以,输出结果:00100004


📄练习3:

cod:

#include <stdio.h>
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);
  return 0;
}

运行结果:

4,2000000

💡解析

  • &a:取数组 a 的地址
  • &a + 1:将指向数组的指针做加法运算,相当于移动了一个整个数组的大小。由于 a 是一个包含4个元素的 int 数组,所以加上1后指向数组之外的内存。
  • (int*)(&a + 1):将指向数组之外的内存地址转换为 int 指针。
    ptr1[-1]:通过指针 ptr1 往前访问前一个位置的值,即指向数组的最后一个元素。
  • (int)a:将数组 a 转换为 int 类型的值,实际上是取数组首元素的地址。
  • (int*)((int)a + 1):将数组首元素地址加上1后再转换为 int 指针。
  • *ptr2:通过指针 ptr2 访问指向的值。
  • 根据上述分析,我们可以预测输出结果。
  • 由于 a 是一个包含4个 int 类型元素的数组,在内存中占用4个 int 大小的连续空间。
  • ptr1[-1]:指向数组最后一个元素,即 a[3] 的值为4。
  • *ptr2:指向数组首元素后面的1字节内存,此内存内容未定义。
  • 因此,我们可以预测输出结果为 4,未定义的值(可能是未初始化的值或者随机值)。

这道题在随着后面的学习,会有更深刻的理解,可以在学习完《数组在内存中的储存》后,再来看这道题


📄练习4:

code:

#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

💡解析

这里的数组初始化可不是下面这样:

int a[3][2] = { (0, 1), (2, 3), (4, 5) };

里面是逗号表达式

逗号表达式:从左向右依次计算,但是整个表达式的结果是最后一个表达式的结果

int a[3][2] = { (0, 1), (2, 3), (4, 5) };
<===>
int a[3][2] = { 1, 3, 5 };

a[0]表示第一行数组名,是第一行首元素的地址,即a[0]==>&a[0][0]

p[0]==>*(p+0),即1

⭕所以,输出结果:1


📄练习5:

X86环境:

code:

#include <stdio.h>
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;
}

运行结果:

FFFFFFFC,-4

💡解析

int a[5][5];

int(*p)[4];
  p = a;


&p[4][2]


&p[4][2] - &a[4][2]


printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);

以%p打印和以%d打印是不一样的

%d打印就直接是-4

%p打印的是-4的补码,即FFFFFFFC


📄练习6:

code:

#include <stdio.h>
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

💡解析

int* ptr1 = (int*)(&aa + 1);

将&aa + 1强制转换成int* 型,因为ptr1是int*型

然后赋值给ptr1

*(ptr1 - 1)是10


int* ptr2 = (int*)(*(aa + 1));

*(aa + 1)<==>aa[1]

aa[1]本来就是一个地址,这里的强制类型转换其实是没有意义的,完全就是迷惑人的

将aa[1]赋值给ptr2

那么*(ptr2 - 1)就是5


📄练习7:

code:

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

运行结果:

at

💡解析

这里是将‘w’,’a’,‘a’的地址存到数组里面去

内存布局:


char** pa = a;
pa++;

a是数组首元素地址,即char*的地址

pa存放首元素地址


printf("%s\n", *pa);

对pa解引用,访问的就是第二个元素,即打印at


📄练习8:

code:

#include <stdio.h>
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

💡解析

本道题是比较难的一道题目!!!笔试原题

char* c[] = { "ENTER","NEW","POINT","FIRST" };

内存布局:


char** cp[] = { c + 3,c + 2,c + 1,c };

内存布局:


char*** cpp = cp;

图解:


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

++cpp=cpp+1

在这里插入图片描述

*++cpp第一层解引用,访问到的是c+2

通过c+2,得到的是P的地址

则再次解引用,以%s打印,得到POINT


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

再次++cpp

从上面基础上再+1

*++cpp解引用,得到c+1

– * ++cpp:在c+1上减去1,得到c

此时也就不再指向N,而是指向E

*-- * ++cpp:再解引用,得到E的地址

*-- * ++cpp + 3:得到ER


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

cpp[-2]其实就是*(cpp-2)

*cpp[-2]+3也就是 * *(cpp-2)+3

cpp-2在上面的额基础上减2,cpp不会变

*(cpp-2)通过解引用,得到c+3

**(cpp-2) 再次解引用,得到F

**(cpp-2)+3 ,得到ST


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

cpp[-1][-1]也就是 * (*(cpp-1)-1)

即, * (*(cpp-1)-1)+1

cpp-1图解:

*(cpp-1)解引用,得到c+2,也就是P的地址

*(cpp-1)-1,得到N的地址

*(*(cpp-1)-1)再次解引用,拿到N的值
* (*(cpp-1)-1)+1 ,再加1,得到EW

🚩总结

本节题目来自于公司笔试,前面的题目不算很难,需要我们熟练掌握

后面的题目比较难,需要我们逐个分析

理解完这些题目,相信大家对指针有更深刻理解

😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏😏


目录
相关文章
|
10月前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
262 1
|
11月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
11月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
11月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
11月前
|
容器
在使用指针数组进行动态内存分配时,如何避免内存泄漏
在使用指针数组进行动态内存分配时,避免内存泄漏的关键在于确保每个分配的内存块都能被正确释放。具体做法包括:1. 分配后立即检查是否成功;2. 使用完成后及时释放内存;3. 避免重复释放同一内存地址;4. 尽量使用智能指针或容器类管理内存。
|
11月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
202 4
|
11月前
使用指针访问数组元素
【10月更文挑战第31天】使用指针访问数组元素。
121 2
|
11月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
893 13
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
444 4
|
12月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
144 0

推荐镜像

更多
  • DNS