C生万物 | 指针进阶 · 炼狱篇-3

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: C生万物 | 指针进阶 · 炼狱篇

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

解析:

来分析一下本题该如何进行计算

  • 首先是给到了一个结构体,然后使用这个结构体定义出来一个结构体指针p,对其进行偏移的操作,那既然是结构体的话,就要先知道其大小,这里题目就给出了为20个字节,如果不懂的同学可以看看校招热门考点 —— 结构体内存对齐
  • 相信很多同学一看到这个0x1就懵了,不知道这是什么东西,0x的话代表一个十六进制,==在内存中我们表示地址一般用的都是十六进制==。那么题目给出条件说p的值为0x100000,我们知道进制之间是可以相互转换,其实这就是一个整型数值,那p是一个结构体,则怎么能指向一个整型地址呢,于是在三条打印语句的前面,我们还应该加上这句话,将这个地址强制类型转换成一个【结构体指针】类型


p = (struct Test*)0x100000;
  • 那接下去我们就来分析一下三条打印语句最后会输出的结果是什么
  • 首先是p + 0x1,对于0x1上面讲到过了是一个十六进制,那它就是十进制的1,这个表达式相当于就是p + 1,此时对一个结构体指针 + 1的话跳过的便是一个结构体,那结构体的大小我们刚才算了是20个字节,转换成十六进制变为【14】,所以最后的结果就是0x100014
  • 接下去第二个 (unsigned long)p + 0x1,这里将这个结构体指针p强转成一个无符号的长整型,那么现在这个p就不再是一个指针类型了,它就是一个整型,0x1也是整型,两个整型相加也就是我们小学就学过的计算题,最后的结果便是0x100001
  • 最后第三个(unsigned int*)p + 0x1,这里将这个结构体指针p强转成一个整型指针,然后再 + 1,那指针 + 1我们知道取决于它所指向的元素类型为int,那么 + 1便跳过了4个字节,最后的结果便是0x100004

运行结果

  • 最后打印结果来看一下【十六进制会将前面的0x转换为00

image.png

笔试题3

代码:


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

解析:

来分析一下本题该如何进行计算

  • 本题和第一题其实很类似,也是&a取到了整个数组的地址,然后 + 1跳过整个数组,再将其强转为int*类型的地址,便可以让ptr1指向这块地址
  • 第二个的话就有点新奇了,首先a既没有单独放在sizeof()内部,也没有&数组名,因此其代表的就是首元素的地址,那有同学就感到很奇怪,把一个地址强转为int,也就整型,真的可以吗?

💬 这当然是可以的,地址我们都是使用十六进制来表示的,强转为整型那其实就是转为十进制

  • 我们可以假设它的地址为0x00000015,强转之后就变成了 21,接下去再对这个整数 + 1那就变成了22,然后看到外面又有一个强制类型转换,转为int*,那也就是再把它转换成一个地址的形式,以十六进制来进行表示,即0x00000016
  • 如上你去对比一下上面这两个地址就可以知道,它们之间相差了一个字节的大小,那其实这样的操作使得ptr2指向了数组首元素地址的往后一个字节

光这么说说太抽象了,我们一起来画个图理解一下

  • 可以看到,这里我画出了这个数组在内存中的布局,因为放到内存中数组里面的每个数一定是以十六进制的形式来进行存放,即0x 00 00 00 010x 00 00 00 02这样,又因为数组元素在内存中都是连续存放的,所以我们可以将它们放在并排的位置上,==而且对于VS来说是以【小端】的形式进行存放,因此可以看出我是倒着画的==
  • 首先ptr2上面有分析过了,- 1的话往前访问4个字节的数据,那么也就刚好来到了04这个地方;而对于ptr1来说,它指向的位置则是01向后数一个字节,即00这个位置。又因为这两个指针的类型都是int*,所以在打印的时候可以访问4个字节的数据image.png


printf("%x, %x", ptr1[-1], *ptr2);
  • 现在要使用printf()将结果打印在屏幕上了,那既然我们以小端的形式倒着存入内存中,拿出来也要以小端的形式倒着拿,那么拿出来后,前者便是02 00 00 00,后者便是00 00 00 04。打印在屏幕上的话就为【200000】和【4】,会自动去除前导的0image.png

运行结果

  • 最后打印结果来看一下

image.png延伸拓展【汇编观察】

  • 我们在打印语句中加上这两句代码,通过ptr1ptr2去修改数组中的一些内容


ptr1[-1] = 1;
*ptr2 = 1;
  • 通过汇编可以查看到,数组a在内存中的存放形式,就是将我上图所画的内容分为四行即可

image.png

  • 接下去可以看到,通过ptr[-1] = 1这句代码,将数组中第四个元素改成了01 00 00 00,那么从内存中取出来便是00 00 00 01,那也就是【1】

image.png

  • 那对于ptr2来说,对其进行解引用便可以向后访问四个字节,可以看到数组第一个元素所占的后三个字节和数组第二个元素所占的第一个字节发生了修改【看红色标记】。我也将其改为了1,此时ptr2就实现了指定的字节访问并修改对应数据的操作

image.png来看看最终的结果验证一下,确实就是像我分析的那样

image.png

笔试题4

代码:


int main()
{
  int a[3][2] = { (0, 1), (2, 3), (4, 5) }; //逗号表达式
  int* p;   //*p一次访问四个字节
  p = a[0];
  printf("%d", p[0]);   //*(p + 0)
  return 0;
}
//程序的结果是什么?

解析:

来分析一下本题该如何进行计算

  • 首先可以看到,定义并初始化了一个3行2列的二维数组,然后声明了一个指针,将二维数组的首元素地址即第一行的地址赋给指针p,最后打印p[0]
  • 那是否看出哪里有问题呢?其实在第一行代码就出现了问题,仔细观察数组初始化的大括号{},里面的(0, 1), (2, 3), (4, 5)是二维数组的初始化吗? 如果忘了就在看看数组章节的内容吧,正确的初始化方式应该是{{0, 1}, {2, 3}, {4, 5}};外面是大括号,里面的每行也是大括号

💬 那有同学问:那这个里面的小括号()是什么呢?数组有初始化吗?

  • 还记得我们在操作符章节介绍的【逗号】表达式吗?忘了就再去看一下,对于(0, 1)编译器会将其当做是一个表达式,这整个表达式最后的结果是最后一个逗号后面的表达式,也就是【1】,那对于后面的也是一样,所以数组最后的初始化结果应该是{1, 3, 5}

我们通过画图来理解一下

  • 下面就是这个二维初始化完后的样子,因为每行只有2个元素,所以5初始化的就是第二行的第一列。此时再往下看到p = a[0],那么p就指向了这个二维数组第一行的地址,其实也就是&a[0][0]


p = a[0];

image.png

  • 此刻再去访问p[0]的话其实就是访问&a[0][0]这块地址上的内容,它也可以转换成*(p + 0),最后的结果就是【1】


printf("%d", p[0]);

运行结果

  • 最后打印结果来看一下

image.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;
}
//程序的结果是什么?

解析:

来分析一下本题该如何进行计算

  • 首先声明了一个五行五列的二维数组,还有一个数组指针,这个数组指针指向一个有4个元素,每个元素都是int的数组。接下去把a赋值给到指针p,a是单独出现的,因此表示的就是首元素地址,即第一行的地址。但是细心的同学一定发现二维数组的每一列都是5个元素,但是数组指针却只能存放有4个元素的一维数组

💬 那这不是乱套了吗?

  • 我们通过打印来看看,确实可以看出编译器报出了类型不兼容的问题,但是这不会有很大的影响,既然p只能存放4个元素的一维数组,那最后一个不要不就好了👈

image.png

所以其实可以初步感受到本题不是那么容易,接下去我通过画图来进行分析

  • 首先看到打印语句中的&a[4][2],其为a数组第5行第2列的元素所在的地址,在下面我也整个二维数组画成了并排的样子,这其实就是它在内存中真实存放的样子,那我们很快就可以定位到a[4][2]这个元素,然后取到它所在的这块地址
  • &p[4][2]呢?刚才我们分析到了指针p只能存放元素个数为4的数组,那在【指针初阶】的时候有讲到过==数组指针的类型决定了它所能访问的字节个数==,去掉指针名后,我们可以看出它类型是int (*)[4],所以 + 1可以一次向后访问4个字节,那么 + 2,+ 3呢?看看下图就一目了然了

image.png

  • 接下去我们要去取到&p[4][2],当数组指针p进行了4次偏移后,我们可以找到p + 4的位置,那根据指针和数组的转换公式可以得知*(p + 4)就可以取到这一行,那*(*(p + 4) + 2)就相当于p[4][2],具体可以看上图,那么对这个数组元素取地址&也就取到了它所在的这块地址

  • 最后,我们就要去打印&p[4][2] - &a[4][2]的结果了,分别是以【%p】和【%d】的形式来进行打印


printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
  • 那在内存中我们知道,左边是低地址,右边是高地址,那么通过图示就可以看出&p[4][2]的地址其实是要比&a[4][2]来得小的,那么前者 - 后者的话就会是一个负数,二者都是地址,地址在内存中其实就是指针,那根据前面所学过的知识,两个指针相减计算的是它们之间所相差的元素个数,那么从图中很明显可以看出它们之间相差的元素个数即为 4
  • 那么使用%d进行打印的时候最后的结果就是【-4

image.png💬 那使用%p进行打印呢?会是什么样子

  • 上面也有讲到过,若是使用%p进行打印的话最后就是以十六进制的形式显示,如果你有自己自己看做数据在计算机内部的存储,那可以知道在计算机内部都是二进制,而且都是以补码的形式在进行计算,不过输出到外设(显示器)上都是以原码的形式
  • 所以对于这个【-4】来说,我们要将其以%p也就是地址的形式打印出来,不过地址不讲究什么原、反、补的概念,所以它会将放到计算机内部的这个补码当做是地址进行打印,那我们还要将一串的二进制序列4位为一组转换成十六进制才可以,那最后的结果便是【FFFFFC】

image.png

运行结果

  • 最后打印结果来看一下

image.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", *(ptr1 - 1), *(ptr2 - 1));
  return 0;
}
//程序的结果是什么?

解析:

来分析一下本题该如何进行计算

  • 首先这还是一个二维数组,我依旧是把它画成了内存并排的样子,也是为了能够让读者更加清楚数组在内存中的布局,首先第一个ptr上面也有讲到过很多了,这里便不再赘述;然后是ptr2,aa即为数组首元素地址,那也就是第一行的地址,其类型为int (*)[5],+ 1跳过一整行,此时也是指向了第二行的地址,再对其进行*解引用也就访问到了第二行,最后再将其转换为int*类型赋给ptr2
  • 那么打印语句就很好理解了,ptr1 - 1指针往前偏移了4个字节,指向了数组元素10所在的这块地址,*解引用也就拿到了【10】,ptr2 - 1也是同理,因为二维数组在内存中也是连续存放的,所以6前面的元素即为5,此时拿到了数组元素【5】

image.png

运行结果

  • 最后打印结果来看一下

image.png

笔试题7

代码:


int main()
{
  char* a[] = { "work","at","alibaba" };
  char** pa = a;
  pa++;
  printf("%s\n", *pa);
  return 0;
}
//程序的结果是什么?

解析:

来分析一下本题该如何进行计算

  • 首先看到有一个数组a,它是一个【指针数组】,数组里面存放的每一个元素都是char*,从下图可以看来,数组里面的三个指针分别存放三个字符串的首元素地址。接下去又取出这个数组的首元素地址,使pa指向它,此时二级指针pa的类型即为char**,这第一颗*是在告诉我们pa所指向的类型是一个【char*】的地址,这后一个*则是在告诉我们pa它是一个指针

image.png

  • 那么此时pa++就跳过了一个【char*】的元素,指向了该指针数组a的第二个元素所在地址,然后又通过*pa解引用找到了第二个元素中的内容,然后一看它也指向一块地址,于是呢就顺着这个地址找到了"at"的【a】在内存中的地址,因为字符串在内存中的空间是连续的,所以最后使用%s就打印出了"at"这个字符串

image.png

运行结果

  • 最后打印结果来看一下

image.png

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

解析:

本题由于比较复杂,所以我全程通过图示来讲解,准备上车:car:

  • 首先看到,还是和上题一样,有一个指针数组分别存放了四个字符串的首字符地址,然后接下去,又有一个二级指针数组存放了这个指针数组每一行的地址,而且是倒着存放的,即cp的第一行存放的是c数组第四行的地址,cp的第四行存放的是c数组第一行的地址
  • 接下去呢又有一个三级字符指针指向了二级指针数组cp的首元素地址,即第一行的地址

image.png

  • 首先来看到第一个打印语句,首先++cpp,那么cpp就会向后访问一个一个char*的元素,来到了cp数组的第二行,接下去第一个*解引用就拿到了c + 2这个地址,那么就顺着这个地址找到了c数组所在的这行地址,然后第二个*解引用则是拿到了c数组这一行的地址中所存放的内容,一看是一个地址,便顺着这个地址找到了P,然后使用%s进行打印最终的结果便是【POINT】


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

可以看一下动图演示

https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/80c029266d784463bae02516cc183a8e~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.awebp#?w=901&h=372&s=444459&e=gif&f=154&b=fffefe

好,接下去再来看第二个,这句的话应该算是最复杂了,不过我们一一分析也不是什么难事

  • 首先看到++cpp的优先级最高,那么它就来到了cp数组的第三个位置,接下去进行*解引用,就拿到了这一行数组中的内容,即c + 1,接下去又进行了一个操作是--,那也就是对我们取出的c + 1进行运算即c + 1 - 1 = c,那么此时里面存放的就不再是c + 1这块地址,而是c这块地址,顺藤摸瓜找到了这块地址后再对其进行解引用,此时也就找到了E所在的地址,最后再 + 3即向后偏移3个字节也就是3个字符,就来到了后面的E所在的位置,使用%s进行打印便打印出了后面的【ER】


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

看一下动图演示


好,接下去第三句打印,我们再来看看

  • 首先对于下面的表达式可以写成这种形式* *(cpp - 2) + 3,意思就是先让cpp向前偏移2个char**的位置,然后找到这个地址中所存放的值为c + 3,顺着这个地址找到了数组c这一行所在的地址,可以看到最前面还有一个*解引用,那么就获取到了字符F的地址,最后再 + 3然后以%s的形式打印,结果便是【ST】


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

来看最后一个,对指针的功底也要很深厚✒

  • 对于下面的表达式可以写成*(*(cpp - 1) - 1) + 1,那么首先就是将cpp向前偏移一个char**的位置【注意上一题cpp的移动不会影响此题】,然后对其进行*引用拿到cp数组第二行的内容,为c + 2,接下去对这个内容再 - 1,即为c + 2 - 1 = c + 1,那么此时它便指向了数组c中c + 1的这块,然后别忘了最前面还有一个*解引用,那么此时就拿到了c + 1这块地址中所存放的内容,是一个地址,继续顺藤摸瓜便找到了N,但是最后还有一个 + 1,那么就偏到了E,以%s进行打印最后的结果即为【EW】


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

运行结果

  • 最后打印结果来看一下【全体起立!!!】

image.png

视频解说📺

这里附上这道题的视频讲解版,上传到b站了,同学们可自己配合文章观看学习📺

[video(video-wFU5FUcV-1683212540615)(type-bilibili)(url-player.bilibili.com/player.html…)]


✍总结与提炼

好,来总结一下本文学习到的内容:book:

【指针初阶 · 入门篇】

在初阶篇中我们初步认识了什么是指针,主要照顾到对指针不够了解的同学,可以先有一个基本的概念

  • 首先我们了解了什么是指针,知道了【指针】、【地址】、【内存】三者之间的关系,清楚了内存中的指针和我们口头上所说的指针有和区别。还记得一个指针是几个字节吗❓
  • 初步认识指针后我们进一步加深了对指针的理解,明白了不同类型的指针所存在的意义:1.访问字节的范围 2.类型决定步长。然后我们就来谈了谈【野指针】,知道了在写代码的时候为何会产生野指针,也明白了如何对野指针去进行一个规避
  • 对指针有了一个理解后便开始上手操作指针,使用指针去进行一个运算。当然也清楚了指针和数组之间的关联
  • 最后呢我们浅浅地谈了谈二级指针,知道了什么是二级指针,以及它和指针与普通变量之间的区别;

【指针进阶 · 提升篇】

在进阶篇中我们对指针有了进一步的认识,清楚了字符指针的作用,辨析了指针常量与常量指针、指针数组与数组指针、指针函数与函数指针

  • 对于字符指针而言,它既可以指向单个字符,也可以指向一个字符串,其保存的便是这个字符串首字符的地址。有了这些基础知识后,我们就做了一道【剑指offer】的题目,还记得常量字符串的地址在内存中保存几份吗🙄
  • 听说*const也会擦出🔥火花🔥 对于指针常量来说,指针自己就是一个常量,不可以修改它的指向,但却可以修改其所指向地址中的内容,例:int* const p;对于常量指针来说,其所指向的地址中的内容是一个常量,不可修改,但却可以去修改它的指向,例:const int* p。有记忆口诀后相信很快分清它们之间的区别了。最后,还记得为了买一份凉皮而分手的情侣吗🤣
  • 指针还是数组真是傻傻分不清😵 对于指针数组来说,它是一个【数组】,数组里面存放的每一个元素都是指针,例:int* arr[5];对于数组指针来说,它是一个【指针】,这个指针所指向的是一个数组的地址,例:int (*p)[5]
  • 有了这些基础知识后,将指针与数组同函数去进行一个结合,实现一个传参,最好每一个下去都自己思考着想一遍,应该传递什么样的实参,形参又用什么来接受,==是一级指针呢,还是二级指针?是指针数组呢,还是数组指针?==
  • 接下去,真正地将指针融入到了函数中。首先我们讲了简单一些的指针函数,它就是一个普通的函数,只是返回类型是一个指针。不过要小心,我们不可以返回函数内部所申请的临时空间,因为它除了当前函数的作用域就销毁了,因此我特意在堆上去申请空间。例:int* Open(int, int)
  • 然后就是较难理解的函数指针了,它本质是一个指针,所指向的是一个函数的地址,例:int (*p)(int, int) = &Add当然你在把函数的一直给到指针的时候也可以不加&,那也就相当于是【赋值】了,因此在使用函数指针我们可以不加前面的*,原本的调用形式是(*p)(3, 4),但你也可以写成p(3, 4)
  • 有了上面的基础知识后,我讲解了两道《C陷阱与缺陷》中的代码题,听完我的分析后你还对它们存在恐惧吗😆


(*(void (*)())0)();


void (*signal(int, void(*)(int)))(int);
  • 指针和数组又碰到一起了,原来还有存放函数指针的数组😮,这简直是太奇妙了。有了它,在面对多个函数逻辑的时候,我们不需要再写一个庞大的switch...case语句,而是直接使用【函数指针数组】存放这些函数的地址,这样就可以通过数组下标的控制去访问对应的函数了,例:int (* pfArr[2])(int, int) = {Add, Sub};还记得我们实现的 转移表 吗?
  • 那既然有数组指针这个东西,可以存放一个数组的地址,那我们上面所说的函数指针数组的地址也可以存放到一个指针中去,它就叫做【指向函数指针数组的指针】,既然它是一个指针,就要和*先结合,我们不需做太大的改动,只需要在函数指针数组的基础上给指针名ppfunArr前面加上一个*即可,不过为了防止其和[]先结合记得加上()哦。例:void (*(*ppfunArr)[5])(const char*) = &pfunArr;
  • 提升篇的末尾,我又讲了一个东西叫做【回调函数】,它是函数指针的一个经典应用,在实际开发的场景中也是被广泛地使用。我们可以将一个函数的地址传递给另一个函数,那这就需要另一个函数提供一个函数指针的参数。在这个函数内部,如是在某种特定条件成立的情况下,我们便可以通过这个函数指针去找到这个函数的地址,那么此时这个被调用的函数就被称为回调函数。知道了这些后,还记得回调函数使用的三种场景吗?
  • [ 场景一 ]:模拟计算器的加减乘除。这里我们没用用到函数指针数组,而是将一开始的计算器做了一个修改
  • [ 场景二 ]:模拟qsort函数。这个场景尤为重要,我画了大量的精力进行讲解,配合图示,希望读者可以理解回调函数被调用的整个流程
  • [ 场景三 ]:模拟文件下载模块。这个算是拓展模块,如果有学习过C++的同学可以看看,是一家公司某年的面试题

【指针进阶 · 炼狱篇】

在炼狱篇中我们对指针有了更加深层次的一个理解,主要是围绕指针与数组混搭的一些笔试题来进行学习

  • 首先的话我们又去看了看指针的大小,此时不仅仅是一开始的整型指针、字符型指针,我们还去观察了指针常量、常量指针、指针数组、数组指针、指针函数、函数指针以及二级指针等等,同是在32位平台下进行运行,它们的大小均为4个字节
  • 有了上面这些基础后,我们就可以进行大量的练习了,首先我给出了作业题中错的多而且需要经过一定的思考才可以做出来的一些题目,若是你仔细地看了这几道题的话,对指针运算、指针偏移、指针访问字节数、指针数组、数组指针以及函数指针的理解一定又能更上一层楼
  • 接下去,我们就进入了笔试题的学习,首先的话我们通过【数组与指针】的混合来辨析sizeof()strlen()之间的区别,通过回顾了前面的字符指针、字符数组、一维数组、二维数组,来很好地明确指针的大小是多少、一个数组元素的大小是多少、一个数组的大小又是多少,以及指针 + 1可以跳过几个字节,可以访问到后面的多少数据
  • 最后的话,我们就来到了指针相关历年笔试真题的学习,通过八道笔试真题的演练,相信你也清楚了指针在实际的校招中是如何去进行考察的,若是没有像我这样一步步地分析、画图、调试,想要解出来这些题目还是比较困难的,尤其是最后一题,涉及到三级指针,因此我专程录了一个视频📺做讲解,希望读者可以理解

📚推荐书籍阅读


==第一本:《C和指针》==

复制代码

本书给出了很多编程技巧和提示,每章后面有针对性很强的练习,对初学指针的同学非常友好,推荐先行阅读

image.png==第二本:《C陷阱与缺陷》==

复制代码

本书分别从词法分析、语法语义、连接、库函数、预处理器、可移植性缺陷等几个方面分析了C编程中可能遇到的问题,适合有一定开发基础的C程序员进行阅读

image.png==第三本:《深入理解C指针》==

复制代码

本书专门研究指针,旨在提供比其他图书更全面和深入的C 指针和内存管理知识,适合进阶阅读学习

image.png


以上就是本文要阐述的所有内容,我花了两个月的时间整理了本文,诣在帮助广大读者可以真正学懂指针,了解指针,知道指针其实并不是那么可怕的,只要你去学会去理解、通过画图思考分析,总能够明白一些👉 ==你,也是可以学好指针的== 👈

非常感谢您对本文的阅读,如果疑问可于评论区提出或者私信我:rose::rose::rose:

相关文章
|
3月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
3月前
|
C语言
指针进阶(回调函数)(C语言)
指针进阶(回调函数)(C语言)
|
3月前
|
存储 C语言 C++
指针进阶(函数指针)(C语言)
指针进阶(函数指针)(C语言)
|
3月前
|
编译器 C语言
指针进阶(数组指针 )(C语言)
指针进阶(数组指针 )(C语言)
|
3月前
|
搜索推荐
指针进阶(2)
指针进阶(2)
39 4
|
3月前
指针进阶(3)
指针进阶(3)
31 1
|
3月前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
34 0
|
24天前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
24天前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
|
24天前
|
存储 编译器 C语言
【C初阶——指针3】鹏哥C语言系列文章,基本语法知识全面讲解——指针(3)
【C初阶——指针3】鹏哥C语言系列文章,基本语法知识全面讲解——指针(3)
下一篇
DDNS