c指针的详解

c语言指针详细讲解

注:本文章参考《c和指针》这本著作,并加上自己的讲解,本文章大量引用该书的表述,同时也给出自己的注解,并且将大量与指针关联的知识点组织起来,已经有轮子,自己就没必要在造轮子,也希望读者耐心读下去,对你的指针有很大帮助,让你不再惧怕指针。
在这里插入图片描述



一、左值和右值

为了理解有些操作符存在的限制,必须理解左值(L-value)和右值(R-value)之间的区别。

简单来说左值就是那些能够出现在赋值符号左边的东西。右值就是那些可以出现在赋值符号右边的东西。这里有个例子:

a = b + 25;

a是个左值,因为它标识了一个可以存储结果值的地点,b + 25是个右值,因为它指定了一个值。
若把它们交换会发生什么?

b + 25 = a;

原先用作左值的a 此时也可以当作右值,因为每个变量的位置的里面都包含一个值。然而,b+25不能作为左值,因为它并未标识一个特定的位置。因此,这条赋值语句是非法的。

注意当计算机计算b+25时,它的结果必然保存于机器的某个地方。但是,程序员并没有办法预测该结果会存储在什么地方,也无法保证这个表达式的值下次还会存储于那个地方。其结果是,这个表达式不是一个左值。基于同样的理由,字面值常量也都不是左值

看上去似乎是变量可以作为左值而表达式不能作为左值,但这个推断并不准确。在下面的赋值语句中,左值便是一个表达式。

int  arry[30];
...
arry[b + 10] = 0;

下标引用实际上是一个操作符,所以表达式的左边实际上是个表达式,但它却是一个合法的左值,因为它标识了一个特定的位置,我们以后可以在程序中引用它。这里有另外一个例子:

int a, *pi;
...
pi = &a;
*pi = 20;

请看第⒉条赋值语句,它左边的那个值显然是一个表达式,但它却是一个合法的左值。为什么?指针 pi的值是内存中某个特定位置的地址,==*操作符(也叫间接访问操作符)==使机器指向那个位置。当它作为左值使用时,这个表达式指定需要进行修改的位置。当它作为右值使用时,它就提取当前存储于这个位置的值。
有些操作符,如间接访问和下标引用,它们的结果是个左值。其余操作符的结果则是个右值。

每个操作符的所有属性都列在如下表所示的优先级表中。表中各个列分别代表操作符、它的功能简述、用法示例、它的结果类型、它的结合性以及当它出现时是否会对表达式的求值顺序施加控制。表中术语lexp表示左值表达式,rexp表示右值表达式。记住,左值意味着一个位置,而右值意味着一个值。所以,在使用右值的地方也可以使用左值,但是在需要左值的地方不能使用右值。
在这里插入图片描述

二、内存和地址

我们可以把计算机的内存看作是一条长街上的一排房屋。每座房子都可以容纳数据,并通过一个房号来标识。
在这里插入图片描述
这些位置的每一个都被称为字节(byte),每个字节都包含了存储一个字符所需要的位数。在计算机的世界里,每个字节包含8个位,可以存储无符号值0至255,或有符号值-128至127。上面这张图并没有显示这些位置的内容,但内存中的每个位置总是包含一些值。每个字节通过地址来标识,如上图方框上面的数字所示。

为了存储更大的值,我们把两个或更多个字节合在一起作为一个更大的内存单位。例如,许多机器以为单位存储整数,每个字一般由2个或4个字节组成。,这取决于计算机系统的总线宽度,例如32位系统的字由4个字节组成,下面这张图所表示的内存位置与上面这张图相同,但这次它以4个字节的字来表示。
在这里插入图片描述
注意,尽管一个字包含了4个字节,它仍然只有一个地址。至于它的地址是它最左边那个字节的位置还是最右边那个字节的位置,不同的机器有不同的规定(大小端问题)。
另一个需要注意的硬件事项是边界对齐(boundary alignment)。在要求边界对齐的机器上,整型值存储的起始位置只能是某些特定的字节,通常是2或4的倍数。但这些问题是硬件设计者的事情,它们很少影响C程序员。我们只对两件事情感兴趣:

  1. 内存中的每个位置由一个独一无二的地址标识。
  2. 内存中的每个位置都包含一个值。

2.1 地址与内容

在这里插入图片描述
这里显示了5个整数,每个都位于自己的中。如果你记住了一个值的存储地址,你以后可以根据这个地址取得这个值。
但是,要记住所有这些地址实在是太笨拙了,所以高级语言所提供的特性之一就是通过名字而不是地址来访问内存的位置。下面这张图与上图相同,但这次使用名字来代替地址。
在这里插入图片描述
当然,这些名字就是我们所称的变量。有一点非常重要,你必须记住,名字与内存位置之间的关联并不是硬件所提供的,它是由编译器为我们实现的。(编译器负责给变量和函数分配映射到内存中的地址。)所有这些变量给了我们一种更方便的方法记住地址——硬件仍然通过地址访问内存位置

三、指针变量

3.1 指针变量的内容

我们先来声明几个变量:

int  a = 112, b = -1;
float c = 3.14;
int *d = &a;
float *e =&c

可以看出他们在内存中的样子,假设a的地址为100,b的地址为104,e的地址为108,依次类推
在这里插入图片描述
d和e的内容是地址而不是整型或浮点型数值。事实上,从图中可以容易地看出,d的内容与a的存储地址一致,而e的内容与c的存储地址一致,这也正是我们对这两个指针进行初始化时所期望的结果。

我们来讨论一下这些变量的值: a的值是112,b的值是-1,c的值是3.14。指针变量其实也很容易,d的值是100,e的值是108。如果你认为d和e的值分别是112和3.14,那么你就犯了一个极为常见的错误。d和e被声明为指针并不会改变这些表达式的求值方式:如果你简单地认为由于d和e是指针,所以它们可以自动获得存储于位置100和108的值,那么你又错了。变量的值就是分配给该变量的内存位置所存储的数值,即使是指针变量也不例外。

3.2 间接访问操作符 *

通过一个指针访问它所指向的地址的过程称为间接访问(indirection)或解引用指针(dereferencingthe pointer)。这个用于执行间接访问的操作符是单目操作符 *。这里有一些例子:
在这里插入图片描述
d的值是100。当我们对d使用间接访问操作符时,它表示访问内存位置100并察看那里的值。因此,*d 的右值是112——位置100的内容,它的左值是位置100本身。也就是说 *d 拥有了和a一样的左值和右值,相当于就是变量a(前提是d被赋予地址,后面非法指针那里还会在讲)。

正常情况下,我们并不知道编译器为每个变量所选择的存储位置,所以我们事先无法预测它们的地址。这样,当我们绘制内存中的指针图时,用实际数值表示地址是不方便的。所以有的书改用箭头来代替,如下所示:
在这里插入图片描述
但是,这种记法可能会引起误解,因为箭头可以会使你误以为执行了间接访问操作,但事实上它并不一定会进行这个操作。例如,根据上图,你会推断表达式d的值是什么?如果你的答案是112,那么你就被这个箭头误导了。正确的答案是a的地址,而不是它的内容。但是,这个箭头似乎会把你的注意力吸引到a 上。要使你的思维不受箭头影响是不容易的,这也是问题所在:除非存在间接引用操作符,否则不要被箭头所误导。
下面这个修正后的箭头记法试图消除这个问题。
在这里插入图片描述
当执行间接访问操作时,这种记法才使用实线箭头表示实际发生的内存访问。
注意箭头起始于方框内部,因为它表示存储于该变量的值。同样,箭头指向一个位置,而不是存储于该位置的值。这种记法提示跟随箭头执行间接访问操作的结果将是一个左值。

3.3 未初始化和非法的指针

下面这个代码段说明了一个极为常见的错误:

int *pi;
...
*pi = 12;

但是究竟pi指向哪里呢?我们声明了这个指针变量,但从未对它进行初始化(即没有赋予指向的地址),因此对其进行解引用(间接访问操作),所以我们没有办法预测12这个值将存储于什么地方。说人话就是指针将12这个值搬到哪里,没有人知道,如果指针pi是静态变量,那么它会被初始化为0,即它指向0这块内存,对其进行解引用那么12就被赋予0这块内存里面了。

有些人会说这么低级的错误我不会犯,但是在链表那块,有些同学创建如下的节点:

#define FRAM_PACK_SIZE 1024
typedef unsigned     char   uint8_t;
typedef unsigned     int    uint32_t;

typedef struct framedata_pack
{
	uint32_t pake_size;                       //图像包的大小
	uint8_t databuf[FRAM_PACK_SIZE];		//图像的数据包
	struct framedata_pack *next;
}FRAMPACK;

有些人认为ptr已经指向这个结构体了,然后对ptr进行解引用,这和上面的*pi = 12是一个原因,那就是指针没有指向分配的空间。
还有一种错误,认为ptr的内存大小是和FRAMPACK一样大的,这就是经典的错误,标准的零分,我们来举个例子:

#include <stdio.h>
#include <stdlib.h>
#define FRAM_PACK_SIZE 1024
typedef unsigned     char   uint8_t;
typedef unsigned     int    uint32_t;

#pragma pack(1)//设定为1字节对齐 
typedef struct framedata_pack
{
    uint8_t databuf[FRAM_PACK_SIZE];		//图像的数据包
	uint32_t pake_size;                       //图像包的大小
	struct framedata_pack *next;
}FRAMPACK;
int main()
{
    int vr=12;
    char str[]="hello";
    FRAMPACK *ptr=malloc(sizeof(FRAMPACK));
    ptr->next=NULL;
    int *itp=&vr;
    char *chp=str;
    printf("FRAMPACK结构体指针ptr占的内存大小%d\n",sizeof(ptr));
    printf("整型指针itp占的内存大小%d\n",sizeof(itp));
    printf("字符指针chp占的内存大小%d\n",sizeof(chp));
    printf("FRAMPACK结构体所占的内大小%d\n",sizeof(*ptr));
    printf("FRAMPACK结构体pake_size所占的内大小%d\n",sizeof(ptr->pake_size));
    printf("FRAMPACK结构体next所占的内大小%d\n",sizeof(ptr->next));
}

运行结果如下:
在这里插入图片描述
可以看出无论是整型指针还是字符指针,占内存大小都是8个字节,这也很好理解,毕竟指针的右值(也就是指针里面的值)是用来存放地址的,而我所使用的是Win10是一个64位系统,因此系统总线宽度为64bit,因此地址也是64位的,而FRAMPACK 结构体则是1036,这是取消了结构体对齐的结果,也就是没有优化后最原始的大小,它由1024个字节的数组 + 4字节的pake_size + 8字节的next组成。
所以在对指针进行解引用之前一定要确保指针指向的内存空间

3.4 指针、间接访问和左值

涉及指针的表达式能不能作为左值?如果能,又是哪些呢?
给定下面这些声明:

int a=2;
int *d=&a;

在这里插入图片描述
指针变量可以作为左值,并不是因为它们是指针,而是因为它们是变量。
前面提过左值代表的是变量的地址,它其实就是个值,也可以充当右值赋值给指针变量。例如:

1.  *d = 10 - *d;
2.   d = 10 - *d;

第1条语句包含了两个间接访问操作。右边的间接访问作为右值使用,所以它的值是d 所指向的位置所存储的值(a的值)。左边的间接访问作为左值使用,所以d 所指向的位置(a把赋值符右侧的表达式的计算结果作为它的新值。
第⒉条语句是非法的,因为它表示把一个整型数量(10 - *d )存储于一个指针变量中。当我们实际使用的变量类型和应该使用的变量类型不一致时,编译器会发出警告。

3.5 常量指针

学过嵌入式都知道GPIO的概念,就是已知外设的地址,然后将这个地址强制转换为指针变量,从而很方便的操作寄存器,如下例子:

typedef struct
{
  __IO uint32_t MODER;    /*!< GPIO port mode register,               Address offset: 0x00      */
  __IO uint32_t OTYPER;   /*!< GPIO port output type register,        Address offset: 0x04      */
  __IO uint32_t OSPEEDR;  /*!< GPIO port output speed register,       Address offset: 0x08      */
  __IO uint32_t PUPDR;    /*!< GPIO port pull-up/pull-down register,  Address offset: 0x0C      */
  __IO uint32_t IDR;      /*!< GPIO port input data register,         Address offset: 0x10      */
  __IO uint32_t ODR;      /*!< GPIO port output data register,        Address offset: 0x14      */
  __IO uint16_t BSRRL;    /*!< GPIO port bit set/reset low register,  Address offset: 0x18      */
  __IO uint16_t BSRRH;    /*!< GPIO port bit set/reset high register, Address offset: 0x1A      */
  __IO uint32_t LCKR;     /*!< GPIO port configuration lock register, Address offset: 0x1C      */
  __IO uint32_t AFR[2];   /*!< GPIO alternate function registers,     Address offset: 0x20-0x24 */
} GPIO_TypeDef;

#define GPIOA_BASE            (AHB1PERIPH_BASE + 0x0000)
#define GPIOB_BASE            (AHB1PERIPH_BASE + 0x0400)
#define GPIOC_BASE            (AHB1PERIPH_BASE + 0x0800)
#define GPIOD_BASE            (AHB1PERIPH_BASE + 0x0C00)

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)
#define GPIOC               ((GPIO_TypeDef *) GPIOC_BASE)
#define GPIOD               ((GPIO_TypeDef *) GPIOD_BASE)

由于结构体的内存是连续的,已知外设的基地址,这时候就可以将这块基地址强制转换为一个结构体类型的指针,我们举个通俗的例子:

1.   *100 = 25;
2.   *int *100 = 25;

假定变量a存储于位置100
第一句看上去像是把25赋值给a,因为a是位置100所存储的变量。但是,这是错的!这条语句实际上是非法的,因为字面值100的类型是整型,而间接访问操作只能作用于指针类型表达式。
如果换成第二句通过强制类型转换把值100 从“整型”转换为“指向整型的指针”,这样对它进行间接访问就是合法。
最后我们就可以使用GPIOA这个指针变量来访问寄存器了,例如:

void GPIO_Set(GPIO_TypeDef* GPIOx,u32 BITx,u32 MODE,u32 OTYPE,u32 OSPEED,u32 PUPD)
{  
	u32 pinpos=0,pos=0,curpin=0;
	for(pinpos=0;pinpos<16;pinpos++)
	{
		pos=1<<pinpos;	//一个个位检查 
		curpin=BITx&pos;//检查引脚是否要设置
		if(curpin==pos)	//需要设置
		{
			GPIOx->MODER&=~(3<<(pinpos*2));	//先清除原来的设置
			GPIOx->MODER|=MODE<<(pinpos*2);	//设置新的模式 
			if((MODE==0X01)||(MODE==0X02))	//如果是输出模式/复用功能模式
			{  
				GPIOx->OSPEEDR&=~(3<<(pinpos*2));	//清除原来的设置
				GPIOx->OSPEEDR|=(OSPEED<<(pinpos*2));//设置新的速度值  
				GPIOx->OTYPER&=~(1<<pinpos) ;		//清除原来的设置
				GPIOx->OTYPER|=OTYPE<<pinpos;		//设置新的输出模式
			}  
			GPIOx->PUPDR&=~(3<<(pinpos*2));	//先清除原来的设置
			GPIOx->PUPDR|=PUPD<<(pinpos*2);	//设置新的上下拉
		}
	}
} 

...
GPIO_Set(GPIOA,PIN9|PIN10,GPIO_MODE_AF,GPIO_OTYPE_PP,GPIO_SPEED_50M,GPIO_PUPD_PU);//PA9,PA10,复用功能,上拉输出

这个技巧唯一有用之处是你偶尔需要通过地址访问内存中某个特定的位置,它并不是用于访问某个变量,而是访问硬件本身。例如,操作系统需要与输入输出设备控制器通信,启动I/O 操作并从前面的操作中获得结果。在有些机器上,与设备控制器的通信是通过在某个特定内存地址读取和写入值来实现的。但是,与其说这些操作访问的是内存,还不如说它们访问的是设备控制器接口。这样,这些位置必须通过它们的地址来访问,此时这些地址是预先已知的。

3.6 指针的指针

考虑下面这些声明:

int a = 12;
int *b = &a;
int **c = &b;

它们在内存中的样子如下:
在这里插入图片描述
问题是: c的类型是什么?显然它是一个指针,但它所指向的是什么?变量b是一个“指向整型的指针”,所以任何指向b的类型必须是指向“指向整型的指针”的指针,更通俗地说,是一个指针的指针。
它合法吗?是的!指针变量和其他变量一样,占据内存中某个特定的位置,所以用&操作符取得它的地址是合法的。
让我们对它进行分析。* 操作符具有从右向左的结合性,所以这个表达式相当于 * ( *c ),我们必须从里向外逐层求值。*c访问c所指向的位置,我们知道这是变量b。第2个间接访问操作符访问这个位置所指向的地址,也就是变量a。指针的指针并不难懂,你只要留心所有的箭头,如果表达式中出现了间接访问操作符,你就随箭头访问它所指向的位置。

那么指针的指针,也叫二级指针有什么用呢,我们同样来举个例子:

void jpeg_comm_init(struct udp_pcb *jpeg_pcb)
{
	err_t err;
	ip_addr_t rmtipaddr;  	//远端ip地址
	jpeg_pcb=udp_new();
	if(jpeg_pcb!=NULL)
	{
		IP4_ADDR(&rmtipaddr,lwipdev.remoteip[0],lwipdev.remoteip[1],
							lwipdev.remoteip[2],lwipdev.remoteip[3]);
		err=udp_connect(jpeg_pcb,&rmtipaddr,lwipdev.jpeg_port);//UDP客户端连接到指定IP地址和端口号的服务器
		if(err==ERR_OK){
			err=udp_bind(jpeg_pcb,IP_ADDR_ANY,lwipdev.jpeg_port);//绑定本地IP地址与端口号
		}
	}
}

struct udp_pcb *p_jpeg_pcb=NULL;
jpeg_comm_init(p_jpeg_pcb);
...
udp_send(p_jpeg_pcb,ptr);	//udp发送数据 

udp_new();这个函数是用来给udp_pcb 分配所要指向地址空间的,这样就完成了对jpeg_pcb的初始化,正当我以为p_jpeg_pcb完成了udp的配置,准备发送数据时,系统却没有按照我的预期去走,最后排查发现是指针的问题,这里进行传参时,形参指针jpeg_pcb和实参指针p_jpeg_pcb进行传参
jpeg_pcb=p_jpeg_pcb;
1)由于p_jpeg_pcb初始化为NULL,因此形参指针jpeg_pcb也被初始化为NULL,如下所示:
在这里插入图片描述
2)随后调用udp_new()分配地址空间给形参指针jpeg_pcb,此时实参指针仍然为NULL。
在这里插入图片描述
3)最后jpeg_comm_init运行结束后,形参指针被撤销,但是结构体仍然在内存中,没有任何指针指向
在这里插入图片描述
这个问题之所以会出现,就是因为实参指针没有真正的指向有意义的内存空间,将实参指针传给形参指针,最后实参指针始终没有指向。解决这个问题有两个办法:

  1. 返回指针
struct udp_pcb * jpeg_comm_init()
{
	struct udp_pcb *jpeg_pcb;
	err_t err;
	ip_addr_t rmtipaddr;  	//远端ip地址
	jpeg_pcb=udp_new();
	if(jpeg_pcb!=NULL)
	{
		IP4_ADDR(&rmtipaddr,lwipdev.remoteip[0],lwipdev.remoteip[1],
							lwipdev.remoteip[2],lwipdev.remoteip[3]);
		err=udp_connect(jpeg_pcb,&rmtipaddr,lwipdev.jpeg_port);//UDP客户端连接到指定IP地址和端口号的服务器
		if(err==ERR_OK){
			err=udp_bind(jpeg_pcb,IP_ADDR_ANY,lwipdev.jpeg_port);//绑定本地IP地址与端口号
		}
	}
	return jpeg_pcb;
}

struct udp_pcb *p_jpeg_pcb=NULL;
p_jpeg_pcb = jpeg_comm_init();
...
udp_send(p_jpeg_pcb,ptr);	//udp发送数据 

这个最后形参指针将他的右值(也就是它里面的值)赋值给了实参指针
p_jpeg_pcb=jpeg_pcb;
在这里插入图片描述
2. 二级指针

/**
  * @Brief  jpeg图像和udp控制块的初始化  
  * @Call   jpeg_comm_init
  * @Param  传入jpeg udp控制块的二级指针
  * @Note
  * @Retval None
  */
void jpeg_comm_init(struct udp_pcb **jpeg_pcb)
{
	err_t err;
	ip_addr_t rmtipaddr;  	//远端ip地址
	*jpeg_pcb=udp_new();
	if((*jpeg_pcb)!=NULL)
	{
		IP4_ADDR(&rmtipaddr,lwipdev.remoteip[0],lwipdev.remoteip[1],
							lwipdev.remoteip[2],lwipdev.remoteip[3]);
		err=udp_connect(*jpeg_pcb,&rmtipaddr,lwipdev.jpeg_port);//UDP客户端连接到指定IP地址和端口号的服务器
		if(err==ERR_OK){
			err=udp_bind(*jpeg_pcb,IP_ADDR_ANY,lwipdev.jpeg_port);//绑定本地IP地址与端口号
		}
	}
}

struct udp_pcb *p_jpeg_pcb=NULL;
jpeg_comm_init(&p_jpeg_pcb);
...
udp_send(p_jpeg_pcb,ptr);	//udp发送数据 

而二级指针的传参过程如下:
1)jpeg_pcb=&p_jpeg_pcb;
在这里插入图片描述
2)*jpeg_pcb=udp_new(),将结构体首地址赋给了 *jpeg_pcb,也就是p_jpeg_pcb
在这里插入图片描述

3.7 指针表达式

现在让我们观察各种不同的指针表达式,并看看当它们分别作为左值和右值时是如何进行求值,首先,让我们来看一些声明。

char ch = 'a';
char *cp = &ch;

在这里插入图片描述
图中还显示了ch后面的那个内存位置,因为我们所求值的有些表达式将访问它(尽管是在错误情况下才会对它进行访问)。由于我们并不知道它的初始值,所以用一个问号来代替。粗椭圆提示变量的值就是表达式的值,也就是右值粗方框标记左值就是表达式的值

  1. 表达式:cp
    它的右值如图所示就是cp的值。它的左值就是cp所处的内存位置。由于这个表达式并不进行间接访问操作,所以你不必依箭头所示进行间接访问。
    在这里插入图片描述
    2)表达式:&ch
    作为右值,这个表达式的值是变量ch 的地址。注意这个值同变量cp中所存储的值一样,但这个表达式并未提及 cp,所以这个结果值并不是因为它而产生的。这样,图中椭圆并不画于cp的箭头周围。为什么这个表达式不是一个合法的左值?优先级表格显示&操作符的结果是个右值,它不能当作左值使用。但是为什么呢?答案很简单,当表达式&ch进行求值时,它的结果应该存储于计算机的什么地方呢?它肯定会位于某个地方,但你无法知道它位于何处。这个表达式并未标识任何机器内存的特定位置,所以它不是一个合法的左值。
    在这里插入图片描述

3)表达式:&cp
这个结果的类型是指向字符的指针的指针。同样,这个值的存储位置并未清晰定义,所以这个表达式不是一个合法的左值。
在这里插入图片描述
4)表达式:*cp
现在我们加入了间接访问操作,所以它的结果应该不会令人惊奇。但接下来的几个表达式就比较有意思。
在这里插入图片描述
5)表达式: *cp + 1

这个图涉及的东西更多,所以让我们一步一步来分析它。这里有两个操作符。*的优先级高于 + ,所以首先执行间接访问操作(如图中cp到ch 的实线箭头所示),我们可以得到它的值(如虚线椭圆所示)。我们取得这个值的一份拷贝并把它与1相加,表达式的最终结果为字符’ b’。图中虚线表示表达式求值时数据的移动过程。这个表达式的最终结果的存储位置并未清晰定义,所以它不是一个合法的左值。优先级表格证实+的结果不能作为左值。
在这里插入图片描述
6)表达式: *(cp+1)
在这个例子中,我们在前面那个表达式中增加了一个括号。这个括号使表达式先执行加法运算,就是把1和cp中所存储的地址相加。此时的结果值是图中虚线椭圆所示的指针。接下来的间接访问操作随着箭头访问紧随ch之后的内存位置。这样,这个表达式的右值就是这个位置的值,而它的左值是这个位置本身。
在这里插入图片描述
这里我只列出常用的表达式,后面更复杂的就不在介绍了,想必看到这里的读者脑袋都已经晕了吧,掌握左右值是理解这些的关键。


四、总结

c语言是嵌入式工程师必备的语言,因为它可以通过指针操作硬件,就如同上述的常量指针,其实还有许多东西没有讲到,如指针数组、数组指针、函数指针、指针函数、函数指针数组这些概念。这里提一嘴,中国人喜欢把定语放在前面,后面才是东西的本质,就拿函数指针数组而言,首先,它是一个数组,里面存放着函数指针,而函数指针是指针,这个指针指向函数,这个通常用来做菜单,例如:

void (*oper_fuc[4])();//函数指针数组
void Menuitem_Init(void)
{
    oper_fuc[0]=send_type_server;
    oper_fuc[1]=Set_chart_div_line;
    oper_fuc[2]=clear_step;
	oper_fuc[3]=smooth_filter;
}
static void roller_event_handler(lv_obj_t * obj, lv_event_t event)
{
    static unsigned char count=0;
	 if(event==LV_EVENT_VALUE_CHANGED)
    {
        count=lv_roller_get_selected(obj);
    }
    if(event==LV_EVENT_CLICKED)
    {
        oper_fuc[count]();
    }
}

这样就不必使用switch来切换功能函数了,是一个使用技巧。
这段时间又要忙于考研,博客有时间就更新。

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Keltop

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值