函数指针、函数指针数组详解及典型应用_韦东山 函数指针数组-程序员宅基地

技术标签: c语言  嵌入式linux学习笔记  C语言学习  指针  嵌入式  

20200305 杨千嬅唱的《处处吻》真是太好听了,下个他他吻她他吻她吻他吻她… 已沉醉

一、何为函数指针

我们知道指针变量指向内存单元的地址,比如存放普通变量int a;的地址的就是一重指针,存放一重指针变量的地址的就是二重指针,指针变量存地址,以此来实现传址调用,
函数指针,顾名思义,就是指向函数的指针,那么何为指向函数呢?按照上面的逻辑,我们得有一个指针变量,这个指针变量里存放着该函数块在内存中的首地址。
要想理解这个,我们要从指针的层次上理解函数——函数的函数名实际上就是该函数的代码在内存中的首地址的别名,说白了还是个地址,这个有点类似于数组名和数组首元素地址的关系,随便找个反汇编代码就可印证上面所说的,下面的是汇编代码和反汇编调用main函数的例子
1.关于别名:586、587行
2.关于调用:我们可以看看汇编中是如何调用函数的,可以发现,PC指向了文字池中的内容30000848,这不正是mian函数的首地址吗,传址调用就好了
在这里插入图片描述

二、函数指针变量的类型及定义

类型表征着特征,而能够描述一个函数的特征的无非就是入口参数(形参)和返回值了

形式1:返回类型(*函数指针变量名)(参数表)

char*pFun)(int); //定义了一个名为pFun的函数指针变量,要求指向的函数的要有一个int类型的形参并要求该函数返回值为char类型
char glFun(int a)
{
    
	return a;
}
void main()
{
    
	pFun =glFun;
	
	/*函数指针的普通使用,以下两种方式个都可*/
	pFun (2);//与其指向的函数用法无异  
	(*pFun)(2);//此处*pf两端括号必不可少  
}

第1行:pFun是 char (*)(int) 类型的指针
第2行:定义了一个函数glFun().该函数正好是一个以int为参数返回char的函数。即char (int) 类型
第8行:指针变量pFun指向了glFun函数的首地址

形式2:使用typedef更直接

typedef char(*PTRFUN)(int)
PTRFUN pFun;
char glFun(int a)
{
    
	return a;
}
void main()
{
    
	pFun = glFun;
	(*pFun)(2);
}

typedef的功能是定义新的类型。第一句就是定义了一种PTRFUN的类型,并定义这种类型为指向某种函数的指针,这种函数以一个int为参数并返回char类型。

三、举例说明用途

简单的计算函数回调

#include <stdio.h>
#define SIZE 4
/*typedef的功能是定义新的类型。
下面这句就是定义了一种PFUNC的类型,并定义这种类型为指向某种函数的指针,这种函数以两个int为参数并返回int类型。*/
typedef int (*PFUNC)(int, int);
PFUNC fucp_arr[SIZE] = {
    NULL};
int add(int a, int b)
{
    
	return a + b;
}
int sub(int a, int b)
{
    
	return a - b;
}
int mul(int a, int b)
{
    
	return a * b;
}
int div(int a, int b)
{
    
	return b ? a / b : -1;
}

/*返回函数类型,实现函数回调*/
PFUNC calc_func(char op)
{
    
	switch (op)
	{
    
	case '+':
		return add;
	case '-':
		return sub;
	case '*':
		return mul;
	case '/':
		return div;
	default:
		return NULL;
	}
	return NULL;
}
	int main(void)
{
    

	/*无论是什么类型的指针,在32位系统下都占4字节*/
	printf("sizeof(PFUNC) = %d\n", sizeof(PFUNC));
	/*实现函数回调*/
	printf("%d + %d = %d\n", 13, 14, (calc_func('+'))(13, 14));
	printf("%d - %d = %d\n", 13, 14, (calc_func('-'))(13, 14));
	printf("%d * %d = %d\n", 13, 14, (calc_func('*'))(13, 14));
	printf("%d / %d = %d\n", 13, 14, (calc_func('/'))(13, 14));
	
	return 0;
}


编译运行后得到

root@youngfar-PC:/mnt/hgfs/Linux教程/mytest# gcc -o exe test.c -m32
root@youngfar-PC:/mnt/hgfs/Linux教程/mytest# ./exe
sizeof(PFUNC) = 4
13 + 14 = 27
13 - 14 = -1
13 * 14 = 182
13 / 14 = 0

四、利用函数指针数组进行函数的注册和调用

#include <stdio.h>
#define SIZE 4
/*typedef的功能是定义新的类型。
下面这句就是定义了一种PFUNC的类型,并定义这种类型为指向某种函数的指针,这种函数以两个int为参数并返回int类型。*/
typedef int (*PFUNC)(int, int);
PFUNC fucp_arr[SIZE] = {
    NULL};
int add(int a, int b)
{
    
	return a + b;
}
int sub(int a, int b)
{
    
	return a - b;
}
int mul(int a, int b)
{
    
	return a * b;
}
int div(int a, int b)
{
    
	return b ? a / b : -1;
}

/*返回函数类型,实现函数回调*/
PFUNC calc_func(char op)
{
    
	switch (op)
	{
    
	case '+':
		return add;
	case '-':
		return sub;
	case '*':
		return mul;
	case '/':
		return div;
	default:
		return NULL;
	}
	return NULL;
}

/* 
 不使用typedef,直接让函数返回一个函数指针,写起来比较麻烦,不直观,所以不推荐

 定义了一个函数,s_calc_func是函数名,
 s_calc_func这个函数需要char类型的形参,
 且最终返回一个函数指针,
 且要求这个函数指针指向的函数有两个int类型的形参和一个int类型的返回值
 */
int (*s_calc_func(char op))(int, int) /* 注意:这个函数的用途与上一个名为calc_func的函数的作业和调用方式完全相同*/
{
    
	return calc_func(op);
}

/*我们要从指针的层次上理解函数-函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。*/
void Fp_Register(int position, PFUNC fp)
{
    
	fucp_arr[position] = fp;
}

int main(void)
{
    
/*无论是什么类型的指针,在32位系统下都占4字节*/
	printf("sizeof(PFUNC) = %d\n", sizeof(PFUNC));
	// /*实现函数回调*/
	// printf("%d + %d = %d\n", 13, 14, (calc_func('+'))(13, 14));
	// printf("%d - %d = %d\n", 13, 14, (calc_func('-'))(13, 14));
	// printf("%d * %d = %d\n", 13, 14, (calc_func('*'))(13, 14));
	// printf("%d / %d = %d\n", 13, 14, (calc_func('/'))(13, 14));

	/*函数注册*/
	Fp_Register(0, add);
	Fp_Register(1, sub);
	Fp_Register(2, mul);
	Fp_Register(3, div);

	/*函数调用*/
	printf("%d + %d = %d\n", 13, 14, fucp_arr[0](13, 14));
	printf("%d - %d = %d\n", 13, 14, fucp_arr[1](13, 14));
	printf("%d * %d = %d\n", 13, 14, fucp_arr[2](13, 14));
	printf("%d / %d = %d\n", 13, 14, fucp_arr[3](13, 14));

	return 0;
}

运行结果和上边的一样

函数指针数组应用实例

函数指针数组一般用于有相同操作流程但细节却不能使用一个函数统一描述的的函数的调用,比如在2440处理中断时的一些操作,下面贴出代码,举例说明,先来看2440中断的处理流程
在这里插入图片描述
The S3C2440A has two interrupt pending registers: source pending register (SRCPND) and interrupt pending register (INTPND). These pending registers indicate whether an interrupt request is pending or not. When the interrupt sources request interrupt the service, the corresponding bits of SRCPND register are set to 1, and at the same time, only one bit of the INTPND register is set to 1 automatically after arbitration procedure. If interrupts are masked, then the corresponding bits of the SRCPND register are set to 1. This does not cause the bit of INTPND register changed. When a pending bit of INTPND register is set, the interrupt service routine will start whenever the I-flag or F-flag is cleared to 0. The SRCPND and INTPND registers can be read and written, so the service routine must clear the pending condition by writing a 1 to the corresponding bit in the SRCPND register first and then clear the pending condition in the INTPND registers by using the same method.
S3C2440A有两个中断暂挂寄存器:源暂挂寄存器(SRCPND)和中断暂挂寄存器(INTPND)。这些挂起的寄存器指示中断请求是否挂起。当中断源请求中断服务时,SRCPND寄存器的相应位被设置为1,同时,经过仲裁程序,INTPND寄存器中只有1位被设置为1。如果中断被屏蔽,那么SRCPND寄存器的相应位被设置为1。这不会导致INTPND寄存器的位发生变化。当设置一个暂挂的INTPND寄存器位时,中断服务例程将在I-flag或F-flag被清除为0时启动。SRCPND和INTPND寄存器可以读写,因此服务例程必须先通过将1写入SRCPND寄存器中相应的位来清除挂起条件,然后使用相同的方法清除INTPND寄存器中的挂起条件。
在这里插入图片描述
在这里插入图片描述
下面是2440代码的按键中断处理函数

#include "s3c2440_soc.h"

/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTMSK 用来屏蔽中断, 1-masked
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTOFFSET : 用来显示INTPND中哪一位被设置为1
 */

/* 初始化中断控制器 Initial State 为1, 0 = Service available*/
void interrupt_init(void)
{
    
	INTMSK &= ~((1 << 0) | (1 << 2) | (1 << 5)); /*按键中断源配置*/
	INTMSK &= ~(1 << 10);						 /* timer0 中断源配置*/
}

/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
    
	/* 配置GPIO为中断引脚 */
	GPFCON &= ~((3 << 0) | (3 << 4));
	GPFCON |= ((2 << 0) | (2 << 4)); /* S2,S3被配置为中断引脚 */

	GPGCON &= ~((3 << 6) | (3 << 22));
	GPGCON |= ((2 << 6) | (2 << 22)); /* S4,S5被配置为中断引脚 */

	/* 设置中断触发方式: 双边沿触发 */
	EXTINT0 |= (7 << 0) | (7 << 8); /* S2,S3 */
	EXTINT1 |= (7 << 12);			/* S4 */
	EXTINT2 |= (7 << 12);			/* S5 */

	/* 设置EINTMASK使能eint11,19 */
	EINTMASK &= ~((1 << 11) | (1 << 19));
}

/* 读EINTPEND分辨率哪个EINT产生(eint4~23)
 * 清除中断时, 写EINTPEND的相应位
 */

void key_eint_irq(int irq)
{
    
	unsigned int val = EINTPEND;
	unsigned int val1 = GPFDAT;
	unsigned int val2 = GPGDAT;

	if (irq == 0) /* eint0 : s2 控制 D12 */
	{
    
		if (val1 & (1 << 0)) /* s2 --> gpf6 */
		{
    
			/* 松开 */
			GPFDAT |= (1 << 6);
		}
		else
		{
    
			/* 按下 */
			GPFDAT &= ~(1 << 6);
		}
	}
	else if (irq == 2) /* eint2 : s3 控制 D11 */
	{
    
		if (val1 & (1 << 2)) /* s3 --> gpf5 */
		{
    
			/* 松开 */
			GPFDAT |= (1 << 5);
		}
		else
		{
    
			/* 按下 */
			GPFDAT &= ~(1 << 5);
		}
	}
	else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
	{
    
		if (val & (1 << 11)) /* eint11 */
		{
    
			if (val2 & (1 << 3)) /* s4 --> gpf4 */
			{
    
				/* 松开 */
				GPFDAT |= (1 << 4);
			}
			else
			{
    
				/* 按下 */
				GPFDAT &= ~(1 << 4);
			}
		}
		else if (val & (1 << 19)) /* eint19 */
		{
    
			if (val2 & (1 << 11))
			{
    
				/* 松开 */
				/* 熄灭所有LED */
				GPFDAT |= ((1 << 4) | (1 << 5) | (1 << 6));
			}
			else
			{
    
				/* 按下: 点亮所有LED */
				GPFDAT &= ~((1 << 4) | (1 << 5) | (1 << 6));
			}
		}
	}

	EINTPEND = val; //写1清除中断标志
}

void handle_irq_c(void)
{
    
	/* 分辨中断源 */
	int bit = INTOFFSET;

	/* 调用对应的处理函数 */
	if (bit == 0 || bit == 2 || bit == 5) /* eint0,2,eint8_23 */
	{
    
		key_eint_irq(bit); /* 处理中断, 清中断源EINTPEND */
	}
	else if (bit == 10)
	{
    
		timer_irq();
	}

	/* 清中断 : 从源头开始清 */
	SRCPND = (1 << bit);
	INTPND = (1 << bit);
}

每次编写中断相关函数需要

汇编中:
1.中断发生前:

01.设置中断向量表
02.调用c中的mian函数

2.中断发生时:

跳转到中断向量表,去执行相应的处理(硬件自动)
01.设置 sp_irq
02.保存现场 :
在irq异常处理函数中有可能会修改r0-r12, 所以先保存
lr-4是异常处理完后的返回地址, 也要保存
05. 汇编中调用c中断处理函数处理中断
06. 恢复现场

C语言中 :
3.中断发生前

01.main函数由汇编调用,初始化各个中断控制器,配置中断源

4.中断发生时:

由汇编调用中断处理函数,该函数应包含:
01.中断产生时分辨中断源
02.调用对应的处理函数
03.清中断标志

函数指针数组主要用来解决上述 4 中断处理函数的流程调用,未使用函数指针数组时,每配置中断,都需要改变handle_irq_c来完成 4 中的01、02、03,改动interrupt_init来配置对应的MASK位,而使用之后我们就不需要改动了,由上述 2 中的05调用 后可自动配置,也是一种优化,下面是使用函数指针数组改进后的代码:

#include "s3c2440_soc.h"

typedef void(*irq_func)(int) ;
irq_func irq_array[32];


/* SRCPND 用来显示哪个中断产生了, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTMSK 用来屏蔽中断, 1-masked
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTPND 用来显示当前优先级最高的、正在发生的中断, 需要清除对应位
 * bit0-eint0
 * bit2-eint2
 * bit5-eint8_23
 */

/* INTOFFSET : 用来显示INTPND中哪一位被设置为1
 */

/* 初始化中断控制器 */
// void interrupt_init(void)
// {
    
// 	INTMSK &= ~((1<<0) | (1<<2) | (1<<5));
// 	INTMSK &= ~(1<<10);  /* enable timer0 int */
// }

/* 读EINTPEND分辨率哪个EINT产生(eint4~23)
 * 清除中断时, 写EINTPEND的相应位
 */


void key_eint_irq(int irq)
{
    
	unsigned int val = EINTPEND;
	unsigned int val1 = GPFDAT;
	unsigned int val2 = GPGDAT;
	
	if (irq == 0) /* eint0 : s2 控制 D12 */
	{
    
		if (val1 & (1<<0)) /* s2 --> gpf6 */
		{
    
			/* 松开 */
			GPFDAT |= (1<<6);
		}
		else
		{
    
			/* 按下 */
			GPFDAT &= ~(1<<6);
		}
		
	}
	else if (irq == 2) /* eint2 : s3 控制 D11 */
	{
    
		if (val1 & (1<<2)) /* s3 --> gpf5 */
		{
    
			/* 松开 */
			GPFDAT |= (1<<5);
		}
		else
		{
    
			/* 按下 */
			GPFDAT &= ~(1<<5);
		}
		
	}
	else if (irq == 5) /* eint8_23, eint11--s4 控制 D10, eint19---s5 控制所有LED */
	{
    
		if (val & (1<<11)) /* eint11 */
		{
    
			if (val2 & (1<<3)) /* s4 --> gpf4 */
			{
    
				/* 松开 */
				GPFDAT |= (1<<4);
			}
			else
			{
    
				/* 按下 */
				GPFDAT &= ~(1<<4);
			}
		}
		else if (val & (1<<19)) /* eint19 */
		{
    
			if (val2 & (1<<11))
			{
    
				/* 松开 */
				/* 熄灭所有LED */
				GPFDAT |= ((1<<4) | (1<<5) | (1<<6));
			}
			else
			{
    
				/* 按下: 点亮所有LED */
				GPFDAT &= ~((1<<4) | (1<<5) | (1<<6));
			}
		}
	}

	EINTPEND = val;
}


void handle_irq_c(void)
{
    
	/* 分辨中断源 */
	int bit = INTOFFSET;

	/* 调用对应的处理函数 */
	irq_array[bit](bit);
	
	/* 清中断 : 从源头开始清 */
	SRCPND = (1<<bit);
	INTPND = (1<<bit);	
}

void register_irq(int irq, irq_func fp)
{
    
	irq_array[irq] = fp;

	INTMSK &= ~(1<<irq);
}


/* 初始化按键, 设为中断源 */
void key_eint_init(void)
{
    
	/* 配置GPIO为中断引脚 */
	GPFCON &= ~((3<<0) | (3<<4));
	GPFCON |= ((2<<0) | (2<<4));   /* S2,S3被配置为中断引脚 */

	GPGCON &= ~((3<<6) | (3<<22));
	GPGCON |= ((2<<6) | (2<<22));   /* S4,S5被配置为中断引脚 */
	

	/* 设置中断触发方式: 双边沿触发 */
	EXTINT0 |= (7<<0) | (7<<8);     /* S2,S3 */
	EXTINT1 |= (7<<12);             /* S4 */
	EXTINT2 |= (7<<12);             /* S5 */

	/* 设置EINTMASK使能eint11,19 */
	EINTMASK &= ~((1<<11) | (1<<19));

	register_irq(0, key_eint_irq);
	register_irq(2, key_eint_irq);
	register_irq(5, key_eint_irq);
}


只需在初始化时注册一下相应的处理函数
register_irq(0, key_eint_irq);
register_irq(2, key_eint_irq);
register_irq(5, key_eint_irq);
待到中断到来时即可自动完成 分辨中断源 、调用对应的处理函数 、清中断这一系列操作,时代码更加便于维护

注:本文参考了
typedef函数指针的用法(C++)

C/C++ 函数指针使用总结

和韦东山老师的讲解,转载请注明出处,本人初学Linux,如有错误,欢迎评论区批评指出,共同进步

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_28816873/article/details/104684373

智能推荐

【小白必胜-xpath】lxml.etree.HTML(),lxml.etree.fromstring()和lxml.etree.tostring()三者的区别与联系_html.fromstring()含义-程序员宅基地

文章浏览阅读3.5k次,点赞5次,收藏30次。对于使用xpath()之前的文档格式化问题,可能不同的人,会遇到不一样的情况,但是基本上只要搞懂了lxml.etree.HTML(),lxml.etree.fromstring()和lxml.etree.tostring()这三者之间的区别和联系,那么文档格式化这一步一定不会有问题……_html.fromstring()含义

Android过渡动画基础使用_gradentdrawable过渡-程序员宅基地

文章浏览阅读2k次。前言Android从API19引入了Transition过渡动画框架,它通过场景Scene概念来表述动画的关键帧,只要提供了开始和结束场景的内容就会自动做动画。过渡动画其实是对属性动画的一种封装,它能够一次对多个对象做动画而不需要特别复杂的配置。除此之外Activity之间的切换效果也可以使用过渡动画来实现。基础使用首先需要定义Scene也就是场景对象,场景其实就是某个事件点所有的..._gradentdrawable过渡

h2ouve工具使用_GitHub - sha310139/Edit_BIOS_Setting_Interface: H2OUVE是使用command修改BIOS設定的工具,此介面結合H2OUVE,對...-程序员宅基地

文章浏览阅读4.7k次。Edit_BIOS_Setting_Interface簡介H2OUVE是個可透過command的方式修改BIOS設定的工具,我們利用Python3撰寫一個簡易的介面,透過H2OUVE去修改BIOS的設定,省去使用者一一下指令並修改設定檔的麻煩。當多台server需套用同一個BIOS設定時,只要先針對一台電腦修改所需的BIOS設定,匯出設定檔後,即可透過deploy kit執行這個程式,讓多台ser..._h2ouve

超时尚的UI电子商务PSD分层模板,临摹学习必备-程序员宅基地

文章浏览阅读124次。用户体验或更准确地说,电子商务用户体验(UX)是所有UI的重要方面,在处理电子商务应用程序时是必需的。当前,电子商务业务变得越来越有竞争力,各种各样的选择使客户忠诚度成为无可挑剔的现象。UX主要是指用户在操作电子商务网站时的体验。UX封装了用户在经营电子商务商店时所经历的一系列印象,包括可访问性,便利性,满意度等。拥有“好”用户体验是指用户可以高效,愉快地满足其需求的情况。此外,用户体验优化与转化化不同。尽管UX优化的重点是满意度,可用性和将其推荐给朋友的热情等指标,但其他形式的优化却着眼于提高

Ubuntu18.04安装Cartographer_error: cannot launch node of type [cartographer_ro-程序员宅基地

文章浏览阅读3.4k次,点赞5次,收藏44次。你好你好#include <iostream>using namespace std;int main(int argc, char* argv[]){ return 0;}_error: cannot launch node of type [cartographer_ros/cartographer_node]: cart

无法理解高等数学怎么办?_高数-程序员宅基地

文章浏览阅读5.8k次,点赞26次,收藏39次。我们学高等数学的时1候是这样的:这当然学不懂了,跨度太大了。这个锅,教材(对,说的就是同济《高等数学》)肯定得背。1 应该怎么学习?学习应该循序渐进,意思就是,应该从已有的知识出发,保持足够小的步伐前进。让我们把已有的知识称作 ,足够小的步伐称为 ,那么:才是最有效的学习方法。比如:注意:什么是 是比较主观的问题。下面我尝试用 的方法,解释下..._高数

随便推点

C++学习笔记——C++中四个点代表什么意思?_c++四个点什么意思-程序员宅基地

文章浏览阅读8k次,点赞5次,收藏6次。问:C++中四个点代表什么意思? 答: 表示类的成员。(你说的是两个冒号“:”吧) 如类CA中有成员int a; 则:CA::a表示CA类中的成员a; 举个例子: class CA { int a; int b(); }; 那么:函数int b();的定义就必须是这样的: int CA::b() { //… }..._c++四个点什么意思

Cython的简单使用-程序员宅基地

文章浏览阅读83次。from:http://www.cnblogs.com/freeweb/p/6548208.html补充:在我mac上的加速效果:最初:runing1 time: 0.233466 sruning2 time: 0.930724 s使用Cython编译:runing1 time: 0.178522 sruning2 time: 0.646462 s使用cdef等静态变量:runi..._cython查询数据库节点属性

ES6新特总结_new easy let 选择那个不同的-程序员宅基地

文章浏览阅读250次。ES6新特性Babel转码器ECMAScript 6 简介学习网址:http://es6.ruanyifeng.com/ECMAScript是javascript标准ES6就是ECMAScript的第6个版本ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。ECMAScript 和 JavaScript 的关系ECM_new easy let 选择那个不同的

idea 如何配置软回车_idea 软换行什么意思-程序员宅基地

文章浏览阅读598次。软回车: 只是视觉上的换行, 其实文本内容并没实际换行, 这是为了我们可以直接看到整行内容, 而无需再使用鼠标水平滚动窗口idea 中可以配置编辑器软换行editor > general > 勾选 soft wrap these files: * (其中 * 表示软换行对所有文件生效)配置控制台软换行editor > console > 勾选 use soft wraps in console编辑器和控制台软换行效果如图..._idea 软换行什么意思

Vue 2项目如何升级到Vue 3?_vue2项目换成vue3-程序员宅基地

文章浏览阅读2.4w次,点赞14次,收藏67次。Vue2项目如何升级到Vue3_vue2项目换成vue3

Activiti工作流使用详细介绍_activiti开启工作流-程序员宅基地

文章浏览阅读554次。Activiti项⽬是⼀项新的基于Apache许可的开源BPM平台,BPM,即Business Process Management,业务流程管理,通常,BPM也指针对流程管理的信息化系统,其特点是注重流程驱动为核⼼,实现端到端全流程信息化管理。BPMN,即Business Process Modeling Notation,业务流程建模符号。BPMN定义了⼀个业务流程图。_activiti开启工作流

推荐文章

热门文章

相关标签