精华内容
下载资源
问答
  • nucleus plus学习总结

    2017-08-15 09:09:32
    之前看过一些linux内核的一些东西,但都是停留在文字上,代码看的很少,这个nucleus plus内核的代码量不大,看过source code确实对很多OS的知识有了更深入的认识,收获还是挺多的,把到的东西记录下来。...
    前言:
        最近一直都在看nucleus plus,之前看过一些linux内核的一些东西,但都是停留在文字上,代码看的很少,这个nucleus plus内核的代码量不大,看过source code确实对很多OS的知识有了更深入的认识,收获还是挺多的,把学到的东西记录下来。

    内容:
    一、nucleus plus特点:
        1.内核采用微内核的设计,方便移植,资料写着更reliable,但是我不这么认为,与linux相比,以ARM平台为例,NU只用到了SVC mode,内核与用户任务都运行在同一个状态下,也就是说所有的task都拥有访问任何资源的权限,这样很reliable么?
        2.real-time OS,NU是一个软实时操作系统(VxWorks是硬实时),thread control component支持多任务以及任务的抢占,对于中断的处理定义了两种服务方式,LISR和HISR,这个与linux中的上、下半部机制类似,linux中的下半部是通过软中断来实现的,NU的HISR只是作为一种优先级总是高于task的任务出现。
        3.NU是以library的方式应用的,通过写自己的app task与裁剪后的NU内核及组件链接起来,NU并没有CLI

    二、组件

    1.IN component
        初始化组件由三个部分组成,硬件在reset后首先进入INT_initialize(),进行板级的相关初始化,首先设置SVC mode,关中断,然后将内核从rom中拷贝至ram中,建立bss段,依次建立sys stack, irq stack和fiq stack,最后初始化timer,建立timer HISR的栈空间,看了一下2410平台的代码,一个tick大概是15.8ms,完成板级的初始化后就进入了INC_initialize,初始化各个组件,其中包括Application initialize,create task和HISR,最后将控制权交给schedule,主要看了一下RAM中地址空间的安排
    |timer HISR stack = 1024|
    |FIQ stack = 512|
    |IRQ stack = 1024|
    |SVC stack = 1024|
    |.bss|
    |.data|
    |.text|
    其中SVC stack的大小与中断源的个数相关,nested irq发生时,irq_context保存在SVC stack中,IRQ的stack只是做了临时栈的作用。

    2.thread control component
        TC组件是NU内核的最重要组成部分,主要涵盖了调度、中断、任务的相关操作、锁、时钟几个方面,下面分别介绍。

    调度(schedule)

        NU中的线程类型(在同一个地址空间内)有两种,HISR和task,HISR可以理解为一种优先级较高的task,但又不是task,HISR优先级高于task的实现方式就是schdule时,先去查看当前是否有active的HISR,再去查看task。task有suspend、ready、finished和terminated四种状态,而HISR只有executing和no-active这两种状态。

        每一个task都有一个线程控制的数据结构(TCB thread control block),其中包括了task的优先级、状态、时间片、task栈、protect信息、signal操作的标志位和signal_handler等,task在创建时初始化这些信息,将task挂到一个create_list上,初始设定task为pure_suspend,如果设定auto start,调用resume_task()唤醒task,这里有个细节,如果在application initialize中create_task(),则task不会自动运行,因为初始化还未完成,控制权还没有交给schedule,无法调度task。task被唤醒后状态改变为ready,并挂在一个TCD_Priority_List[256]上,数组的每个元素是一个指向TCB环形双向链表的指针,根据task的tc_priority找到对应优先级的TCB head pointer。
                               22003667_1343546801PBr9.jpg
        每一个HISR都有一个HISR控制的数据结构(HCB HISR control block),其中只有优先级,HISR栈和HISR entry信息,因此HISR是不可以suspend,同时也没有time slice以及signal的相关操作,一般情况下当发生了中断后,HISR被activate,schedule就会调度HISR运行,期间如果不发生中断,HISR的执行是不会被打断的,HISR的优先级只有0、1、2,timer的HISR优先级为2,也就是说由外部中断激活的HISR很难被抢占的,只有更高优先级的中断HISR才可以。与task不同,被激活的HISR使用head_list和tail_list将HCB挂在一个单项的链表上,因为相同优先级的HISR不会抢占对方,因此不需要双向链表,使用两个指针目的是加快HISR执行的速度。

        一个实时操作系统的核心就是对于任务的调度,NU的调度策略是time slice和round robin的算法,
    调度的部分主要有三个函数control_to_system()用于保存上下文,建立solicited stack,关中断,关system time slice,并重置task的time slice为预设值,将sp更新为system_stack_pointer,调用schedule(),调度的过程是非常简单的查询,就是查看两个全局的变量,TCD_Execute_HISR和TCD_Execute_Task,schedule部分的关键是打开了中断,不然如果当前没有ready的task或是被激活的HISR,则shedule死循环下去,查询到下一个应该执行的线程后跳转至control_to_thread(),在这里重新开启system time slice,然后将线程的tc_stack_ptr加入到sp中,切换至线程的栈中,依次pop出来,即完成了任务调度。

        任务的切换主要是上下文的切换,也就是task栈的切换,函数的调用会保存部分regs和返回地址,这些动作都是编译器来完成的,而OS中的任务切换是运行时(runtime)的一种状态变化,因此编译器也无能为力,所以对于上下文的保存需要代码来实现。

        任务的抢占是异步的因此必须要通过中断来实现,一般每次timer的中断决定当前的task的slice time是否expired,然后设置TCT_Set_Execute_Task为相同优先级的其他task或更高优先级的task;高优先级的task抢占低优先级的task,一般是外部中断触发,在HISR中resume_task()唤醒高优先级的task,然后schedule到高优先级的task中,因为timer的HISR是在系统初始化就已经注册的,只是执行timeout和time slice超时后的操作,并没有执行resume_task的动作。

        NU中的stack有两种solicited stack和interrupt stack,solicited stack是一种minmum stack,而interrupt stack是对当前所有寄存器全部保存,TCB中的minimum stack size = 申请得到stack size - solicited stack(在arm mode下占44字节,thumb mode下占48字节),thumb标志用来记录上下文保存时的ARM的工作模式,c代码编译为thumb模式,这样可以减小code size,提高代码密度,assembly代码编译为arm模式提升代码的效率,NU中内核的代码不多,主要是assembly代码。stack的类型与其中PC指向的shell无关,interrupt stack保存的是task或是HISR在执行的过程中被中断时的现场,solicited stack建立的地方包括 control_to_system()、schedule_protect()和send_signals()发送给占有protect资源的task的情况,HISR_Shell()执行完后会建立solicited stack,再跳转至schedule。

    1. (Lower Address) Stack Top -> 1 (Interrupt stack type)
    2. CPSR Saved CPSR
    3. r0 Saved r0
    4. r1 Saved r1
    5. r2 Saved r2
    6. r3 Saved r3
    7. r4 Saved r4
    8. r5 Saved r5
    9. r6 Saved r6
    10. r7 Saved r7
    11. r8 Saved r8
    12. r9 Saved r9
    13. r10 Saved r10
    14. r11 Saved r11
    15. r12 Saved r12
    16. sp Saved sp
    17. lr Saved lr
    18. (Higher Address) Stack Bottom-> pc Saved pc

    1. (Lower Address) Stack Top -> 0 (Solicited stack type)
    2. !!FOR THUMB ONLY!! 0/0x20 Saved state mask
    3. r4 Saved r4
    4. r5 Saved r5
    5. r6 Saved r6
    6. r7 Saved r7
    7. r8 Saved r8
    8. r9 Saved r9
    9. r10 Saved r10
    10. r11 Saved r11
    11. r12 Saved r12
    12. (Higher Address) Stack Bottom-> pc Saved pc
    一个简单的例子说明stack的情况,首先是一个task在ready(executing)的状态下,而且time slice超时了,timer中断发生后,保存task上下文interrupt_contex_save(),在task的tc_stack_ptr指向的地方建立中断栈
    taskA    |interrupt stack|___tc_stack_ptr 栈顶端是pc=lr-4
    ARM对于中断的判定发生在当前指令完成execute时,同时pipeline的原因pc=pc+8,入栈时就把lr-4首先放在stack的最高端(high)。

    timer的LISR完成后激活了HISR,执行TCC_Time_slice()将当前task移到相同优先级的尾端,并且设置下一个要执行的task,HISR在栈顶端保存的是这个HISR_shell的入口地址,因为task的执行完就finished,HISR是可重入的
    HISR     |solicited stack|  栈顶端是HISR_shell_entry

    中断(interrupt)

    前面已经提及了中断的基本操作,这里就写一些代码路径的细节,中断的执行主要是两个部分LISR和HISR,分成两个部分的目的就是将关中断的时间最小化,并且在LISR中开中断允许中断的嵌套,以及建立中断优先级,都可以减少中断的延迟,保证OS的实时性。
    NU的中断模式是可重入的中断处理方式,也就是基于中断优先级和嵌套的模式,中断的嵌套在处理的过程中应对lr_irq_mode寄存器进行保存,因为高优先级的中断发生时会覆盖掉低优先级中断的r14和spsr,因此要利用系统的栈来保存中断栈。

    NU对于中断上下文的保存具体操作如下:
    (1)在中断发生后执行的入口函数INT_IRQ()中,将r0-r4保存至irq的栈中
    (2)查找到对应的interrupt_shell(),clear中断源,更新全局的中断计数器,然后进行interrupt_contex_save
    (3)首先利用r1,r2,r3保存irq模式下的sp,lr,spsr,这里sp是用来切换至系统栈后拷贝lr和spsr的,这里保存lr和spsr是目的是task被抢占后,当再次schedule时可以返回task之前的状态。
    (4)切换至SVC模式,如果是非嵌套的中断则保存上下文至task stack中,将irq模式下的lr作为顶端PC的返回值入栈,将SVC模式下的r6-r14入栈,将irq模式下的sp保存至r4中入栈,最后将保存在irq_stack中的r0-r4入栈
    (5)如果是嵌套中断,中断的嵌套发生在LISR中,在执行LISR时已经切换至system stack,因此嵌套中断要将中断的上下文保存至system stack中,与task stack中interrupt stack相比只是少了栈顶用来标记嵌套的标志(1 not nested)
    (6)有一个分支判断,就是如果当前线程是空,即TCD_Current_Thread == NULL,表明当前是schedule中,因为初始化线程是关中断的,这样就不为schedule线程建立栈帧,因为schedule不需要保存上下文,在restore中断上下文时直接跳转至schedule。

    中断上下文的恢复
    全局的中断计数器INT_Count是否为0来判定当前出栈的信息,如果是嵌套则返回LISR中,否则切换至system stack执行schedule

    timer
    timer与中断紧密相关,其实timer也是中断的一种,只是发生中断的频率较高,且作用重大,一个实时操作系统,时间是非常重要的一部分,NU中的timer主要有四个作用:
    (1)维护系统时钟 TMD_system_clock
    (2)task的time slice
    (3)task的suspend timeout timer
    (4)application timer
    其中(3)(4)共用一种机制,一个全局的时间轴TMD_timer,timeout timer和app timer都建立在一个TM_TCB的数据结构上,通过tm_remaining_time来表征当前timer的剩余时间,例如当前有timer_list上有三个TM_TCB,依次是Ta = 5,Tb = 7, Tc = 20,那么建立的链表上剩余时间依次是5,2,8,如果现在要加入一个新的timer根据timer值插入至合适的位置,如果插入的timer为13,则安排在Tb后面,剩余时间为1,后面的8改为7,当发生了timer expired,则触发timer_HISR,如果是app timer则执行timer callback,如果是task timeout timer,则执行TCC_Task_Timeout唤醒task。

    (2)的实现也是依赖于全局的time slice时间轴,每一个task在执行时都会将自己的时间片信息更新至全局的时间轴上,当一个task的time slice执行完在timer HISR中调用TCC_task_Timeout将当前的task放在相同优先级list的最尾端,并设置下一个最高优先级的任务。task在执行的过程中只有被中断后time slice会保存下来,其他让出处理器的情况都会将time slice更新为预设值。

    protect
    protect与linux的锁机制类似,互斥访问,利用开关中断来实现,并且拥有protect的task是不可以suspend的,必须要将protect释放后才可以挂起,当一个优先级较低的task占有protect资源,如果被抢占,一个高优先级的task或HISR在请求protect资源时会执行TCC_schedule_protect()让出处理器给低优先级的task执行,直到低优先级的task执行unprotect()为止,此时task或HISR建立的是solicited stack,同时在control_to_thread前开关中断一次,这样可以减少一次上下文的切换。NU中常用到的是system_protect,它就是一把大锁,保护内核中所有全局数据结构的顺序访问,粒度很大。

    LISR中不可以请求protect资源,因为LISR是中断task后执行,如果task占有protect资源,这时LISR又去请求protect资源,会发生死锁,因为LISR让出处理器后,schedule没办法再次调度到LISR执行,则发生死锁错误,因此在LISR中除了activate_HISR()以外不可以使用system call,例如resume_task等等,这写系统调用都会请求protect资源。

    对于protect的请求按照一定的顺序可以防止死锁,NU的源码中一般将system_protect资源的请求放在后面,其他如DM_protect先请求。


    <script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSize":"16"},"share":{}};with(document)0[(getElementsByTagName('head')[0]||body).appendChild(createElement('script')).src='http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion='+~(-new Date()/36e5)];</script>
    阅读(43) | 评论(0) | 转发(0) |
    0

    上一篇:nucleus学习

    下一篇:nucleus 学习 中断

    给主人留下些什么吧!~~
    评论热议
    展开全文
  • 百度云及其他网盘下载地址:点我 编辑推荐 一本经久不衰的C++畅销经典教程;一本支持C++11新标准的程序设计图书。 它被誉为“开发人员学习C++的教程,没有之一”! Amazon网站“Language”类销售排名第三的超级...

     

    百度云及其他网盘下载地址:点我

    编辑推荐

    一本经久不衰的C++畅销经典教程;一本支持C++11新标准的程序设计图书。 
      它被誉为“开发人员学习C++的教程,没有之一”! Amazon网站“Language”类销售排名第三的超级畅销书,之前版本在美国销售超10万! 
      《C++ Primer Plus(第6版)中文版》可以说是一本面向从未学习过C语言甚到是从未学习过编程的人的入门书籍,它的一章从基础内容讲起,先介绍了传统编程的规则,后面才着重讲解有关面向对象——C++的精髓之一——的有关内容。整个书的结构安排较为合理,难度爬升较慢。 
      如果你是一个从未学过C语言(或者压根没学会C)的读者,那么,我相信这本书更适合你。

    媒体推荐

    约20年前,我是看Stephen Prata的C Primer Plus学C语言的。现在Stephen Prata的新书又回到我手上了,这次是C++ Primer Plus(第六版)中文版。对于系统编程语言来说,除了C++,我们没有太多其他的选择。D语言曾经有挑战C++的机会,但机会似乎已经消失,我预期未来GO语言也会挑战C++,但不管怎样,目前C++依然是王者。这本C++ Primer Plus(第6版)中文版是C++图书中内容最新最丰富者。C++是一门很容易误用的语言,学习C++需要钜细靡遗。我选择用这本书复习并更新我的C++知识。

    ——创新工场首席架构师 蔡学镛
    C++很有用,但也很难学。学C++之难,一是学习曲线陡峭,二是容易误入歧途。C++11标准颁布之后,这个问题就更严重。C++ Primer Plus 是在市场的多年检验中脱颖而出的一本名著,它的价值,正在于降低陡峭的学习曲线,并且确保读者学到“正确”的C++。
    ——孟岩
    如果说C++ Primer是C++语言的一本百科全书,讲述了C++语言里面“有什么”;那么C++ Primer Plus就是这门语言的一本通识课本,它实实在在地教给程序员如何理解和使用这种内容丰富、威力强大的语言。本书最大的特点就是务实,通过类比、举例和习惯多维度的方式,为程序员打下坚实的、使用C++进行日常工作的基础。
    ——高博

    作者简介

    Stephen Prata,在美国加州肯特菲尔得的马林学院教授天文、物理和计算机科学。他毕业于加州理工学院,在美国加州大学伯克利分校获得博士学位。他单独或与他人合作编写的编程图书有十多本,其中《New C Primer Plus》获得了计算机出版联合会1990年度“How-to”计算机图书奖,《C++ Primer Plus》获得了计算机出版联合会1991年度“How-to”计算机图书奖提名。

    目录

    第1章 预备知识
    1.1 C++简介
    1.2 C++简史
    1.2.1 C语言
    1.2.2 C语言编程原理
    1.2.3 面向对象编程
    1.2.4 C++和泛型编程
    1.2.5 C++的起源
    1.3 可移植性和标准
    1.3.1 C++的发展
    1.3.2 本书遵循的C++标准
    1.4 程序创建的技巧
    1.4.1 创建源代码文件
    1.4.2 编译和链接
    1.5 总结
    第2章 开始学习C++
    2.1 进入C++
    2.1.1 main()函数
    2.1.2 C++注释
    2.1.3 C++预处理器和iostream文件
    2.1.4 头文件名
    2.1.5 名称空间
    2.1.6 使用cout进行C++输出
    2.1.7 C++源代码的格式化
    2.2 C++语句
    2.2.1 声明语句和变量
    2.2.2 赋值语句
    2.2.3 cout的新花样
    2.3 其他C++语句
    2.3.1 使用cin
    2.3.2 使用cout进行拼接
    2.3.3 类简介
    2.4 函数
    2.4.1 使用有返回值的函数
    2.4.2 函数变体
    2.4.3 用户定义的函数
    2.4.4 用户定义的有返回值的函数
    2.4.5 在多函数程序中使用using编译指令
    2.5 总结
    2.6 复习题
    2.7 编程练习
    第3章 处理数据
    3.1 简单变量
    3.1.1 变量名
    3.1.2 整型
    3.1.3 整型short、int、long和long long
    3.1.4 无符号类型
    3.1.5 选择整型类型
    3.1.6 整型字面值
    3.1.7 C++如何确定常量的类型
    3.1.8 char类型:字符和小整数
    3.1.9 bool类型
    3.2 const限定符
    3.3 浮点数
    3.3.1 书写浮点数
    3.3.2 浮点类型
    3.3.3 浮点常量
    3.3.4 浮点数的优缺点
    3.4 C++算术运算符
    3.4.1 运算符优先级和结合性
    3.4.2 除法分支
    3.4.3 求模运算符
    3.4.4 类型转换
    3.4.5 C++11中的auto声明
    3.5 总结
    3.6 复习题
    3.7 编程练习
    第4章 复合类型
    4.1 数组
    4.1.1 程序说明
    4.1.2 数组的初始化规则
    4.1.3 C++11数组初始化方法
    4.2 字符串
    4.2.1 拼接字符串常量
    4.2.2 在数组中使用字符串
    4.2.3 字符串输入
    4.2.4 每次读取一行字符串输入
    4.2.5 混合输入字符串和数字
    4.3 string类简介
    4.3.1 C++11字符串初始化
    4.3.2 赋值、拼接和附加
    4.3.3 string类的其他操作
    4.3.4 string类I/O
    4.3.5 其他形式的字符串字面值
    4.4 结构简介
    4.4.1 在程序中使用结构
    4.4.2 C++11结构初始化
    4.4.3 结构可以将string类作为成员吗
    4.4.4 其他结构属性
    4.4.5 结构数组
    4.4.6 结构中的位字段
    4.5 共用体
    4.6 枚举
    4.6.1 设置枚举量的值
    4.6.2 枚举的取值范围
    4.7 指针和自由存储空间
    4.7.1 声明和初始化指针
    4.7.2 指针的危险
    4.7.3 指针和数字
    4.7.4 使用new来分配内存
    4.7.5 使用delete释放内存
    4.7.6 使用new来创建动态数组
    4.8 指针、数组和指针算术
    4.8.1 程序说明
    4.8.2 指针小结
    4.8.3 指针和字符串
    4.8.4 使用new创建动态结构
    4.8.5 自动存储、静态存储和动态存储
    4.9 类型组合
    4.10 数组的替代品
    4.10.1 模板类vector
    4.10.2 模板类array(C++11)
    4.10.3 比较数组、vector对象和array对象
    4.11 总结
    4.12 复习题
    4.13 编程练习
    第5章 循环和关系表达式
    5.1 for循环
    5.1.1 for循环的组成部分
    5.1.2 回到for循环
    5.1.3 修改步长
    5.1.4 使用for循环访问字符串
    5.1.5 递增运算符(++)和递减运算符(——)
    5.1.6 副作用和顺序点
    5.1.7 前缀格式和后缀格式
    5.1.8 递增/递减运算符和指针
    5.1.9 组合赋值运算符
    5.1.10 复合语句(语句块)
    5.1.11 其他语法技巧——逗号运算符
    5.1.12 关系表达式
    5.1.13 赋值、比较和可能犯的错误
    5.1.14 C—风格字符串的比较
    5.1.15 比较string类字符串
    5.2 while循环
    5.2.1 for与while
    5.2.2 等待一段时间:编写延时循环
    5.3 do while循环
    5.4 基于范围的for循环(C++11)
    5.5 循环和文本输入
    5.5.1 使用原始的cin进行输入
    5.5.2 使用cin.get(char)进行补救
    5.5.3 使用哪一个cin.get()
    5.5.4 文件尾条件
    5.5.5 另一个cin.get()版本
    5.6 嵌套循环和二维数组
    5.6.1 初始化二维数组
    5.6.2 使用二维数组
    5.7 总结
    5.8 复习题
    5.9 编程练习
    第6章 分支语句和逻辑运算符
    6.1 if语句
    6.1.1 if else语句
    6.1.2 格式化if else语句
    6.1.3 if else if else结构
    6.2 逻辑表达式
    6.2.1 逻辑OR运算符:‖
    6.2.2 逻辑AND运算符:&&
    6.2.3 用&&来设置取值范围
    6.2.4 逻辑NOT运算符:!
    6.2.5 逻辑运算符细节
    6.2.6 其他表示方式
    6.3 字符函数库cctype
    6.4 ?:运算符
    6.5 switch语句
    6.5.1 将枚举量用作标签
    6.5.2 switch和if else
    6.6 break和continue语句
    6.7 读取数字的循环
    6.8 简单文件输入/输出
    6.8.1 文本I/O和文本文件
    6.8.2 写入到文本文件中
    6.8.3 读取文本文件
    6.9 总结
    6.10 复习题
    6.11 编程练习
    第7章 函数——C++的编程模块
    7.1 复习函数的基本知识
    7.1.1 定义函数
    7.1.2 函数原型和函数调用
    7.2 函数参数和按值传递
    7.2.1 多个参数
    7.2.2 另外一个接受两个参数的函数
    7.3 函数和数组
    7.3.1 函数如何使用指针来处理数组
    7.3.2 将数组作为参数意味着什么
    7.3.3 更多数组函数示例
    7.3.4 使用数组区间的函数
    7.3.5 指针和const
    7.4 函数和二维数组
    7.5 函数和C—风格字符串
    7.5.1 将C—风格字符串作为参数的函数
    7.5.2 返回C—风格字符串的函数
    7.6 函数和结构
    7.6.1 传递和返回结构
    7.6.2 另一个处理结构的函数示例
    7.6.3 传递结构的地址
    7.7 函数和string对象
    7.8 函数与array对象
    7.9 递归
    7.9.1 包含一个递归调用的递归
    7.9.2 包含多个递归调用的递归
    7.10 函数指针
    7.10.1 函数指针的基础知识
    7.10.2 函数指针示例
    7.10.3 深入探讨函数指针
    7.10.4 使用typedef进行简化
    7.11 总结
    7.12 复习题
    7.13 编程练习
    第8章 函数探幽
    8.1 C++内联函数
    8.2 引用变量
    8.2.1 创建引用变量
    8.2.2 将引用用作函数参数
    8.2.3 引用的属性和特别之处
    8.2.4 将引用用于结构
    8.2.5 将引用用于类对象
    8.2.6 对象、继承和引用
    8.2.7 何时使用引用参数
    8.3 默认参数
    8.4 函数重载
    8.4.1 重载示例
    8.4.2 何时使用函数重载
    8.5 函数模板
    8.5.1 重载的模板
    8.5.2 模板的局限性
    8.5.3 显式具体化
    8.5.4 实例化和具体化
    8.5.5 编译器选择使用哪个函数版本
    8.5.6 模板函数的发展
    8.6 总结
    8.7 复习题
    8.8 编程练习
    第9章 内存模型和名称空间
    9.1 单独编译
    9.2 存储持续性、作用域和链接性
    9.2.1 作用域和链接
    9.2.2 自动存储持续性
    9.2.3 静态持续变量
    9.2.4 静态持续性、外部链接性
    9.2.5 静态持续性、内部链接性
    9.2.6 静态存储持续性、无链接性
    9.2.7 说明符和限定符
    9.2.8 函数和链接性
    9.2.9 语言链接性
    9.2.10 存储方案和动态分配
    9.3 名称空间
    9.3.1 传统的C++名称空间
    9.3.2 新的名称空间特性
    9.3.3 名称空间示例
    9.3.4 名称空间及其前途
    9.4 总结
    9.5 复习题
    9.6 编程练习
    第10章 对象和类
    10.1 过程性编程和面向对象编程
    10.2 抽象和类
    10.2.1 类型是什么
    10.2.2 C++中的类
    10.2.3 实现类成员函数
    10.2.4 使用类
    10.2.5 修改实现
    10.2.6 小结
    10.3 类的构造函数和析构函数
    10.3.1 声明和定义构造函数
    10.3.2 使用构造函数
    10.3.3 默认构造函数
    10.3.4 析构函数
    10.3.5 改进Stock类
    10.3.6 构造函数和析构函数小结
    10.4 this指针
    10.5 对象数组
    10.6 类作用域
    10.6.1 作用域为类的常量
    10.6.2 作用域内枚举(C++11)
    10.7 抽象数据类型
    10.8 总结
    10.9 复习题
    10.10 编程练习
    第11章 使用类
    11.1 运算符重载
    11.2 计算时间:一个运算符重载示例
    11.2.1 添加加法运算符
    11.2.2 重载限制
    11.2.3 其他重载运算符
    11.3 友元
    11.3.1 创建友元
    11.3.2 常用的友元:重载<<运算符
    11.4 重载运算符:作为成员函数还是非成员函数
    11.5 再谈重载:一个矢量类
    11.5.1 使用状态成员
    11.5.2 为Vector类重载算术运算符
    11.5.3 对实现的说明
    11.5.4 使用Vector类来模拟随机漫步
    11.6 类的自动转换和强制类型转换
    11.6.1 转换函数
    11.6.2 转换函数和友元函数
    11.7 总结
    11.8 复习题
    11.9 编程练习
    第12章 类和动态内存分配
    12.1 动态内存和类
    12.1.1 复习示例和静态类成员
    12.1.2 特殊成员函数
    12.1.3 回到Stringbad:复制构造函数的哪里出了问题
    12.1.4 Stringbad的其他问题: 赋值运算符
    12.2 改进后的新String类
    12.2.1 修订后的默认构造函数
    12.2.2 比较成员函数
    12.2.3 使用中括号表示法访问字符
    12.2.4 静态类成员函数
    12.2.5 进一步重载赋值运算符
    12.3 在构造函数中使用new时应注意的事项
    12.3.1 应该和不应该
    12.3.2 包含类成员的类的逐成员复制
    12.4 有关返回对象的说明
    12.4.1 返回指向const对象的引用
    12.4.2 返回指向非const对象的引用
    12.4.3 返回对象
    12.4.4 返回const对象
    12.5 使用指向对象的指针
    12.5.1 再谈new和delete
    12.5.2 指针和对象小结
    12.5.3 再谈定位new运算符
    12.6 复习各种技术
    12.6.1 重载<<运算符
    12.6.2 转换函数
    12.6.3 其构造函数使用new的类
    12.7 队列模拟
    12.7.1 队列类
    12.7.2 Customer类
    12.7.3 ATM模拟
    12.8 总结
    12.9 复习题
    12.10 编程练习
    第13章 类继承
    13.1 一个简单的基类
    13.1.1 派生一个类
    13.1.2 构造函数:访问权限的考虑
    13.1.3 使用派生类
    13.1.4 派生类和基类之间的特殊关系
    13.2 继承:is—a关系
    13.3 多态公有继承
    13.4 静态联编和动态联编
    13.4.1 指针和引用类型的兼容性
    13.4.2 虚成员函数和动态联编
    13.4.3 有关虚函数注意事项
    13.5 访问控制:protected
    13.6 抽象基类
    13.6.1 应用ABC概念
    13.6.2 ABC理念
    13.7 继承和动态内存分配
    13.7.1 第一种情况:派生类不使用new
    13.7.2 第二种情况:派生类使用new
    13.7.3 使用动态内存分配和友元的继承示例
    13.8 类设计回顾
    13.8.1 编译器生成的成员函数
    13.8.2 其他的类方法
    13.8.3 公有继承的考虑因素
    13.8.4 类函数小结
    13.9 总结
    13.10 复习题
    13.11 编程练习
    ……
    第14章 C++中的代码重用
    第15章 友元、异常和其他
    第16章 string类和标准模板库
    第17章 输入、输出和文件
    第18章 探讨C++新标准
    附录A 计数系统
    附录B C++保留字
    附录C ASCII字符集
    附录D 运算符优先级
    附录E 其他运算符
    附录F 模板类string
    附录G 标准模板库方法和函数
    附录H 精选读物和网上资源
    附录I 转换为ISO标准C++
    附录J 复习题答案

    百度云及其他网盘下载地址:点我

    转载于:https://www.cnblogs.com/awesome-share/p/10035213.html

    展开全文
  • log4cplus 学习

    千次阅读 2015-05-17 20:02:43
    (一)log4cplus是C++编写的开源的日志系统,功能非常全面,用到自己开发的工程中会比较专业的,:),本文介绍了log4cplus基本概念,以及如何安装,配置。  ### 简介 ### log4cplus是C++编写的开源的日志系统,...

    转自:http://honey-bee.iteye.com/blog/65805


    (一)log4cplus是C++编写的开源的日志系统,功能非常全面,用到自己开发的工程中会比较专业的,:),本文介绍了log4cplus基本概念,以及如何安装,配置。 
    ### 简介 ###
    log4cplus是C++编写的开源的日志系统,前身是java编写的log4j系统.受Apache Software License保护。作者是Tad E. Smith。log4cplus具有线程安全、灵活、以及多粒度控制的特点,通过将信息划分优先级使其可以面向程序调试、运行、测试、和维护等全生命周期; 你可以选择将信息输出到屏幕、文件、
    NT event log、甚至是远程服务器;通过指定策略对日志进行定期备份等等。
    ### 下载 ###
    最新的log4cplus可以从以下网址下载 http://log4cplus.sourceforge.net本文使用的版本为:1.0.2

    ### 安装 ###

    1. linux下安装

    tar xvzf log4cplus-x.x.x.tar.gz
    cd log4cplus-x.x.x
    ./configure --prefix=/where/to/install
    make
    make install

    这里我采用缺省安装路径:/usr/local,下文如无特别说明,均以此路径为准。

     2. windows下安装

    不需要安装,有一个msvc6存放包括源代码和用例在内的开发工程(for VC6 only),使用之前请先编译
    "log4cplus_dll class"工程生成dll,或者编译"log4cplus_static class"工程生成lib.

     ### 使用前的配置 ###

    1. linux下的配置

    确保你的Makefile中包含 /usr/local/lib/liblog4cplus.a(静态库)或  -llog4cplus(动态库)即可,
    头文件在/usr/local/include/log4cplus目录下。对于动态库,要想正常使用,还得将库安装路径加入到
    LD_LIBRARY_PATH 中,我一般是这样做的:以管理员身份登录,在/etc/ld.so.conf中加入安装路径,这里
    是/usr/local/lib,然后执行ldconfig使设置生效即可。

    2. windows下的配置

    将"log4cplus_dll class"工程或"log4cplus_static class"工程的dsp 文件插入到你的工程中,或者直接
    把两个工程编译生成的库以及头文件所在目录放到你的工程的搜索路径中,如果你使用静态库,请在你的工程中
    "project/setting/C++"的preprocessor definitions中加入LOG4CPLUS_STATIC。

     ### 构成要素介绍 ###

    虽然功能强大,应该说log4cplus用起来还是比较复杂的,为了更好地使用它,先介绍一下它的基本要素。

    Layouts      :布局器,控制输出消息的格式.
    Appenders    :挂接器,与布局器紧密配合,将特定格式的消息输出到所挂接的设备终端
                   (如屏幕,文件等等)。
    Logger       :记录器,保存并跟踪对象日志信息变更的实体,当你需要对一个对象进行
                   记录时,就需要生成一个logger。
    Categories   :分类器,层次化(hierarchy)的结构,用于对被记录信息的分类,层次中
                   每一个节点维护一个logger的所有信息。
    Priorities   :优先权,包括TRACE, DEBUG, INFO, WARNING, ERROR, FATAL。


    本文介绍了log4cplus基本概念,以及如何安装,配置,下一篇将通过例子介绍如何使用log4cplus。

    (二)
    本文介绍了使用log4cplus有六个步骤,并提供了一些例子引导你了解log4cplus的基本使用。				
    ### 基本使用 ###
    使用log4cplus有六个基本步骤:
    1. 实例化一个appender对象
    2. 实例化一个layout对象
    3. 将layout对象绑定(attach)到appender对象
    4. 实例化一个logger对象,调用静态函数:log4cplus::Logger::getInstance("logger_name")
    5. 将appender对象绑定(attach)到logger对象,如省略此步骤,标准输出(屏幕)appender对象会绑定到logger
    6. 设置logger的优先级,如省略此步骤,各种有限级的消息都将被记录
    下面通过一些例子来了解log4cplus的基本使用。
    〖例1〗
    cpp 代码
    1. /*    严格实现步骤1-6,appender输出到屏幕, 其中的布局格式和LogLevel后面会详细解释。*/
    2. #include <log4cplus logger.h=""></log4cplus>
    3. #include <log4cplus consoleappender.h=""></log4cplus>
    4. #include <log4cplus layout.h=""></log4cplus>   
    5. using namespace log4cplus;
    6. using namespace log4cplus::helpers;   
    7. int main(){
    8.     /* step 1: Instantiate an appender object */    
    9.     SharedObjectPtr _append (new ConsoleAppender());    
    10.     _append->setName("append for test");   
    11.     /* step 2: Instantiate a layout object */    
    12.     std::string pattern = "%d{%m/%d/%y %H:%M:%S}  - %m [%l]%n";    
    13.     std::auto_ptr _layout(new PatternLayout(pattern));   
    14.     /* step 3: Attach the layout object to the appender */    
    15.     _append->setLayout( _layout );   
    16.     /* step 4: Instantiate a logger object */    
    17.     Logger _logger = Logger::getInstance("test");   
    18.     /* step 5: Attach the appender object to the logger  */   
    19.     _logger.addAppender(_append);   
    20.     /* step 6: Set a priority for the logger  */    
    21.     _logger.setLogLevel(ALL_LOG_LEVEL);   
    22.      /* log activity */    
    23.     LOG4CPLUS_DEBUG(_logger, "This is the FIRST log message...")    
    24.    sleep(1);    
    25.    LOG4CPLUS_WARN(_logger, "This is the SECOND log message...")   
    26.    return 0;
    27. }   
    输出结果:
    10/14/04 09:06:24  - This is the FIRST log message... [main.cpp:31]
    10/14/04 09:06:25  - This is the SECOND log message... [main.cpp:33]
    				
    〖例2〗
    /*
        简洁使用模式,appender输出到屏幕。
    */
    #include <log4cplus logger.h=""></log4cplus>
    #include <log4cplus consoleappender.h=""></log4cplus><log4cplus></log4cplus>
    using namespace log4cplus;
    using namespace log4cplus::helpers;
    int main()
    {
        /* step 1: Instantiate an appender object */
        SharedAppenderPtr _append(new ConsoleAppender());
        _append->setName("append test");
        /* step 4: Instantiate a logger object */
        Logger _logger = Logger::getInstance("test");
        /* step 5: Attach the appender object to the logger  */
        _logger.addAppender(_append);
        /* log activity */
        LOG4CPLUS_DEBUG(_logger, "This is the FIRST log message...")
        sleep(1);
        LOG4CPLUS_WARN(_logger, "This is the SECOND log message...")
        return 0;
    }
    输出结果:
    DEBUG - This is the FIRST log message...
    WARN - This is the SECOND log message...
    				
    〖例3〗
    /*
        iostream模式,appender输出到屏幕。
    */
    #include <log4cplus logger.h=""></log4cplus>
    #include <log4cplus consoleappender.h=""></log4cplus>
    #include <iomanip></iomanip> /* 其实这个东东还是放到log4cplus头文件中比较合适些,个人意见:) */using namespace log4cplus;
    int main()
    {
        /* step 1: Instantiate an appender object */
        SharedAppenderPtr _append(new ConsoleAppender());
        _append->setName("append test");
        /* step 4: Instantiate a logger object */
        Logger _logger = Logger::getInstance("test");
        /* step 5: Attach the appender object to the logger  */
        _logger.addAppender(_append);
        /* log activity */
        LOG4CPLUS_TRACE(_logger, "This is"  << " just a t" << "est." << std::endl)
        LOG4CPLUS_DEBUG(_logger, "This is a bool: " << true)
        LOG4CPLUS_INFO(_logger, "This is a char: " << 'x')
        LOG4CPLUS_WARN(_logger, "This is a int: " << 1000)
        LOG4CPLUS_ERROR(_logger, "This is a long(hex): " << std::hex << 100000000)
        LOG4CPLUS_FATAL(_logger, "This is a double: "  << std::setprecision(15)  << 1.2345234234)
        return 0;
    }
    输出结果:
    DEBUG - This is a bool: 1
    INFO - This is a char: x
    WARN - This is a int: 1000
    ERROR - This is a long(hex): 5f5e100
    FATAL - This is a double: 1.2345234234
    				
    〖例4〗
    /*
        调试模式,通过loglog来控制输出调试、警告或错误信息,appender输出到屏幕。
    */
    #include <iostream></iostream>
    #include <log4cplus loglog.h="" helpers=""></log4cplus>
    using namespace log4cplus::helpers;
    void printMsgs(void)
    {
        std::cout << "Entering printMsgs()..." << std::endl;
        LogLog::getLogLog()->debug("This is a Debug statement...");
        LogLog::getLogLog()->warn("This is a Warning...");
        LogLog::getLogLog()->error("This is a Error...");
        std::cout << "Exiting printMsgs()..." << std::endl << std::endl;
    }
    int main()
    {
        /*
           LogLog类实现了debug, warn, error 函数用于输出调试、警告或错误信息,
           同时提供了两个方法来进一步控制所输出的信息,其中:
           setInternalDebugging方法用来控制是否屏蔽输出信息中的调试信息,当输入
           参数为false则屏蔽,缺省设置为false。
           setQuietMode方法用来控制是否屏蔽所有输出信息,当输入参数为true则屏蔽,
           缺省设置为false。
           LogLog::getLogLog()->setInternalDebugging(false);
        */
        printMsgs();
        std::cout << "Turning on debug..." << std::endl;
        LogLog::getLogLog()->setInternalDebugging(true);
        printMsgs();
        std::cout << "Turning on quiet mode..." << std::endl;
        LogLog::getLogLog()->setQuietMode(true);
        printMsgs();
        return 0;
    }
    输出结果:
    Entering printMsgs()...
    log4cplus:WARN This is a Warning...
    log4cplus:ERROR This is a Error...
    Exiting printMsgs()...
    Turning on debug...
    Entering printMsgs()...
    log4cplus: This is a Debug statement...
    log4cplus:WARN This is a Warning...
    log4cplus:ERROR This is a Error...
    Exiting printMsgs()...
    Turning on quiet mode...
    Entering printMsgs()...
    Exiting printMsgs()...
    需要指出的是,输出信息中总是包含"log4cplus:"前缀,有时候会感觉不爽,这是因为LogLog在实现时候死定了要这么写:
    LogLog::LogLog()
     : mutex(LOG4CPLUS_MUTEX_CREATE),
       debugEnabled(false),
       quietMode(false),
       PREFIX( LOG4CPLUS_TEXT("log4cplus: ") ),
       WARN_PREFIX( LOG4CPLUS_TEXT("log4cplus:WARN ") ),
       ERR_PREFIX( LOG4CPLUS_TEXT("log4cplus:ERROR ") )
    {
    }
    你可以把这些前缀换成自己看着爽的提示符号,然后重新编译,hihi。除非万不得已或者实在郁闷的不行,否则还是不要这样干。
    				
    〖例5〗
    
    /*    文件模式,appender输出到文件。*/ #include <log4cplus/logger.h> #include <log4cplus/fileappender.h> using namespace log4cplus; int main() {     /* step 1: Instantiate an appender object */     SharedAppenderPtr _append(new FileAppender("Test.log"));     _append->setName("file log test");     /* step 4: Instantiate a logger object */     Logger _logger = Logger::getInstance("test.subtestof_filelog");     /* step 5: Attach the appender object to the logger  */     _logger.addAppender(_append);     /* log activity */     int i;     for( i = 0; i < 5++i )     {         LOG4CPLUS_DEBUG(_logger, "Entering loop #" << i << "End line #")     }     return 0; }
    输出结果(Test.log文件):
    DEBUG - Entering loop #0End line #
    DEBUG - Entering loop #1End line #
    DEBUG - Entering loop #2End line #
    DEBUG - Entering loop #3End line #
    DEBUG - Entering loop #4End line #
    (三)

    本文介绍了三种控制输出格式的布局管理器的概念和使用情况,通过掌握这些知识,可以更有效地控制log系统输出尽可能贴近你需求的信息来。

    				
    ### 如何控制输出消息的格式 ###
    前面已经讲过,log4cplus通过布局器(Layouts)来控制输出的格式,log4cplus提供了三种类型的Layouts,
    分别是SimpleLayout、PatternLayout、和TTCCLayout。其中:
    1. SimpleLayout
    是一种简单格式的布局器,在输出的原始信息之前加上LogLevel和一个"-"。
    比如以下代码片段:
        ... ...
        /* step 1: Instantiate an appender object */
        SharedObjectPtr _append (new ConsoleAppender());
        _append->setName("append for test");
        /* step 2: Instantiate a layout object */
        std::auto_ptr  _layout(new log4cplus::SimpleLayout());
        /* step 3: Attach the layout object to the appender */
        _append->setLayout( _layout );
        /* step 4: Instantiate a logger object */
        Logger _logger = Logger::getInstance("test");
        /* step 5: Attach the appender object to the logger  */
        _logger.addAppender(_append);
         /* log activity */
        LOG4CPLUS_DEBUG(_logger, "This is the simple formatted log message...")
        
        ... ...
        
        
    将打印结果:
    DEBUG - This is the simple formatted log message...
    2. PatternLayout
    是一种有词法分析功能的模式布局器,一提起模式就会想起正则表达式,这里的模式和正则表达式类似,但是
    远比后者简单,能够对预定义的标识符(称为conversion specifiers)进行解析,转换成特定格式输出。以下
    代码片段演示了如何使用PatternLayout:
        ... ...
        /* step 1: Instantiate an appender object */
        SharedObjectPtr _append (new ConsoleAppender());
        _append->setName("append for test");
       
        /* step 2: Instantiate a layout object */
        std::string pattern = "%d{%m/%d/%y %H:%M:%S}  - %m [%l]%n";
        std::auto_ptr _layout(new PatternLayout(pattern));
        
        /* step 3: Attach the layout object to the appender */
        _append->setLayout( _layout );
        /* step 4: Instantiate a logger object */
        Logger _logger = Logger::getInstance("test_logger.subtest");
        /* step 5: Attach the appender object to the logger  */
        _logger.addAppender(_append);
         /* log activity */
        LOG4CPLUS_DEBUG(_logger, "teststr")
        
        ... ...
        
    输出结果:
    10/16/04 18:51:25  - teststr [main.cpp:51]
    可以看出通过填写特定格式的模式字符串"pattern",原始信息被包含到一堆有格式的信息当中了,这就使得
    用户可以根据自身需要来定制显示内容。"pattern"可以包含普通字符串和预定义的标识符,其中:
    (1)普通字符串,能够被直接显示的信息。
    (2)预定义标识符,通过"%"与一个或多个字符共同构成预定义的标识符,能够产生出特定格式信息。
    关于预定义标识符,log4cplus文档中提供了详细的格式说明,我每种都试了一下,以上述代码为例,根据不同
    的pattern,各种消息格式使用情况列举如下:
    (1)"%%",转义为%, 即,std::string pattern = "%%" 时输出: "%"
    (2)"%c",输出logger名称,比如std::string pattern ="%c" 时输出: "test_logger.subtest",
         也可以控制logger名称的显示层次,比如"%c{1}"时输出"test_logger",其中数字表示层次。
    (3)"%D",显示本地时间,当std::string pattern ="%D" 时输出:"2004-10-16 18:55:45",%d显示标准时间,
         所以当std::string pattern ="%d" 时输出 "2004-10-16 10:55:45" (因为我们是东8区,差8个小时啊)。
         可以通过%d{...}定义更详细的显示格式,比如%d{%H:%M:%s}表示要显示小时:分钟:秒。大括号中可显示的
         预定义标识符如下:
         
    %a -- 表示礼拜几,英文缩写形式,比如"Fri"
    %A -- 表示礼拜几,比如"Friday"
    %b -- 表示几月份,英文缩写形式,比如"Oct"
    %B -- 表示几月份,"October"
    %c -- 标准的日期+时间格式,如 "Sat Oct 16 18:56:19 2004"
    %d -- 表示今天是这个月的几号(1-31)"16"
    %H -- 表示当前时刻是几时(0-23),如 "18"
    %I -- 表示当前时刻是几时(1-12),如 "6"
    %j -- 表示今天是哪一天(1-366),如 "290"
    %m -- 表示本月是哪一月(1-12),如 "10"
    %M -- 表示当前时刻是哪一分钟(0-59),如 "59"
    %p -- 表示现在是上午还是下午, AM or PM
    %q -- 表示当前时刻中毫秒部分(0-999),如 "237"
    %Q -- 表示当前时刻中带小数的毫秒部分(0-999.999),如 "430.732"
    %S -- 表示当前时刻的多少秒(0-59),如 "32"
    %U -- 表示本周是今年的第几个礼拜,以周日为第一天开始计算(0-53),如 "41"
    %w -- 表示礼拜几,(0-6, 礼拜天为0),如 "6"
    %W -- 表示本周是今年的第几个礼拜,以周一为第一天开始计算(0-53),如 "41"
    %x -- 标准的日期格式,如 "10/16/04"
    %X -- 标准的时间格式,如 "19:02:34"
    %y -- 两位数的年份(0-99),如 "04"
    %Y -- 四位数的年份,如 "2004"
    %Z -- 时区名,比如 "GMT"
    (4)"%F",输出当前记录器所在的文件名称,比如std::string pattern ="%F" 时输出: "main.cpp"
    (5)"%L",输出当前记录器所在的文件行号,比如std::string pattern ="%L" 时输出: "51"
    (6)"%l",输出当前记录器所在的文件名称和行号,比如std::string pattern ="%L" 时输出:
         "main.cpp:51"
    (7)"%m",输出原始信息,比如std::string pattern ="%m" 时输出: "teststr",即上述代码中
         LOG4CPLUS_DEBUG的第二个参数,这种实现机制可以确保原始信息被嵌入到带格式的信息中。
    (8)"%n",换行符,没什么好解释的
    (9)"%p",输出LogLevel,比如std::string pattern ="%p" 时输出: "DEBUG"
    (10)"%t",输出记录器所在的线程ID,比如std::string pattern ="%t" 时输出: "1075298944"
    (11)"%x",嵌套诊断上下文NDC (nested diagnostic context) 输出,从堆栈中弹出上下文信息,NDC可以用对
          不同源的log信息(同时地)交叉输出进行区分,关于NDC方面的详细介绍会在下文中提到。
    (12)格式对齐,比如std::string pattern ="%-10m"时表示左对齐,宽度是10,此时会输出"teststr   ",当
          然其它的控制字符也可以相同的方式来使用,比如"%-12d","%-5p"等等(刚接触log4cplus文档时还以为
          "%-5p"整个字符串代表LogLevel呢,呵呵)。
          
    3. TTCCLayout
    是在PatternLayout基础上发展的一种缺省的带格式输出的布局器, 其格式由时间,线程ID,Logger和NDC 组
    成(consists of time, thread, Logger and nested diagnostic context information, hence the name),
    因而得名(怎么得名的?Logger里哪里有那个"C"的缩写啊!名字起得真够烂的,想扁人)。提供给那些想显示
    典型的信息(一般情况下够用了)又懒得配置pattern的同志们。
    TTCCLayout在构造时有机会选择显示本地时间或GMT时间,缺省是按照本地时间显示:
    TTCCLayout::TTCCLayout(bool use_gmtime  = false)
    以下代码片段演示了如何使用TTCCLayout:
        ... ...
        /* step 1: Instantiate an appender object */
        SharedObjectPtr _append (new ConsoleAppender());
        _append->setName("append for test");
        /* step 2: Instantiate a layout object */
        std::auto_ptr _layout(new TTCCLayout());
        /* step 3: Attach the layout object to the appender */
        _append->setLayout( _layout );
        /* step 4: Instantiate a logger object */
        Logger _logger = Logger::getInstance("test_logger");
        /* step 5: Attach the appender object to the logger  */
        _logger.addAppender(_append);
         /* log activity */
        LOG4CPLUS_DEBUG(_logger, "teststr")
        
        ... ...
        
    输出结果:
    10-16-04 19:08:27,501 [1075298944] DEBUG test_logger <> - teststr
    				
    当构造TTCCLayout对象时选择GMT时间格式时:
        ... ...
        
        /* step 2: Instantiate a layout object */
        std::auto_ptr _layout(new TTCCLayout(true));
        
        ... ...
        
    输出结果:
    10-16-04 11:12:47,678 [1075298944] DEBUG test_logger <> - teststr
    				
    本文介绍了控制log信息格式的相关知识,下一部分将详细介绍log信息的几种文件操作方式。
     
    (四)

    将log信息记录到文件应该说是日志系统的一个基本功能,log4cplus在此基础上,提供了更多的功能,可以按照你预先设定的大小来决定是否转储,当超过该大小,后续log信息会另存到新文件中,依次类推;或者按照日期来决定是否转储。本文将详细介绍这些用法。

    				
    ### 如何将log记录到文件 ###
    我们在例5中给出了一个将log记录到文件的例子,用的是FileAppender类实现的,log4cplus提供了三个类用于
    文件操作,它们是FileAppender类、RollingFileAppender类、DailyRollingFileAppender类。
    1. FileAppender类
    实现了基本的文件操作功能,构造函数如下:
    FileAppender(const log4cplus::tstring& filename,
                         LOG4CPLUS_OPEN_MODE_TYPE mode = LOG4CPLUS_FSTREAM_NAMESPACE::ios::trunc,
                         bool immediateFlush = true);
                         
    filename       : 文件名
    mode           : 文件类型,可选择的文件类型包括app、ate、binary、in、out、trunc,因为实际上只是对
                     stl的一个简单包装,呵呵,这里就不多讲了。缺省是trunc,表示将先前文件删除。
    immediateFlush :缓冲刷新标志,如果为true表示每向文件写一条记录就刷新一次缓存,否则直到FileAppender
                     被关闭或文件缓存已满才更新文件,一般是要设置true的,比如你往文件写的过程中出现
                     了错误(如程序非正常退出),即使文件没有正常关闭也可以保证程序终止时刻之前的所有
                     记录都会被正常保存。
    FileAppender类的使用情况请参考例5,这里不再赘述。
    				
    2. RollingFileAppender类
    构造函数如下:
    log4cplus::RollingFileAppender::RollingFileAppender(const log4cplus::tstring& filename,
                                                        long maxFileSize,
                                                        int maxBackupIndex,
                                                        bool immediateFlush)
    filename       : 文件名
    maxFileSize    : 文件的最大尺寸
    maxBackupIndex : 最大记录文件数
    immediateFlush : 缓冲刷新标志
                                                        
    RollingFileAppender类可以根据你预先设定的大小来决定是否转储,当超过该大小,后续log信息会另存到新
    文件中,除了定义每个记录文件的大小之外,你还要确定在RollingFileAppender类对象构造时最多需要多少个
    这样的记录文件(maxBackupIndex+1),当存储的文件数目超过maxBackupIndex+1时,会删除最早生成的文件,
    保证整个文件数目等于maxBackupIndex+1。然后继续记录,比如以下代码片段:
        ... ...
        
        #define LOOP_COUNT 200000
        
        SharedAppenderPtr _append(new RollingFileAppender("Test.log", 5*1024, 5));
        _append->setName("file test");
        _append->setLayout( std::auto_ptr<layout></layout>(new TTCCLayout()) );
        Logger::getRoot().addAppender(_append);
        Logger root = Logger::getRoot();
        Logger test = Logger::getInstance("test");
        Logger subTest = Logger::getInstance("test.subtest");
        for(int i=0; i    {
            NDCContextCreator _context("loop");
            LOG4CPLUS_DEBUG(subTest, "Entering loop #" << i)
        }
        
        ... ...   
    运行结果:
    运行后会产生6个输出文件,Test.log、Test.log.1、Test.log.2、Test.log.3、Test.log.4、Test.log.5
    其中Test.log存放着最新写入的信息,而最后一个文件中并不包含第一个写入信息,说明已经被不断更新了。
    需要指出的是,这里除了Test.log之外,每个文件的大小都是200K,而不是我们想像中的5K,这是因为
    log4cplus中隐含定义了文件的最小尺寸是200K,只有大于200K的设置才生效,<= 200k的设置都会被认为是
    200K.
    				
    3. DailyRollingFileAppender类
    构造函数如下:
    DailyRollingFileAppender::DailyRollingFileAppender(const log4cplus::tstring& filename,
                                                       DailyRollingFileSchedule schedule,
                                                       bool immediateFlush,
                                                       int maxBackupIndex)
                                                       
    filename       : 文件名
    schedule       : 存储频度
    immediateFlush : 缓冲刷新标志
    maxBackupIndex : 最大记录文件数
    DailyRollingFileAppender类可以根据你预先设定的频度来决定是否转储,当超过该频度,后续log信息会另存
    到新文件中,这里的频度包括:MONTHLY(每月)、WEEKLY(每周)、DAILY(每日)、TWICE_DAILY(每两天)、
    HOURLY(每时)、MINUTELY(每分)。maxBackupIndex的含义同上所述,比如以下代码片段:
        ... ...
        
        SharedAppenderPtr _append(new DailyRollingFileAppender("Test.log", MINUTELY, true, 5));
        _append->setName("file test");
        _append->setLayout( std::auto_ptr<layout></layout>(new TTCCLayout()) );
        Logger::getRoot().addAppender(_append);
        Logger root = Logger::getRoot();
        Logger test = Logger::getInstance("test");
        Logger subTest = Logger::getInstance("test.subtest");
        for(int i=0; i    {
            NDCContextCreator _context("loop");
            LOG4CPLUS_DEBUG(subTest, "Entering loop #" << i)
        }
        
        ... ...
    				
    运行结果:
    运行后会以分钟为单位,分别生成名为Test.log.2004-10-17-03-03、Test.log.2004-10-17-03-04和
    Test.log.2004-10-17-03-05这样的文件。
    需要指出的是,刚看到按照频度(如HOURLY、MINUTELY)转储这样的概念,以为log4cplus提供了内部定时器,
    感觉很奇怪,因为日志系统不应该主动记录,而loging事件总是应该被动触发的啊。仔细看了源代码后才知道
    这里的"频度"并不是你写入文件的速度,其实是否转储的标准并不依赖你写入文件的速度,而是依赖于写入
    的那一时刻是否满足了频度条件,即是否超过了以分钟、小时、周、月为单位的时间刻度,如果超过了就另存。
    本部分详细介绍log信息的几种文件操作方式,下面将重点介绍一下如何有选择地控制log信息的输出。
     
    (五)
    日志系统的另一个基本功能就是能够让使用者按照自己的意愿来控制什么时候,哪些log信息可以输出。
    如果能够让用户在任意时刻设置允许输出的LogLevel的信息就好了,log4cplus通过LogLevelManager、
    LogLog、Filter三种方式实现了上述功能。
    				
    ### 优先级控制 ###
    在研究LogLevelManager之前,首先介绍一下log4cplus中logger的存储机制,在log4cplus中,所有
    logger都通过一个层次化的结构(其实内部是hash表)来组织的,有一个Root级别的logger,可以通
    过以下方法获取:
        Logger root = Logger::getRoot();
        
    用户定义的logger都有一个名字与之对应,比如:
        Logger test = Logger::getInstance("test");
        
    可以定义该logger的子logger:
        Logger subTest = Logger::getInstance("test.subtest");
        
    注意Root级别的logger只有通过getRoot方法获取,Logger::getInstance("root")获得的是它的
    子对象而已。有了这些具有父子关系的logger之后可分别设置其LogLevel,比如:
    root.setLogLevel( ... );
    Test.setLogLevel( ... );
    subTest.setLogLevel( ... );
    				
    logger的这种父子关联性会体现在优先级控制方面,log4cplus将输出的log信息按照LogLevel
    (从低到高)分为:
    NOT_SET_LOG_LEVEL (   -1) :接受缺省的LogLevel,如果有父logger则继承它的LogLevel
    ALL_LOG_LEVEL     (    0) :开放所有log信息输出
    TRACE_LOG_LEVEL   (    0) :开放trace信息输出(即ALL_LOG_LEVEL)
    DEBUG_LOG_LEVEL   (10000) :开放debug信息输出
    INFO_LOG_LEVEL    (20000) :开放info信息输出
    WARN_LOG_LEVEL    (30000) :开放warning信息输出
    ERROR_LOG_LEVEL   (40000) :开放error信息输出
    FATAL_LOG_LEVEL   (50000) :开放fatal信息输出
    OFF_LOG_LEVEL     (60000) :关闭所有log信息输出
    LogLevelManager负责设置logger的优先级,各个logger可以通过setLogLevel设置自己的优先级,
    当某个logger的LogLevel设置成NOT_SET_LOG_LEVEL时,该logger会继承父logger的优先级,另外,
    如果定义了重名的多个logger, 对其中任何一个的修改都会同时改变其它logger,我们举例说明:
    〖例6〗
    #include "log4cplus/logger.h"
    #include "log4cplus/consoleappender.h"
    #include "log4cplus/loglevel.h"
    #include <iostream></iostream><iostream></iostream>
    using namespace std;
    using namespace log4cplus;
    int main()
    {
        SharedAppenderPtr _append(new ConsoleAppender());
        _append->setName("test");
        Logger::getRoot().addAppender(_append);
        Logger root = Logger::getRoot();
        Logger test = Logger::getInstance("test");
        Logger subTest = Logger::getInstance("test.subtest");
        LogLevelManager& llm = getLogLevelManager();
        cout << endl << "Before Setting, Default LogLevel" << endl;
        LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel()))
        LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel()))
        LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))
        cout << endl << "Setting test.subtest to WARN" << endl;
        subTest.setLogLevel(WARN_LOG_LEVEL);
        LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel()))
        LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel()))
        LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))
        cout << endl << "Setting test.subtest to TRACE" << endl;
        test.setLogLevel(TRACE_LOG_LEVEL);
        LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel()))
        LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel()))
        LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()))
        cout << endl << "Setting test.subtest to NO_LEVEL" << endl;
        subTest.setLogLevel(NOT_SET_LOG_LEVEL);
        LOG4CPLUS_FATAL(root, "root: " << llm.toString(root.getChainedLogLevel()))
        LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel()))
        LOG4CPLUS_FATAL(root, "test.subtest: " << llm.toString(subTest.getChainedLogLevel()) << '\n')
        cout << "create a logger test_bak, named \"test_\", too. " << endl;
        Logger test_bak = Logger::getInstance("test");
        cout << "Setting test to INFO, so test_bak also be set to INFO" << endl;
        test.setLogLevel(INFO_LOG_LEVEL);
        LOG4CPLUS_FATAL(root, "test: " << llm.toString(test.getChainedLogLevel()))
        LOG4CPLUS_FATAL(root, "test_bak: " << llm.toString(test_bak.getChainedLogLevel()))
        return 0;
    }
    输出结果:
    Before Setting, Default LogLevel
    FATAL - root: DEBUG
    FATAL - test: DEBUG
    FATAL - test.subtest: DEBUG
    Setting test.subtest to WARN
    FATAL - root: DEBUG
    FATAL - test: DEBUG
    FATAL - test.subtest: WARN
    Setting test.subtest to TRACE
    FATAL - root: DEBUG
    FATAL - test: TRACE
    FATAL - test.subtest: WARN
    Setting test.subtest to NO_LEVEL
    FATAL - root: DEBUG
    FATAL - test: TRACE
    FATAL - test.subtest: TRACE
    create a logger test_bak, named "test_", too.
    Setting test to INFO, so test_bak also be set to INFO
    FATAL - test: INFO
    FATAL - test_bak: INFO
    				
    下面的例子演示了如何通过设置LogLevel来控制用户的log信息输出:
    〖例7〗
    #include "log4cplus/logger.h"
    #include "log4cplus/consoleappender.h"
    #include "log4cplus/loglevel.h"
    #include <iostream></iostream><iostream></iostream>
    using namespace std;
    using namespace log4cplus;
    void ShowMsg(void)
    {
        LOG4CPLUS_TRACE(Logger::getRoot(),"info")
        LOG4CPLUS_DEBUG(Logger::getRoot(),"info")
        LOG4CPLUS_INFO(Logger::getRoot(),"info")
        LOG4CPLUS_WARN(Logger::getRoot(),"info")
        LOG4CPLUS_ERROR(Logger::getRoot(),"info")
        LOG4CPLUS_FATAL(Logger::getRoot(),"info")
    }
    int main()
    {
        SharedAppenderPtr _append(new ConsoleAppender());
        _append->setName("test");
        _append->setLayout(std::auto_ptr<layout></layout>(new TTCCLayout()));
        Logger root = Logger::getRoot();
        root.addAppender(_append);
        cout << endl << "all-log allowed" << endl;
        root.setLogLevel(ALL_LOG_LEVEL);
        ShowMsg();
        cout << endl << "trace-log and above allowed" << endl;
        root.setLogLevel(TRACE_LOG_LEVEL);
        ShowMsg();
        cout << endl << "debug-log and above allowed" << endl;
        root.setLogLevel(DEBUG_LOG_LEVEL);
        ShowMsg();
        cout << endl << "info-log and above allowed" << endl;
        root.setLogLevel(INFO_LOG_LEVEL);
        ShowMsg();
        cout << endl << "warn-log and above allowed" << endl;
        root.setLogLevel(WARN_LOG_LEVEL);
        ShowMsg();
        cout << endl << "error-log and above allowed" << endl;
        root.setLogLevel(ERROR_LOG_LEVEL);
        ShowMsg();
        cout << endl << "fatal-log and above allowed" << endl;
        root.setLogLevel(FATAL_LOG_LEVEL);
        ShowMsg();
        cout << endl << "log disabled" << endl;
        root.setLogLevel(OFF_LOG_LEVEL);
        ShowMsg();
        return 0;
    }
    输出结果:
    all-log allowed
    10-17-04 10:11:40,587 [1075298944] TRACE root <> - info
    10-17-04 10:11:40,590 [1075298944] DEBUG root <> - info
    10-17-04 10:11:40,591 [1075298944] INFO root <> - info
    10-17-04 10:11:40,591 [1075298944] WARN root <> - info
    10-17-04 10:11:40,592 [1075298944] ERROR root <> - info
    10-17-04 10:11:40,592 [1075298944] FATAL root <> - info
    trace-log and above allowed
    10-17-04 10:11:40,593 [1075298944] TRACE root <> - info
    10-17-04 10:11:40,593 [1075298944] DEBUG root <> - info
    10-17-04 10:11:40,594 [1075298944] INFO root <> - info
    10-17-04 10:11:40,594 [1075298944] WARN root <> - info
    10-17-04 10:11:40,594 [1075298944] ERROR root <> - info
    10-17-04 10:11:40,594 [1075298944] FATAL root <> - info
    debug-log and above allowed
    10-17-04 10:11:40,595 [1075298944] DEBUG root <> - info
    10-17-04 10:11:40,595 [1075298944] INFO root <> - info
    10-17-04 10:11:40,596 [1075298944] WARN root <> - info
    10-17-04 10:11:40,596 [1075298944] ERROR root <> - info
    10-17-04 10:11:40,596 [1075298944] FATAL root <> - info
    info-log and above allowed
    10-17-04 10:11:40,597 [1075298944] INFO root <> - info
    10-17-04 10:11:40,597 [1075298944] WARN root <> - info
    10-17-04 10:11:40,597 [1075298944] ERROR root <> - info
    10-17-04 10:11:40,598 [1075298944] FATAL root <> - info
    warn-log and above allowed
    10-17-04 10:11:40,598 [1075298944] WARN root <> - info
    10-17-04 10:11:40,598 [1075298944] ERROR root <> - info
    10-17-04 10:11:40,599 [1075298944] FATAL root <> - info
    error-log and above allowed
    10-17-04 10:11:40,599 [1075298944] ERROR root <> - info
    10-17-04 10:11:40,600 [1075298944] FATAL root <> - info
    fatal-log and above allowed
    10-17-04 10:11:40,600 [1075298944] FATAL root <> - info
    log disabled
    用户也可以自行定义LogLevel,操作比较简单,首先要定义LEVEL值,比如HELLO_LOG_LEVEL定义如下:
    /* DEBUG_LOG_LEVEL  < HELLO_LOG_LEVEL < INFO_LOG_LEVEL */
    const LogLevel HELLO_LOG_LEVEL = 15000;
    然后定义以下宏即可:
    /* define MACRO LOG4CPLUS_HELLO */
    #define LOG4CPLUS_HELLO(logger, logEvent) \
        if(logger.isEnabledFor(HELLO_LOG_LEVEL)) { \
            log4cplus::tostringstream _log4cplus_buf; \
            _log4cplus_buf << logEvent; \
     logger.forcedLog(HELLO_LOG_LEVEL, _log4cplus_buf.str(), __FILE__, __LINE__); \
        }
    不过log4cplus没有提供给用户一个接口来实现LEVEL值与字符串的转换,所以当带格式输出LogLevel字符
    串时候会显示"UNKNOWN", 不够理想。比如用TTCCLayout控制输出的结果可能会如下所示:
    10-17-04 11:17:51,124 [1075298944] UNKNOWN root <> - info
    而不是期望的以下结果:
    10-17-04 11:17:51,124 [1075298944] HELLO root <> - info
    要想实现第二种结果,按照log4cplus现有的接口机制,只能改其源代码后重新编译,方法是在loglevel.cxx
    中加入:
    #define _HELLO_STRING LOG4CPLUS_TEXT("HELLO")
    然后修改log4cplus::tstring  defaultLogLevelToStringMethod(LogLevel ll)函数,增加一个判断:
    case HELLO_LOG_LEVEL:    return _HELLO_STRING;
    重新编译log4cplus源代码后生成库文件,再使用时即可实现满意效果。
    				
    ### 调试模式 ###
    即通过loglog来控制输出调试、警告或错误信息,见例4,这里不再赘述。 
    ### 基于脚本配置来过滤log信息 ###
    除了通过程序实现对log环境的配置之外,log4cplus通过PropertyConfigurator类实现了基于脚本配置的功能。
    通过脚本可以完成对logger、appender和layout的配置,因此可以解决怎样输出,输出到哪里的问题,我将在
    全文的最后一部分中提到多线程环境中如何利用脚本配置来配合实现性能测试,本节将重点介绍基脚本实现过
    滤log信息的功能。
    首先简单介绍一下脚本的语法规则:
    包括Appender的配置语法和logger的配置语法,其中:
    1.Appender的配置语法:
    (1)设置名称:
    /*设置方法*/
    log4cplus.appender.appenderName=fully.qualified.name.of.appender.class
    例如(列举了所有可能的Appender,其中SocketAppender后面会讲到):
    log4cplus.appender.append_1=log4cplus::ConsoleAppender
    log4cplus.appender.append_2=log4cplus::FileAppender
    log4cplus.appender.append_3=log4cplus::RollingFileAppender
    log4cplus.appender.append_4=log4cplus::DailyRollingFileAppender
    log4cplus.appender.append_4=log4cplus::SocketAppender
    (2)设置Filter:
    包括选择过滤器和设置过滤条件,可选择的过滤器包括:LogLevelMatchFilter、LogLevelRangeFilter、
    和StringMatchFilter:
    对LogLevelMatchFilter来说,过滤条件包括LogLevelToMatch和AcceptOnMatch(true|false), 只有
    当log信息的LogLevel值与LogLevelToMatch相同,且AcceptOnMatch为true时才会匹配。
    LogLevelRangeFilter来说,过滤条件包括LogLevelMin、LogLevelMax和AcceptOnMatch,只有当log信息
    的LogLevel在LogLevelMin、LogLevelMax之间同时AcceptOnMatch为true时才会匹配。
    对StringMatchFilter来说,过滤条件包括StringToMatch和AcceptOnMatch,只有当log信息的LogLevel值
    与StringToMatch对应的LogLevel值与相同, 且AcceptOnMatch为true时会匹配。
    				
    过滤条件处理机制类似于IPTABLE的Responsibility chain,(即先deny、再allow)不过执行顺序刚好相反,
    后写的条件会被先执行,比如:
    log4cplus.appender.append_1.filters.1=log4cplus::spi::LogLevelMatchFilter
    log4cplus.appender.append_1.filters.1.LogLevelToMatch=TRACE
    log4cplus.appender.append_1.filters.1.AcceptOnMatch=true
    #log4cplus.appender.append_1.filters.2=log4cplus::spi::DenyAllFilter
    会首先执行filters.2的过滤条件,关闭所有过滤器,然后执行filters.1,仅匹配TRACE信息。
    (3)设置Layout
    可以选择不设置、TTCCLayout、或PatternLayout
    如果不设置,会输出简单格式的log信息。
    设置TTCCLayout如下所示:
    log4cplus.appender.ALL_MSGS.layout=log4cplus::TTCCLayout
    设置PatternLayout如下所示:
    log4cplus.appender.append_1.layout=log4cplus::PatternLayout
    log4cplus.appender.append_1.layout.ConversionPattern=%d{%m/%d/%y %H:%M:%S,%Q} [%t] %-5p - %m%n
    				
    2.logger的配置语法
    包括rootLogger和non-root logger。
    对于rootLogger来说:
    log4cplus.rootLogger=[LogLevel], appenderName, appenderName, ...
    对于non-root logger来说:
    log4cplus.logger.logger_name=[LogLevel|INHERITED], appenderName, appenderName, ...
    				
    脚本方式使用起来非常简单,只要首先加载配置即可(urconfig.properties是自行定义的配置文件):
    PropertyConfigurator::doConfigure("urconfig.properties");
    				
    下面我们通过例子体会一下log4cplus强大的基于脚本过滤log信息的功能。
    				
    〖例8〗
    /*
     *    urconfig.properties
     */
    log4cplus.rootLogger=TRACE, ALL_MSGS, TRACE_MSGS, DEBUG_INFO_MSGS, FATAL_MSGS
    log4cplus.appender.ALL_MSGS=log4cplus::RollingFileAppender
    log4cplus.appender.ALL_MSGS.File=all_msgs.log
    log4cplus.appender.ALL_MSGS.layout=log4cplus::TTCCLayout
    log4cplus.appender.TRACE_MSGS=log4cplus::RollingFileAppender
    log4cplus.appender.TRACE_MSGS.File=trace_msgs.log
    log4cplus.appender.TRACE_MSGS.layout=log4cplus::TTCCLayout
    log4cplus.appender.TRACE_MSGS.filters.1=log4cplus::spi::LogLevelMatchFilter
    log4cplus.appender.TRACE_MSGS.filters.1.LogLevelToMatch=TRACE
    log4cplus.appender.TRACE_MSGS.filters.1.AcceptOnMatch=true
    log4cplus.appender.TRACE_MSGS.filters.2=log4cplus::spi::DenyAllFilter
    log4cplus.appender.DEBUG_INFO_MSGS=log4cplus::RollingFileAppender
    log4cplus.appender.DEBUG_INFO_MSGS.File=debug_info_msgs.log
    log4cplus.appender.DEBUG_INFO_MSGS.layout=log4cplus::TTCCLayout
    log4cplus.appender.DEBUG_INFO_MSGS.filters.1=log4cplus::spi::LogLevelRangeFilter
    log4cplus.appender.DEBUG_INFO_MSGS.filters.1.LogLevelMin=DEBUG
    log4cplus.appender.DEBUG_INFO_MSGS.filters.1.LogLevelMax=INFO
    log4cplus.appender.DEBUG_INFO_MSGS.filters.1.AcceptOnMatch=true
    log4cplus.appender.DEBUG_INFO_MSGS.filters.2=log4cplus::spi::DenyAllFilter
    log4cplus.appender.FATAL_MSGS=log4cplus::RollingFileAppender
    log4cplus.appender.FATAL_MSGS.File=fatal_msgs.log
    log4cplus.appender.FATAL_MSGS.layout=log4cplus::TTCCLayout
    log4cplus.appender.FATAL_MSGS.filters.1=log4cplus::spi::StringMatchFilter
    log4cplus.appender.FATAL_MSGS.filters.1.StringToMatch=FATAL
    log4cplus.appender.FATAL_MSGS.filters.1.AcceptOnMatch=true
    log4cplus.appender.FATAL_MSGS.filters.2=log4cplus::spi::DenyAllFilter
    				
    /*
     *    main.cpp
     */
    #include <log4cplus logger.h=""></log4cplus>
    #include <log4cplus configurator.h=""></log4cplus>
    #include <log4cplus helpers="" stringhelper.h=""></log4cplus><log4cplus></log4cplus>
    using namespace log4cplus;
    static Logger logger = Logger::getInstance("log");
    void printDebug()
    {
        LOG4CPLUS_TRACE_METHOD(logger, "::printDebug()");
        LOG4CPLUS_DEBUG(logger, "This is a DEBUG message");
        LOG4CPLUS_INFO(logger, "This is a INFO message");
        LOG4CPLUS_WARN(logger, "This is a WARN message");
        LOG4CPLUS_ERROR(logger, "This is a ERROR message");
        LOG4CPLUS_FATAL(logger, "This is a FATAL message");
    }
    int main()
    {
        Logger root = Logger::getRoot();
        PropertyConfigurator::doConfigure("urconfig.properties");
        printDebug();
        return 0;
    }
    运行结果:
    1. all_msgs.log
    10-17-04 14:55:25,858 [1075298944] TRACE log <> - ENTER: ::printDebug()
    10-17-04 14:55:25,871 [1075298944] DEBUG log <> - This is a DEBUG message
    10-17-04 14:55:25,873 [1075298944] INFO log <> - This is a INFO message
    10-17-04 14:55:25,873 [1075298944] WARN log <> - This is a WARN message
    10-17-04 14:55:25,874 [1075298944] ERROR log <> - This is a ERROR message
    10-17-04 14:55:25,874 [1075298944] FATAL log <> - This is a FATAL message
    10-17-04 14:55:25,875 [1075298944] TRACE log <> - EXIT:  ::printDebug()
    2. trace_msgs.log
    10-17-04 14:55:25,858 [1075298944] TRACE log <> - ENTER: ::printDebug()
    10-17-04 14:55:25,875 [1075298944] TRACE log <> - EXIT:  ::printDebug()
    3. debug_info_msgs.log
    10-17-04 14:55:25,871 [1075298944] DEBUG log <> - This is a DEBUG message
    10-17-04 14:55:25,873 [1075298944] INFO log <> - This is a INFO message
    4. fatal_msgs.log
    10-17-04 14:55:25,874 [1075298944] FATAL log <> - This is a FATAL message
    本部分详细介绍了如何有选择地控制log信息的输出,最后一部分我们将介绍一下多线程、
    和C/S模式下该如何操作,顺便提一下NDC的概念。

    log4cplus在很多方面做的都很出色,但是使用过程有些地方感觉不爽。在继续吹捧之前我先把不爽之处 稍微提一提,然后继续介绍关于线程和套接字的知识。

    ### 一些可以改进之处 ###
    1. 用户自定义LogLevel的实现机制不够开放
    在第五篇中曾经介绍过如何实现用户自行定义LogLevel,为了实现比较理想的效果,甚至还需要改log4cplus
    的源代码。:(
    2. 生成Logger对象的机制可以改进
    我在使用时候,经常需要在不同的文件、函数中操作同一个logger,虽然log4cplus实现了树状存储以及根据
    名称生成Logger,却没有充分利用这样的特点确保同一个名称对应的logger对象的唯一性,比如以下代码:
        ... ...
        
        Logger logger1 = Logger::getInstance("test");
        Logger logger2 = Logger::getInstance("test");
        Logger * plogger1 = &logger1;
        Logger * plogger2 = &logger2;
        std::cout << "plogger1: " << plogger1 << std::endl << "plogger2: " << plogger2 << std::endl;
        
        ... ...
        
        
    运行结果:
    plogger1: 0xbfffe5a0
    plogger2: 0xbfffe580
    				
    从结果可以看出,明明是同一个Logger,但每次调用都会产生一个Logger副本,虽然结果是正确的(因为将存
    储和操作分开了),但是资源有些浪费,我看了一下log4cplus的代码,其实可以按照如下方式实现(示意性
    的):
    #include <iostream></iostream>
    #include <string></string>
    #include 
    /* forward declaration */
    class Logger;
    class LoggerContainer
    {
    public:
        ~LoggerContainer();
        Logger * getinstance(const std::string & strLogger);
    private:
        typedef std::map<:string,> LoggerMap;
        LoggerMap loggerPtrs;
    };
    class Logger
    {
    public:
         Logger() {std::cout << "ctor of Logger " << std::endl; }
        ~Logger() {std::cout << "dtor of Logger " << std::endl; }
        static Logger * getInstance( const std::string & strLogger)
        {
            static LoggerContainer defaultLoggerContainer;
            return defaultLoggerContainer.getinstance(strLogger);
        }
    };
    LoggerContainer::~LoggerContainer()
    {
        /* release all ptr in LoggerMap */
        LoggerMap::iterator itr = loggerPtrs.begin();
        for( ; itr != loggerPtrs.end(); ++itr )
     {
         delete (*itr).second;
     }
    }
    Logger * LoggerContainer::getinstance(const std::string & strLogger)
    {
       LoggerMap::iterator itr = loggerPtrs.find(strLogger);
       if(itr != loggerPtrs.end())
       {
           /* logger exist, just return it */
           return (*itr).second;
       }
       else
       {
           /* return a new logger */
           Logger * plogger = new Logger();
           loggerPtrs.insert(std::make_pair(strLogger, plogger));
           return plogger;
       }
    }
    int main()
    {
        Logger * plogger1 = Logger::getInstance("test");
        Logger * plogger2 = Logger::getInstance("test");
        std::cout << "plogger1: " << plogger1 << std::endl << "plogger2: " << plogger2 << std::endl;
        return 0;
    }
    				
    运行结果:
    ctor of Logger
    plogger1: 0x804fc30
    plogger2: 0x804fc30
    dtor of Logger
    这里的LoggerContainer相当于log4cplus中的Hierarchy类,结果可以看出,通过同一个名称可以获取相同的
    Logger实例。
    				
    还有一些小毛病比如RollingFileAppender和DailyRollingFileAppender的参数输入顺序可以调整成统一方式
    等等,就不细说了。
    本部分提到了使用log4cplus时候感觉不爽的地方,最后一部分将介绍一下log4cplus中线程和套接字实现情况

    (七)

    经过短暂的熟悉过程,log4cplus已经被成功应用到了我的项目中去了,效果还不错,:)除了上文提及的
    功能之外,下面将介绍log4cplus提供的线程和套接字的使用情况。

    ### NDC ###
    首先我们先了解一下log4cplus中嵌入诊断上下文(Nested Diagnostic Context),即NDC。对log系统而言,
    当输入源可能不止一个,而只有一个输出时,往往需要分辩所要输出消息的来源,比如服务器处理来自不同
    客户端的消息时就需要作此判断,NDC可以为交错显示的信息打上一个标记(stamp), 使得辨认工作看起来
    比较容易些,呵呵。这个标记是线程特有的,利用了线程局部存储机制,称为线程私有数据(Thread-specific
     Data,或TSD)。 看了一下源代码,相关定义如下,包括定义、初始化、获取、设置和清除操作:
    linux pthread				
    #   define LOG4CPLUS_THREAD_LOCAL_TYPE pthread_key_t*
    #   define LOG4CPLUS_THREAD_LOCAL_INIT ::log4cplus::thread::createPthreadKey()
    #   define LOG4CPLUS_GET_THREAD_LOCAL_VALUE( key ) pthread_getspecific(*key)
    #   define LOG4CPLUS_SET_THREAD_LOCAL_VALUE( key, value ) pthread_setspecific(*key, value)
    #   define LOG4CPLUS_THREAD_LOCAL_CLEANUP( key ) pthread_key_delete(*key)
    win32
    #   define LOG4CPLUS_THREAD_LOCAL_TYPE DWORD
    #   define LOG4CPLUS_THREAD_LOCAL_INIT TlsAlloc()
    #   define LOG4CPLUS_GET_THREAD_LOCAL_VALUE( key ) TlsGetValue(key)
    #   define LOG4CPLUS_SET_THREAD_LOCAL_VALUE( key, value ) \
           TlsSetValue(key, static_cast<lpvoid></lpvoid>(value))
    #   define LOG4CPLUS_THREAD_LOCAL_CLEANUP( key ) TlsFree(key)
    				
    使用起来比较简单,在某个线程中:
        NDC& ndc = log4cplus::getNDC();
        ndc.push("ur ndc string");
        LOG4CPLUS_DEBUG(logger, "this is a NDC test");
        ... ...
        
        ndc.pop();
        
        ... ...
        
        LOG4CPLUS_DEBUG(logger, "There should be no NDC...");
        ndc.remove();
        
    当设定输出格式(Layout)为TTCCLayout时,输出如下:
    10-21-04 21:32:58, [3392] DEBUG test <ur string="" ndc=""></ur> - this is a NDC test
    10-21-04 21:32:58, [3392] DEBUG test <> - There should be no NDC...
    也可以在自定义的输出格式中使用NDC(用%x) ,比如:
        ... ...
        
        std::string pattern = "NDC:[%x]  - %m %n";
        std::auto_ptr<layout></layout> _layout(new PatternLayout(pattern));
        ... ...
        
        LOG4CPLUS_DEBUG(_logger, "This is the FIRST log message...")
        NDC& ndc = log4cplus::getNDC();
        ndc.push("ur ndc string");
        LOG4CPLUS_WARN(_logger, "This is the SECOND log message...")
        ndc.pop();
        ndc.remove();
        
        ... ...
        
    输出如下:
    NDC:[]  - This is the FIRST log message...
    NDC:[ur ndc string]  - This is the SECOND log message...
    				
    另外一种更简单的使用方法是在线程中直接用NDCContextCreator:
        NDCContextCreator _first_ndc("ur ndc string");
        LOG4CPLUS_DEBUG(logger, "this is a NDC test")
        
    不必显式地调用push/pop了,而且当出现异常时,能够确保push与pop的调用是匹配的。
        
    ### 线程 ###
    线程是log4cplus中的副产品, 而且仅作了最基本的实现,使用起来也异常简单,只要且必须要
    在派生类中重载run函数即可:
    class TestThread : public AbstractThread
    {
    public:
        virtual void run();
    };				
    void TestThread::run()
    {
        /* do sth. */
        ... ...
    }
    log4cplus的线程没有考虑同步、死锁,有互斥,实现线程切换的小函数挺别致的:
    void log4cplus::thread::yield()
    {
    #if defined(LOG4CPLUS_USE_PTHREADS)
        ::sched_yield();
    #elif defined(LOG4CPLUS_USE_WIN32_THREADS)
        ::Sleep(0);
    #endif
    }
    				
    ### 套接字 ###
    套接字也是log4cplus中的副产品,在namespace log4cplus::helpers中,实现了C/S方式的日志记录。
    1. 客户端程序需要做的工作:
    /* 定义一个SocketAppender类型的挂接器 */
    SharedAppenderPtr _append(new SocketAppender(host, 8888, "ServerName"));
    /* 把_append加入到logger中 */
    Logger::getRoot().addAppender(_append);
    /*  SocketAppender类型不需要Layout, 直接调用宏就可以将信息发往loggerServer了 */
    LOG4CPLUS_INFO(Logger::getRoot(), "This is a test: ")
    				
    【注】 这里对宏的调用其实是调用了SocketAppender::append,里面有一个数据传输约定,即先发送
    一个后续数据的总长度,然后再发送实际的数据:
        ... ...
        SocketBuffer buffer = convertToBuffer(event, serverName);
        SocketBuffer msgBuffer(LOG4CPLUS_MAX_MESSAGE_SIZE);
        msgBuffer.appendSize_t(buffer.getSize());
        msgBuffer.appendBuffer(buffer);
       
        ... ...
    				
    2. 服务器端程序需要做的工作:
    /* 定义一个ServerSocket */
    ServerSocket serverSocket(port); 
    /* 调用accept函数创建一个新的socket与客户端连接 */
    Socket sock = serverSocket.accept();
    				
    此后即可用该sock进行数据read/write了,形如:
    SocketBuffer msgSizeBuffer(sizeof(unsigned int));
    if(!clientsock.read(msgSizeBuffer))
    {
        return;
    }
    unsigned int msgSize = msgSizeBuffer.readInt();
    SocketBuffer buffer(msgSize);
    if(!clientsock.read(buffer))
    {
        return;
    }
    为了将读到的数据正常显示出来,需要将SocketBuffer存放的内容转换成InternalLoggingEvent格式:
    spi::InternalLoggingEvent event = readFromBuffer(buffer);
    然后输出:
    Logger logger = Logger::getInstance(event.getLoggerName());
    logger.callAppenders(event);				
    【注】 read/write是按照阻塞方式实现的,意味着对其调用直到满足了所接收或发送的个数才返回。

    log4cplus的三个例程

    http://log4cplus.sourceforge.net/codeexamples.html
    里面自带的三个例程

    Hello World Example
    #include <log4cplus/logger.h>
    #include 
    <log4cplus/configurator.h>
    #include 
    <iomanip>

    using namespace log4cplus;

    int
    main()
    {
        BasicConfigurator config;
        config.configure();

        Logger logger 
    = Logger::getInstance("main");
        LOG4CPLUS_WARN(logger, 
    "Hello, World!");
        
    return 0;
    }

    ostream
    Example (Show how to write logging messages.)
    #include <log4cplus/logger.h>
    #include 
    <log4cplus/configurator.h>
    #include 
    <iomanip>

    using namespace std;
    using namespace log4cplus;

    int
    main()
    {
        BasicConfigurator config;
        config.configure();
        Logger logger 
    = Logger::getInstance("logger");

        LOG4CPLUS_WARN(logger,   
    "This is"
                               
    << " a reall"
                               
    << "y long message." << endl
                               
    << "Just testing it out" << endl
                               
    << "What do you think?")
        LOG4CPLUS_WARN(logger, 
    "This is a bool: " << true)
        LOG4CPLUS_WARN(logger, 
    "This is a char: " << 'x')
        LOG4CPLUS_WARN(logger, 
    "This is a short: " << (short)-100)
        LOG4CPLUS_WARN(logger, 
    "This is a unsigned short: " << (unsigned short)100)
        LOG4CPLUS_WARN(logger, 
    "This is a int: " << (int)1000)
        LOG4CPLUS_WARN(logger, 
    "This is a unsigned int: " << (unsigned int)1000)
        LOG4CPLUS_WARN(logger, 
    "This is a long(hex): " << hex << (long)100000000)
        LOG4CPLUS_WARN(logger, 
    "This is a unsigned long: " 
                       
    << (unsigned long)100000000)
        LOG4CPLUS_WARN(logger, 
    "This is a float: " << (float)1.2345)
        LOG4CPLUS_WARN(logger, 
    "This is a double: " 
                              
    << setprecision(15
                              
    << (double)1.2345234234)
        LOG4CPLUS_WARN(logger, 
    "This is a long double: " 
                              
    << setprecision(15
                              
    << (long double)123452342342.342)

        
    return 0;
    }


    LogLevel Example (Shows how log messages can be filtered at runtime by adjusting the LogLevel.)
    #include <log4cplus/logger.h>
    #include 
    <log4cplus/configurator.h>
    #include 
    <iostream>

    using namespace std;
    using namespace log4cplus;

    Logger logger 
    = Logger::getInstance("main");

    void printMessages()
    {
        LOG4CPLUS_TRACE(logger, 
    "printMessages()");
        LOG4CPLUS_DEBUG(logger, 
    "This is a DEBUG message");
        LOG4CPLUS_INFO(logger, 
    "This is a INFO message");
        LOG4CPLUS_WARN(logger, 
    "This is a WARN message");
        LOG4CPLUS_ERROR(logger, 
    "This is a ERROR message");
        LOG4CPLUS_FATAL(logger, 
    "This is a FATAL message");
    }



    int
    main()
    {
        BasicConfigurator config;
        config.configure();

        logger.setLogLevel(TRACE_LOG_LEVEL);
        cout 
    << "*** calling printMessages() with TRACE set: ***" << endl;
        printMessages();

        logger.setLogLevel(DEBUG_LOG_LEVEL);
        cout 
    << "\n*** calling printMessages() with DEBUG set: ***" << endl;
        printMessages();

        logger.setLogLevel(INFO_LOG_LEVEL);
        cout 
    << "\n*** calling printMessages() with INFO set: ***" << endl;
        printMessages();

        logger.setLogLevel(WARN_LOG_LEVEL);
        cout 
    << "\n*** calling printMessages() with WARN set: ***" << endl;
        printMessages();

        logger.setLogLevel(ERROR_LOG_LEVEL);
        cout 
    << "\n*** calling printMessages() with ERROR set: ***" << endl;
        printMessages();

        logger.setLogLevel(FATAL_LOG_LEVEL);
        cout 
    << "\n*** calling printMessages() with FATAL set: ***" << endl;
        printMessages();

        
    return 0;
    }

    
    展开全文
  • React从零学起

    千次阅读 2016-05-19 14:04:39
    初接触React,除了不习惯其组件化的设计原则外,往往它所‘依赖’的众多module也会让初学者感到困惑,使得不知从何学起。此文只是我对React的一些浅析,希望能帮助想要学习React的朋友入门。 1.React从来就是独立的...

    原文请戳

    初接触React,除了不习惯其组件化的设计原则外,往往它所‘依赖’的众多module也会让初学者感到困惑,使得不知从何学起。此文只是我对React的一些浅析,希望能帮助想要学习React的朋友入门。

    1.React从来就是独立的

    正如上面我提到的,React’依赖’了很多module,但是这种依赖并不是所谓的耦合依赖,只是为了更好的去实现React。换句话说,React只是一个View层面的框架,它可以和其他module自然的融合(更好的去实现)。

    我们可以只利用React去实现官网上那个Counter的demo,这里我做了一个简易版。

    index.html

    <!DOCTYPE html>
    <html>
      <head>
        <title>Redux counter example</title>
      </head>
      <body>
        <div id="root">
        </div>
        <script src="/static/bundle.js"></script>
      </body>
    </html>

    页面只引了一个js文件,该文件为webpack打包而成,具体webpack打包原理不在这里赘述。

    MyCounter.js

    var React = require('react');
    
    var Counter = React.createClass({
        getInitialState: function() {
            return {value: 0};
        },
        plus: function() {
            this.setState({
                value: ++this.state.value
            });
        },
        minus: function() {
            this.setState({
                value: --this.state.value
            });
        },
        render: function() {
            return (
                <div>
                    <button onClick={this.plus}>+</button>
                    <span>{this.state.value}</span>
                    <button onClick={this.minus}>-</button>
                </div>
            );
        }
    });
    
    module.exports = Counter;

    这是典型的React的Component,它的内部实现了计数的算法以及state的管理机制,这个Component的实例就是计数器,该计数器也很简单,可以实现增加和减少。

    最后是 index.js

    var React = require('react');
    var ReactDOM = require('react-dom');
    var MyCounter = require('./components/MyCounter');
    
    ReactDOM.render(
        <MyCounter />,
        document.getElementById('root')
    );

    到此,我们就利用React实现了一个小小的计数器,尽管它很简单,但是却未使用任何其他module,所以我在上面提到,React本身就是独立的。

    所以,引用其他module,只是为了实现更复杂的React App,使得其更具有扩展性。

    2.Container

    那么问题来了,如果我还像要一个类似的Component,但是每次计数的时候不是加1或者减1,而是乘2或除2,那怎么做呢?

    你可别告诉我重新一个类似上面MyCounter的Component,然后绑定不同的事件,那如果是这样的话React也太low了吧,这还叫什么组件化呢,组件化最基本的特点就是复用啊。

    所以React期望我们这么做:

    对于任何Compoent,尽量将其作为静态展示的Component,即其只负责展示UI,然后在它的外层嵌套一个Container, Container中定义了该Compoent所需要的参数以及方法,这样,当我们需要复用Component时,UI已经是现成的了,而Container中的逻辑部分也可以共享,换个“壳子”就是一个具有其他功能的Compoent了。

    于是,分解如下:

    MyCounterContainer.js

    var React = require('react');
    var Counter = require('../components/MyCounter');
    
    var CounterContainer = React.createClass({
        getInitialState: function() {
            return {
                value: this.props.value
            };
        },
        plus: function() {
            this.setState({
                value: this.state.value + 1
            });
        },
        minus: function() {
            this.setState({
                value: this.state.value - 1
            });
        },
        render: function() {
            return (
                <Counter plus={this.plus}
                         minus={this.minus}
                         value={this.state.value} />
            );
        }
    });
    
    module.exports = CounterContainer;

    MyCounter

    var React = require('react');
    
    var Counter = React.createClass({
        render: function() {
            return (
                <div>
                    <span>{this.props.value}</span>
                    <button onClick={this.props.plus}>+</button>
                    <button onClick={this.props.minus}>-</button>
                </div>
            );
        }
    });
    
    module.exports = Counter;

    index.js

    var React = require('react');
    var ReactDOM = require('react-dom');
    var MyCounterContainer = require('./container/MyCounterContainer');
    
    ReactDOM.render(
        <MyCounterContainer value={0} />,
        document.getElementById('root')
    );

    UI与逻辑分离成功,是不是感觉瞬间清爽许多。
    关于什么时Contianer Component和Presentation Component,推荐此文

    3.Use store to help dispatch actions

    分离了UI后,的确逻辑上清楚了许多,但仔细观察会发现,上面的 MyCounterContainer 状态的改变只是两个button。而React认为Component的状态变化必定是由一个行为,即action造成的,因此,我们需要将上面的加减法抽象为一个行为驱动的事件,即一个行为对应一种状态结果。而 redux 就是干这事儿的,它通过createStore去创建一个store,这个store可以管理和知晓这个Component的状态,它通过dispatch分发action然后得到最新的状态结果。

    利用store,我们将 MyCounterContainer 重构如下:

    var React = require('react');
    var Counter = require('../components/MyCounter');
    var createStore = require('redux').createStore;
    
    
    var counter = function(state, action) {
        switch (action.type) {
            case 'PLUS': 
                return state + action.value;
            case 'MINUS': 
                return state - action.value;
            default: 
                return state;
        }
    };
    
    var store = createStore(counter, 1000);
    var CounterContainer = React.createClass({
        plus: function() {
            var nextState = store.dispatch({
                type: "PLUS",
                value: 2
            });
            this.setState(nextState);
        },
        minus: function() {
            var nextState = store.dispatch({
                type: "MINUS",
                value: 2
            });
            this.setState(nextState);
        },
        render: function() {
            return (
                <Counter plus={this.plus} 
                         minus={this.minus}
                         value={store.getState()} />
            );
        }
    });
    
    module.exports = CounterContainer;

    4.Use connect to manage the dispatch and reducer

    仔细观察上次重构,不难发现还是有些问题:

    其一,尽管利用store帮我们管理了state,但是还是得我们手动setState,太过耦合。

    其二,对于传递给子Component的参数,还是写死在Container里,不具有封装性和灵活性。

    为了解决这个问题,我们可以利用 react-redux 的connect来解决。
    connect可以把自定义的state和dispatch分发事件绑定到Component上,其中mapStateToProps正如其名,可以将state作为Component的props传递下去;而mapDispatchToProps则可以把action触发逻辑传递下去,这样我们可以很灵活的传递功能事件了。

    利用connect我们继续重构,MyCounterContainer 如下:

    var React = require('react');
    var Counter = require('../components/MyCounter');
    var createStore = require('redux').createStore;
    var connect = require('react-redux').connect;
    
    var mapStateToProps = function(state) {
        return {
            value: state
        }
    };
    
    var mapDispatchToProps = function(dispatch) {
        return {
            plus: function() {
                dispatch({
                    type: "PLUS", 
                    value: 2
                });
            },
            minus: function() {
                dispatch({
                    type: "MINUS", 
                    value: 2
                });
            }
        }
    };
    
    var CounterContainer = connect(
        mapStateToProps,
        mapDispatchToProps
    )(Counter);
    
    module.exports = CounterContainer;

    5.Split actions

    上面的例子已经很接近React的初级App的设计了,但当我们的Component特别复杂时,往往action也会难抽象,像上面的dispatch({type: “PLUS”, value: 2});偶合度太高,因为我们根本不知道这个action为什么是这样,就好比我们随便写了一个常量而并未定义任何变量名一样,别人是很难阅读的。因此比较好的做法是把action更小的分离,比如上面的action,我们可以分离成如下:

    module.exports.plusAction = function(val) {
        return {
            type: "PLUS",
            value: val
        };
    }
    
    module.exports.minusAction = function(val) {
        return {
            type: "MINUS",
            value: val
        };
    }

    这样,在dispatch时,也会显得很简洁。

    6.ES6 refactor

    ES6部分就不在这里赘述了,大多都是语法问题,建议大家可以参考阮老师的书ES6入门

    7.写在最后

    个人认为,学习React十分不推荐一上手就用各种module,或者照猫画虎式的去填空,这样只能是到头来什么也不会。当你从头开始去理解时,才能找到痛点,而当你有痛点时你才需要重构,那么此时可能某个module就是你想要的。你用它只是为了省时,而不是你做不出来才用它。借用我前几天在知乎上回答的问题“用库丢脸不?”,我的观点是:用库不丢脸,不懂库还非要用库才丢脸原文请戳

    展开全文
  • 菜鸟的JWT学习总结说明一、mybatis plus 说明 更新时间:2020/8/19 11:09,更新了JWT相关内容 本文主要对mybatis plus结合redis二级缓存的学习总结,里面涉及mybatis plus和redis的相关知识,本文会持续更新,不断地...
  • nucleus plus(Hisr,lisr学习)

    千次阅读 2015-01-11 14:40:49
    之前看过一些linux内核的一些东西,但都是停留在文字上,代码看的很少,这个nucleus plus内核的代码量不大,看过source code确实对很多OS的知识有了更深入的认识,收获还是挺多的,把到的东西记录下来。...
  • 前言:由于目前该工具比较新,...1.下载工具,解压后放到linux下。 2.官网申请一个三十天的许可证,需要填写邮箱。 3.linux下安装python及其相关的依赖模块,可以参考手册在DOC/目录下 (1)安装python sudo apt-get i
  • 《C++ Prime Plus》学习总结

    千次阅读 2016-09-05 20:38:06
    (3) 第三步,继续判断expression是否用括号括的标识符,如果是,则var为指向其类型的引用。 举例: double xx = 4.4; decltype((xx)) r2 = xx; // r2 is double & decltype(xx) w = xx; // w is ...
  • 学习一门语言,一本好的书相当重要,...而当我看了C Primer Plus之后才发现这才叫真的开始接触和学习C语言,之前都不能叫“过”C语言,不得不说国外的这种经典著作比国内很多书籍来说却是好太多了,读C Primer Pl
  • Mybatis-Plus使用教程 完工作量减少一大半!
  • 双剑合璧————Spring Boot + Mybatis Plus

    万次阅读 多人点赞 2018-06-23 15:10:11
    最近在学习Mybatis Plus的使用,希望通过spring boot快速将mybatis plus整合进来。 对于springboot项目,mybatis plus团队也有自己的启动器 :mybatis-plus-boot-starter。这个依赖内部已经整合了mybatis-spring,...
  • C++Primer Plus第6版&C Primer Plus第6版 中文版免费分享啦

    万次阅读 多人点赞 2019-10-06 17:48:03
    但师兄说这个是他在网上花了积分才下载下来的,这让我很是吃惊,因为作为一本经久不衰的经典书籍,这样的资料不应该有很多网友免费分享出来方便大家学习嘛。 本着知识分享学习的思想,我特意找了对应的C语言版,两本...
  • 进入C++ C++对大小写敏感,比如:将cout替换成Cout或COUT,都无法通过编译。 C++程序包括的元素: ...函数体:用{}括 使用C++的cout工具显示消息的语句 结束main()函数的return语句 main() int main()...
  • SpringBoot整合Mybatis-Plus

    2020-08-01 18:54:20
    SpringBoot整合Mybatis-Plus 文章目录SpringBoot整合Mybatis-Plus前言新建项目配置连接快速入门常用注解@TableName@TableId@TableField常用方法查询方法增加方法更新方法删除方法分页查询多数据源配置参考文章 前言 ...
  • SQL*PLUS

    千次阅读 2012-08-09 14:43:38
    在介绍SQL*PLUS命令应用之前,让我们先花点时间来学习一下Oracle SQL*PLUS命令的基本知识。 一、Oracle常用命令介绍 Oracle SQL*PLUS命令不少,而且参数更是众多。要完全熟悉实在不大可能,网上关于Oracle SQL...
  • springboot集成Mybatis-plus

    千次阅读 2020-04-18 22:32:41
    上篇我们已经敲定ORM框架使用Mybatis-plus了,这篇我们就正式来撸。 在开撸之前再强调一遍: 本专题的代码都使用lombok,因此拉下代码后,要运行前请务必装上lombok插件,否则报错。不知道怎么装的再回顾下...
  • MyBatis-Plus实战

    千次阅读 多人点赞 2019-11-24 17:50:55
     MyBatis-Plus为开发人员提供了通用的CRUD方法,免去我们开发大量简单,乏味的代码。使用这些通用方法很简单,只需要让Mapper接口继承 BaseMapper 即可。 public interface UserMapper extends ...
  • Nucleus PLUS简介

    千次阅读 多人点赞 2014-06-26 16:44:30
    近些年来,随着嵌入式系统飞速的发展,嵌入式实时操作系统广泛地应用在制造工业、过程控制、通讯、仪器仪表等方面,用户可以根据自身的软硬件环境...本文概述了实时多任务嵌入式操作系统Nucleus PLUS的特点和应用领域。
  • 转载请注明出处: ... 本章内容包括: ...函数头对函数与程序其他部分之间的接口进行了总结....int main(void),在C++(不是C)中,让括号空着与在括号中使用void等效(在C中,让括号空着意味着对是否接受...本章源代码下载地址
  • 一、下载 下载地址:Oracle Express Edition(简洁版/开发板)。这个版本比较小(安装包300M左右),是Oracle用来学习用的,很适合初学者。 步骤一:进入下载页面,点击Accept。 步骤二:这里以Win64位操作系统为...
  • 全面解析plus token

    万次阅读 2019-09-03 23:11:35
    1. plus token为什么要从做钱包切入? 因为区块链时代的当下,钱包是一个市场需求,做任何创业,必须是做市场有需求的产品。简单讲,你为什么生产面包,因为你知道民以食为天,吃的品类多样,但面包是有很多人会吃...
  • 因为是刚开始学习oracle数据库,想先用这个自带的工具连接上oracle服务器,但是弹出的登陆框不知道用啥来登录,登录界面如下 后面上网查了下资料,用界面上的/ as sysdba来登录就行,口令和主机字符串不用输入,...
  • oracle sql*plus

    2010-03-29 00:23:00
    SQL*PLUS命令学习:Oracle的SQL*PLUS是与oralce进行交互的客户端工具。在sql*plus中,可以运行sql*plus命令与sql*plus语句。我们通常所说的DML、DDL、DCL语句都是sql*plus语句,它们执行完后,都可以保存在一个被...
  • plus币又涨了

    千次阅读 2018-11-05 15:23:02
    对的自己当初的选择!我可不想把你们谁弄掉队了跟上,跟紧点。? ? 别走丢了!有福同享。创世相见! 大户,大咖,大神,已经身价不菲!倘若你非常努力成功走向"创世"级别,仅仅年分红就有150万美金额外的? 值得...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,664
精华内容 7,465
关键字:

学起plus下载