一 有限状态机的实现方式

有限状态机(Finite State Machine或者Finite State Automata)是软件领域中一种重要的工具,很多东西的模型实际上就是有限状态机。
FSM的实现方式:
1) switch/case或者if/else
这无意是最直观的方式,使用一堆条件判断,会编程的人都可以做到,对简单小巧的状态机来说最合适,但是毫无疑问,这样的方式比较原始,对庞大的状态机难以维护。
2) 状态表
维护一个二维状态表,横坐标表示当前状态,纵坐标表示输入,表中一个元素存储下一个状态和对应的操作。这一招易于维护,但是运行时间和存储空间的代价较大。
3) 使用State Pattern
使用State Pattern使得代码的维护比switch/case方式稍好,性能上也不会有很多的影响,但是也不是100%完美。不过Robert C. Martin做了两个自动产生FSM代码的工具,for java和for C++各一个,在http://www.objectmentor.com/resources/index上有免费下载,这个工具的输入是纯文本的状态机描述,自动产生符合State Pattern的代码,这样developer的工作只需要维护状态机的文本描述,每必要冒引入bug的风险去维护code。
4) 使用宏定义描述状态机
一般来说,C++编程中应该避免使用#define,但是这主要是因为如果用宏来定义函数的话,很容易产生这样那样的问题,但是巧妙的使用,还是能够产生奇妙的效果。MFC就是使用宏定义来实现大的架构的。
在实现FSM的时候,可以把一些繁琐无比的if/else还有花括号的组合放在宏中,这样,在代码中可以3)中状态机描述文本一样写,通过编译器的预编译处理产生1)一样的效果,我见过产生C代码的宏,如果要产生C++代码,己软MFC可以,那么理论上也是可行的。

二 状态机的两种写法+实例

有限状态机FSM思想广泛应用于硬件控制电路设计,也是软件上常用的一种处理方法(软件上称为FMM--有限消息机)。它把复杂的控制逻辑分解成有限个稳定状态,在每个状态上判断事件,变连续处理为离散数字处理,符合计算机的工作特点。同时,因为有限状态机具有有限个状态,所以可以在实际的工程上实现。但这并不意味着其只能进行有限次的处理,相反,有限状态机是闭环系统,有限无穷,可以用有限的状态,处理无穷的事务。

有限状态机的工作原理如图1所示,发生事件(event)后,根据当前状态(cur_state),决定执行的动作(action),并设置下一个状态号(nxt_state)。

-------------

|           |-------->执行动作action

发生事件event ----->| cur_state |

|           |-------->设置下一状态号nxt_state

-------------

当前状态

图1 有限状态机工作原理

e0/a0

--->--

|    |

-------->----------

e0/a0 |        |   S0   |-----

|    -<------------    | e1/a1

|    | e2/a2           V

----------           ----------

|   S2   |-----<-----|   S1   |

----------   e2/a2   ----------

图2 一个有限状态机实例

--------------------------------------------

当前状态   s0        s1        s2     | 事件

--------------------------------------------

a0/s0      --       a0/s0   |  e0

--------------------------------------------

a1/s1      --        --     |  e1

--------------------------------------------

a2/s2     a2/s2      --     |  e2

--------------------------------------------

表1 图2状态机实例的二维表格表示(动作/下一状态)

图2为一个状态机实例的状态转移图,它的含义是:

在s0状态,如果发生e0事件,那么就执行a0动作,并保持状态不变;

如果发生e1事件,那么就执行a1动作,并将状态转移到s1态;

如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;

在s1状态,如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;

在s2状态,如果发生e0事件,那么就执行a0动作,并将状态转移到s0态;

有限状态机不仅能够用状态转移图表示,还可以用二维的表格代表。一般将当前状态号写在横行上,将事件写在纵列上,如表1所示。其中“--”表示空(不执行动作,也不进行状态转移),“an/sn”表示执行动作an,同时将下一状态设置为sn。表1和图2表示的含义是完全相同的。

观察表1可知,状态机可以用两种方法实现:竖着写(在状态中判断事件)和横着写(在事件中判断状态)。这两种实现在本质上是完全等效的,但在实际操作中,效果却截然不同。

==================================

竖着写(在状态中判断事件)C代码片段

cur_state = nxt_state;

switch(cur_state)

{                  //在当前状态中判断事件

case s0:                        //在s0状态

if(e0_event)

{ //如果发生e0事件,那么就执行a0动作,并保持状态不变;

执行a0动作;

//nxt_state = s0; //因为状态号是自身,所以可以删除此句,以提高运行速度。

}

else if(e1_event)

{//如果发生e1事件,那么就执行a1动作,并将状态转移到s1态;

执行a1动作;

nxt_state = s1;

}

else if(e2_event)

{ //如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;

执行a2动作;

nxt_state = s2;

}

break;

case s1:                        //在s1状态

if(e2_event)

{    //如果发生e2事件,那么就执行a2动作,并将状态转移到s2态;

执行a2动作;

nxt_state = s2;

}

break;

case s2:                        //在s2状态

if(e0_event)

{ //如果发生e0事件,那么就执行a0动作,并将状态转移到s0态;

执行a0动作;

nxt_state = s0;

}

}

==================================

横着写(在事件中判断状态)C代码片段

==================================

//e0事件发生时,执行的函数

void e0_event_function(int * nxt_state)

{

int cur_state;

cur_state = *nxt_state;

switch(cur_state)

{

case s0:                        //观察表1,在e0事件发生时,s1处为空

case s2:

执行a0动作;

*nxt_state = s0;

}

}

//e1事件发生时,执行的函数

void e1_event_function(int * nxt_state)

{

int cur_state;

cur_state = *nxt_state;

switch(cur_state)

{

case s0:                        //观察表1,在e1事件发生时,s1和s2处为空

执行a1动作;

*nxt_state = s1;

}

}

//e2事件发生时,执行的函数

void e2_event_function(int * nxt_state)

{

int cur_state;

cur_state = *nxt_state;

switch(cur_state)

{

case s0:                        //观察表1,在e2事件发生时,s2处为空

case s1:

执行a2动作;

*nxt_state = s2;

}

}

上面横竖两种写法的代码片段,实现的功能完全相同,但是,横着写的效果明显好于竖着写的效果。理由如下:

1、竖着写隐含了优先级排序(其实各个事件是同优先级的),排在前面的事件判断将毫无疑问地优先于排在后面的事件判断。这种if/else if写法上的限制将破坏事件间原有的关系。而横着写不存在此问题。

2、由于处在每个状态时的事件数目不一致,而且事件发生的时间是随机的,无法预先确定,导致竖着写沦落为顺序查询方式,结构上的缺陷使得大量时间被浪费。对于横着写,在某个时间点,状态是唯一确定的,在事件里查找状态只要使用switch语句,就能一步定位到相应的状态,延迟时间可以预先准确估算。而且在事件发生时,调用事件函数,在函数里查找唯一确定的状态,并根据其执行动作和状态转移的思路清晰简洁,效率高,富有美感。

总之,我个人认为,在软件里写状态机,使用横着写的方法比较妥帖。

竖着写的方法也不是完全不能使用,在一些小项目里,逻辑不太复杂,功能精简,同时为了节约内存耗费,竖着写的方法也不失为一种合适的选择。

在FPGA类硬件设计中,以状态为中心实现控制电路状态机(竖着写)似乎是唯一的选择,因为硬件不太可能靠事件驱动(横着写)。不过,在FPGA里有一个全局时钟,在每次上升沿时进行状态切换,使得竖着写的效率并不低。虽然在硬件里竖着写也要使用IF/ELSIF这类查询语句(用VHDL开发),但他们映射到硬件上是组合逻辑,查询只会引起门级延迟(ns量级),而且硬件是真正并行工作的,这样竖着写在硬件里就没有负面影响。因此,在硬件设计里,使用竖着写的方式成为必然的选择。这也是为什么很多搞硬件的工程师在设计软件状态机时下意识地只使用竖着写方式的原因,盖思维定势使然也。

TCP和PPP框架协议里都使用了有限状态机,这类软件状态机最好使用横着写的方式实现。以某TCP协议为例,见图3,有三种类型的事件:上层下达的命令事件;下层到达的标志和数据的收包事件;超时定时器超时事件。

上层命令(open,close)事件

-----------------------------------

--------------------

|       TCP        |  <----------超时事件timeout

--------------------

-----------------------------------

RST/SYN/FIN/ACK/DATA等收包事件

图3 三大类TCP状态机事件

由图3可知,此TCP协议栈采用横着写方式实现,有3种事件处理函数,上层命令处理函数(如tcp_close);超时事件处理函数(tmr_slow);下层收包事件处理函数(tcp_process)。值得一提的是,在收包事件函数里,在各个状态里判断RST/SYN/FIN/ACK/DATA等标志(这些标志类似于事件),看起来象竖着写方式,其实,如果把包头和数据看成一个整体,那么,RST/SYN/FIN/ACK/DATA等标志就不必被看成独立的事件,而是属于同一个收包事件里的细节,这样,就不会认为在状态里查找事件,而是总体上看,是在收包事件里查找状态(横着写)。

在PPP里更是到处都能见到横着写的现象,有时间的话再细说。我个人感觉在实现PPP框架协议前必须了解横竖两种写法,而且只有使用横着写的方式才能比较完美地实现PPP。

用C语言实现有限状态机--读《C专家编程》

有限状态机(finite state machine)是一个数学概念,如果把它运用于程序中,可以发挥很大的作用。它是一种协议,用于有限数量的子程序("状态")的发展变化。每个子程序进行一些处理并选择下一种状态(通常取决于下一段输入)。

有限状态机(FSM)可以用作程序的控制结构。FSM对于那些基于输入的在几个不同的可选动作中进行循环的程序尤其合适。投币售货机就是FSM的一个好例子。另外一个你可以想到的复杂的例子就是你正在用的东西,想到了吗?没错,就是操作系统。在投币售货机的例子中,输入是硬币,输出是待售商品,售货机有"接受硬币","选择商品","发送商品"和"找零钱"等几种状态。

它的基本思路是用一张表保存所有可能的状态,并列出进入每个状态时可能执行的所有动作,其中最后一个动作就是计算(通常在当前状态和下一次输入字符的基础上,另外再经过一次表查询)下一个应该进入的状态。你从一个"初始状态"开始。在这一过程中,翻译表可能告诉你进入了一个错误状态,直到到达结束状态。

在C语言中,有好几种方法可以用来表达FSM,但它们绝大多数都是基于函数指针数组。一个函数指针数组可以像下面这样声明:

void (*state[MAX_STATES]) ();

如果知道了函数名,就可以像下面这样对数组进行初始化。

extern int a(),b(),c(),d();

int (*state[]) ()={a,b,c,c};

可以通过数组中的指针来调用函数:

(*state[i]) ();

所有函数必须接受同样的参数,并返回同种类型的返回值(除非你把数组元素做成一个联合)。函数指针是很有趣的。注意,我们可以去掉指针形式,把上面的调用写成:

state[i] ();

甚至

(******state[i]) ();

这是一个在ANSI C中流行的不良方法:调用函数和通过指针调用函数(或任意层次的指针间接引用)可以使用同一种语法。

如果你想干得漂亮一点,可以让状态函数返回一个指向通用后续函数的指针,并把它转换为适当的类型。这样,就不需要全局变量了。如果你不想搞得太花哨,可以使用一个switch语句作为一种简朴的状态机,方法是赋值给控制变量并把switch语句放在循环内部。关于FSM还有最后一点需要说明:如果你的状态函数看上去需要多个不同的参数,可以考虑使用一个参数计数器和一个字符串指针数组,就像main函数的参数一样。我们熟悉的int argc,char *argv[]机制是非常普遍的,可以成功地应用在你所定义的函数中。

实例:密码锁:以思维密码校验作为状态机的例子,连续输入2479就可以通过密码测试。

代码一:

#include <stdio.h>

#include<stdlib.h>

#include<string.h>

typedef enum

{

STATE0 = 0,

STATE1,

STATE2,

STATE3,

STATE4,

}STATE;

typedef enum

{

INPUT1 = '2',

INPUT2 = '4',

INPUT3 = '7',

INPUT4 = '9',

}INPUT;

int main()

{

char ch;

STATE current_state = STATE0;

while(1)

{

printf("please input number to decode:");

while((ch = getchar())!='/n')

{

if((ch<'0')||(ch>'9'))

{

printf("not number, please input again!/n");

break;

}

switch(current_state)

{

case STATE0:

if(ch == '2') current_state = STATE1;

break;

case STATE1:

if(ch == '4') current_state = STATE2;

break;

case STATE2:

if(ch == '7') current_state = STATE3;

break;

case STATE3:

if(ch == '9') current_state = STATE4;

break;

default:

current_state = STATE0;

break;

}

}

if(current_state == STATE4)

{

printf("corrent, lock is open!/n");

current_state = STATE0;

}

else

{

printf("wrong, unlocked!/n");

current_state = STATE0;

}

}

return 0;

}

代码二:

//FSMstate.h

typedef enum{

STATE0 = 0,

STATE1,

STATE2,

STATE3,

STATE4,

}STATE;

typedef enum{

INPUT1 = '2',

INPUT2 = '4',

INPUT3 = '7',

INPUT4 = '9',

}INPUT;

typedef struct

{

STATE cur_state;

INPUT input;

STATE next_state;

}STATE_TRANS;

//FSMstate.c

#include<stdio.h>

#include"FSMstate.h"

/*typedef enum

{

STATE0 = 0,

STATE1,

STATE2,

STATE3,

STATE4,

}STATE;

typedef enum

{

INPUT1 = '2',

INPUT2 = '4',

INPUT3 = '7',

INPUT4 = '9',

}INPUT;

typedef struct

{

STATE cur_state;

INPUT input;

STATE next_state;

}STATE_TRANS;

*/

STATE_TRANS state_trans_array[]=

{

{STATE0,INPUT1,STATE1},

{STATE1,INPUT2,STATE2},

{STATE2,INPUT3,STATE3},

{STATE3,INPUT4,STATE4},

};

#define STATE_TRANS_CNT (sizeof(state_trans_array)/sizeof(state_trans_array[0]))

int main()

{

int i;

char ch;

STATE state_machine = STATE0;

while(ch!= 'e')

{

ch = getchar();

if((ch>= '0')&&(ch<='9'))

{

for(i = 0;i<STATE_TRANS_CNT ;i++)

{

if((ch==state_trans_array[i].input)&&(state_machine==state_trans_array[i].cur_state))

{

state_machine = state_trans_array[i].next_state;

//continue;

break;

}

else if(i==(STATE_TRANS_CNT-1))

{

state_machine = STATE0;

}

}

if(state_machine == STATE4)

printf("Password correct,state transfer machine pass!/n");

}

}

return 0;

}

四 有限状态机自动机

状态图--一个图的数据结构!

1.while + switch;

2.状态机:就是指定系统的所有可能的状态及状态间跳转的条件,然后设一个初始状态输入给这台机器,机器就会自动运转,或最后处于终止状态,或在某一个状态不断循环。

游戏中状态切换是很频繁的。 可能以前要切换状态就是if~else,或者设标志,但这些都不太结构化, 如果把它严格的设为一种标准的状态机,会清楚的多。

比如控制一扇门的运动, 初始时门是关的, 当有力作用在门上时, 门开始慢慢打开,力的作用完后,门渐渐停止不动, 当有反向的力时,门又渐渐关上, 知道回到初始关的状态。 这个你会怎么来编程实现呢。 似乎很麻烦, 的确,没有状态机的思想时会很烦,设很多标志,一堆if条件。

用状态机的话,不只是代码更清晰, 关键是更符合逻辑和自然规律, 不同状态不同处理, 满足条件则跳转到相关状态。

伪码如下:

enum

{

CLOSED, // 关上状态

CLOSING, // 正在关状态

OPENED, // 打开状态

OPENING, // 正在开的状态

}doorState = CLOSED; // 初始为关

Update()

{

switch(doorState)

case CLOSED:

if (有人推门)

doorState = OPENING; // 跳转到正在开状态

break;

case OPENING:

door.Rotation += DeltaAngle; // 门的旋转量递增

if (门的速度为零) / / 力的作用已去

doorState = OPENED; // 跳转到开状态

break;

case OPENED:

if (有人关门)

doorState = CLOSING;

break;

case CLOSING:

door.Rotation -= DeltaAngle; // 门的旋转量递减

if (门的旋转角度减为零)

doorState = CLOSED; // 门已关上

break;

}

// 而绘制代码几乎不用怎么变, 门就是会严格按照状态机的指示去运动, 该停就会停

Render()

{

RotateView(door.Rotation);

DrawDoor(door.Position);

}

这是一个简单但很典型的例子, 状态机的应用太多了。

就说一个基本游戏的运转: (用到了一个状态然后还有子状态)

UpdateGame()

BEGIN;

switch(gameState)

case 等待选择菜单: //它有三个子状态。

if (选择菜单项 == 开始)

{

游戏初始;

gameState = 开始游戏

}

if (选择菜单项 == 选项)

gameState = 设置

if (选择菜单项 == 退出)

gameState = 退出

case 开始:

游戏运行;

if (用户按退出键)

gameState = 等待选择菜单 ;

...其他的状态跳转处理;

case 退出:

释放资源;

退出;

case 设置:

分别处理不同的选项, 跳转不同的子状态;

case .... // 其他状态的处理

END;

某一个状态可以包含更多的子状态, 这样最好是同一层次的状态设为一个枚举, 并分到另一个switch处理如 enum STATES{state1, state2, state3}; state2又包含若干状态则再定义enum SUB_STATE2{sub_state2_1, sub_state2_2, sub_state2_3,};

想很多基本的渲染效果, 如淡入淡出, 闪烁等等, 用状态的思想会事半功倍, 思路也更清晰。

其实像Opengl, Direct3D这样的渲染引擎本身就是状态机, 当你设置渲染的状态, 这台机器就保持这个状态进行渲染工作,如保持光照位置,保持片元颜色, 直到你再次改变它的状态。

状态机的应用实在太广, 相关理论也很多, 最近上课学的随机过程里也讲到一些, 数字电路里的时序逻辑器件也是用状态机来描述。 这些不必多说了。

总之, 用状态机的角度去看待问题, 往往能把比较复杂的系统分解为能单独处理的众多子状态, 处理也会得心应手。希望大家多用它, 很好的东西。

五.用C语言实现一个状态机,这是一位好心的网友的毕业设计,用nRF24L01组建了一个简单的网络,做的一个小的状态机,网络中三个节点,开始拓扑为网状,后来为星型。

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

//Finite state machine declaration

//state declaration

#define IDLE 0 //idle state in rx mode

#define M_BROADCAST 1 //broadcast state in tx mode,broadcast to be a master point

#define M_WAIT_BROADCAST_ACK 2 //wait for broadcast ack state in rx mode,wait for the point ack in a specific time window

#define M_WAIT_COMMAND 3 //wait for command state,wait for PC command via UART

#define M_BROADCAST_CANCEL 4 //broadcast cancel state,broadcast to cancel master point

#define S_BROADCAST_ACK 5 //slave mode,send back self physical address

#define S_WAIT_COMMAND 6 //slave mode, wait for command from the master point

//state transition trig

//used in master mode

int isReqBeMaster = 0;//Is PC request the point to be master?

int isTimeout = 0;//Is time out?

int isReqCancelMaster = 0;//Is request to cancel master?

//used in slave mode

int isRxBroadcast = 0;//Is there a point broadcast to be master?

int isRxBroadcastCancel = 0;//Is receive broadcast cancel master?

typedef struct fsmtag

{

int state; //state

int timeouttime; //time out time in milliseconds

}fsm;

//function prototype

int main()

{

fsm f;

f.state = IDLE;

f.timeouttime = 0;

while(1)

{

switch(f.state)

{

case IDLE:

puts("IDLE/nWait for isReqBeMaster(1/0) isRxBroadcast(1/0):");

scanf("%d %d",&isReqBeMaster,&isRxBroadcast);

if(isReqBeMaster)

{

f.state = M_BROADCAST;

break;

}

else if(isRxBroadcast)

{

f.state = S_BROADCAST_ACK;

break;

}

else

break;

case M_BROADCAST:

puts("M_BROADCAST/nBroadcasting.../n");

f.state = M_WAIT_BROADCAST_ACK;

case M_WAIT_BROADCAST_ACK:

puts("M_WAIT_BROADCAST_ACK/nWaiting for isTimeout(1/0):");

scanf("%d",&isTimeout);

if(isTimeout)

{

f.state = M_WAIT_COMMAND;

break;

}

else

break;

case M_WAIT_COMMAND:

puts("M_WAIT_COMMAND/nWaiting for isReqCancelMaster(1/0):");

scanf("%d",&isReqCancelMaster);

if(isReqCancelMaster)

{

f.state = IDLE;

break;

}

else

break;

//Slave mode routine

case S_BROADCAST_ACK:

puts("S_BROADCAST_ACK/nAcking.../n");

f.state = S_WAIT_COMMAND;

break;

case S_WAIT_COMMAND:

puts("S_WAIT_COMMAND/nWaiting for isRxBroadcastCancel(1/0):");

scanf("%d",&isRxBroadcastCancel);

if(isRxBroadcastCancel)

{

f.state = IDLE;

break;

}

else

break;

default:

puts("default");

printf("%d/n",rand());

f.state = IDLE;

}

}

return 0;

}

整理:状态机的C语言编程相关推荐

  1. 状态机的C语言编程(转)

    感谢网友的分享,我才这么快掌握状态机编程,怕丢失了,把网友的帖子全部总结在这里以便查看 一 有限状态机的实现方式 有限状态机(Finite State Machine或者Finite State Au ...

  2. 成都c语言程序设计,成都c语言编程学习入门

    导语概要 成都c语言编程学习入门 ?小编推荐成都童程童美,成都童程童美是知名品牌,名师执教.通俗易懂.深受广大学员所欢迎.下面是小编整理的 成都c语言编程学习入门的一些资料,仅供参考. 学少儿编程的费 ...

  3. c语言程序设计库搜索app,C语言编程宝典app

    C语言编程宝典app帮助学生能够更好的去进行编程知识的学习,拥有非常详细的c语言的学习知识,帮助用户去从c语言的入门学习到c语言的掌握,能够帮助用户去应对给类型的校内c语言考试,并且还能够为用户学习其 ...

  4. c语言分支编程改错题,二级C语言改错 二级C语言编程题 汇总整理篇.doc

    二级C语言改错 二级C语言编程题 汇总整理篇 360教育在线 宇创IT培训07年9月内部资料 内部资料,请勿公开传播 二.改错题 考试做题要求: 1.在/**********found******** ...

  5. c二级语言程序编程题,二级C语言编程题 汇总整理篇.doc

    二级C语言编程题 汇总整理篇 360教育在线 宇创IT培训07年9月内部资料 内部资料,请勿公开传播 三.编程题 考试做题要求: 1.按题目要求编写部分代码,不要改动已经有的代码. 2.在运行时如果遇 ...

  6. c语言编程步骤Vision4,【2017年整理】keil_μVision4使用详解教程.pdf

    [2017年整理]keil_μVision4使用详解教程 Keil μVision4 使用详解 zxmh6 前言 单片机开发中除必要的硬件外,同样离不开软件,我们写的汇编语言源程序要变为 CPU 可以 ...

  7. C 语言编程 — 静态库、动态库和共享库

    目录 文章目录 目录 文章目录 程序函数库 静态链接 创建静态库文件 动态链接 创建共享库文件 共享库文件的名字 共享库文件的存储路径 LD_LIBRARY_PATH 环境变量 ldconfig 指令 ...

  8. 电信笔试c语言编程,华工电信院电子与通信工程 923复试面试,笔试考什么?复试真题...

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 华工考研刚刚落下帷幕,考研经验对广大备考华南理工大学硕士研究生入学考试的同学们而言至关重要,尤其是华工考研的相关经验文章,是往届学姐学长留给大家的宝贵财富 ...

  9. 内存编程 c语言 c,C语言编程入门之内存管理

    本篇教程探讨了C语言编程入门之内存管理,希望阅读本篇文章以后大家有所收获,帮助大家对相关内容的理解更加深入. < 自动变量与静态变量 auto自动变量 auto是默认的关键字,如实际中int a ...

最新文章

  1. ElasticSearch 深入理解 三:集群部署设计
  2. linux系统中如何查看日志 (常用命令)
  3. 书------编程(理论方面)
  4. php el表达式,JSP EL表达式学习
  5. mplayer-php,jQuery MPlayer网站音乐播放器插件
  6. 操作符offset 和 jmp指令
  7. Magic Squares 魔板 (BFS+HASH)
  8. 表现层(jsp)、持久层(类似dao)、业务层(逻辑层、service层)、模型(javabean)、控制层(action)...
  9. 腾讯TBS加载网页无法自适应记录
  10. db2 脚本运行错误返回错误原因_电脑运行错误代码大全,遇到报错请自己对照断电原因所在吧...
  11. Lucene6.5.0 下中文分词IKAnalyzer编译和使用
  12. z-index属性简介
  13. java代码实现读写txt文件(txt文件转换成java文件)
  14. js焦点图片层叠轮播切换滚动
  15. 计算机怎么硬盘重做系统,怎么为双硬盘的电脑重装系统
  16. 第24期、宠物医院管理系统
  17. 学习matlab(十四)——GUI
  18. Python数据分析与展示:实例:图像的手绘效果
  19. 信息学奥赛一本通-1042
  20. matlab读Excel表格数据画图,matlab读Excel表格数据画图-matlab如何从excel表格中读取数据?...

热门文章

  1. 在CentOS7上源码安装MongoDB 3.2.7
  2. css 实现鼠标悬浮时等比放大图片,鼠标移出时还原图片
  3. html2canvas生成放大图片
  4. vue实现时间段选择组件,分星期,最小粒度半点
  5. 永磁同步电机滑膜控制SMO控制仿真模型
  6. 谷歌浏览器怎么设置默认隐身模式启动
  7. 联合证券|内外利好共振 今年A股可更乐观一点
  8. d610网络计算机,尼康D610,10年来我见过的真心值得买的全幅单反
  9. excel2007计算机试题,巧用excel2007/2003进行拍照
  10. 【RGB手持补光棒调光照明方案】 单节双节电池LED升压恒流驱动调光芯片FP7208,PWM内部转模拟调光,无频闪顾虑低亮无抖动