C语言指针完全指南:从繁冗到明晰,掌握指针的八种核心用法
大家好,欢迎来到【小孙的嵌入式工坊】!大家是否经常看见数组指针、结构体指针、函数指针等绕的蒙头转向,不用担心,这篇文章带大家梳理一下指针的各种指向,让大家面对指针的各种应用不再迷茫。
首先我们先来回顾一下,什么是指针:指针本质上就是一个变量,但是他与普通的变量不同,他内部存储的是内存地址,而不是具体的数据值,通过指针,我们可以直接操作内存,实现间接引用。
回顾完指针的基本概念之后,我们来讲讲指针都有哪些核心用法
一、基本数据类型指针
大家知道C语言有几种常见的基本数据类型,整型有char、short、int、long;浮点类有float、double、long double。指针的具体用法如下:
我们可以看到,这时候指针指向各个数据类型,存储他们的地址,如果我们解引用,就可以得到指向的变量的值
二、数组指针
数组名本身就是一个指针常量,指向数组首元素的地址,我们以一维数组举例:
我们可以看到,使用数组名和使用指针,都可以进行数组的遍历操作,但是我们要注意一点区别,第一种用数组名+i的操作,随着for循环,每次加的i是递增的,第一次是arr[0]+0,第二次是arr[0]+1,最后是arr[0]+4;但是第二种指针就不同,他是依次的进行遍历,是不断的进行+1操作。
二维数组也是同理,我们简单验证一下:
同样给大家拓展一点,大家有没有发现,这里我在获取数组的时候,并没有用*去解引用,而是直接输入的ptr[i][j],这里其实就涉及到C语言语法上的一个特点:编译器自动对数组进行了解引用操作,意思就是arr[i]经过编译器的自动解引用后,实际上完全等价于*(arr+i),ptr[i]与*(ptr+i)也是完全等价的。
三、结构体指针
定义了一个stu1的结构体,通过stu_ptr去指向stu1的地址,随后改变stu_ptr的姓名、年龄、得分,调用printstudent函数,将stu_ptr存储的的地址(注意,并不是将stu_ptr自身的地址传递过去)传递给stu结构体,进行打印操作。
四、函数指针
给大家梳理一下结构,比如add函数:
首先我们定义了三个函数,分别是add、subtract、multiply三个函数,返回值就是他们函数的运算结果;接着我们定义一个计算函数calculate,这里其实涉及到回调函数的概念,我们可以先把calculate函数理解为平台,上面的三个函数就是具体的工具,在这个平台上面,我们只需要告诉他需要的参数,他就可以开始执行;在主函数中,我们定义了一个函数指针*(func_ptr)(int ,int),将add函数的地址传递给这个指针,随后进入calculate这个平台,将x、y、以及add函数的地址都传递过去,这里要注意,只是把add这个函数传递过去了,但是operation的两个参数是由calculate的int a和int b传的,也就是x和y。传递之后,operation其实就相当于是add函数了。随后执行a+b的操作。
五、动态内存指针
使用malloc、calloc等函数动态分配内存
首先定义整型指针arr,通过malloc函数动态分配内存空间,此时arr指向分配内存的首地址。通过指针算术运算arr[i]来访问和操作内存块,这实质上是*(arr + i)的语法糖,使用完毕后,通过free(arr)释放指针所指向的内存区域,完成资源清理。
六、void指针
void类型指针,他是泛指型指针,可以指向任何数据类型,但是他不能直接解引用,必须要转换成具体的数据类型才能访问数据,比如第一个打印操作中, *(int*)void_ptr其实就涉及到两个转换,第一步是(int*)void_ptr,将void指针转换为int指针,第二步*(int*)void_ptr就是解引用转换后的int指针,获取整数值。后面的float和char都是同理。
七、多级指针(指向指针的指针)
大家都知道的指针存储的是地址,那么如果一个指针存储的地址是另一个指针的地址,这时候就叫做多级指针,相当于嵌套
我们可以看到ptr存储的是value的地址,而pptr存储的就是ptr的地址、ppptr存储的就是pptr的地址,那么对应的解引用就是加上相对数量的*,比如一开始的时候*ptr就可以得到value的值,pptr要想得到value的值就需要两个*,ppptr就需要三个*。
八。常量指针与指针常量
我们分为三部分来看:
第一部分,指向常量的指针,const int *ptr1 = &a,为什么不能修改指向的值,而能修改指针指向:因为const在int前面,表示指向的数据是常量,而指针本身不是常量,可以指向其他地址。
第二部分,指针常量,int *const ptr2 = &a,为什么能修改指向的值,而无法修改指针指向:因为指向的数据不是常量,而const在指针变量名前面,表示指针本身是常量
第三部分,指向常量的指针常量,const int *const ptr3 = &a,就是结合了前两部分。
一个区分的小技巧:const后面最近的是谁,那么谁就改不了,既不能修改指向的值,也不能修改指针指向。
最后强调几个指针经常出现的错误:
1.未初始化指针,就会变成野指针,这时候有几种方法,我们可以采取其中一种方式比如让指针指向NULL(空)。
2.指针越界,这个数组遍历的时候可能会遇到超出数组长度的情况。
3、动态内存管理要配对,malloc之后一定要有free去释放内存。
暂时先列举这么多,后续我们会对在嵌入式中常见的指针用法进行更加详细的介绍,让指针不再是漂浮的符号,而是落地的工具。
如果觉得这篇“动手指南”对你有帮助,请别忘记关注【小孙的嵌入式工坊】。在这里,我们一起动手,把代码变成现实。
— 小孙