C语言进阶第四课-----------指针的进阶----------指针和数组笔试解释 2

简介: C语言进阶第四课-----------指针的进阶----------指针和数组笔试解释

二维数组

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

相同颜色的相当于一个一维数组,二维数组相当于是一维数组的数组,我们可以理解二维数组的元素个数是一维数组的个数,但是我们可以想象成以下场景


a[0]、a[1]、a[2]可以认为是数组名,一一对应第几行,a[0]第一行, a[1]第二行,a[2]第三行

解释1

这里单独传入了数组名,代表整个数组,计算整个数组的大小,单位为字节,大小为48字节

解释2

这里arr[0][0]是一个整形数据,大小为4字节

解释3

这里相当于传入了一个数组名,代表的是a[0]的一维数组,计算的是a[0]一维数组的大小,就是16字节

解释4


首先,a[0]相当于一个一维数组的数组名,没有单独放在sizeof里,所以代表的是这一维数组的首元素地址,加1,得出第二元素的地址,是地址,大小就是4/8字节

解释5

前面我们已经知道a[0]是一维数组的数组名,数组名加1,得到第二元素的地址,地址解引用指向第二元素,第二元素是一个整形,大小为4

解释6

可以看出这里传入的是数组a首元素的地址,加一是第二元素的地址,是地址大小就是4/8个字节,类型是int(*)[4]

解释7

a数组首元素的地址,加1,是第二元素的地址,解引用得到第二元素, (a + 1) = a[1], 因为数组a是二维数组,相当于是一维数组的数组a[1],传入数组名,计算的是一维数组的大小,所以结果为16字节
解释8

这里是取出a[0]的地址,加1 就是a[1]的地址,是地址,那就是4/8字节,类型为int(*)[4],或者我们可以换个思路,a[0]是一维数组名, 加个&就变成了取出一维数组的地址,然后加1,跳过这个数组,因为二维数组的存储是连续,会指向第二个一维数组地址

解释9

上面我们已经讲过了(&a[0] + 1)是第二个一维数组 的地址,加上
就是a[1],是数组名,计算的是一维数组的大小,结果16字节

解释10

a为二维数组名,没有单独传入,所以是首元素的地址,&a[0],加上*就是一维数组名了,计算的是一维数组的大小,结果为16字节

解释11

有小可爱看到这里,越界了?,越界是要去访问对应的内存,sizeof不会去访问对应的内存,我们前面测试过sizeof(int)和sizeof(1)的大小是一样的,sizeof是根据类型来判断大小的,a[3]的类型和a[0]的类型是一样的, 所以不会去访问内存,不访问内存,就不存在越界

总结:

数组名的意义:

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

结果:

可能看到这里有写些小可爱就会很困惑,

不急,我来讲解

这道题是把&a + 1强转成int类型,加1减1都会越过4个字节,这就是(ptr - 1)最终指向的元素5的地址并解引用

笔试题2

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

这里结构体的大小涉及到内存对齐,后面会讲解,

我们知道这个结构体类型的大小为20字节,所以创建出来的结构体变量的大小也是20字节,

  1. 0x1转换成十进制是1,p + 1 跳过20个字节,因为前面已经定义了变量p是结构体指针变量
    注意一下,p的数组从没有被改变
  2. p强转成unsigned long 类型,加1就是我们正常的十进制加1
  3. p强转成unsigned int *类型,加1 跳过4个字节
  4. %p是打印格式

笔试题3

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

d365006aefec4b6ca81ae1fd2bc0647f.png

前面我们讲过 a[0] <==> a + 0,那这里的ptr1[-1]相当于ptr +(-1),因为ptr = &a + 1,又被强转成int类型,所以减1就指向4的地址,%x为十六进制输出。

(int)((int)a + 1)这里主要是首元素的地址的类型被强转为int, 加1就是数学的加1,

a63bbacdc68d4a47a2eb3ea1560e9aba.png

我们知道int*类型是访问4个字节,这里少了一个字节,那就会往后再访问一个字节,既然我们知道会这样访问,那我们还要知道这些内容是啥,前面我们还理解过内存的存储在vs编译器时小端存储,有不懂的可以看看我的这篇博客C语言进阶第一课

可以看到vs编译器的存储是十六进制的,一个字节是8bit 每4个bit表示一个十六进制位,所以ptr2访问的是 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;
}

这里主要涉及到逗号表达式,化简得int a[3][2] = {1, 3, 5}

前面我们知道二维数组是一维数组的数组, a[0]是第一行一维数组的数组名,元素,所以p存储的是元素1的地址,又因为p[0] <==> *(p + 0)所以这里的结果为1

笔试题5

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

这里定义了一个二维数组a和一个 数组指针变量p, p = a 所以p存储的是 &a[0],也就是二维数组名

可以看到a[4][2]是一个元素 ,&a[4][2]就是该元素的地址,而p变量的类型是int(*)[4] ,加1 就是跳过16字节, 而a[0]的类型是int(*)[5], 所以&p[4][2]是18格子,也就是&a[3][3],而 a[4][2] 是22格,而我们知道数组的内存存储是从小到大存储的,所以p[4][2]是小地址 ,两者相减就是-4,-4要在内存存储是以补码的形式存储,即:

11111111111111111111111111111100,因为是%p输出,不会去区分补码和原码,所以直接打印补码0xfffffffc,

%p是打印地址的,认为内存存储的补码就是地址,

笔试题6

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

定义一个二维数组 , &aa的类型是 int(*)[2][5];

&aa + 1 跳过40个字节, aa数二维数组的首元素的地址, 也就是 &aa[0] , aa + 1就是第二行一维数组的地址,前面我讲过a[0] 等于*(a + 0), 那*(aa + 1) <==> aa[1],或者我们可以理解为二维数组的元素是一维数组名,解引用得到对应的数组名,所以ptr2存储的是6的地址,最终结果是 10 和5


笔试题7

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

这里的a是一个指针数组 里面的元素类型是char , pa是一个二级指针

这样就很明了,我们需要注意的是%s遇到’\0’就停止了,因为pa++,所以是指向第二个元素的地址,因为 p是一个二级指针变量, 类型是 char*, 加1跳过4个字节,而a的一个元素大小是4个字节,所以打印的是at

笔试题8

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

f4d9d3f02e6f4e9586d2d926f8702b06.png这是关系图

这里我们要注意cpp会改变,**++cpp 就是cpp先加1,cpp指向c+2 的地址.解引用两次,结果是POINT

这里我们要清楚运算符的优先级,就是 + 的优先级很低 cpp 先加1 指向c + 1 的地址,然后解引用得出 c + 1 ,c + 1再减1,cp[2] = c,然后解引用得到字符E的地址, E的地址加3 ,结果为ER

前面的cpp = &cp[2]

*cpp[-2]相当于**(cp),然后加 3 ,结果为ST,注意一下,这里的cpp没有改变

cpp = &cp[2]

经过上面的操作 cp[2] = c

cpp[-1][-1] == cp[1][-1] == (c + 2)[-1] == c[1],最终结果为EW

C语言的进阶指针到这里就结束了,有不懂的小可爱可以私聊我

相关文章
|
1月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
101 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
1月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
77 9
|
1月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
51 7
|
2月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
53 1
|
9天前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
46 23
|
9天前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
34 15
|
9天前
|
C语言
【C语言程序设计——函数】亲密数判定(头歌实践教学平台习题)【合集】
本文介绍了通过编程实现打印3000以内的全部亲密数的任务。主要内容包括: 1. **任务描述**:实现函数打印3000以内的全部亲密数。 2. **相关知识**: - 循环控制和跳转语句(for、while循环,break、continue语句)的使用。 - 亲密数的概念及历史背景。 - 判断亲密数的方法:计算数A的因子和存于B,再计算B的因子和存于sum,最后比较sum与A是否相等。 3. **编程要求**:根据提示在指定区域内补充代码。 4. **测试说明**:平台对代码进行测试,预期输出如220和284是一组亲密数。 5. **通关代码**:提供了完整的C语言代码实现
48 24
|
5天前
|
存储 C语言
【C语言程序设计——函数】递归求斐波那契数列的前n项(头歌实践教学平台习题)【合集】
本关任务是编写递归函数求斐波那契数列的前n项。主要内容包括: 1. **递归的概念**:递归是一种函数直接或间接调用自身的编程技巧,通过“俄罗斯套娃”的方式解决问题。 2. **边界条件的确定**:边界条件是递归停止的条件,确保递归不会无限进行。例如,计算阶乘时,当n为0或1时返回1。 3. **循环控制与跳转语句**:介绍`for`、`while`循环及`break`、`continue`语句的使用方法。 编程要求是在右侧编辑器Begin--End之间补充代码,测试输入分别为3和5,预期输出为斐波那契数列的前几项。通关代码已给出,需确保正确实现递归逻辑并处理好边界条件,以避免栈溢出或结果
44 16
|
5天前
|
存储 编译器 C语言
【C语言程序设计——函数】分数数列求和2(头歌实践教学平台习题)【合集】
函数首部:按照 C 语言语法,函数的定义首部表明这是一个自定义函数,函数名为fun,它接收一个整型参数n,用于指定要求阶乘的那个数,并且函数的返回值类型为float(在实际中如果阶乘结果数值较大,用float可能会有精度损失,也可以考虑使用double等更合适的数据类型,这里以float为例)。例如:// 函数体代码将放在这里函数体内部变量定义:在函数体中,首先需要定义一些变量来辅助完成阶乘的计算。比如需要定义一个变量(通常为float或double类型,这里假设用float。
16 3

热门文章

最新文章