例程1:闪烁的LED
1、首先我们要声明一个状态机
//! \brief define a static statemachine
STATIC_FSM(Work_LED_Flash)
NO_INIT PRIVATE uint32_t s_hwLEDDelay;
PRIVATE STATE(LED_Init);
PRIVATE STATE(LED_Toggle);
PRIVATE STATE(LED_Delay);
END_FSM2、接下来我们要实现状态机的本体。
/*----------------------------------------------------------------------------*
* State machine: LED Flash *
*----------------------------------------------------------------------------*/
#if USE_LED_FLASH_SERVICE == ENABLED
/*! \brief initialize work led
*/
PRIVATE STATE(LED_Init) BEGIN
//! gpio initialization
ESSF_GPIO_INIT(LED_WORK_PORT);
ESSF_GPIO_ENABLE(LED_WORK_PORT);
ESSF_GPIO_PIN_CONFIG(LED_WORK_PIN, PIN_FUNC_GPIO, PIN_OUTPUT);
//! transfer to next state
TRANSFER_TO_STATE(LED_Toggle);
EXIT_STATE;
END
/*! \brief toggle led and set delay down counter
*/
PRIVATE STATE(LED_Toggle) BEGIN
//! toggle work led
ESSF_GPIO_TOGGLE(LED_WORK_PIN);
//! initialize delay down counter
s_hwLEDDelay = 10000;
//! transfer to next state
TRANSFER_TO_STATE(LED_Delay);
EXIT_STATE;
END
/*! \brief delay
*/
PRIVATE STATE(LED_Delay) BEGIN
if (s_hwLEDDelay) {
s_hwLEDDelay--;
} else {
//! transfer to state: LED_Toggle
TRANSFER_TO_STATE(LED_Toggle);
}
REFLEXIVE_STATE;
END
#endif
3、最后我们要在初始化应用的时候启动这个状态机——也就是用上面的状态机新建一个调度器任务:/*! \note initialize application
*\param none
*\retval true hal initialization succeeded.
*\retval false hal initialization failed
*/
bool app_init(void)
{
....
//! start state machine
NEW_STATIC_FSM(Work_LED_Flash, REF_STATE(LED_Init));
...
} 占位 ding 只有地板了 下水道? 回复【1楼】Gorgon Meducer 傻孩子
[状态机引擎]
<font color=brown>
作为demo,先放一个状态机调度器的头文件出来,所有用户使用的核心内容,都在里面了
#ifndef __scheduler_h__
#define __scheduler_h__
/*============================ includes ======================================*/
#include ".\app_cfg.h"
#if task_scheduler == enabled
/*============================ macros ========================================*/
/*=====================......
-----------------------------------------------------------------------
是我喜欢的大牛,顶! 连个下水道都没抢上啊 v。。。。。vvvvvvvvvvvvvvvvvB?看的好别扭哦
还有大写太多,话说见到大写我的反应速度直线下降,总是不能很快的反应出这个单词的意思 你的操作系统~~~~~~ mark to 【8楼】 sznetling
这些大写是我自己OOXX得,没有说要用这个系统就一定要用这种不习惯的方式,
毕竟背后都是大家看得到的标准C语言。
to 【9楼】 jrcsh 邪恶的小会会
这不是一个操作系统。如果你非要这么说,我只能告诉你,这是一个安装在开发
人员脑子里面的操作系统。我这是一种和操作系统背道而驰的做法。一般的操作系统
都是想简化开发人员的开发,让他们不要考虑很多任务调度的细节,每个任务都可以
while(1)——但是这对小flash,小sram的嵌入式系统来说太昂贵了,对于某些效率
优先的项目,也有可能造成冲突。我这种全体状态机的开发模式就是要求开发人员清
楚自己要做什么,然后安排好所有运行的细节,从而获得最大的系统效率。 cool 宏~~~ 看不明白啊,看来俺编程功底实在是差,要补要补啊 呵呵,可以在M0的小片子上面试试看。学习了。 听课... 学习!! 不错 不得不说,用宏把C的风格变成pascal风格....实在是....古怪.....
这个具体有啥意义么? 其实这个到底怎么用的,能不能写个简单的测试例子啊? Gorgon Meducer,
其实我个人认为,你应该写几个实用的例子来让大家更深入的了解你的ESnail Software Framework, 就像我们有很多人用UCGUI,UCOS一样,没有多少人一开始就去深入研究他的每一个函数用法,只是在调案例时,有问题才去一点点积累. 你的ESnail Software Framework留给用户应用层的1,2个文件给应用级别的人看和用,当应用到一点成度,自然会深入...
关于状态机的内容,你可以用一些状态机工具生成代码,图一个状态图出来,有兴趣的用户也方便去研究.
因为我们自己整天都泡在自已的代码中,自己看当然是一目了然. 但是给其他人用,先要用一个简单的多任务程序实例跑起来,再深入研究的. 关注 记号,待研究 to 【19楼】 ilovezeno2
类pascal脚本代码是给代码生成器用的……
to 【21楼】 SystemARM
谢谢你的建议,我准备逐步实现这些,工作量不小,不过肯定要做。这个帖子实际上就是
探探大家的兴趣和胃口……ESSF 3.x的想法我藏了很久了……还是忍不住要拿出来……虽然我
自己评估的结果是阻力很大…… 不懂 看看 来个HELLO WORLD
你这个东西干什么用的,能否介绍一下。
你要先给大家扫一下盲! 为什么是英文的,老大在学托福吗,还是要变成美国人吗 to 【28楼】 xyang18 狗头军师
对我来说很危险的一个问题。不敢随便回答你。应该说是在学习英文吧……
直接看代码,其实不需要什么注释。以后例子肯定是中文的。 回复【11楼】Gorgon Meducer 傻孩子
to 【8楼】 sznetling
这些大写是我自己ooxx得,没有说要用这个系统就一定要用这种不习惯的方式,
毕竟背后都是大家看得到的标准c语言。
to 【9楼】 jrcsh 邪恶的小会会
这不是一个操作系统。如果你非要这么说,我只能告诉你,这是一个安装在开发
人员脑子里面的操作系统。我这是一种和操作系统背道而驰的做法。一般的操作系统
都是想简化开发人员的开发,让他们不要考虑很多任务调度的细节,每个任务都可以
while(1)——但是这对小flash,小sram的嵌入式系统来说太昂贵了,对于某些效率
优先的项目,也有可能造成冲突。我这种全体状态机的开发模式就是要求开发人员清
楚自己要做什么,然后安排好所有运行的细节,从而获得最大的系统效率。
-----------------------------------------------------------------------
遥拜大牛啊 头晕呀 看来文档很重要……呵呵……大家看不明白是我的错……还在整理……耐心哈…… 大侠。不过我看的还不是清楚。希望有简单的说明文档。 不懂 看看 :) UML? 也看不懂,干啥用的。程序框架吧,应该不是小的OS吧 楼主写个使用手册吧? 没看懂,纯支持,呵呵 关注! lz很牛!
但是我没看懂,代码生成器 是什么工具?是不是还要学习一下pascal语言? 我会写一个代码解析……大家的关注度让我很觉得很开心……我觉得应该有必要
让更多的人加入到实在的讨论中来。 Gorgon Meducer ,
你可以写一个最简单的多任务,不同优级的闪灯例子嘛!
你可以用你最熟的IDE 写,然后我可以给你改为C51,或ARM在MDK上用波形仿真,那就直观多了.
再上一些分间的图出来,然后再慢慢上状态机..... 对,用一keil 编译几个LED闪烁的最简单的例子来演示一下怎么用 to 【43楼】 SystemARM
好的!我弄好了发上来。最近有点忙,抽空做吧, 回复【1楼】Gorgon Meducer 傻孩子
-----------------------------------------------------------------------
好东西,学习一下。 等待中。。。。。。。。。。 顶了... 很感兴趣:状态机。 有空研究下 ~~~~~~ 暂时看不懂 帮顶 支持适用小rom小ram MCU的准OS 为什么把c转成其它语言用啊 一种尝试……主要是想做一个和具体编译语言无关的脚本语言……现在看来,可能有简化的需要…… 等待应用例子 继续等待实例
继续帮顶 刚发现你这个项目,看起来很不错.
大半年前我也想研究纯状态机自动生成代码,并且在一个项目上面应用了.但是效果不是很好,总的来说还是不习惯,觉得直接改代码更快.
我是用smart state(http://www.smartstatestudio.com/)编辑状态图.
自己写了一个解释器,直接解释他的状态图文件,生成源代码(*.h).
用户再根据自己的需求,填写细节代码(*.c).
支持多层状态机,不同层之间直接跳转.
另外我还有一个方法,已经用一两年.
通过xml编写状态图,描述ui布局,窗体流程,事件跳转
写一个解释器,将xml生成代码(*.h)
用户填细节代码(*.c)
相比xml架构不错,看xml文件就能迅速了解项目概况,现在还一直在产品上使用.不过树状的xml还是不如图形界面友好.
改天仔细研读你的成果. mark 傻孩子的小系统 to 【58楼】 skyler
看来可以一起合作?IDE部分我原本准备用xml存储状态机,使用图形化界面来进行开发
——简单来说就是画好状态图,双击状态,弹出代码框填写代码……和VB类似。
刚才看了一下那个smart state,感觉做得还只是停留在状态机本身,没有处理好虑资源
的问题,应该可以作为很好的参考,然后走得更远,这应该也是制约他没有更大规模被应用的
原因。另外,操作习惯上,他还是很具有亲和力……貌似他用的也是Visual Studio Shell?
嘿嘿,我也准备用这个平台,可以省很多力气……和它不同,我不准备收费……做成一个完全
开源的系统。 回复【60楼】Gorgon Meducer 傻孩子
-----------------------------------------------------------------------
Gorgon Meducer,
都在说什么,我想看懂, 请问需要看哪些书呢?推荐下 可以考虑啊,不过我最近没什么时间。在做调试器,等做完了再去想这个吧。
我的出发点是内核复杂一些也没关系,但用户(程序员)接口一定要友好,
第三者只要看流程图就可以知道整个项目的脉络。
还有一点,我坚持自动生成的代码(*.h)与用户编写的代码(*.c)分离,这样开
发时修改流程图,重新生成机器代码,也不影响用户原来写的代码。如果二者
在同一个文件里,用起来就很不方便了。
你上面的例子,如果需要用户手工编写这样的脚本,我觉得不好,因为要去
重新学习一门新的语言或掌握新的规则。不赞成这样的模式。
ps:你就职于atmel公司吗? 好! to 【62楼】 skyler
手工编写脚本还不是因为缺少IDE……我是在Atmel工作。 Gorgon Meducer ,给一个免费的IDE给你,有部分可以与你的语法兼容. 不知对你有没有帮助.
还有例子代码.
主页在: http://www.plcedit.org/
http://cache.amobbs.com/bbs_upload782111/files_35/ourdev_600347QAO0FN.PNG
(原文件名:PLCEdit.PNG)
http://cache.amobbs.com/bbs_upload782111/files_35/ourdev_600348L0C7R5.png
(原文件名:page0_1.png)
点击此处下载 ourdev_600349B6Z3EV.rar(文件大小:4.01M) (原文件名:PLCEdit.rar) to 【65楼】 SystemARM
谢谢你,这是类PASCAL语言。最近这个项目正在严格测试中。到时候会有完整的Framework发布出来。 期待傻孩子大侠 这是个OS嘛?还是什么,不太明白 回复【11楼】Gorgon Meducer傻孩子
这不是一个操作系统。如果你非要这么说,我只能告诉你,这是一个安装在开发
人员脑子里面的操作系统。我这是一种和操作系统背道而驰的做法。一般的操作系统
都是想简化开发人员的开发,让他们不要考虑很多任务调度的细节,每个任务都可以
while(1)——但是这对小flash,小sram的嵌入式系统来说太昂贵了,对于某些效率
优先的项目,也有可能造成冲突。我这种全体状态机的开发模式就是要求开发人员清
楚自己要做什么,然后安排好所有运行的细节,从而获得最大的系统效率。
-----------------------------------------------------------------------
又懂了一些,谢谢傻孩子 谢谢傻孩子提供的开源代码,正在读代码,还不太明白,希望能够编写一个使用手册之类的文档。 压缩包里好像缺少一个stdafx.h的头文件,请问是标准的VC里的stdafx.h么? to 【71楼】 admvip
把顶层app_cfg.h下#include "utilities\stdafx.h"屏蔽掉。 MARK 收藏啦 - Update new vertion ESSF 3.0 beta1
a. 增加了FSM引擎对EVENT的支持
b. 增加了FSM对休眠的支持
c. 修正了一些BUG
点击此处下载ESSF3.0 源代码 beta1 ourdev_629937IIM7Z0.rar(文件大小:80K) 回复【75楼】Gorgon Meducer 傻孩子
-----------------------------------------------------------------------
傻哥 附件挂了 貌似是下载服务器的问题? 听课,学习中... mark 听课 mark mark, 全状态机开发。 找空瞧瞧. 能给个ESSF3的小实例吗?谢谢傻孩子./emotion/em204.gif aes_fsm.c
#include "app_cfg.h"
#include "..\..\hal\hal.h"
#include "..\..\service\service.h"
#define AES_SUCCESS (0x0001u)
#define AES_ON_GOING (0x0000u)
#define AES_ILLEGAL_PARAMTER (0xFFFFu)
DEF_ARG(AES_Encryption)
uint32_t wHandle;
uint16_t hwResult;
uint16_t hwKey;
uint32_t wOffset;
uint32_t wSize;
uint8_t *pchStream;
AES_KEY_FRAME chKey;
AES_DATA_FRAMEchDataMask;
END_DEF_ARG
DECLARE
PUBLIC STATE( AES_Initialize );
PUBLIC STATE( Load_AES_Key );
PUBLIC STATE( Start_AES_Encrypt );
PRIVATE STATE( AES_Encrypt_Init );
PRIVATE STATE( AES_Encrypt );
END_DECLARE
PUBLIC STATE( AES_Initialize ) BEGIN
do {
if (!CHECK_ARG(AES_Encryption)) {
break;
}
//! load first aes key
do {
uint32_t n = AES_KEY_SIZE;
AES_KEY_FRAME chKeyMask = {<...你自己的mask>};
uint8_t *pchKey = REF_ARG(AES_Encryption).chKey;
uint8_t *pchMask = chKeyMask;
reset_random();
set_random_seed(REF_ARG(AES_Encryption).hwKey);
do {
*pchKey++ = *pchMask++ ^ get_random_u8();
} while(--n);
} while(false);
//! load datamask
do {
uint32_t n = AES_BLOCK_SIZE;
AES_DATA_FRAME chDataMask = {<你自己的mask>};
uint8_t *pchData = REF_ARG(AES_Encryption).chDataMask;
uint8_t *pchMask = chDataMask;
do {
*pchData++ = *pchMask++ ^ get_random_u8();
} while(--n);
} while(false);
} while(false);
EXIT_STATE;
END
PUBLIC STATE( Load_AES_Key ) BEGIN
do {
if (!CHECK_ARG(AES_Encryption)) {
break;
}
uint8_t *pchKey = REF_ARG(AES_Encryption).chKey;
uint32_t n = AES_KEY_SIZE;
do {
*pchKey++ ^= get_random_u8();
} while(--n);
} while(false);
EXIT_STATE;
END
PUBLIC STATE(Start_AES_Encrypt) BEGIN
do {
//! check parameter
if (!CHECK_ARG(AES_Encryption)) {
break;
}else if (NULL == REF_ARG(AES_Encryption).wHandle) {
REF_ARG(AES_Encryption).hwResult = AES_ILLEGAL_PARAMTER;
break;
} else if (0 == REF_ARG(AES_Encryption).wSize) {
REF_ARG(AES_Encryption).hwResult = AES_ILLEGAL_PARAMTER;
break;
} else if (REF_ARG(AES_Encryption).wSize % AES_BLOCK_SIZE) {
REF_ARG(AES_Encryption).hwResult = AES_ILLEGAL_PARAMTER;
break;
}else if (NULL == REF_ARG(AES_Encryption).pchStream) {
REF_ARG(AES_Encryption).hwResult = AES_ILLEGAL_PARAMTER;
break;
}
REF_ARG(AES_Encryption).wOffset = 0;
REF_ARG(AES_Encryption).hwResult = AES_ON_GOING;
CALL_FSM_EX( REF_STATE(AES_Initialize), pArg, REF_STATE(AES_Encrypt_Init), pArg);
}while(false);
EXIT_STATE;
END
PRIVATE STATE( AES_Encrypt_Init ) BEGIN
CALL_FSM_EX( REF_STATE(Load_AES_Key), pArg, REF_STATE(AES_Encrypt), pArg);
EXIT_STATE;
END
PRIVATE STATE( AES_Encrypt ) BEGIN
uint32_t wOffset = REF_ARG(AES_Encryption).wOffset;
uint8_t *pchStream = REF_ARG(AES_Encryption).pchStream;
uint8_t *pchMask = REF_ARG(AES_Encryption).chDataMask;
uint32_t n = AES_BLOCK_SIZE;
pchStream += wOffset;
//! encrypt
AES_PrepareEncrypt(REF_ARG(AES_Encryption).chKey);
do {
AES_KEY_FRAME tKey;
memcpy(tKey,REF_ARG(AES_Encryption).chKey,sizeof(AES_KEY_FRAME));
AES_Encrypt(pchStream,tKey);
} while(false);
do {
*pchStream ^= *pchMask;
*pchMask++ = *pchStream++;
} while(--n);
wOffset += AES_BLOCK_SIZE;
if (wOffset >= REF_ARG(AES_Encryption).wSize) {
//! complete
REF_ARG(AES_Encryption).hwResult = AES_SUCCESS;
} else {
CALL_FSM_EX( REF_STATE( Load_AES_Key ), pArg, REF_STATE( AES_Encrypt ), pArg );
}
REF_ARG(AES_Encryption).wOffset = wOffset;
EXIT_STATE;
END
PUBLIC STATE( AES_Decrypt ) BEGIN
uint32_t wOffset = REF_ARG(AES_Encryption).wOffset;
uint8_t *pchStream = REF_ARG(AES_Encryption).pchStream + wOffset;
uint8_t *pchMask = REF_ARG(AES_Encryption).chDataMask;
uint32_t n = AES_BLOCK_SIZE;
#if DEBUG_LEVEL >= 4 && (DEBUG_FW_DECRYPTION == ENABLED || DEBUG_FW_ENCRYPTION == ENABLED)
DEBUG_LOG_STR("AES Decrypt.\r\n Offset:");
DEBUG_LOG_HEX(wOffset);
DEBUG_LOG_STR("\r\n");
#if DEBUG_LEVEL >= 4 && (DEBUG_FW_DECRYPTION == ENABLED || DEBUG_FW_ENCRYPTION == ENABLED)
DEBUG_LOG_STR("From: ");
#endif
#endif
//! encrypt
do {
uint8_t chTemp = *pchMask;
*pchMask++ = *pchStream;
#if DEBUG_LEVEL >= 5 && (DEBUG_FW_DECRYPTION == ENABLED || DEBUG_FW_ENCRYPTION == ENABLED)
DEBUG_LOG_HEX((*pchStream));
DEBUG_LOG_STR(" ");
#endif
*pchStream++ ^= chTemp;
} while(--n);
#if DEBUG_LEVEL >= 5 && (DEBUG_FW_DECRYPTION == ENABLED || DEBUG_FW_ENCRYPTION == ENABLED)
DEBUG_LOG_STR("\r\n");
#endif
pchStream = REF_ARG(AES_Encryption).pchStream + wOffset;
AES_PrepareDecrypt(REF_ARG(AES_Encryption).chKey);
AES_Decrypt(pchStream,REF_ARG(AES_Encryption).chKey);
#if DEBUG_LEVEL >= 5 && (DEBUG_FW_DECRYPTION == ENABLED || DEBUG_FW_ENCRYPTION == ENABLED)
n = AES_BLOCK_SIZE;
DEBUG_LOG_STR(" To: ");
do {
DEBUG_LOG_HEX((*pchStream));
DEBUG_LOG_STR(" ");
pchStream++;
} while(--n);
DEBUG_LOG_STR("\r\n\r\n");
#endif
wOffset += AES_BLOCK_SIZE;
if (wOffset >= REF_ARG(AES_Encryption).wSize) {
#if DEBUG_LEVEL >= 4 && (DEBUG_FW_DECRYPTION == ENABLED || DEBUG_FW_ENCRYPTION == ENABLED)
DEBUG_LOG_STR("Decrypt AES Data Block Complete.\r\n\r\n");
#endif
//! complete
REF_ARG(AES_Encryption).hwResult = AES_SUCCESS;
} else {
//! on going...
REF_ARG(AES_Encryption).hwResult = AES_ON_GOING;
}
REF_ARG(AES_Encryption).wOffset = wOffset;
EXIT_STATE;
END
<font color=blue>aes_fsm.h<font color=brown>
#ifndef __USE_AES_FSM_CFG_H__
#define __USE_AES_FSM_CFG_H__
#include "app_cfg.h"
#define AES_SUCCESS (0x0001u)
#define AES_ON_GOING (0x0000u)
#define AES_ILLEGAL_PARAMTER (0xFFFFu)
DEF_ARG(AES_Encryption)
uint32_t wHandle;
uint16_t hwResult;
uint16_t hwKey;
uint32_t wOffset;
uint32_t wSize;
uint8_t *pchStream;
AES_KEY_FRAME chKey;
AES_DATA_FRAMEchDataMask;
END_DEF_ARG
EXTERN_STATE( Start_AES_Encrypt );
EXTERN_STATE( AES_Initialize );
EXTERN_STATE( Load_AES_Key );
EXTERN_STATE( AES_Decrypt );
#endif MARK - beta3
a. 修正了选择单任务队列的情况下函数_register_task()缺失返回值的错误。
b. 删除了不必要的宏
c. 使用新的状态返回类型vsf_fsm_rt_t
d. 加入了License. 是不是要加点说明,注释啥的。光看代码头晕。
- Beta 4
- New Features
a. Add support for thread synchronization.
i. Support Event
ii. Support Critical Section.
b. Use optimized interface standard of Versaloon Software
Framework for HAL.
- Other
a. Migerate ESSF into VSF. 标记, 傻孩子ESSF,听课......... 本帖最后由 Gorgon_Meducer 于 2012-11-2 18:40 编辑
[更新日至]
- Update new version VSF(ESSF) 3.x beta4a
a. 修正了临界区的bug
b. 简化了系统,删除了SAFE_TASK_ARG
c. 加入了演示状态机范例代码
1) 普通的静态状态机——闪烁的LED
2) 子状态机的调用——字符串打印
3) 临界区的建立——字符串打印
4) 互斥——温度显示和Demo字符串显示的互斥 [更新日至]
- Update new version VSF(ESSF) 3.x beta4b
a. 优化了调度函数结构,提高了调度效率。 Gorgon_Meducer大侠:
牛人作品,不同凡响.....膜拜....
感觉有些复杂, 有没有说明文档? 大侠能不能讲解些原理性的东西, 普及吾等小白.... foxpro2005 发表于 2012-11-10 15:36 static/image/common/back.gif
Gorgon_Meducer大侠:
牛人作品,不同凡响.....膜拜....
感觉有些复杂, 有没有说明文档? 大侠能不能 ...
原理解析看这个链接,注意61楼
http://www.amobbs.com/forum.php?mod=viewthread&tid=5468237&page=2&extra=
虽然最终实现有很多性能上的改进,但基本思想是可以互通的。 Gorgon_Meducer 发表于 2012-11-10 21:28 static/image/common/back.gif
原理解析看这个链接,注意61楼
http://www.amobbs.com/forum.php?mod=viewthread&tid=5468237&page=2&ext ...
直接看这里吧。
新版论坛中帖子的楼层号是可以点击的,点击后直接获得通往该楼层的超链接: eduhf_123 发表于 2012-11-16 01:11 static/image/common/back.gif
直接看这里吧。
新版论坛中帖子的楼层号是可以点击的,点击后直接获得通往该楼层的超链接: ...
多谢指点~{:victory:} {:handshake:} Gorgon_Meducer 发表于 2012-11-16 10:08 static/image/common/back.gif
多谢指点~
不客气哈~!{:handshake:}
{:lol:} 看看{:smile:}{:smile:} LZ,帅气,学习中。。。 楼主好人啊,跟着学习中 LZ辛苦了,其实强大的注释会对版本后期的管理与升级起相当大作用,即使你今天把A代码用很熟,如果5年后再把A代码拿出来,你还会在很短时间内把它看懂并加以实用吗?? 完全看的头疼啊。。。这个调度器是时间触发的么??现在只能稍微看懂那本《时间触发嵌入式系统模式》。。。想问下傻孩子,把状态机这种思想应用到我们的程序当中??应该怎么从零开始呢?先学面对象,然后再看那本《Object-oriented programming with ANSI-C》,再到自动机原理什么的??有什么好的学习方法可以借鉴借鉴,或者书籍推荐?? 现在写代码很想把这些好的思想用进去,但是总是没有往这方面想的意识。。不知道怎么去练习。。。
页:
[1]
2