单片机软件架构连载(2)-指针

原创 无际单片机编程 2024-07-02 10:57

你点击蓝字关注,回复“入门资料”获取单片机入门到高级开挂教程

 开发板带你入门,我们带你飞

文 | 无际(微信:2777492857)

全文约11693字,阅读大约需要 20 分钟



我工作了10年,大大小小做过几十个项目,用指针解决过很多实际产品的痛点,比如写过小系统,数据结构(队列,链表),模块化编程等等.....

今天贴近实际,给大家总结了c语言指针常用的知识点,吃透指针并灵活应用,你会发现了c语言的"新大陆"。

长文预警,全文13271,写了4天3夜,手指现在还瑟瑟发抖,如果有帮助,麻烦看官们安排个三连。


1. 为什么说指针是 c 语言的灵魂?

因为它提供了对内存的直接控制能力,这是 C 语言和其它高级编程语言的关键特点之一。


基于这个特点,能衍生出非常多灵活的应用,比如内存管理、数组操作、函数参数传递、数据结构、硬件寄存器配置等等。


这样说,可能有点抽象,不要紧,我刚开始接触指针,也是这感觉。


我也是做了单片机开发 6,7 年以后,独立做了一些网关产品,有 Ble、WiFi、4G,研究过一些大厂写的 sdk,也学习过同事的编程思维,吃透他们写的代码,然后才对指针有了更深的理解。


为此,我摸索了很多年,因为网上大多数都是零散的基础内容,真正深入系统的很少。


面临开发瓶颈的我,就像拾荒者,协议栈捡一点,同事那里捡一点,然后消化用于项目上,才慢慢形成系统的知识体系,这个体系,我愿称为程序架构。


其实复杂项目做不稳定,问题的本质,就是缺失程序架构设计和模块化编程能力


而要实现可扩展性,可移植性强的程序架构,指针绝对是绕不开的坎。


有时,我和无际单片机项目特训营的铁子开玩笑说,如果我当时能碰到这些项目就太爽了,少走好几年弯路。


因为项目 3 和项目 6 每个功能的模块化设计,以及整体程序架构,可以说是我们整个职业生涯的结晶。


下面废话不多说,直接开整。


2. 指针变量

2.1 指针变量定义

指针变量定义的格式如下:

变量类型 *变量名

举例:

unsigned char *p;

unsigned char 是数据类型,p 是变量名。


2.2 指针变量赋值

指针变量在使用前,一定要先赋值(指向一个地址)

给指针变量赋的值和普通变量不同,给指针变量赋值只能是地址

那么怎么获取普通变量的地址呢,在 C 语言里可以使用”&”来获取普通变量的地址,一般用以下格式来表示:

&变量名


举例:

// 包含标准输入输出库,用于printf函数等
#include 

// 定义一个无符号字符变量a,其取值范围是0到255
unsigned char a;
// 定义一个指向无符号字符的指针变量p
unsigned char *p;

// 主函数入口点
int main()
{
   // 将值10赋给变量a
   a = 10;
   // 让指针p指向变量a的地址
   p = &a;
   // 打印变量a的地址,%x代表以十六进制形式,\r\n代表回车和换行符
   printf("a=0x%x\r\n",&a);
   // 打印指针p所指向的地址,即变量a的地址,以十六进制形式,后面跟一个回车换行符
   printf("P=0x%x\r\n",p);
   // 打印变量a的值,%d代表以十进制形式,即10
   printf("a=%d\r\n",a);
   // 打印指针p所指向的值,即变量a的值,由于p指向a,所以打印的也是10
   printf("*p=%d\r\n",*p);
   // 函数返回0,表示程序正常结束
   return(0);
}

这段代码演示了如何在 C 语言中使用指针,首先定义了一个无符号字符变量 a 和一个指向无符号字符的指针 p。

然后在 main 函数中,将 a 的值设置为 10,并将 p 指向 a 的地址。接着打印出 a 和 p 的内存地址,以及 a 的值和通过指针 p 访问到的值。


代码输出结果如下:

我们来分析下输出结果:

a=0x404090 - 变量 a 在内存里的存储地址

p=0x404090 - 指针变量 p 指向的地址,因为程序把 a 的地址赋值给 p,所以 p 指向 a 的存储地址。

a=10 - 变量 a 的值

*p=10 - 指针变量 p 指向地址里的值,p 指向地址是 0x404040,而这个地址里存储的值为 10,所以*p=10。


普及两个符号的定义

&” - 取变量地址,“*” - 取地址的值

所以:

&a 代表取 a 在内存里的存储地址

*p 代表取 p 指向那个地址里面存储的值


发散思考?

你有没有想过,既然用符号"*“可以取地址的值,那我直接用*0x404090,是否能取到变量 a 的值?


有想法,就要去做实验,错了也没关系,这是成长必经过程。

我增加了第 14 行代码,编译后出现了一个错误:invaild type argument of 'unary *'

这个错误大概意思就是,我试图对一个不是指针类型的东西,使用*操作符。

对于编译器来说,0x404090 可能识别为一个值,而不是一个地址,所以属于语法错误


我继续改进代码,把 0x404090 这个十六进制值,强制转换成一个地址。

((unsigned char *)0x404090))代表把 0x404090 这个值,强制转换成一个无符号字符类型指针,相当于告诉编译器,这是一个地址。

强制转换的类型要和变量 a 的数据类型保持一致,因为我们想直接通过地址,把变量 a 的值读出来。

最后通过*,把这个地址的数据读出来,结果等于10。

*p 就像是*((unsigned char *)0x404090)的缩写版本。

那可不可以理解成,p 其实就是一个地址?

我个人觉得,这个条件是成立的,概念无所谓,最主要的是通过实践,能验证理论的正确性。

这个例子,*p 可以替代*((unsigned char *)0x404090),其实就初步体现了指针的方便和灵活性了。


2.3 指针变量也占内存

指针本身也是一个变量,也有自己的地址,需要内存存储。

那是不是意味着,指针变量本身也有一个存储的地址。

我们直接来做个实验:

我增加了一条语句,用来打印指针变量 p 存储地址,通过&p 即可获得 p 的在内存中的存储地址。

输出结果显示指针变量 p 的存储地址是 0x4040A0

我再增加一条语句,看下 0x4040A0 这个地址下,存储的是什么值。

没有意外,输出结果 0x4040A0 地址存储的值是 0x404090,即 p 指向的地址(变量a的地址)。

这里需要注意的是,p 指向的值,是 4 个字节的地址,所以要正确输出,需要强制转化成 unsigned int 类型。

这样刨根问底,还挺有意思的,指针概念过于抽象,有时指来指去,都不知道指到哪去了,所以有疑问时,最好的方式,就是直接做实验,看地址的变化。

通过以上的学习,我觉得初学者必须亲自动手做做实验,通过一系列的实验,掌握指针的底层逻辑,为后续指针的高阶应用打下扎实的基础,而不是停留在看懂和记概念。


2.4 通过指针改变某个内存地址里的值

前面我们可以通过指针,去获取某个内存地址的值,那可不可以通过指针,改变某个内存地址里的值呢?

答案是当然可以


格式:

*指针变量 = 数值

如:*p = 20;


代码举例:

前面我们把 p 指向变量 a 的地址,然后通过*p 修改该地址下存储的值,最终其实就是修改的变量 a 的值。


最终代码:

#include 

unsigned char a;
unsigned char *p;

int main()
{
   a = 10;
   p = &a;
   printf("a=0x%x\r\n",&a);
   printf("P=0x%x\r\n",p);
   printf("a=%d\r\n",a);
   printf("*p=%d\r\n",*p);
   printf("*0x404090=%d\r\n",*((unsigned char *)0x404090));
   
   printf("&p=0x%x\r\n",&p);
   printf("*0x4040A0=0x%x\r\n",*((unsigned int *)0x4040A0));
   
   *p = 20;
   printf("a=%d\r\n",a);
   printf("*p=%d\r\n",*p);
   
   return(0);
}


3. 数组与指针

一般编译器会分配连续地址的内存,来存储数组里的元素。


3.1 数组当指针用

其实数组,本质上也是指针。

我们来做个实验:

#include 

// 定义一个无符号字符数组buff,初始化为{1, 2, 3, 4, 5},数组长度为5
unsigned char buff[5] = {1,2,3,4,5};


// 主函数入口点
int main()
{

   // 打印数组buff的首地址,以十六进制形式
   printf("buff=0x%x\r\n",buff);
   // 打印数组buff第一个元素的地址,以十六进制形式
   printf("&buff[0]=0x%x\r\n",&buff[0]);
   
   //打印buff[0]的值,通过指针的形式访问
   printf("buff[0]=%d\r\n",*buff);
   
   // 打印一个空行以便于区分下面的输出
   printf("\r\n");
   
   // 程序正常结束并返回0
   return(0);
}


代码输出结果:

我们分别打印了 buff 和&buff[0]的地址,发现地址是一样的

既然是个地址,那应该可以是用指针的形式,去访问地址里存储的值的。

然后我打印*buff 的值,得到的结果是 1,正好和 buff[0]数组的值对应。

得出结论,数组也可以用指针的形式去用


3.2 通过指针变量引用数组

也可以通过定义指针变量来引用数组,读写数组里的元素。

我们来做个实验:

#include 

// 定义一个无符号字符数组buff,初始化为{1, 2, 3, 4, 5},数组长度为5
unsigned char buff[5] = {1,2,3,4,5};

// 定义两个指向无符号字符的指针变量p1和p2
unsigned char *p1;
unsigned char *p2;

// 主函数入口点
int main()
{
   // 将指针p1指向数组buff的首地址
   p1 = buff;
   // 将指针p2指向数组buff的第一个元素的地址,这和p1指向的是同一个地址
   p2 = &buff[0];
   
   // 打印数组buff的首地址,以十六进制形式
   printf("buff=0x%x\r\n",buff);
   // 打印数组buff第一个元素的地址,以十六进制形式
   printf("&buff[0]=0x%x\r\n",&buff[0]);
   // 打印指针p1的地址,由于它指向buff,所以打印的是buff的地址
   printf("p1_addr=0x%x\r\n",p1);
   // 打印指针p2的地址,由于它也指向buff的第一个元素,所以打印的也是buff的地址
   printf("p2_addr=0x%x\r\n",p2);
   
   // 打印一个空行以便于区分下面的输出
   printf("\r\n");
   
   // 打印数组buff的第一个元素的值,即1
   printf("buff[0]=%d\r\n",buff[0]);
   // 通过指针p1解引用,打印它指向的值,即数组buff的第一个元素的值,也是1
   printf("*p1=%d\r\n",*p1);
   // 通过指针p2解引用,打印它指向的值,即数组buff的第一个元素的值,同样是1
   printf("*p2=%d\r\n",*p2);
   
   // 程序正常结束并返回0
   return(0);
}


代码输出结果如下:

注意:buff 和&buff[0]都是代表数组首地址(即 buff[0]的存储地址)。

从输出结果来看,数组和指针变量的地址都是一样的,所以大家用这几种写法,都是可以读写数组里的值的。

也可以修改指针指向地址的值,达到修改数组值的效果:

#include 

unsigned char buff[5] = {1,2,3,4,5};

unsigned char *p1;
unsigned char *p2;

int main()
{
   p1 = buff;
   p2 = &buff[0];
   printf("buff=0x%x\r\n",buff);
   printf("&buff[0]=0x%x\r\n",&buff[0]);
   printf("p1_addr=0x%x\r\n",p1);
   printf("p2_addr=0x%x\r\n",p2);
   
   printf("\r\n");
   printf("buff[0]=%d\r\n",buff[0]);
   printf("*p1=%d\r\n",*p1);
   printf("*p2=%d\r\n",*p2);
   
   printf("\r\n");
   
   *p1 = 7;
   printf("buff[0]=%d\r\n",buff[0]);
   printf("*p1=%d\r\n",*p1);
   printf("*p2=%d\r\n",*p2);
    
   return(0);
}


代码输出结果:



4. 指针自加自减运算

指针加减运算,常用有以下几种:

p++; //等同于p = p+1;
p--; //等同于p = p-1;
p += 1; //等同于p = p+1;
p -= 1; //等同于p = p-1;

当然,加减法运算的值不限于 1,也可以是其它数,前提是加减完后,仍是有效地址。


p += 9; //等同于p = p+9;
p -= 9; //等同于p = p-9;

注意:加或减运算,是指加减整个指针类型的长度。


我们做个实验:

#include 

// 定义一个无符号字符数组buff,初始化为{1, 2, 3, 4, 5}
unsigned char buff[5] = {1,2,3,4,5};
// 定义一个无符号整型数组buff2,初始化为{10, 11, 12, 13, 14}
unsigned int buff2[5] = {10,11,12,13,14};

// 定义一个指向无符号字符的指针p1
unsigned char *p1;
// 定义一个指向无符号整型的指针p2
unsigned int *p2;

// 主函数入口点
int main()
{
   // 将指针p1指向数组buff的首地址
   p1 = buff;
   // 将指针p2指向数组buff2的首地址
   p2 = buff2;
   
   // 打印数组buff和buff2的地址,以十六进制形式
   printf("buff=0x%x, buff2=0x%x\r\n",buff,buff2);
   // 打印指针p1的地址和它指向的值(即buff的第一个元素1)
   printf("p1_addr=0x%x, *p1=%d\r\n",p1,*p1);
   // 打印指针p2的地址和它指向的值(即buff2的第一个元素10)
   printf("p2_addr=0x%x, *p2=%d\r\n",p2,*p2);
   
   //将指针p1向前移动一个单位,现在它指向buff的第二个元素
   p1++;
   // 将指针p2向前移动一个单位,现在它指向buff2的第二个元素
  p2++;
   
   // 打印一个空行以便于区分下面的输出
   printf("\r\n");

   // 打印移动后的指针p1的地址和它现在指向的值(即buff的第二个元素2)
   printf("p1_addr=0x%x, *p1=%d\r\n",p1,*p1);
   // 打印移动后的指针p2的地址和它现在指向的值(即buff2的第二个元素11)
   printf("p2_addr=0x%x, *p2=%d\r\n",p2,*p2);
   
   //将指针p1向前移动三个单位,现在它指向buff的第五个元素
   p1 = p1+3;
   //将指针p2向前移动三个单位,现在它指向buff的第五个元素
   p2 = p2+3;
   printf("\r\n");

   printf("p1_addr=0x%x, *p1=%d\r\n",p1,*p1);
   printf("p2_addr=0x%x, *p2=%d\r\n",p2,*p2);
   
   // 程序正常结束并返回0
   return(0);
}


代码输出结果如下:


p1++等同于p1=p1+1,这里加1,是指把p1这个地址加上1个字节,因为p1是unsigned char类型,这个类型占用内存1个字节的存储空间。

p2++等同于p2=p2+1,这里加1,是指把p1这个地址加上4个字节,因为p2是unsigned int类型,这个类型占用内存4个字节的存储空间


5. 双重指针

指针变量可以指向字符型、整型、数组等,当然也可以指向指针变量,指向指针类型变量时,也叫双重/二级指针

定义方法:

数据类型 **指针变量名;

例如:unsigned char **p;


直接上实验:

#include 

// 定义一个无符号字符变量
unsigned char a;
// 定义一个指向无符号字符的指针p1
unsigned char *p1;
// 定义一个指向无符号字符的指针的指针p2,即p2是一个双重/二级指针
unsigned char **p2;

// 主函数入口点
int main()
{
   // 将变量a的值设置为10
   a = 10;
   // 将指针p1指向变量a的地址
   p1 = &a;
   // 把p1指针变量在内存中的存储地址,赋值给p2双重指针变量。
   p2 = &p1;
   
   // 打印变量a的地址和a的值,地址以十六进制形式打印,a的值以十进制形式打印
   printf("&a=0x%x, a=%d\r\n",&a,a);
   // 打印指针p1的地址和p1指向的值(即变量a的值),地址和值都以十六进制和十进制形式打印
   printf("p1=0x%x, *p1=%d\r\n",p1,*p1);
   
   //p2 - p2变量的存储地址
   //*p2 - p2指向的值(即p1变量的存储地址)
   //**p2 - p1指向的值(即变量a的值)
   printf("p2=0x%x, *p2=0x%x, **p2=%d\r\n",p2,*p2,**p2);
    // 程序正常结束并返回0
   return(0);
}


代码输出结果:


我们重点来讲下双重指针变量 p2:

p2 - p2 变量的存储地址
*p2 - p2 指向的值(即 p1 变量的存储地址)
**p2 - p1 指向的值(即变量 a 的值)


”*”这个运算符是从右到左进行运算的,**p2 就是*(*p2),先取指向地址,再取指向地址里面存储的值


指针容易把人搞晕的就是,指针变量本身的存储地址和指向的地址分不清楚,这个是两个概念


一般在单片机程序中,尽量少使用这种指向指针的指针,防止出现 Bug 的时候非常难排查,目前我就在队列中使用过。


6. 二维数组与指针

二维数组和双重指针有点类似。

前面我们讲了数组也能当指针用,通过指针可以访问数组任意下标的值。

我们直接上实验:

// 定义一个无符号字符数组buff,初始化为{1, 2, 3, 4, 5},数组长度为5
unsigned char buff[5] = {1,2,3,4,5};

// 主函数入口点
int main()
{
   // 打印数组buff的首地址,以十六进制形式
   printf("buff=0x%x\r\n",buff);
   // 打印数组buff第一个元素的地址,以十六进制形式
   printf("&buff[0]=0x%x\r\n",&buff[0]);
   
       // 打印buff[0]的值
   printf("buff[0]=%d\r\n",*buff);
       // 打印buff[1]的值
   printf("buff[1]=%d\r\n",*(buff+1));
   // 打印buff[0]的值
   printf("buff[2]=%d\r\n",*(buff+2));
   // 程序正常结束并返回0
   return(0);
}


代码输出结果如下:

通过结果,我们可以看到,一维数组,数组名即指针,可以通过指针加/减运算,灵活访问数组每个元素的值。

那二维数组,又怎样通过二维指针的方式用呢?

请注意,请注意,建议你找个没人打扰的角落,因为马上就要进入烧脑实验啦!!!!


下面实验代码来喽:

#include 

// 定义一个3行2列的二维数组buff
unsigned char buff[3][2] = {{1,2},{3,4},{5,6}};

int main()
{
  // 打印buff数组第一行第一列元素的地址和值
   printf("&buff[0][0]=0x%x, buff[0][0]=%d\r\n",buff,**buff);
   // 打印buff数组第一行第二列元素的地址和值
       printf("&buff[0][1]=0x%x, buff[0][1]=%d\r\n",(*buff)+1,*((*buff)+1));

       // 打印一个空行,用于分隔输出
       printf("\r\n");

       // 打印buff数组第二行第一列元素的地址和值
       printf("&buff[1][0]=0x%x, buff[1][0]=%d\r\n",buff+1,*(*(buff+1)));
       // 打印buff数组第二行第二列元素的地址和值
       printf("&buff[1][1]=0x%x, buff[1][1]=%d\r\n",(*(buff+1))+1,*((*(buff+1))+1));

       printf("\r\n");

       // 打印buff数组第三行第一列元素的地址和值
       printf("&buff[2][0]=0x%x, buff[2][0]=%d\r\n",buff+2,*(*(buff+2)));
       // 打印buff数组第三行第二列元素的地址和值
       printf("&buff[2][1]=0x%x, buff[2][1]=%d\r\n",(*(buff+2))+1,*((*(buff+2))+1));

   return(0);
}


代码输出结果:

这个代码演示了,通过二级指针,打印了二维数组每个元素的地址和值。


总结:

*(*(buff+ i) + j)换成二维数组的形式,等同于 buff[i][j]


7. 指针数组

指针数组是一种数组,数组的每个元素都是指针。


定义方法:

数据类型 *指针数组名[数组元素个数];

例如:unsigned char *pBuff[3];


直接上实验代码:

#include 

//定义三个无符号字符数组buff1, buff2, buff3
unsigned char buff1[3] = {1,2,3};
unsigned char buff2[3] = {4,5,6};
unsigned char buff3[3] = {7,8,9};

// 定义一个指针数组pBuff,它可以存储3个指向无符号字符的指针
unsigned char *pBuff[3];

int main()
{
       pBuff[0] = buff1;//pBuff[0]现在指向buff1数组
       pBuff[1] = buff2;//pBuff[1]现在指向buff2数组
       pBuff[2] = buff3;//pBuff[2]现在指向buff3数组
       
//打印buff1数组的地址,pBuff[0]的地址,以及通过pBuff[0]访问的buff1数组的第一个元素的值
   printf("&buff1=0x%x, &pBuff[0]=0x%x, *pBuff[0]=%d\r\n",buff1,pBuff[0],*pBuff[0]);
   printf("&buff2=0x%x, &pBuff[1]=0x%x, *pBuff[1]=%d\r\n",buff2,pBuff[1],*pBuff[1]);
   printf("&buff3=0x%x, &pBuff[2]=0x%x, *pBuff[2]=%d\r\n",buff3,pBuff[2],*pBuff[2]);
       printf("\r\n");

   return(0);
}


代码输出结果:


这个代码,演示了如何使用指针数组,来管理多个数组。

其实还有一个叫概念,叫数值指针,理解起来也很恶心,我做产品开发时,也用的比较少,这里就不讲了。



8. 函数指针

如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址


而且函数名表示的就是这个地址


既然是地址,我们就可以定义一个指针变量来引用,这个指针变量就叫作函数指针变量,简称函数指针


函数指针的产品应用非常多,特别是做一些可扩展性,可移植性强的程序,我愿称之为刚需。


定义方法:

函数返回值类型 (* 指针变量名) (函数参数列表);

例如:unsigned char (*func)(unsigned char,unsigned char);


这样就定义了一个函数指针变量 func, 该函数指针返回值为 unsigned char 类型,然后有 2 个形参,分别是 unsigned char 类型


那么我们定义了这个函数指针变量以后,要怎么使用呢?


我们直接上实验代码:

#include  

// 定义一个函数指针func,它可以指向接受两个无符号字符参数,并返回一个无符号字符类型的值
unsigned char (*func)(unsigned char, unsigned char);

// 定义一个函数add,它接受两个无符号字符参数v1和v2,并返回它们的和
unsigned char add(unsigned char v1, unsigned char v2) 
{
   return (v1 + v2); // 返回两个参数的和
}

int main() 
{
   unsigned char a; // 定义一个无符号字符变量a,用于存储函数的返回值

   // 将函数指针func指向add函数,这样func就可以像调用add函数一样使用
   func = add;
   
   // 通过函数指针func调用add函数,传入1和2作为参数,并将返回值赋给变量a
   a = (*func)(1, 2);
   
   // 使用printf函数打印变量a的值
   printf("a=%d\r\n", a);

   return 0; // 程序正常结束,返回0
}


代码输出结果:

定义一个函数指针 func,定义一个函数 add,将函数指针 func 指向 add 函数,这样调用 func 等同于调用 add 函数,输出结果 a=3.

注意:函数指针指向的函数,返回值类型要一致,形参类型和数量也要一致。


9. 函数指针数组

函数指针数组,我在实际产品开发时,也用的比较多,特别适合一些函数功能类似的场合,比如控制很多个 LED 灯,检测很多个按键输入。


定义方法:

函数返回值类型 (* 指针变量名[数组大小]) (函数参数列表);

Void (*func[3])();


这样就定义了一个可以指向 3 个函数的函数指针数组,没有返回值,没有形参。


定义了以后,我们函数指针需要赋值,赋值的意思就是让它们指向函数首地址。


9.1 函数指针数组两种初始化方式

9.1.1 定义函数指针数组的时候直接初始化。

void func1();
void func2();
void func3();
void (*func[3])() = {func1,func2,func3};


9.1.2 先定义然后再初始化

void func1();
void func2();
void func3();

void (*func[3])();

func[0] = func1;
func[1] = func2;
func[2] = func3;


9.2 函数指针数组使用方式

#include  // 包含标准库头文件,这里用于使用printf函数

// 声明一个函数指针数组func,存储3个函数指针,无返回值,无形参
void (*func[3])();

// 声明三个无返回值,无形参的函数
void func1();
void func2();
void func3();

// 初始化func数组,将func1, func2, func3的地址分别赋给func数组的三个元素
void (*func[3])() = {func1, func2, func3};

void func1() 
{
   printf("无际单片机任务1\r\n");
}

void func2() 
{
   printf("无际单片机任务2\r\n");
}

void func3() 
{
   printf("无际单片机任务3\r\n");
}

// main函数,程序的入口点
int main() {
   // 通过函数指针数组func调用func1, func2, func3函数
   func[0](); // 调用func1函数
   func[1](); // 调用func2函数
   func[2](); // 调用func3函数
   
   return 0;
}


代码输出结果:

我们可以看到直接写 func[0](),func[1]()...,就可以执行函数指针数值指向的函数了。


10. 总结

学到这里,我估计很多童鞋想说卧槽了!指针花样真特么多。

我无意吓你,其实以上这些,都只是基础,真正复杂的是它的应用。

指针太灵活了,这个特性让它能构建更多,更复杂的应用场景,比如我们无际单片机特训营项目里,用指针去实现了回调函数、队列、链表、任务管理等各种高阶的应用。

你或许有点绝望, 太难了。

好消息是,每个人都是这样过来的,先理解,不用去背语法,比如说函数指针这种,我有时也记不住怎么定义,直接百度搜就好了,然后在我们项目实战的过程中,再深入学习指针的应用,怼它几个项目,就很熟悉了。



最近在写单片机软件架构进阶系列的文章,有些内容篇幅过长,后面打算在一些关键点和难点处,增加视频讲解,内容可能会不定期更新,如果这种方式阅读起来不方便,可以找我安排飞书的文档。



end


下面是更多无际原创个人成长经历、行业经验、技术干货

1.电子工程师是怎样的成长之路?10年5000字总结

2.如何快速看懂别人的代码和思维

3.单片机开发项目全局变量太多怎么管理?

4.C语言开发单片机为什么大多数都采用全局变量的形式

5.单片机怎么实现模块化编程?实用程度让人发指!

6.c语言回调函数的使用及实际作用详解

7.手把手教你c语言队列实现代码,通俗易懂超详细!

8.c语言指针用法详解,通俗易懂超详细!


无际单片机编程 单片机编程、全栈孵化。
评论
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 71浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 57浏览
  • 国产光耦合器正以其创新性和多样性引领行业发展。凭借强大的研发能力,国内制造商推出了适应汽车、电信等领域独特需求的专业化光耦合器,为各行业的技术进步提供了重要支持。本文将重点探讨国产光耦合器的技术创新与产品多样性,以及它们在推动产业升级中的重要作用。国产光耦合器创新的作用满足现代需求的创新模式新设计正在满足不断变化的市场需求。例如,高速光耦合器满足了电信和数据处理系统中快速信号传输的需求。同时,栅极驱动光耦合器支持电动汽车(EV)和工业电机驱动器等大功率应用中的精确高效控制。先进材料和设计将碳化硅
    克里雅半导体科技 2024-11-29 16:18 157浏览
  • 艾迈斯欧司朗全新“样片申请”小程序,逾160种LED、传感器、多芯片组合等产品样片一触即达。轻松3步完成申请,境内免费包邮到家!本期热荐性能显著提升的OSLON® Optimal,GF CSSRML.24ams OSRAM 基于最新芯片技术推出全新LED产品OSLON® Optimal系列,实现了显著的性能升级。该系列提供五种不同颜色的光源选项,包括Hyper Red(660 nm,PDN)、Red(640 nm)、Deep Blue(450 nm,PDN)、Far Red(730 nm)及Ho
    艾迈斯欧司朗 2024-11-29 16:55 155浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 88浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 63浏览
  • 光耦合器作为关键技术组件,在确保安全性、可靠性和效率方面发挥着不可或缺的作用。无论是混合动力和电动汽车(HEV),还是军事和航空航天系统,它们都以卓越的性能支持高要求的应用环境,成为现代复杂系统中的隐形功臣。在迈向更环保技术和先进系统的过程中,光耦合器的重要性愈加凸显。1.混合动力和电动汽车中的光耦合器电池管理:保护动力源在电动汽车中,电池管理系统(BMS)是最佳充电、放电和性能监控背后的大脑。光耦合器在这里充当守门人,将高压电池组与敏感的低压电路隔离开来。这不仅可以防止潜在的损坏,还可以提高乘
    腾恩科技-彭工 2024-11-29 16:12 117浏览
  • 在现代科技浪潮中,精准定位技术已成为推动众多关键领域前进的核心力量。虹科PCAN-GPS FD 作为一款多功能可编程传感器模块,专为精确捕捉位置和方向而设计。该模块集成了先进的卫星接收器、磁场传感器、加速计和陀螺仪,能够通过 CAN/CAN FD 总线实时传输采样数据,并具备内部存储卡记录功能。本篇文章带你深入虹科PCAN-GPS FD的技术亮点、多场景应用实例,并展示其如何与PCAN-Explorer6软件结合,实现数据解析与可视化。虹科PCAN-GPS FD虹科PCAN-GPS FD的数据处
    虹科汽车智能互联 2024-11-29 14:35 149浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 54浏览
  • 在电子技术快速发展的今天,KLV15002光耦固态继电器以高性能和强可靠性完美解决行业需求。该光继电器旨在提供无与伦比的电气隔离和无缝切换,是现代系统的终极选择。无论是在电信、工业自动化还是测试环境中,KLV15002光耦合器固态继电器都完美融合了效率和耐用性,可满足当今苛刻的应用需求。为什么选择KLV15002光耦合器固态继电器?不妥协的电压隔离从本质上讲,KLV15002优先考虑安全性。输入到输出隔离达到3750Vrms(后缀为V的型号为5000Vrms),确保即使在高压情况下,敏感的低功耗
    克里雅半导体科技 2024-11-29 16:15 119浏览
  • 国产光耦合器因其在电子系统中的重要作用而受到认可,可提供可靠的电气隔离并保护敏感电路免受高压干扰。然而,随着行业向5G和高频数据传输等高速应用迈进,对其性能和寿命的担忧已成为焦点。本文深入探讨了国产光耦合器在高频环境中面临的挑战,并探索了克服这些限制的创新方法。高频性能:一个持续关注的问题信号传输中的挑战国产光耦合器传统上利用LED和光电晶体管进行信号隔离。虽然这些组件对于标准应用有效,但在高频下面临挑战。随着工作频率的增加,信号延迟和数据保真度降低很常见,限制了它们在电信和高速计算等领域的有效
    腾恩科技-彭工 2024-11-29 16:11 106浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 59浏览
  • By Toradex胡珊逢简介嵌入式领域的部分应用对安全、可靠、实时性有切实的需求,在诸多实现该需求的方案中,QNX 是经行业验证的选择。在 QNX SDP 8.0 上 BlackBerry 推出了 QNX Everywhere 项目,个人用户可以出于非商业目的免费使用 QNX 操作系统。得益于 Toradex 和 QNX 的良好合作伙伴关系,用户能够在 Apalis iMX8QM 和 Verdin iMX8MP 模块上轻松测试和评估 QNX 8 系统。下面将基于 Apalis iMX8QM 介
    hai.qin_651820742 2024-11-29 15:29 150浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦