11.3去掉返回文字 ios_ios9.0.2和ios9.3.3 - CSDN
精华内容
参与话题
  •  1.1 iOS概述 2  1.1.1 iOS介绍 2  1.1.2 iOS 6新特性 2  1.2 开发环境及开发工具 3  1.3 本书中的约定 4  1.3.1 案例代码约定 4  1.3.2 图示的约定 5  第2章 第一个iOS应用程序 7  2.1 创建基于...

    第一部分 基础篇
      第1章 开篇综述 2
      1.1  iOS概述 2
      1.1.1  iOS介绍 2
      1.1.2  iOS 6新特性 2
      1.2  开发环境及开发工具 3
      1.3  本书中的约定 4
      1.3.1  案例代码约定 4
      1.3.2  图示的约定 5


      第2章 第一个iOS应用程序 7
      2.1  创建基于nib的HelloWorld工程 7
      2.1.1  创建工程 7
      2.1.2  Xcode中的iOS工程模板 10
      2.1.3  应用剖析 11
      2.2  基于故事板的HelloWorld工程 13
      2.2.1  使用故事板重构HelloWorld 14
      2.2.2  nib、xib与故事板 15
      2.2.3  故事板中的Scene和Segue 16

    SceneSegue(参见图2-19)是故事板中非常重要的两个概念。每个视图控制器都会对应一个SceneScene
    翻译为“场景”,可以理解为应用的一个界面或屏幕,在这个屏幕中有很多视图或控件,相当于一个xib。这些Scene
    之间通过Segue连接,Segue不但定义了Scene之间的跳转(或导航)方式,还体现了Scene之间的关系。跳转的类
    型分为:PushModalPopover和自定义方式。Scene跳转类型还要跟具体的控制器结合使用。Push是树形导航模
    式,Modal是模态导航模式,Popover是呈现浮动窗口,这些导航模式我们会在后面介绍。

      2.3  应用生命周期 17

    作为应用程序的委托对象, AppDelegate类在应用生命周期的不同阶段会回调不同的方法。


    下面简要介绍一下iOS应用的5种状态。
    Not Running(非运行状态)。应用没有运行或被系统终止。
    Inactive(前台非活动状态)。应用正在进入前台状态,但是还不能接受事件处理。
    Active(前台活动状态)。应用进入前台状态,能接受事件处理。
    Background(后台状态)。应用进入后台后,依然能够执行代码。如果有可执行的代码,就会执行代码,
    如果没有可执行的代码或者将可执行的代码执行完毕,应用会马上进入挂起状态。
    Suspended(挂起状态)。处于挂起的应用进入一种“冷冻”状态,不能执行代码。如果系统内存不够,
    应用会被终止。

    application:didFinishLaunchingWithOptions:UIApplicationDidFinishLaunchingNotification
    应用启动并进行初始化时会调用该方法并发出通知。这个阶段会实例化根视图控制器
    applicationDidBecomeActive: UIApplicationDidBecomeActiveNotification
    应用进入前台并处于活动状态时调用该方法并发出通知。这个阶段可以恢复UI的状态(例如游戏状态等)
    applicationWillResignActive: UIApplicationWillResignActiveNotification
    应用从活动状态进入到非活动状态时调用该方法并发出通知。这个阶段可以保存UI的状态(例如游戏状态等)
    applicationDidEnterBackground: UIApplicationDidEnterBackgroundNotification
    应用进入后台时调用该方法并发出通知。这个阶段可以保存用户数据,释放一些资源 (例如释放数据库资源等)
    applicationWillEnterForeground: UIApplicationWillEnterForegroundNotification
    应用进入到前台,但是还没有处于活动状态时调用该方法并发出通知。这个阶段可以恢复用户数据
    applicationWillTerminate: UIApplicationWillTerminateNotification
    应用被终止时调用该方法并发出通知,但内存清除时除外。这个阶段释放一些资源,也可以保存用户数据

      2.3.1  非运行状态——应用启动场景 18

    Not running→Inactive→Active

    Not running→Inactive阶段。调用application:didFinishLaunchingWithOptions:方法,发出UIApplicationDidFinishLaunchingNotification通知。

    Inactive→Active阶段。调用applicationDidBecomeActive:方法,发出UIApplicationDidBecomeActiveNotification通知。


      2.3.2  点击Home键——应用退出场景 19

    HelloWorld-Info.plist文件该设置项对应的键是UIApplicationExitsOnSuspend
    1. Active → Inactive → Background→Suspended
    Active→Inactive阶段。调用applicationWillResignActive:方法,发出UIApplicationWillResignActiveNotification通知。
    Inactive→Background阶段。应用从非活动状态进入到后台(不涉及我们要重点说明的方法和通知)。
    Background→Suspended阶段。调用applicationDidEnterBackground:方法,发出UIApplicationDidEnterBackgroundNotification通知。

    2. 应用不可以在后台运行

    Active → Inactive → Background→Suspended→Not running
    Active→Inactivd阶段。应用由活动状态转为非活动状态(不涉及我们要重点说明的方法和通知)。
    Inactive→Background阶段。应用从非活动状态进入到后台(不涉及我们要重点说明的方法和通知)。
    Background→Suspended阶段。调用applicationDidEnterBackground:方法,发出UIApplicationDidEnterBackgroundNotification通知。
    Suspended→Not running阶段。调用applicationWillTerminate:方法,发出UIApplicationWillTerminateNotification通知。

      2.3.3  挂起重新运行场景 20

    Suspended → Background → Inactive → Active
    Suspended→Background阶段。应用从挂起状态进入后台(不涉及我们讲述的这几个方法和通知)。
    Background→Inactive阶段。调用applicationWillEnterForeground:方法,发出UIApplicationWillEnterForegroundNotification通知。
    Inactive→Active阶段。调用applicationDidBecomeActive:方法,发出UIApplicationDidBecomeActiveNotification通知。

      2.3.4  内存清除——应用终止场景 21
      2.4  视图生命周期 21
      2.4.1  视图生命周期与视图控制器关系 22

    在视图不同的生命周期中,视图控制器会回调不同的方法

    didReceiveMemoryWarning:方法的主要职能是释放内存,包括视图控制器中的一些成员变量和视图的释放。现举例如下:


      2.4.2  iOS 6 UI状态保持和恢复 23

    以下3种地方实现状态保持和恢复:
    应用程序委托对象
    视图控制器
    自定义视图

    Restoration ID(恢复标识)为viewController
    恢复标识是iOS为了实现UI状态保持和恢复添加的设置项目。我们还需要在应用程序委托对象AppDelegate
    码部分做一些修改,添加的代码如下:
    -(BOOL) application:(UIApplication *)application shouldSaveApplicationState:
    (NSCoder *)coder
    {
    return YES;
    }

    -(BOOL) application:(UIApplication *)application shouldRestoreApplicationState:
    (NSCoder *)coder
    {
    return YES;
    }
    - (void)application:(UIApplication *)application willEncodeRestorableStateWithCoder:
    (NSCoder *)coder
    {
    [coder encodeFloat:2.0 forKey:@"Version"];
    }
    - (void)application:(UIApplication *)application didDecodeRestorableStateWithCoder:
    (NSCoder *)coder
    {
    float lastVer = [coder decodeFloatForKey:@"Version"];



    其中application:shouldSaveApplicationState:方法在应用退出时调用,负责控制是否允许保存状态,返
    YES情况是可以保存,NO是不保存。
    application:shouldRestoreApplicationState:方法在应用启动时调用,负责控制是否恢复上次退出
    时的状态,返回YES表示可以恢复,返回NO表示不可以恢复。
    application:willEncodeRestorableStateWithCoder:方法在保存时调用,在这个方法中实现UI状态
    或数据的保存,其中[coder encodeFloat:2.0 forKey:@"Version"]语句是保存简单数据。
    application:didDecodeRestorableStateWithCoder:方法在恢复时调用,在这个方法中实现UI状态或
    数据的恢复,其中[coder decodeFloatForKey:@"Version"]语句用于恢复上次保存的数据。
    想要实现具体界面中控件的保持和恢复,还需要在它的视图控制器中添加一些代码。我们在ViewController.m
    中添加的代码如下:
    -(void)encodeRestorableStateWithCoder:(NSCoder *)coder
     {
    [super encodeRestorableStateWithCoder:coder];
    [coder encodeObject:self.txtField.text forKey:kSaveKey];
    }
    -(void)decodeRestorableStateWithCoder:(NSCoder *)coder
    {
    [super decodeRestorableStateWithCoder:coder];
    self.txtField.text = [coder decodeObjectForKey:kSaveKey];
    }

    iOS 6之后,视图控制器都添加了两个方法——encodeRestorableStateWithCoder:decodeRestorableStateWithCoder:,用来实现该控制器中的控件或数据的保存和恢复。其中encodeRestorableStateWithCoder:
    方法在保存时候调用, [coder encodeObject:self. txtField.textforKey:kSaveKey]语句是按照指定的键保存
    文本框的内容,decodeRestorableStateWithCoder:方法在恢复时调用,[coder decodeObjectForKey:kSaveKey]
    在恢复文本框内容时调用,保存和恢复事实上就是向一个归档文件中编码和解码的过程。
    为了测试是否能够保持和恢复,我们可以将工程属性文件HelloWorld-Info.plist中的相关属性Application does
    not run in background设置为YES,使应用退出时终止程序的运行

      2.5  设置产品属性 25

    为了禁止应用在后台运行,我们将HelloWorld-Info.plist文件中的Application

    does not run in background属性修改为YES(即UIApplicationExitsOnSuspend = YES) ,这项操作就属于
    产品属性的设置。

      2.5.1  Xcode中的Project和Target 25

    一个工程只有一个Project,但可以有一个或多个Target
    首先,依次选择File→New→Target菜单项,此时会弹出一个模板选择对话框

      2.5.2  设置常用的产品属性 27

    Target继承了Project。对于TargetProject下都有的设置项,可根据需要对Target进行再设置,此设置可覆盖Project的设置。
    1. 设定屏幕方向
    2. 设置设备支持情况

      2.6  iOS API简介 29

    1. Cocoa Touch
    该层提供了构建iOS应用的一些基本系统服务(如多任务、触摸输入和推送通知等)和关键框架(见表2-3)。
    2-3 Cocoa Touch层包括的框架
    框 架           前 缀        说 明
    Address Book UI   AB         访问用户的联系人信息
    Event Kit UI     EK           访问用户的日历事件数据
    Game Kit         GK          提供能够进行点对点的网络通信的API
    iAd              AD            在应用中嵌入广告
    Map Kit          MK          在应用中嵌入地图和地理信息编码等
    Message UI       MF          提供与发送E-mail相关的API
    Twitter          TW            提供发送Twitter的接口
    UIKit            UI           提供UI
    2. Media
    Media层提供了图形、音频、视频和AirPlay技术,包括的框架如表2-4所示。
    2-4 Media层包括的框架
    框 架              前 缀          说 明
    Assets Library      AL             提供访问用户的图片和视频的接口

    AudioToolbox         Audio         录制或播放音频、音频流以及格式转换
    AudioUnit Audio,    AU             提供使用内置音频单元服务,以及音频处理模块
    AV Foundation       AV             提供播放与录制音频和视频的Objective-C接口
    Core Audio          Audio           提供录制、制作、播放音频的C语言接口
    Core Graphics       CG             提供Quartz 2D接口
    Core Image          CI               提供操作视频和静态图像的接口
    Core MIDI            MIDI             提供用于处理MIDI数据低层的API
    Core Text             CT             提供渲染文本和处理字体的简单、高效的C语言接口
    Core Video          CV                提供用于处理音频和视频的API
    Image I/O            CG             包含一些读写图像数据类
    GLKit               GLK             包含了构建复杂OpenGL ES应用的Objective-C实用类
    Media Player        MP              包含全屏播放接口
    OpenAL              AL              包含了OpenAL(跨平台的音频)的C语言接口
    OpenGL ES EAGL,    GL              包含OpenGL ES(跨平台的2D/3D图形库)的C语言接口
    Quartz Core        CA               提供动画接口类
    Sprite Kit           SK              是苹果提供的基于2D2.5D游戏的开发引擎,可以开发iOSMac OS X下的游戏。

    3. Core Services
    该层提供了iCloud、应用内购买、SQLite数据库和XML支持等技术,包括的主要框架如表2-5所示。
    2-5 Core Services层包括的框架
    框 架 前 缀 说 明
    Accounts AC 用于访问用户的Twitter账户(iOS 5之后才有此API
    AddressBook AB 访问用户的联系人信息
    AdSupport AS 获得iAD广告标识
    CFNetwork CF 提供了访问Wi-Fi网络和蜂窝电话网络的API
    Core Data NS 提供管理应用数据的ORM接口
    CoreFoundation CF 它是iOS开发中最基本的框架,包括数据集
    Core Location CL 提供定位服务的API
    CoreMedia CM 提供AV Foundation框架使用的底层媒体类型。可以精确控制音频或视频的创建及展
    CoreMotion CM 接收和处理重力加速计以及其他的运动事件
    CoreTelephony CT 提供访问电话基本信息的API
    Event Kit EK 访问用户的日历事件数据
    Foundation NS Core Foundation框架的许多功能提供Objective-C封装,是Objective-C最为基本框架

    JavaScriptCore.framework JS提 供 了 基 于Objective-C语 言 封 装 的 标 准 JavaScript 对 象 , 通 过 该 框 架 可 以 实 现
    Objective-CJavaScript之间的相互调用
    MobileCoreServices UT 定义统一类型标识符(UTI)使用的底层类型
    Newsstand Kit NK 提供在后台下载杂志和新闻的API接口(iOS 5之后才有此API
    Pass Kit PK 提供访问各种优惠券的APIiOS 6之后才有此API
    QuickLook QL 该框架可以预览无法直接查看的文件内容,例如打开PDF文件
    Social SL 提供社交网络访问API,中国区提供新浪微博APIiOS 6之后才有此API
    Store Kit SK 提供处理应用内置收费的资金交易
    SystemConfiguration SC 用于确定设备的网络配置(例如,使用该框架判断Wi-Fi或者蜂窝连接是否正在使用
    中) ,也可以用于判断某个主机服务是否可以使用


    4. Core OS
    该层提供了一些低级功能,开发中一般不直接使用它。该层包括的主要框架如表2-6所示。
    2-6 Core OS层包括的框架
    框 架 前 缀 说 明
    Accelerate AC 访问重力加速计API
    Core Bluetooth CB 访问低能耗蓝牙设备API
    External Accessory EA 访问外围配件API接口
    Generic Security Services gss提供一组安全相关的服务
    Security CSSMSec管理证书、公钥、私钥和安全信任策略API

    .6.1  API概述 29

      2.6.2  如何使用API帮助 31

    如果想查询比较完整的、全面的帮助文档,可以按住Alt键双击didReceiveMemoryWarning方法名,这样就会打开一个XcodeAPI帮助文档

      2.7  小结 33

    IOS-Foundation框架结构,iosfoundation框架


    这些东西,等用的时候查资料就行,用的多了,自然就记住了,大概过一下

    发现一个不错的 ios 学习博客:http://www.cnblogs.com/kenshincui,非常好,推荐看看。FOundation系列笔记,是作为个人复习用,内容除了书本,个人经验还有一些是借鉴的它的博文。

    Foundation 框架

    它是IOS应用程序开发的基础,常用的框架有80多个,而 foundation 是他们所有的基础,提供了许多基本的对象类和数据类型,比如数字,字符串,数组,集合,字典,处理日期时间,自动化内存管理,文件,归档,处理几何数据结构等。它为所有应用程序提供基本的数据服务,

    Foundation和界面无关,其前缀为NS 。

    在Foundation中有些类仅被MAC OS支持而不被IOS支持。 

    IOS 或者 MAC OS 里面的Cocoa是什么呢?

    Cocoa不是一种编程语言(它可以运行多种编程语言),它也不是一个开发工具(通过命令行我们仍然可以开发Cocoa程序),它是创建Mac OS X和IOS程序的原生面向对象API,为这两者应用提供了编程环境。我们通常称为“Cocoa框架”。

    Cocoa本身是一个框架的集合,它包含了众多子框架,其中最重要的“Foundation”和“UIKit”。

    前者是框架的基础,和界面无关,其中包含了大量常用的API;后者是基础的UI类库,以后我们在IOS开发中会经常用到。这两个框架在系统中的位置如下图:

    所有的Mac OS X和IOS程序都是由大量的对象构成,而这些对象的根对象都是NSObject,NSObject就处在Foundation框架之中,具体的类结构如下:

    通常分为:

    UIKit主要用于界面构架,这里我们不妨也看一下它的类结构:

     




    第3章 UIView与控件  
    3.1  视图“始祖”--UIView  
    Objective-C中,NSObject是所有类的“根”类。同样,在UIKit框架中,也存在一个如此神奇的类UIView从继承关系上看,UIView是所有视图的根,我们形象地称其为“始祖”。

    3.1.1  UIView“家族” 
    UIView“家族”大体分为“控件”和“视图”两类,二者均继承于UIView


     

    UIControl类是控件类,其子类有UIButtonUITextFieldUISilder等。之所以称它们为“控件类”,是因为它们都有能力响应一些高级事件。
    其中Send Events栏中的内容就是UIButton相对应的高级事件UIControl类以外的视图没有这些高级事件,
    这可以借助HelloWorld工程中的Label控件验证一下。选中UILabel控件,打开连接检查器,如图3-3所示。可以发
    UILabel的连接检查器中没有Send Events栏,即没有高级事件,不可以响应高级事件。
    事实上,视图也可以响应事件,但这些事件比较低级,需要开发人员自己进行处理。很多手势的开发都以这
    些低级事件为基础的。
     
    3.1.2  应用界面的构建层次  
    如图3-4所示是一个应用界面的构建
    层次图,该应用有一个UIWindow,其中包含一个UIView根视图。根视图下又有3个子视图——Button1Label2
    UIView(View2),其中子视图UIView(View2)中存在一个按钮Button3

    应用界面的构建层次是一种树形结构,UIWindow是“树根”,根视图是“树干”,其他对象为树冠。在层次
    结构中,上下两个视图是“父子关系”。除了UIWindow,每个视图的父视图有且只有一个,子视图可以有多个。
    superview。获得父视图对象。
    subviews。获得子视图对象集合。
    window。获得视图所在的UIWindow对象。

    3.1.3  视图分类  
     
    为了便于开发,苹果将UIKit框架中的视图分成以下几个类别。
    控件。继承自UIControl类,能够响应用户高级事件。
    窗口。它是UIWindow对象。一个iOS应用只有一个UIWindow对象,它是所有子视图的“根”容器。  (ios应用一个应用只启动一个UIWindow ,而android 每一个activity对应一个UIWindow)
    容器视图。它包括了UIScrollViewUIToolbar以及它们的子类。UIScrollView的子类有UITextViewUITableViewUICollectionView,在内容超出屏      幕时,它们可以提供水平或垂直滚动条。UIToolbar是非常特殊的容器,它能够包含其他控件,一般置于屏幕底部,特殊情况下也可以置于屏幕顶部。
    显示视图。用于显示信息,包括UIImageViewUILabelUIProgressViewUIActivityIndicatorView等。
    文本和Web视图。提供了能够显示多行文本的视图,包括UITextViewUIWebView,其中UITextView也属于容器视图,UIWebView是能够加载和显示HTML代      码的视图。
    导航视图。为用户提供从一个屏幕到另外一个屏幕的导航(或跳转)视图,它包括UITabBarUINavigationBar
    警告框和操作表。用于给用户提供一种反馈或者与用户进行交互。UIAlertView视图是一个警告框,它会以动画形式弹出来;而UIActionSheet视图给用户提供可选的操作,它会从屏幕底部滑出。
     
    在后面章节中,很多视图(如UILabel、文本视图和进度条等)并未继承UIControl类,但我们也习惯
    称之为“控件”,这是开发中约定俗成的一种常用归类方式,与严格意义上的概念性分类有差别。

    3.2  标签控件和按钮控件 
    3.2.1  标签控件 
     
    3.2.2  按钮控件  

    3.2.3  动作和输出口  
    3.3  TextField控件和TextView控件  
    3.3.1  TextField控件  
    3.3.2  TextView控件  
    3.3.3  键盘的打开和关闭  
    3.3.4  关闭和打开键盘的通知  
    3.3.5  键盘的种类  
    3.4  开关控件、滑块控件和分段控件  
    3.4.1  开关控件  
    3.4.2  滑块控件  
    3.4.3  分段控件

    分段控件也是一种选择控件,其功能类似于Windows中的单选按钮。它由两段或更多段构成,每个段相当于
    一个独立的按钮。它有三种样式——PlainBorderedBar样式,但是iOS 7之后这三种样式没有什么区别,都如
    3-29所示。
      
    3.5  网页控件WebView  
    UIKit中的UIWebView类能够为用户提供显示多行文本的视图,能够冠以“Web”就说明它可以使用Web等技
    术进行显示HTML、解析CSS和执行JavaScript等操作。事实上,UIWebView的内核是开源的WebKit浏览器
    引擎。
    从桌面到移动应用开发,一直有两大阵营的争论,关于是本地好还是Web好的话题本书不做过多讨论,只是
    给出技术解决方案。事实上,除了本地和Web应用,我们还有第三条路可以走——HybridHybrid是本地+Web
    混合产物,而WebView控件是Hybrid应用的关键技术,它不仅是负责解析HTML的控件,更是本地和Web进行沟通
    的桥梁。

    3.5.1  WebView介绍  

    3.5.2  使用WebView构建Hybrid应用 
    Hybrid应用同时融合了本地技术和Web技术,它能够同时发挥本地技术和Web技术各自的优势。纯Web技术的
    应用虽然禁止在App Store中发布,但Hybrid却可以获得发布许可。有一个Hybrid框架——PhoneGaphttp://phonegap.
    com/)用于移动平台的开发

    PhoneGap现在被Adobe收购了,它为开发者提供了13个调用本地的API,采用的是JavaScriptCSS3
    HTML5Web技术。PhoneGap的理想情况是不需要本地技术,而只用Web技术就可以完成本地移动应用
    的开发。
     
    这里我们简要介绍一下PhoneGap的基本核心原理,包括内容:
    本地代码调用JavaScript
    JavaScript调用本地代码
    WebView是实现Hybrid应用的核心,也是实现本地技术和Web技术融合
    的核心。所有移动平台都有类似WebView的控件: 在iOS平台中是UIWebView
    Android平台中是WebViewWindows Phone平台中是WebBrowser
    下面我们通过一个案例来了解如何通过本地代码调用JavaScript代码以及如
    何通过JavaScript来调用本地代码。如图3-33所示,该界面是WebView。在案例启
    动的时候,WebView页面加载的时候,本地代码会调用JavaScript函数,并在
    WebView页面中输出“从iOS对象中调用JS Ok.”字符串。在WebView页面可以点
    击“调用iOS对象”按钮(这是一个HTML按钮),来调用iOS本地代码在日志输
    出一些内容。
    使用Single View Application模板,创建一个名为MyPhoneGap的工程。打开
    Interface Builder设计界面,拖曳一个WebView


    3.6  屏幕滚动控件ScrollView  
    3.6.1  ScrollView属性的设置
    它有两个子类——UITextViewUITableView它们在内容超出屏幕时提供水平或垂直滚动条。还有UICollectionView

      
    3.6.2  键盘与其他控件的协同  
    3.7  等待相关的控件与进度条  
    3.7.1  活动指示器ActivityIndicatorView  
    3.7.2  进度条ProgressView  
    3.8  警告框和操作表  
    3.8.1  警告框AlertView  
    3.8.2  操作表ActionSheet  
    3.9  工具栏和导航栏  
    3.9.1  工具栏  
    3.9.2  导航栏  
    3.10  屏幕布局  
    3.10.1  iPad、iPhone和iPhone 5屏幕布局  

    iPhone 4之前,屏幕的物理尺寸为3.5英寸,屏幕分辨率是480×320像素。iPhone 4iPhone 4S
    用视网膜屏幕技术,屏幕的物理尺寸为3.5英寸,分辨率达到了960×640像素。iPhone 5采用视网膜屏幕技术,屏幕
    物理尺寸为4英寸,分辨率是1136×640像素。

    Interface Builder设计器中,屏幕或控件的尺寸以点(point)为单位。在视网膜屏幕技术中,1个点包括了4
    个像素,而没有采用视网膜屏幕技术的还是1个点包括1个像素。因此,也可以说iPhone 5之前的手机的分辨率
    480×320点,iPhone 5的分辨率是568×320点。 为了方便设计,若无特殊说明,屏幕或控件尺寸的单位是“点”。



    iPadiPhone屏幕布局中,一般会有状态栏、工具栏、导航栏以及内容视图部分,它们的尺寸也是固定的。
    如图3-64所示,在iPhone竖屏幕中,状态栏占用20点,导航栏(或工具栏)占用44点,标签栏占用49点。实际上,
    这些尺寸在iPhone横屏幕和iPad上也保持不变。

    3.10.2  绝对布局和相对布局  
    3.10.3  使用AutoLayout布局
    AutoLayout为空间布局定义了一套约束(constraint),
    约束定义了控件与视图之间的关系。约束定义可以通过Interface Builder或代码实现,因为通过Interface Builder
    定约束相对简单直观,所以本书重点向读者推荐这种方式。

      
    3.10.4  旋转你的屏幕  
    3.11  选择器  
    3.11.1  日期选择器  

    日期选择器有4种模
    式:日期、日期时间、时间和倒计时定时器,

    3.11.2  普通选择器  
    3.11.3  数据源协议与委托协议  

    UITextField控件不同,UIPickerViewUITableView等复杂控件除了委托协议外,还有数据源协议。
    UIPickerView的委托协议是UIPickerViewDelegate,数据源是UIPickerViewDataSource
    在上一章中,我们介绍委托设计模式的时候也提到过数据源。数据源与委托一样,都是委托设计模式的具体
    实现,只不过它们的角色不同:委托对象负责控制控件外观,如选择器的宽度、选择器的行高等信息,此外,还
    负责对控件的事件和状态变化作出反应。数据源对象是控件与应用数据(模型)的桥梁,如选择器的行数、拨轮
    数等信息。委托中的方法在实现时是可选

    UIPickerViewDataSource中的方法有如下两种。
    numberOfComponentsInPickerView:。为选择器中拨轮的数目。
    pickerView:numberOfRowsInComponent:。为选择器中某个拨轮的行数


    3.12  集合视图  
    3.12.1  集合视图介绍  

    为了增强网格视图开发,iOS 6中开放了集合视图API。这种网格视图的开源代码在开源社区中很早就有,但
    是都比较麻烦,而iOS 6的集合视图API使用起来却非常方便。

    3.12.2  集合视图单元格  
    3.12.3  数据源协议与委托协议  
    3.13  小结  

    第4章 表视图  
    视图是iOS开发中使用最频繁的视图。一般情况下,我们都会选择以表的形式来展现数据,比如通讯录和
    频道列表等。在表视图中,分节、分组和索引等功能使我们所展示的数据看起来更规整、更有条理。更令人兴奋
    的是,表视图还可以利用细节展示等功能多层次地展示数据。但与其他控件相比,表视图的使用相对比较复杂

    4.1  概述  
    本节中,我们将了解表视图中的一些概念、相关类、表视图的分类、单元格的组成和样式以及表视图的两
    个协议——UITableViewDelegate委托和UITableViewDataSource数据源。

    4.1.1  表视图的组成  
    表头视图(table header view。表视图最上边的视图,用于展示表视图的信息,例如表视图刷新信息,
    如图4-2所示。
    表脚视图(table footer view
    单元格(cell。它是组成表视图每一行的单位视图。
    节(section
    节头。节的头,描述节的信息,如图4-3所示,文字左对齐。
    节脚

    4.1.2  表视图的相关类  
    表 视 图 (UITableView)继 承 自UIScrollView, 它 有 两 个 协 议 :UITableViewDelegate委 托 协 议 和
    UITableViewDataSource数据源协议。

    4.1.3  表视图分类  
    普通表视图。主要用于动态表,而动态表一般在单元格数目未知的情况下使用。
    分组表视图。一般用于静态表,用来进行界面布局,它会将表分成很多“孤岛”,这个“孤岛”由一些类
    似的单元格组成,从图4-6可以看出扁平化后的iOS 7分组表视图有很大的变化。静态表一般用于控件的界
    面布局,它是在iOS 5之后由故事板提供的。

    4.1.4  单元格的组成和样式  
    4.1.5  数据源协议与委托协议  
    4.2  简单表视图  
    4.2.1  创建简单表视图  
    4.2.2  自定义单元格  
    4.2.3  添加搜索栏  
    4.3  分节表视图  
    4.3.1  添加索引  
    4.3.2  分组与静态表  
    4.4  修改单元格  
    4.4.1  删除和插入单元格  
    表视图一旦进入删除和插入状态,单元格的左边就会出现一个“编辑控件”,如图4-48所示。这个区域会显示删
    除控件 或插入控件 ,具体显示哪个图标在表视图委托协议的tableView:editingStyleForRowAtIndexPath:
    方法中设定。

    4.4.2  移动单元格  
    4.5  表视图UI设计模式  

    4.5.1  分页模式  
    根据触发方式的不同,请求分为主动请求和被动请求。(主动请求就是在滑动到底部的时候另外再加载50条,被动请求则在底部放一个button点击之后重新加载)

    4.5.2  下拉刷新模式  
    下拉刷新是重新刷新表视图或列表,以便重新加载数据,这种模式广泛用于移动平台。

    下拉刷新与分页相反,
    当翻动屏幕到顶部时,再往下拉屏幕,程序就开始重新请求数据,此时表视图的表头部分会出现活动指示器,请求结束后表视图表头消失。
    在很多开源社区中,都有下拉刷新的实现代码供大家参考,比如Github上的githttps://github.com/leah/PullToRefresh.git

    4.5.3  iOS 7下拉刷新控件  
    随着下拉刷新模式的影响力越来越大,苹果不得不考虑把它列入自己的规范之中,并在iOS 6 API中推出了下
    拉刷新控件。图4-65所示的是iOS 6中的下拉刷新,有点像是在拉“胶皮糖”,当这个“胶皮糖”拉断时,就会出
    现活动指示器

    4.6  小结  

    第5章 视图控制器与导航模式  
    5.1  概述  
    5.1.1  视图控制器的种类  
    UIViewController。用于自定义视图控制器的导航。例如,对于两个界面的跳转,我们可以用一个
    UIViewController来控制另外两个UIViewController
    UINavigationController。导航控制器,它与UITableViewController结合使用,能够构建树形结
    构导航模式。
    UITabBarController。标签栏控制器,用于构建树标签导航模式。
    UIPageViewController。呈现电子书导航风格的控制器。
    UISplitViewController。可以把屏幕分割成几块的视图控制器,主要为iPad屏幕设计。
    UIPopoverController。呈现“气泡”风格视图的控制器,主要为iPad屏幕设计。

    5.1.2  导航模式
      
    5.1.3  模态视图  
    在导航过程中,有时候需要放弃主要任务转而做其他次要任务,然后再返回到主要任务,这个“次要任务”
    就是在“模态视图”中完成的。图5-4为模态视图示意图,该图中的主要任务是登录后进入主界面,如果用户没有
    注册,就要先去“注册”。“注册”是次要任务,当用户注册完成后,他会关闭注册视图,回到登录界面继续进行
    主任务。

    5.2  平铺导航  
    5.2.1  应用场景  
    5.2.2  基于分屏导航的实现  
    5.2.3  基于分页导航的实现  
    5.3  标签导航  
    标签导航模式是非常重要的导航模式。使用标签栏时,有一定的指导原则:标签栏位于屏幕下方,占有49
    的屏幕空间,有时可以隐藏起来;为了点击方便,标签栏中的标签不能超过5个,如果超过5个,则最后一个显示
    为“更多”,点击“更多”标签会出现更多的列表,

    5.3.1  应用场景  
    5.3.2  实现  
    5.4  树形结构导航  
    对于每一个城市,如果还想看到更加详细的信息,比如想知道长春市在百度百科上的信息网址http://
    baike.baidu.com/view/2172.htm,这种情况下吉林省长春网址就构成了一种从属关系,是一种层次模型,此时就
    可以使用树形导航模式。如果按照这样的分组在iPhone上展示这些城市信息,需要使用三级视图

    5.4.1  应用场景  
    5.4.2  实现  
    5.5  组合使用导航模式  
    有些情况下,我们会将3种导航模式综合到一起使用,其中还会用到模态视图。
    5.5.1  应用场景  
    5.5.2  实现  
    5.6  小结  

    第6章 iOS常用设计模式  
    设计模式是个很庞杂的知识体系,即便是同一设计模式在不同开发语言环境下也存
    在很大的差异,而真正能驾驭设计模式的开发者的确不多。
    软件设计模式大都来源于GoF23种设计模式。该书的设计模式都是面向对象的,在C++JavaC#领域都
    有广泛的应用。 CocoaCocoa Touch框架中的设计模式也基本上是这23种设计模式的演变,但是具体来说,Cocoa
    Cocoa Touch中的设计模式仍然存在着差异。
    6.1  单例模式
    单例模式的作用是解决“应用中只有一个实例”的一类问题。
      
    6.1.1  问题提出
    在一个iOS应用的生命周期中,有时候我们只需要某个类的一个实例。例如,iOS设备都有一个重力加速计硬
    件设备,要访问设备在x轴、y轴和z轴上的重力加速度,就必然要有一个类能够与硬件设备沟通来实时获得这些数
    据,这个类就是UIAccelerometer。除了实时地获得数据,该类还能够保持x轴、y轴和z轴的状态。但是这个类
    只需要一个实例就够了,如果有多个实例,就会占用过多的内存。
    再有,当应用程序启动时,应用的状态由UIApplication类的一个实例维护,这个实例代表了整个“应用程
    序对象”,它只能是一个实例,其作用是实现应用程序中一些共享资源的访问和状态的保持等。

      
    6.1.2  实现原理 
     单例模式一般会封装一个静态属性,并提供静态实例的创建方法,
    实现的参考代码如下:
    //
    // Singleton.h
    //
    @interface Singleton : NSObject
    + (Singleton*)sharedManager;
    @property (nonatomic ,strong) NSString* singletonData;
    @end
    //
    // Singleton.m
    //
    #import "Singleton.h"
    @implementation Singleton
    @synthesize singletonData = _singletonData;
    static Singleton *sharedManager = nil;
    + (Singleton*)sharedManager
    {
    static dispatch_once_t once;
    dispatch_once(&once, ^{
    sharedManager = [[self alloc] init];
    });
    return sharedManager;
    }
    @end
    其 中static Singleton *sharedManager为 静 态 变 量 , 类 方 法 为+ (Singleton*)sharedManager
    sharedManager方法采用了GCDGrand Central Dispatch)技术,这是一种基于C语言的多线程访问技术。在上述
    代码中, dispatch_once函数就是由 GCD提供的,它的作用是在整个应用程序生命周期中只执行一次代码块
    ^{…})。dispatch_once_tGCD提 供 的 结 构 体 , 使 用 时 需 要 将GCD地 址 传 给dispatch_once函 数 。
    dispatch_once函数能够记录该代码块是否被调用过。
    dispatch_once函数不仅意味着代码仅会被运行一次,而且还意味着此运行还是线程同步的。也就是说,
    当我们使用了 dispatch_once函数时,就不再需要使用诸如@synchronized之类的语句。


    6.1.3  应用案例  
    1.UIApplication
    2. UIAccelerometer
    3. NSUserDefaults
    4. NSNotificationCenter
    5.NSFileManager
    6.NSBundle


    6.2  委托模式 
     委托模式从GoF装饰(Decorator)模式、适配器(Adapter)模式和模板方法(Template Method)模式等演变
    而来。几乎每一个应用都会或多或少地用到委托模式。不只是Cocoa Touch框架,在Cocoa框架中,委托模式也得
    到了广泛的应用。


    6.2.1  问题提出 
        假设这一系列的处理都是在上帝类UIApplication中完成的。之所以叫“上帝类”(god class),是因为它“无
    所不能”、“包含所有”。 在面向对象的软件设计中,“上帝类”不是很友好,需要重构。在编程过程中,要尽量避
    免使用上帝类,因为上帝类是高耦合的,职责不清,难以维护。我们需要“去除上帝类”,把看似功能很强且很
    难维护的类,按照职责将它的属性或方法分派到各自的类中或分解成功能明确的类。
       幸运的是,苹果没有把UIApplication类设计成“上帝类”,而是将它们分割到两个不同的角色类中:其中
    一个扮演框架类角色,框架类具有通用、可重复使用、与具体应用无关等特点;另一个扮演应用相关类的角色,应用相关类与具体应用有关。
    由于受到框架类的控制,应用相关类常常被设计为“协议”,在Java中称为“接口”。
    开发人员需要在具体的应用中实现这个“协议”。
    UIApplication不直接依赖于AppDelegate类,而是依赖于UIApplicationDelegate协议,这在面向对象软件
    设计原则中叫做“面向接口的编程”。 AppDelegate类实现协议UIApplicationDelegate,它是委托类。
    委托是为了降低一个对象的复杂度和耦合度,使其能够更具通用性而将其中一些处理置于委托对象中的编码
    方式。通用类因为通用性(与具体应用的无关性)而变为框架类,框架类保持委托对象的指针,并在特定时刻向
    委托对象发送消息。消息可能只是通知委托对象做一些事情,也可能是对委托对象进行控制。


    6.2.2  实现原理
      
    6.2.3  应用案例  

    6.3  观察者模式 
    观察者(Observer)模式也叫发布/订阅(Publish/Subscribe)模式,是MVC( 模型视图控制器)模式的重要组成部分。

     
    6.3.1  问题提出  
    6.3.2  实现原理 
    它有4个角色,具体如下所示。
    抽象主题( Subject。在Objective-C中,抽象主题是一个协议,它是一个观察者集合容器,定义了添加
    观察者( attach)方法、移除观察者( detach)方法和为所有观察者发送通知的方法( notifyObserver)。
    抽象观察者( Observer。在Objective-C中,抽象观察者是一个协议,它有一个更新(update)方法。
    具体观察者( ConcreteObserverObserver协议的具体实现。
    具体主题( ConcreteSubjectSubject协议的具体实现。
    6.3.3  通知机制和KVO机制  
    Cocoa Touch框架中,观察者模式的具体应用有两个——通知(notification) 机制和KVOKey-Value Observing
    机制,下面简要介绍这两种机制。
    1. 通知机制
    通知机制与委托机制不同的是,前者是“一对多”的对象之间的通信,后者是“一对一”的对象之间的通信。
    2. KVO机制
    KVO不像通知机制那样通过一个通知中心通知所有观察者对象,而是在对象属性变化时通知会被直接发送给
    观察者对象。图6-19KVO机制解析图。


    6.4  MVC模式  
    MVCModel-View-Controller,模型视图控制器)模式是相当古老的设计模式之一,它最早出现在Smalltalk语言中。现在,很多计算机语言和架构都采用了MVC模式。


    6.4.1  MVC模式概述
    MVC模式是一种复合设计模式,由“观察者”(Observer)模式、“策略”(Strategy)模式和“合成”(Composite
    模式等组成。

    模型。保存应用数据的状态,回应视图对状态的查询,处理应用业务逻辑,完成应用的功能,将状态的变
    化通知视图。
    视图。为用户展示信息并提供接口。用户通过视图向控制器发出动作请求,然后再向模型发出查询状态的
    申请,而模型状态的变化会通知给视图。
    控制器。接收用户请求,根据请求更新模型。另外,控制器还会更新所选择的视图作为对用户请求的回应。
    控制器是视图和模型的媒介,可以降低视图与模型的耦合度,使视图和模型的权责更加清晰,从而提高开
    发效率。

      
    6.4.2  Cocoa Touch中的MVC模式  
    CocoaCocoa Touch框架中的MVC模式与传统的MVC模式略有不同,CocoaCocoa Touch框架中的模型与视图不能进行任何通信,所有的通信都是通    过控制器完成的.
    6.5  小结  

    第7章 iPhone与iPad应用开发的差异  
    7.1  概述
       首先,我们回顾一下iPhoneiPad的几个参数:iPhone 4的屏幕尺寸为3.5英寸,分辨率为960×640像素;iPhone
    5的屏幕尺寸为4英寸,分辨率为1136×640像素;iPad 1iPad 2的屏幕尺寸是9.7英寸,分辨率为1024×768像素。
    屏幕尺寸的不同导致了应用场景的不同,应用场景的不同直接导致了设计和开发的不同。
     
    7.1.1  应用场景差异  
             iPhone是让用户一只手使用的设备,因此它适合在等车时拿出来看看天气、收发邮件、看看周围有哪些银行或者饭店,等等。而iPad是两只
    手使用的设备,它不太适合处理iPhone用户的场景。据调查,iPad多数用在家里,用来浏览网页、收发电子邮件、
    看照片、看视频、听音乐、玩电子游戏和看电子书等。作为平板电脑,它比笔记本电脑更轻便、更适合移动使用。
    基于应用场景的不同,同样一款应用在iPhoneiPad上的功能选取和界面布局有着明显的不同。有些应用只
    能做成iPhone版本的,有些应用只能做成iPad版本的。与iPhone用户相比,iPad用户更期待具有高保真的、艺术品
    般的、高品质的应用,而绝非简单地放大iPhone

    7.1.2  设计和开发需注意的问题 
              iPhoneiPad都使用一个操作系统——iOS,因此,它们的API基本上是一样的,但有一些是iPad专用的,比
    UIPopoverController控制器和UISplitViewController控制器,其中UIPopoverController控制器用
    于呈现“漂浮”类型的视图,UISplitViewController控制器用于将屏幕分栏。这两个控制器在E-mail应用中
    都用过。
     
     
    7.1.3  构建自适应的iPhone和iPad工程  
    7.2  iPad专用API 
     
     
    7.2.1  UIPopoverController控制器  
    7.2.2  UISplitViewController控制器  
    7.2.3  模态视图专用属性  
    7.3  小结  

    第8章 iOS分层架构设计  
       设计模式只是解决某一特定问题的策略,是面向局部的;而架构设计则是一个将设计模式宏观、全面、有机地组织起来解决整个应用系统的方案。
       衡量一个软件架构设计好坏的标准是可复用性和可扩展性。可复用性和可扩展性强的软件系统能够满足用户不断变化的需求。为了使我们的软件系统具有可复用性和可扩展性,我主张采用分层架构设计。

    8.1  低耦合企业级系统架构设计 
             
    表示层。用户与系统交互的组件集合。用户通过这一层向系统提交请求或发出指令,系统通过这一层接收
    用户请求或指令,待指令消化吸收后再调用下一层,接着将调用结果展现到这一层。表示层应该是轻薄的,
    不应该具有业务逻辑。
    业务逻辑层。系统的核心业务处理层。负责接收表示层的指令和数据,待指令和数据消化吸收后,再进行
    组织业务逻辑的处理,并将结果返回给表示层。
    数据持久层。数据持久层用于访问信息系统层,即访问数据库或文件操作的代码只能放到数据持久层中,
    而不能出现在其他层中。
    信息系统层。系统的数据来源,可以是数据库、文件、遗留系统或者网络数据。

    8.2   iOS分层架构设计  
              有关信息处理的应用一般会采用分层架构设计,而游戏等应用一般不会这种采用分层架构设计。
       提示游戏开发一般都会采用引擎。事实上,游戏引擎包含了架构设计解决方案,但其架构一般不是分层的,而
    是树形结构的。



    8.2.1  基于同一工程的分层  
    如果我们要编写一个基于iOSiPhoneiPad两个平台)的MyNotes应用,它具有增加、删除和查询备忘录的
    基本功能。图8-3MyNotes应用的用例图。分层设计之后,表示层可以有iPhone版和iPad版本,而业务逻辑层、
    数据持久层和信息系统层可以公用,这样大大减少了我们的工作量。


    8.2.2  基于一个工作空间不同工程的分层  
    有时候,我们需要将某一层复用给其他的团队、公司或者个人,但由于某些原因,我们不能提供源代码,此
    时就可以将业务逻辑层和数据持久层编写成静态链接库( static librarystatically-linked library)。

    提示库是一些没有main函数的程序代码的集合。除了静态链接库,还有动态链接库,它们的区别是:静态链
    接库可以编译到你的执行代码中,应用程序可以在没有静态链接库的环境下运行;动态链接库不能编译到
    你的执行代码中,应用程序必须在有链接库文件的环境下运行。

    创建三个工程他们之间的依赖关系是:BusinessLogicLayer依赖于PersistenceLayer,PersistenceLayer依赖于BusinessLayer和persistentLayer,因此创建
    顺序应该是PersistenceLayer-->BusinessLayer-->PresentationLayer
    1. 持久层PersistenceLayer工程
    床架过程File--》new--》Project...在打开的对话框中选择FrameWork&Library-->Cocoa Touch Static Library工程模板。同时添加工程到我的工作空间
    2. 业务逻辑层BusinessLayer工程



    8.3  小结  

    第9章 iOS 7中文字排版和渲染引擎--Text Kit  
    iOS 7之前,应用中字体的大小用户是不能设置的,而且开发人员要想实现多种样式的文字排版是件非常麻
    烦的事情。在iOS 7之后,这些问题都解决了,Text Kit就是解决这些问题的钥匙。本章将向大家介绍iOS 7中文字
    排版和渲染引擎——Text Kit

    9.1  Text Kit基础
      Text Kit最主要的作用就是为程序提供文字排版和渲染的功能。通过Text Kit可以对文字进行存储、布局,以
    更加精准的排版方式来显示文本内容。Text Kit隶属于UIKit框架,其中包含了一些文字排版的相关类和协议.

    9.1.1  文字的排版和渲染  
    iOS 7之前也有一种用于文字排版和渲染的技术——Core Text,而引入Text Kit的目的并非要取代Core Text
    Core Text是面向底层的文字排版和渲染技术,如果我们需要将文本内容直接渲染到图形上下文时,从性能角度考
    虑,最佳方案就是使用Core Text。但是从易用性角度考虑,使用Text Kit是最好的选择,因为它能够直接使用UIKit
    提供的一些文本控件,例如:UITextViewUILabelUITextField,对文字进行排版。
    Text Kit具有很多优点:文本控件UITextViewUITextFieldUILabel是构建于Text Kit之上的。Text Kit完全掌
    控着文字的排版和渲染:可以调整字距、行距、文字大小,指定特定的字体,对文字进行分页或分栏,支持富文
    本编辑、自定义文字截断,支持文字的换行、折叠和着色等处理,支持凸版印刷

    9.1.2  Text Kit架构  
    9.1.3  Text Kit中的核心类  
    NSTextContainer。定义了文本可以排版的区域。默认情况下是矩形区域,如果是其他形状的区域,需
    要通过子类化NSTextContainer来创建。
    NSLayoutManager。该类负责对文字进行编辑排版处理,将存储在NSTextStorage中的数据转换为可以
    在视图控件中显示的文本内容,并把字符编码映射到对应的字形上,然后将字形排版到NSTextContainer
    定义的区域中。
    NSTextStorage。主要用来存储文本的字符和相关属性,是NSMutableAttributedString的子类(见
    9-3)。此外,当NSTextStorage中的字符或属性发生改变时,会通知NSLayoutManager,进而做到文
    本内容的显示更新。
    NSAttributedString。支持渲染不同风格的文本。
    NSMutableAttributedString。可变类型的NSAttributedString,是NSAttributedString的子类
    (见图9-3

    9.1.3  实例:凸版印刷效果  
    9.2  文字图片混合排版  
    9.3  动态字体  
    9.4  小结  

    第10章 应用程序设置  
    曾经见过我的同事将服务器的IP地址“硬编码”在程序中的尴尬事情,结果当用户服务器的IP地址发生变
    化时,程序就无法连接到服务器了,只能修改源代码,再重新编译。为了避免发生这种情况,我们需要在程序中
    添加一个能够让用户修改服务器IP的功能,这个功能叫做应用程序设置或配置。

    10.1  概述  
    10.1.1  设置  
    10.1.2  配置  
    10.2  应用程序设置包  
    10.3  设置项目种类  
    10.3.1  文本字段  
    10.3.2  开关  
    10.3.3  滑块  
    10.3.4  值列表  
    10.3.5  子界面  
    10.4  读取设置  
    10.5  小结  

    第11章 国际化  
    11.1  概述  
       国际化(Internationalization,简写为I18N)是指在设计软件时,将软件与特定语言及地区脱钩,使之能适用
    不同语言和地区的过程。与国际化相关的概念是本地化(Localization,简写为L10n),指当移植软件时,加上与
    特定区域设置有关的信息和翻译文件的过程。


    11.1.1  需要国际化的内容
    文本信息国际化。它是首先被考虑的,包括应用的名称、按钮、警告提示信息以及界面中显示的静态文
    字等。
    xib和故事板文件国际化。同一个界面和场景可以提供多个本地化版本的xib和故事板文件。
    资源文件国际化。它包括图片和音频等资源的国际化。 图片国际化包括应用图标和一般图片的国际化。资源
    文件的国际化要根据具体情况而定,否则工作量还是很大的。




      
    11.1.2  国际化目录结构  
    11.2  文本信息国际化  
    11.2.1  系统按钮和信息国际化  
    11.2.2  应用名称国际化  
    11.2.3  程序代码输出的静态文本国际化  
    11.2.4  使用genstring工具  
    11.3  xib和故事板文件国际化  
    11.3.1  使用Base国际化技术  
    11.3.2  AutoLayout与国际化  
    11.4  资源文件国际化  
    11.4.1  图片资源文件国际化  
    11.4.2  声音资源文件国际化  
    11.5  小结  

    第12章 数据持久化  
    12.1  概述 
     iOS有一套完整的数据安全体系,iOS应用程序只能访问自己的目录,这个目录称为沙箱目录,而应用程序间
    禁止数据的共享和访问。访问一些特殊的应用,如:联系人应用,必须通过特定API访问。

    12.1.1  沙箱目录 
     沙箱目录是一种数据安全策略,很多系统都采用沙箱设计,实现HTML5规范的一些浏览器也采用沙箱设计。
    沙箱目录设计的原理就是只能允许自己的应用访问目录,而不允许其他的应用访问。在Android平台中,我们通过
    Content Provider技术将数据共享给其他应用。而在iOS系统中,特有的应用(联系人等)需要特定的API才可以共
    享数据,而其他的应用之间都不能共享数据。

    下面的目录是iOS平台的沙箱目录,我们可以在模拟器下面看到,在真实设备上也是这样存储的:
    /Users/<用户>/Library/Application Support/iPhone Simulator/6.0/
    Applications/A262B02A-1975-4A7A-AB8C-C181E2CC059A
    其中A262B02A-1975-4A7A-AB8C-C181E2CC059A是应用程序ID,在安装时由系统分配。DocumentsLibrarytmp
    都是沙箱目录的子目录,其目录结构如下所示。
    ├── Documents
    │ └── NotesList.sqlite3
    ├── Library
    │ ├── Caches
    │ └── Preferences
    ├── tmp
    └── PresentationLayer.app
    下面我们分别介绍这3个子目录,它们有不同的用途、场景和访问方式。



     
    12.1.2  持久化方式  
    持久化方式就是数据存取方式。iOS支持本地存储和云端存储,本章主要介绍本地存储,主要涉及如下4种机制。
    属性列表。 集合对象可以读写到属性列表文件中。
    对象归档。 对象状态可以保存到归档文件中。
    SQLite数据库。 SQLite是一个开源嵌入式关系型数据库。
    Core Data 它是一种对象关系映射技术( ORM),本质上也是通过SQLite存储的。
    属性列表文件和对象归档一般用于存储少量数据。属性列表文件的
    访问要比对象归档的访问简单, Foundation框架集合对象都有对应的方法读写属性列表文件,而对象归档是借助
    NSData实现的,使用起来比较麻烦。SQLite数据库和Core Data一般用于有几个简单表关系的大量数据的情况。如
    果是复杂表关系而且数据量很大,应该考虑把数据放在远程云服务器中。




    12.2  属性列表  
    12.3  对象归档  
    12.4  使用SQLite数据库  
    12.4.1  SQLite数据类型  
    12.4.2  创建数据库  
    12.4.3  查询数据  
    12.4.4  修改数据  
    12.5  Core Data  
    12.5.1  ORM  
    12.5.2  Core Data堆栈  
    12.5.3  建模和生成实体  
    12.5.4  采用Core Data分层架构设计  
    12.5.5  查询数据  
    12.5.6  修改数据  
    12.6  小结  

    第13章 访问通讯录  
     移动设备上都有一个很重要的内置数据库——通讯录,苹果把它扩展到了iCloud上,使苹果设备间可以共享
    通讯录信息。在iOS上,通讯录放在SQLite3数据库中,但是应用之间不能直接访问,也就是说我们自己编写的应
    用不能采用数据持久化技术直接访问通讯录数据库。为了实现通讯录数据库的访问,苹果开放了一些专门的API
    与其他应用(如定位服务授权)不同的是,通讯录对一个应用只授权一次,即便是这个应用删除后重新安装,也不必再次授权。

    13.1  概述 
      在开发访问通讯录的应用中,我们使用了两个框架:AddressBookAddressBookUI
    AddressBook框架主要提
    供了直接访问通讯录中记录和属性等API。由于使用这些API,需要自己构建UI界面,所以它们被称为“低级API”。
    AddressBook框架中常用的类见表13-1
    13-1 AddressBook框架中常用的类
    类 名 说 明
    ABAddressBook 封装访问通讯录接口。 Core Foundation框架中对应的类型是ABAddressBookRef
    ABPerson 封装通讯录个人信息数据,是数据库的一条记录。 Core Foundation框架中对应的类型是ABPersonRef
    ABGroup 封装通讯录组信息数据,一个组包含了多个人的信息,一个人也可以隶属多个组。 Core Foundation框架中对应的类型是ABGroupRef
    ABRecord 封装了数据库的一条记录,记录由属性组成。 Core Foundation框架中对应的类型是ABRecordRef

    提示Core Foundation框架和Foundation框架紧密相关,它们为相同功能提供接口,但Foundation框架提供
    Objective-C接口,Core Foundation框架提供C接口。如果将Foundation对象和Core Foundation类型混合使用,
    则可利用两个框架之间的“无开销桥接”( toll-free bridging)。无开销桥接是说Core FoundationFoundation
    框架中的某些类型可以互相转换,如表13-1中的ABAddressBookABAddressBookRef
    AddressBookUI框架提供了4个视图控制器和4个对应的委托协议,它们已经提供UI界面,不需要我们自己构
    建,因此称为“高级API”。这4个视图控制器和对应的委托协议如表13-2所述。
    13-2 AddressBookUI框架中的视图控制器
    视图控制器                                      说 明
    ABPeoplePickerNavigationController它是从数据库中选取联系人的导航控制器,对应的委托协议为
    ABPeoplePickerNavigationControllerDelegate
    ABPersonViewController 查看并编辑单个联系人信息,对应的委托协议为
    ABPersonViewControllerDelegate
    ABNewPersonViewController 创建新联系人信息,对应的委托协议为
    ABNewPersonViewControllerDelegate
    ABUnknownPersonViewController 呈现记录部分信息,这些信息可以创建新联系人信息,或添加到已经存在的联系
    人,对应的委托协议为ABUnknownPersonViewControllerDelegate




       
     
    13.2  读取联系人信息  
    13.2.1  查询联系人记录  
    13.2.2  读取单值属性  
    13.2.3  读取多值属性  
    13.2.4  读取图片属性  
    13.3  写入联系人信息  
    13.3.1  创建联系人  
    13.3.2  修改联系人  
    13.3.3  删除联系人  
    13.4  高级API  
    13.4.1  选择联系人  
    13.4.2  显示和修改联系人  
    13.4.3  创建联系人  
    13.5  小结  

    第二部分  网  络  篇

    第14章 访问Web Service  
    14.1  概述  
    14.2  数据交换格式  
    14.2.1  XML文档结构  
    14.2.2  解析XML文档  
    ios SDK提供了两个XML框架,
    NSXML。它是基于Objective-C语言的SAX解析框架,是iOS SDK默认的XML解析框架,不支持DOM模式。
    libxml2.它是基于c语言的CML解析器,被苹果整合在iOS SDK中支持SAX和DOM模式
    此外在iOS中解析XML时,还有很多第三方框架可以采用。
    TBXML。它是基于DOM模式的解析库。与TBXML类似,只能读取XML文档,不能写XML文档。
    KissXML。它是基于DOM的解析库,基于TouchXML,主要的不同是可以写入XML文档。
    TinyXML。它是基于C++语言的COM模式解析库,他可以读XML文档,不支持XPath。
    GDataXML。它是基于DOM模式的解析库,由Google开发,可以读取XML文档,支持XPath查询。

    14.2.3  JSON文档结构  
    14.2.4  JSON数据解码
    json比较成熟,在ios上有很多框架可以进行JSON的编码/解码
    SBJson。它是比较老的JSON编码解码框架,原名是json-framework
    TouchJSON。它也是比较老的JSON编码/解码框架,支持ARC和MRC
    YAJL。它是比较优秀的JSON框架,基于SBJson进行了优化,底层api使用C编写,上层API使用Objective-C编写,使用者可以有多重不同的选择。它不支持ARC
    JSONKit。
    NextiveJson
    NSJSONSerialization
      
    14.3  REST Web Service  
    14.3.1  HTTP和HTTPS协议  
    1. HTTP协议

    2. HTTPS协议
    HTTPSHypertext Transfer Protocol Secure,即超文本传输安全协议,是超文本传输协议和SSL的组合,用以
    提供加密通信及对网络服务器身份的鉴定。
    简单地说, HTTPSHTTP的升级版,与HTTPS的区别是:HTTPS使用https://代替http://HTTPS使用端口443
    HTTP使用端口80来与TCP/IP进行通信。SSL使用40位关键字作为RC4流加密算法,这对于商业信息的加密是合
    适的。HTTPSSSL支持使用X.509数字认证,如果需要的话,用户可以确认发送者是谁。
    14.3.2  同步GET请求方法  

    14.3.3  异步GET请求方法 
     同步请求的用户体验不是很好,因此很多情况下我们会采用异步调用。iOS SDK也提供了异步请求的方法,
    而异步请求会使用NSURLConnection委托协议NSURLConnectionDelegate。在请求的不同阶段,会回调委托
    对象的方法。 NSURLConnectionDelegate协议的方法有如下几个。
    connection:didReceiveData: 。请求成功,建立连接,开始接收数据。如果数据量很多,它会被多次调用。
    connection:didFailWithError: 。加载数据出现异常。
    connectionDidFinishLoading: 。成功完成数据加载,在connection:didReceiveData方法之后执行。



    14.3.4  POST请求方式  

    14.3.5  调用REST Web Service的插入、修改和删除方法  
    14.4  使用轻量级网络请求框架MKNetworkKit  
    14.4.1  ASIHTTPRequest、AFNetworking和MKNetworkKit比较 
    14-2 ASIHTTPRequestAFNetworkingMKNetworkKit比较

     ASIHTTPRequest、AFNetworking和MKNetworkKit比较

     

    ASIHTTPRequest

    AFNetworking

    MKNetworkKit

    支持iOS和Mac OS X

    支持ARC

    断点续传

    同步异步请求

    支持同步异步

    只支持异步

    只支持异步

    图片缓存到内存

    后台下载

    下载进度

    缓存离线请求

    Cookies

    HTTPS

      

    14.4.2  安装和配置MKNetworkKit框架  
    14.4.3  网络请求  
    14.4.4  下载数据  
    14.4.5  上传数据  
    14.5  反馈网络信息改善用户体验  
    14.5.1  使用下拉刷新控件改善用户体验  
    14.5.2  使用等待指示器控件  
    14.5.3  使用网络等待指示器  
    14.6  小结  

    第15章 定位服务与地图应用
    iOS中,定位服务与地图应用是完全不同的两套API,但是它们结合很紧密,因此把它们放在一章中介绍给
    大家。
      
    15.1  定位服务 
    Wi-Fi。通过Wi-Fi路由器的地理位置信息查询,比较省电。iPhoneiPod touchiPad都可以采用这种方式
    定位。
    蜂窝式移动电话基站。通过移动运用商基站定位。只有iPhone3G版本的iPod touchiPad可以采用这种方
    式定位。
    GPS卫星。通过GPS卫星位置定位,这种方式最为准确,但是耗电量大,不能遮挡。iPhoneiPod touch
    iPad都可以采用这种方式定位。
    iBeacon微定位iOS 7支持iBeacon技术,iBeacon技术是苹果公司研发的,它使用低功耗蓝牙技术,通过
    多个iBeacon基站可以创建一个信号区域(地理围栏),当设备进入该区域时,相应的应用程序便会提示用
    户进入了这个地理围栏。


    15.1.1  定位服务编程  
    15.1.2  地理信息反编码  
    15.1.3  地理信息编码查询
                   
                      使用geohash将经纬度转成字符串。方便入库以及查询
                        http://download.cnet.com/Geohash/3000-12940_4-201071.html#full-specs  

    15.1.4  关于定位服务的测试  
    15.2  使用iOS苹果地图  
    15.2.1  显示地图  
    15.2.2  添加标注  
    15.2.3  跟踪用户位置变化  
    15.3  使用程序外地图  
    15.3.1  调用iOS苹果地图  
    15.3.2  调用谷歌Web地图  
    15.4  小结  

    第三部分  进  阶  篇

    第16章 升级?  
    16.1  从iOS 6到iOS 7的升级  
    16.1.1  iOS 7全新的扁平化设计  
    16.1.2  屏幕适配问题  
    16.1.3  iOS 7状态栏隐藏  
    16.1.4  iOS 7状态栏样式设置  
    16.2  从Xcode 4到Xcode 5的升级  
    16.2.1  ARC与MRC之争  
    16.2.2  故事板与xib之争  
    16.2.3  找回普通显示屏的模拟器  
    16.2.4  找回老版本的Interface Builder  
    16.2.5  使用资源目录管理图片  
    16.3  从iPhone 4到iPhone 5的升级  
    16.3.1  屏幕适配问题  
    16.3.2  从32位到64位的升级  
    16.4  小结  

    第17章 iOS中的商业模式  
    17.1  收费策略  
    17.1.1  iOS如何赚钱  
    产品定价
    植入广告苹果自身带有iAd广告,做得比较好的还有被谷歌收购的AdMob
    应用内购买一般有购买虚拟道具、关卡解锁、延长
    服务时间、扩充存储空间、升级用户权限、提升隐私级别等方式。现在很多应用采用免费模式,然后在产
    品中鼓励、刺激用户进行消费。



    17.1.2  避免定价策略误区  
    17.1.3  免费软件的艺术  
    17.1.4  在适当的时间、适当的地点植入广告  
    17.1.5  尝试不同的盈利模式  
    上面提到,在App Store上面发布产品有很多盈利方式,那么究竟选择哪种方式呢?免费+广告?还是免费+
    用内购买?或者是收费+应用内购买?其实,最好的方法是把这些方式都试一下,看哪个盈利模式最适合你的应
    用。不要单纯地以自己的主观想法去选择盈利方式,实践才是检验真理的唯一标准。
    要应用保持良好的收益,经常做一些分析是很有必要的。知己知彼,百战不殆。从产品自身角度、市场角度、
    客户群体、竞争对手、合作伙伴、用户价值等方面分析,不断地完善我们的应用。

    17.2  使用苹果iAd广告  
    17.2.1  横幅广告 
    iPhone 4以及之前设备和iPod touch4代以及设备横屏的情况下,横幅广告的尺寸是480×32
    iPhone 5iPod touch5代横屏的情况下,横幅广告的尺寸是568×32
    iPhoneiPod touch竖屏的情况下,横幅广告的尺寸是320×50
    iPad横屏的情况下,横幅广告的尺寸是1024×66
    iPad竖屏的情况下,横幅广告的尺寸是768×66
    提示 iPhone 5中,情况有些特殊,iPhone 5的屏幕尺寸是320×568点,而不是iPhone 4320× 480点,因此在iOS
    6iPhone 5的最低版本是iOS 6)中,横幅广告应该是自动适应宽度,在设计时要把这个问题考虑在内。

    直接从对象库中拖曳的这种方式在横屏和竖屏切换时不够灵活,在应对iPhone 5横屏情况时也不方便,因此,
    我们推荐采用编码方式动态设定ADBannerView的位置。无论采用哪一种方式,在使用iAd之前,都需要引入iAd
    框架。如图17-2
    所示,在框架库中选择iAd.framework并将其添加到当前工程中。


     
    17.2.2  插页广告 
     
    17.2.3  查看你的收入  


    17.3  使用谷歌AdMob广告  
    17.3.1  注册AdMob账号和管理应用  
    17.3.2  下载谷歌AdMob Ads SDK和示例代码  
    17.3.3  添加AdMob横幅广告  
    17.3.4  添加AdMob插页广告  
    17.3.5  为广告提交用户和位置信息  
    17.3.6  搜索广告  
    17.3.7  查看你的收入  
    17.4  应用内购买 
     应用内购买(In-App Purchase)是另外一种收费策略,很适合游戏、杂志期刊类的应用。这种收费策略有点
    像买水果时的先尝后买,游戏的前几个关卡免费,当你觉得有意思需要用到后面的关卡时则需要付费。一般购买
    内容包括内容类型、功能扩展、服务和订阅等。

    苹果在App Store上提供的应用内购买产品类型有如下3种。
    消耗型。产品购买之后即被消费,再次购买该产品时还需要支付,只能应用于当前设备。
    非消耗型。该类产品一旦购买可以一直使用,而且可以在与该用户账号关联的多个设备上使用。App Store
    会保留用户的购买记录。
    订阅型。订阅类产品在订阅周期内如同非消费型购买一样,在订阅期过后如消费型购买一样。作为开发者,
    需要确保用户订阅的内容在其iTunes同步的设备上都有效,可以在程序内部加入自己的订阅计划更新机
    制。苹果期望订阅类产品可以通过外部服务器交付。另外,订阅类产品可以在与该用户账号关联的多个设
    备上使用。订阅类产品又可以细分为:自动再生订阅类、非自动再生订阅类和免费订阅类。


    内置产品类型。需要交付的产品已经在程序内部,通常用于一些功能的锁定,这些功能原本是在程序中,但
    是需要购买这些功能才能解锁,开发人员需要记录这些购买记录,并且能够备份和恢复这些信息。它的优点
    是能很快交付产品给客户。大多数的内置产品应用为非消耗型产品。这种模式是我们本书重点介绍的模式。
    服务器产品类型。在这种模式下,需要开发商或运行商提供另外的服务器,将要交付的内容、服务和订阅
    的产品更新到服务器上。应用程序与服务器和App Store交互获取信息。这种方式非常灵活,但是投入比较
    大,适合于订阅、内容和服务类产品。




    17.4.1  概述  
    17.4.2  测试环境搭建
      
    17.4.3  在程序中实现应用内购买  
    17.4.4  测试应用内购买  
    17.5  小结  

    第18章 找出程序中的bug--调试  
    18.1  Xcode调试工具  
    18.1.1  定位编译错误  
    18.1.2  查看和显示日志  
    18.1.3  设置和查看断点  
    18.1.4  调试工具栏  
    18.1.5  输出窗口  
    18.1.6  变量查看窗口  
    18.1.7  查看线程  
    18.2  日志与断言输出  
    18.2.1  使用NSLog函数  
    18.2.2  使用NSAssert宏  
    18.2.3  移除NSLog和NSAssert  
    18.3  LLDB调试工具  
    18.3.1  断点命令  
    18.3.2  观察点命令  
    18.3.3  查看变量和计算表达式命令  
    18.4  异常堆栈报告分析  
    18.4.1  跟踪异常堆栈  
    18.4.2  分析堆栈报告  
    18.5  在iOS设备上调试  
    18.5.1  创建开发者证书  
    18.5.2  设备注册  
    18.5.3  创建App ID  
    18.5.4  创建配置概要文件  
    18.5.5  设备调试  
    18.6  Xcode设备管理工具  
    18.6.1  管理设备配置概要文件  
    18.6.2  查看设备上的应用程序  
    18.6.3  设备控制台  
    18.6.4  设备日志  
    18.7  小结  

    第19章 测试驱动下的iOS应用开发  
    19.1  测试驱动的软件开发概述  
    19.1.1  测试驱动的软件开发流程  
    19.1.2  测试驱动的软件开发案例  
    19.1.3  iOS 7单元测试框架  
    19.2  使用XCTest测试框架  
    19.2.1  添加XCTest到工程  
    19.2.2  编写XCTest测试方法  
    19.2.3  运行测试程序  
    19.2.4  分析测试报告  
    19.3  iOS单元测试最佳实践  
    19.3.1  测试数据持久层  
    19.3.2  测试业务逻辑层  
    19.3.3  测试表示层  
    19.4  小结  

    第20章 让你的程序“飞”起来--性能优化  
    20.1  内存优化 
     分别是MRRManual Retain Release,手动保持释放)、ARCAutomaticReference Counting,自动引用计数)和GCGarbage Collection,垃圾收集)
    MRR。也称为MRCManual Reference Counting,手动引用计数),就是由程序员自己负责管理对象生命
    周期,负责对象的创建和销毁。
    ARC。采用与MRR一样的内存引用计数管理方法,但不同的是,它在编译时会在合适的位置插入对象内
    存释放(如releaseautoreleaseretain等),程序员不用关心对象释放的问题。苹果推荐在新项目
    中使用ARC,但在iOS 5之前的系统中不能采用ARC
    GC GC技术不能应用于iOS开发,只能应用于Mac OS X开发。
    20.1.1  内存泄漏问题的解决  
    内存泄漏指当一个对象或变量在使用完成后没有释放掉,这个对象一直占用着内存,直到应用停止。
    Xcode提 供 了 两 种 工 具 帮助 查 找 泄 漏点 :AnalyzeInstruments
    Analyze 是 静 态分 析 工 具 。可 以 通 过
    Product→Analyze菜单项启动,图20-1所示为静态分析之后的代码界面。Instruments是动态分析工具,它与Xcode
    集成在一起,可以在Xcode中通过Product→Profile菜单项启动。如图20-2所示,Instruments有很多跟踪模板可以动
    态分析和跟踪内存、 CPU和文件系统。

    20-7 InstrumentsLeaks模板
    20-8 Instruments检测到的内存泄漏
    20-9查看泄漏的详细信息
    20-10查看泄漏点
    20-12 InstrumentsZombies模板



    20.1.2  查找和解决僵尸对象  
    存泄漏是指一个对象或变量在使用完成后没有释放掉。如果我们走向了另外一个极端,会是什么样的呢?
    这就导致了过度释放问题,从而使对象“僵尸化”,该对象被称为僵尸对象。如果一个对象已经被释放过了,或
    者调用者没有这个对象的所有权却释放了它,就会造成过度释放,产生僵尸对象。
    对于很多人来说,僵尸对象或许听起来很恐怖,也很陌生,但是如果说起EXEC_BAD_ACCESS异常,可能大
    家并不陌生。

    20.1.3  autorelease的使用问题  
    20.1.4  响应内存警告 
    好的应用应该在系统内存警告的情况下释放一些可以重新创建的资源。在iOS中,我们可以在应用程序委托
    对象、视图控制器以及其他类中获得系统内存警告消息。
     
    20.1.5  选择xib还是故事板  
    故事板是苹果在iOS 5之后推出的技术,本意是集成多个xib文件于一个故事板文件,管理起来方便。故事板
    还能反应控制器之间的导航关系,很多导航只需要连线就可以了,不需写代码,使用起来很方便。但是我告诫读
    者,从内存占用角度来看,故事板不是一个好的技术。

    事实上,xib仍然是比较好的技术,只不过不能表达界面之间的导航关系,界面导航要手工编写代码。



    20.2  优化资源文件  
    20.2.1  图片文件优化  
    图片文件优化包括文件格式和文件大小的优化。在移动设备中,支持的图片格式主要是PNGGIFJPEG
    式,苹果推荐使用PNG格式。在Xcode中,集成了第三方PNG优化工具pngcrush,它可以在编译的时候对PNG
    式文件进行优化和压缩,而我们只需要设定如图20-19所示的编译参数Compress PNG FilesYES就可以了。


    综上所述,如果在本地资源情况下,我们应该优先使用PNG格式文件;如果资源来源于网络,最好采用JPEG
    格式文件。
    另外,图片是一种很特殊的资源文件。创建UIImage对象时,可以使用类级构造方法+ imageNamed: 和实例
    构造方法-initWithContentsOfFile: + imageNamed: 方法会在内存中建立缓存,这些缓存直到应用停止才

    清除。如果是贯穿整个应用的图片(如图标、logo等),推荐使用+ imageNamed:创建;如果是仅使用一次的图
    片,推荐使用下面的语句:
    NSString *path = [[NSBundle mainBundle] pathForResource:@"animal-2" ofType:@"png"];
    UIImage *image = [[UIImage alloc] initWithContentsOfFile:path];
    ……
    [image release]; //MRR情况下调用



    20.2.2  音频文件优化  
    WAV文件WAV是一种由微软和IBM联合开发的用于音频数字存储的文件格式。WAV文件的格式灵活,
    可以存储多种类型的音频数据。由于文件较大,不太适合于移动设备这些存储容量小的设备。
    MP3MPEG Audio Layer 3)文件MP3利用MPEG Audio Layer 3技术,将数据以110 甚至 112 的压
    缩率压缩成容量较小的文件。MP3是一种有损压缩格式,它尽可能地去掉人耳无法感觉的部分和不敏感的
    部分。这么高的压缩比率非常适合移动设备等存储容量小的设备,现在非常流行。
    CAFFCore Audio File Format)文件CAFF是苹果开发的专门用于Mac OS XiOS系统的无压缩音频
    格式,它被设计用来替换老的WAV格式。
    AIFFAudio Interchange File Format)文件AIFF是苹果开发的专门用于Mac OS X系统的专业的音频文
    件格式。AIFF的压缩格式是AIFF-C(或AIFC),将数据以41 压缩率进行压缩,应用于Mac OS X


    20.3  延迟加载 
    延迟加载(lazy load)指一些对象不是在应用和视图等初始化时创建,而是在用到它的时候创建。当应用中
    有一些对象并不经常使用时,延迟加载可以提高程序性能。
     
    20.3.1  资源文件的延迟加载  
    20.3.2  故事板和xib文件的延迟加载  
    20.4  数据持久化的优化  
    20.4.1  使用文件  
    20.4.2  使用SQLite数据库  
    20.4.3  使用Core Data  
    20.5  可重用对象的使用  
    20.5.1  表视图中的可重用对象  
    20.5.2  集合视图中的可重用对象  
    20.5.3  地图视图中的可重用对象  
    20.6  并发处理与多核CPU  
    20.6.1  主线程阻塞问题  
    20.6.2  选择NSThread还是GCD  
    20.7  编译参数  
    20.8  小结  

    第21章 管理好你的程序代码--代码版本控制  
    21.1  概述  
    21.1.1  版本控制历史 
    集中管理模式。这以一个服务器作为代码库,团队人员本地没有代码库只能与服务器进行交互。这种类型
    的版本控制工具有VSSVisual Source Safe,微软开发的Microsoft Visual Studio套件中的软件之一)、CVS
    Concurrent Versions System,并发版本系统)、SVNSubversion)等,其中SVN是目前这种模式的佼佼者。
    分布式管理模式。这是更为先进的模式,不仅有一个中心代码库,而且每位团队人员本地也都有代码库,
    在不能上网的情况下也可以提交代码。该类型的版本控制工具有GitMercurialBazzarDarcs
     
    21.1.2  基本概念
    代码库(repository。存放项目代码以及历史备份的地方。
    分支( branch。为了验证和实验一些想法、版本发布、缺陷修改等需要,建立一个开发主干之外的分支,
    这个分支被隔离在各自的开发线上。当改变一个分支中的文件时,这些更改不会出现在开发主干和其他分
    支中。
    合并分支( merging branch。完成某分支工作后,将该分支上的工作成果合并到主分支上。
    签出( check out。从代码库获得文件或目录,将其作为副本保存在工作目录下,此副本包含了指定代码
    库的最新版本。
    提交( commit。将工作目录中修改的文件或目录作为新版本复制回代码库。
    冲突( conflict。有时候提交文件或目录时可能会遇到冲突,当两个或多个开发人员更改文件中的一些相
    同行时,将发生冲突。
    解决( resolution。遇到冲突时,需要人为干预解决,这必须通过手动编辑该文件进行处理,必须有人逐
    行检查该文件,以接受一组更改并删除另一组更改。除非冲突解决,否则存在冲突的文件无法成功提交到
    代码库中。
    索引( indexGit工具特有的概念。在修改的文件提交到代码库之前被做出一个快照,这个快照被称为
    “索引”,它一般会暂时存储在一个临时存储区域中。


      
    21.2  Git代码版本控制  
    21.2.1  服务器搭建  
    21.2.2  Gitolite服务器管理  
    21.2.3  Git常用命令  
    21.2.4  Git分支  
    21.2.5  Git协同开发  
    21.2.6  Xcode 5中Git的配置与使用  
    21.3  GitHub代码托管服务  
    21.3.1  创建和配置GitHub账号  
    21.3.2  创建代码库  
    21.3.3  派生代码库  
    21.3.4  使用GitHub协同开发  
    21.3.5  管理组织  
    21.4  小结  

    第22章 把你的应用放到App Store上  
    22.1  收官  
    22.1.1  在Xcode 5下添加图标 
    iOS应用图标(App Icon)分为设备上使用的图标(见图22-1)和App Store上使用的图标(见图22-2),区别只是尺寸不同。
    此外,iOS上使用的图标还有Spotlight搜索图标、设置图标、工具栏(或导航栏)图标、标签栏图标。


     
    22.1.2  Xcode 5添加启动界面 
     启动界面是应用启动与进入到第一个屏幕之间的界面。如果应用没有启动界面,那么在进入第一个屏幕之
    前就是黑屏,这会影响用户体验。虽然这在开发阶段没有什么影响,但是在应用发布前,还是需要添加启动
    界面的。



    22.1.3  调整Identity和Deployment Info属性 
     在编程过程中,有些产品的属性并不影响开发,即便属性设置不正确,一般也不会有什么影响。但是在产品
    发布时,正确地设置这些属性就很重要了。如果设置不正确,就会影响产品的发布。这些产品属性主要是TARGETS
    中的 IdentityDeployment Info属性,如图22-11所示。


    22.1.4  为发布进行编译  
    从编写到发布应用会经历3个阶段:在模拟器上运行调试、在设备上运行调试和发布编译。为了防止非法设
    备和非开发人员调试和发布应用,苹果使用了配置概要文件控制在设备上运行调试和发布编译阶段。配置概要文
    件分为两种——开发配置概要文件和发布配置概要文件,它们的创建非常相似(具体可参考18.5.4节)。完整的编
    译发布流程如图22-15所示,其中创建开发者证书和创建App ID与调试阶段没有区别,具体请参考18.5节,下面介
    绍最后的两个步骤。


    22.1.5  应用打包
      在把应用上传到App Store之前,我们需要把编译的二进制文件和资源文件打成压缩包,压缩格式是ZIP。首
    先找到编译到什么地方,这个很重要但不太好找,我们可以看看图 22-26所示的编译日志,找到其中的Create
    universal binary HelloWorld…的内容并展开,具体如下:

    22.2  发布流程 
     程序打包后,就可以发布我们的应用了。发布应用在iTunes Connect中完成,发布完成后等待审核,审核通过
    后就可以到App Store上销售了。详细的发布流程如图
    22-30所示。

    其中第A步、第B步、第C步和第D步主要在iOS开发中心的配置门户网站中完成,本章前面已经介绍过了。下面
    我们介绍其他几个流程,其中主要的流程是在iTunes Connect中完成的,而上传应用要使用Application Loader工具实现。



    22.2.1  创建应用及基本信息  
    通过网址https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa,打开iTunes Connect登录页面,使用苹
    果开发账号登录,登录成功后的iTunes Connect页面如图22-31所示。
    点击Manage Your Applications图标,进入应用管理页面,如图22-32所示,在这里可以管理我们审核中的、未
    通过的以及已经上线的所有应用。

    22.2.2  应用定价信息 
     
    22.2.3  最后的信息输入
      1.版本信息
    版本信息输入页面如图22-35所示。Version Number是应用的版本号,它必须与图22-11所示应用Target属性中
    Version(应用版本号)一致,否则上传应用会失败。
    2.元数据
    元数据输入页面如图22-36所示。Description是应用描述信息,这段描述对应用很重要,将出现在App Store
    应用介绍中。用户购买应用时,主要通过这段文字来了解我们的应用到底是做什么的,有什么用。因此,要认真、
    用心地准备这段文字,描述清楚应用的所有功能,体现出应用的特点、特色等,从而吸引用户来购买。

    3.应用审核信息
    应用审核信息输入页面如图 22-37所示,这里的信息主要是给苹果审核团队的工作人员看的。在Contact
    Information中填写开发者团队中负责与苹果审核小组联系的人员的信息,包括姓名、邮箱和电话号码。
    4.最终用户许可协议
    最终用户许可协议输入页面如图22-38所示。最终用户许可协议只有用户同意后才能下载我们的应用。如果没
    有特别的,建议不要添加。

    5.上传应用图标和截图
    上传应用图标和截图填写页面如图22-39所示,这里可以上传应用的一些图片,包括应用图标(在App Store
    上使用的图标)、 iPhoneiPod touch截图、iPhone 5和第5iPod touch截图以及iPad的一些截图等。这里要注意所
    有图片尺寸的要求、格式要求以及DPI要求。随着系统升级,苹果要求的内容也一直在变化,详细内容可以参考
    苹果说明

     

    22.2.4  上传应用  


    22.3  审核不通过的常见原因  
    App Store的审核是出了名地严格,相信大家也都略有耳闻。苹果官方提供了一份详细的审核指南,包括22
    项、 100多小项的拒绝上线条款,并且条款在不断增加中。此外,还包含一些模棱两可的条例,所以稍有“闪失”,
    应用就有可能被拒绝。但是有一点比较好,那就是每次遭到拒绝时,苹果会给出拒绝的理由,并指出你违反了审
    核指南的哪一条,开发者可以根据评审小组给的回复修改应用重新提交。下面我们讨论一下被拒绝的常见原因。
    1.功能问题
    在发布应用之前,我们一定要认真测试,如果在审核中出现了程序崩溃或者程序错误,无疑这是会被审核小
    组拒绝的。如果我们想发布一个演示版的程序,通过它给客户演示,这也是不会通过的。应用的功能与描述不相
    符,或者应用中含有欺诈虚假的功能,应用都会被拒绝。比如在应用中有某个按钮,但是点击这个按钮时没有反
    应或者不能点击,这样的程序也是不会通过的。
    苹果不允许访问私有API,有浏览器的网络程序必须使用iOS WebKit框架和WebKit JavaScript。还有几点比较
    头痛的规则,那就是如果你的App没有什么显著的功能或者没有长久的娱乐价值,也会被拒绝。如果你的应用在
    市场中已经存在了,在相关产品比较多的时候也可能被拒绝。
    2.用户界面问题
    苹果审核指南规定开发者的应用必须遵守苹果《 iOS用户界面指导原则》中解释的所有条款和条件,如果违
    反了这些设计原则,就会被拒绝上线,所以开发者在设计和开发产品之前一定要认真阅读《 iOS用户界面指导原
    则》。这些原则中也渗透着苹果产品的一些理念,阅读它们不仅是为了避免程序被拒绝,而且还为了让开发者设
    计出更好的App。苹果不允许开发者更改自身按键的功能(包括声音按键以及静音按键),如果开发者使用了这些
    按键并利用它们做一些别的功能,将会被拒绝。
    3.商业问题
    要发布的应用,首先不能侵犯苹果公司的商标及版权。简单地说,在应用中不能出现苹果的图标,不能使用
    苹果公司现在产品的类似名字为应用命名,涉及iPhoneiPadiTunes等相关或者相近的名字都是不可以的。苹果
    认为这会误导用户,认为该应用是来自苹果公司的产品。误导用户认为该应用是受到苹果公司的肯定与认可的,
    也是不行的。
       私自使用受保护的第三方材料(商标、版权、商业机密和其他私有内容),需要提供版权认可。如果应用涉

    及第三方版权的信息,开发者就要仔细考虑考虑了。有些开发者的版权法律意识比较淡薄,总会忽视这一点,然
    而这一点是非常致命的。苹果对于这种被起诉的侵权应用,最轻的处罚是下架应用,有时需要将开发者账户里的
    钱转到起诉者账户。严重的就是,起诉者将你告上法庭,除了自己账户中的钱被扣除外,还要另赔付起诉者相关
    费用。
    4.不当内容
    一些不合适、不和谐的内容,苹果当然不会允许上架的。比如具有诽谤、人身攻击的应用,含有暴力倾向的
    应用,低俗、令人反感、厌恶的应用,赤裸裸的色情应用等。含有赌博性质的应用必须并且必须明确表示苹果不
    是发起者,也没有以任何方式参与活动。
    5.其他问题
    关于宗教、文化或种族群体的应用或评论包含诽谤性、攻击性或自私性内容的应用不会被通过,使用第三方
    支付的应用会被拒绝,模仿iPod界面的应用将会被拒绝,怂恿用户造成设备损坏的应用会被拒绝。这里有个小故
    事,有一款应用,功能是比比谁将设备扔得高,最后算积分,这个应用始终没能上架,因为在测试应用的时候就
    摔坏了两部手机。此外,未获得用户同意便向用户发送推送通知,要求用户共享个人信息的应用都会被拒绝。


    22.4  小结  

    第四部分  实  战  篇

    第23章 重构MyNotes应用--iOS网络通信中的设计模式与架构设计  
    23.1  移动网络通信应用的分层架构设计  
    23.2  基于委托模式实现  
    23.2.1  网络通信与委托模式  
    23.2.2  在异步网络通信中使用委托模式实现分层架构设计  
    23.2.3  类图  
    23.2.4  时序图  
    23.2.5  数据持久层重构  
    23.2.6  业务逻辑层的代码实现  
    23.2.7  表示层的代码实现  
    23.3  基于观察者模式的通知机制实现  
    23.3.1  观察者模式的通知机制回顾  
    23.3.2  异步网络通信中通知机制的分层架构设计  
    23.3.3  类图  
    23.3.4  时序图  
    23.3.5  数据持久层的重构  
    23.3.6  业务逻辑层的代码实现  
    23.3.7  表示层的代码实现  
    23.4  小结  

    第24章 iOS敏捷开发项目实战--2016里约热内卢奥运会应用开发及App Store发布  
    24.1  应用分析与设计  
    24.1.1  应用概述  
    24.1.2  需求分析  
    24.1.3  原型设计  
    24.1.4  数据库设计  
    24.1.5  架构设计  
    24.2  iOS敏捷开发  
    24.2.1  敏捷开发宣言  
    24.2.2  iOS适合敏捷开发吗  
    24.2.3  iOS敏捷开发最佳实践  
    24.3  任务1:创建应用基本工作空间  
    24.4  任务2:信息系统层与持久层开发  
    24.4.1  迭代2.1:编写数据库DDL脚本  
    24.4.2  迭代2.2:插入初始数据到数据库  
    24.4.3  迭代2.3:编写实体类  
    24.4.4  迭代2.4:DAO类XCTest单元测试  
    24.4.5  迭代2.5:编写DAO类  
    24.4.6  迭代2.6:发布到GitHub  
    24.5  任务3:业务逻辑层开发  
    24.5.1  迭代3.1:比赛项目业务逻辑类XCTest单元测试  
    24.5.2  迭代3.2:编写比赛项目业务逻辑类  
    24.5.3  迭代3.3:比赛日程业务逻辑类XCTest单元测试  
    24.5.4  迭代3.4:编写比赛日程业务逻辑类  
    24.5.5  迭代3.5:发布到GitHub  
    24.6  任务4:表示层开发  
    24.6.1  迭代4.1:使用资源目录管理图片和图标资源  
    24.6.2  迭代4.3:根据原型设计初步设计iPad故事板  
    24.6.3  迭代4.3:根据原型设计初步设计iPhone故事板  
    24.6.4  迭代4.4:首页模块  
    24.6.5  迭代4.5:比赛项目模块  
    24.6.6  迭代4.6:比赛日程模块  
    24.6.7  迭代4.7:倒计时模块表示层  
    24.6.8  迭代4.8:关于我们模块表示层  
    24.6.9  迭代4.9:发布到GitHub  
    24.7  任务5:收工  
    24.7.1  迭代5.1:添加图标  
    24.7.2  迭代5.2:设计和添加启动界面  
    24.7.3  迭代5.3:植入谷歌AdMob横幅广告  
    24.7.4  迭代5.4:性能测试与改善  
    24.7.5  迭代5.5:发布到GitHub  
    24.7.6  迭代5.6:在App Store上发布应用  
    24.8  小结

    
    
    
    
    展开全文
  • 《Boost程序完全开发指南》

    千次阅读 2016-01-27 20:23:42
    1.3开发环境 由于Boost大量使用了C++高级特性(如模板偏特化、ADL),因此不是所有的编译器都 能够很好地支持Boost。 ...在VC集成环境中使用嵌入工程编译的方式需要定义宏BOOST_ALL_NO_LIB或者 ...
    1.3开发环境
    由于Boost大量使用了C++高级特性(如模板偏特化、ADL),因此不是所有的编译器都
    能够很好地支持Boost。


    在VC集成环境中使用嵌入工程编译的方式需要定义宏BOOST_ALL_NO_LIB或者
    BOOST_XXX_NO_LIB(XXX是某个库的名称),以指示BOOST库不要使用自动链接功能。


    如果在debug版工程,不要忘记在Preprocessor页中定义宏"_STLP_DEBUG"和"
    __STL_DEBUG"以使得STLport。


    第2章 时间与日期
    2.6处理日期
    date_time库的日期基于格里高利历,支持从1400-01-01到9999-12-31之间的日期计算,
    它不能处理公元前的日期。


    boost.smart_ptr库是对C++98标准的一个绝佳补充。它提供了六种智能指针,包括
    scoped_ptr、scoped_array、shared_ptr、shared_array、weak_ptr和intrusive_ptr,
    从各文件来增强std::auto_ptr,而且是异常安全的。库中的两个类---shared_ptr和'
    weak_ptr已被收入到C++新标准的TR1库中。


    它们对于所指的类型T仅有一个很小且很合理的要求:类型T的析构函数不能抛出异常。


    这些智能指针都位于名字空间boost,为了使用smart_ptr组件,需要包含头文件
    <boost/smart_ptr.hpp>。


    根据C++标准,对0(空指针)执行delete操作是安全的。


    scoped_ptr的成员get(),它返回scoped_ptr内部保存的原始指针,可以在某些要求必
    须是原始指针的场景(如底层的C接口)。但要记住,我们不能对这个指针做delete
    并且没有置0的操作,否则scoped_ptr析构时会对已经删除的指针再进行操作。


    不能对智能指针对象使用delete操作,因为智能指针是一个行为类似指针的对象,而不
    是指针,对一个对象应用delete是不允许的。


    scoped_ptr与auto_ptr一样,不能用作容器的元素,但原因不同,auto_ptr是因为它的
    转移语义,而scoped_ptr则是因为不支持拷贝和赋值,不符合容器对元素类型的要求。


    scoped_ptr与auto_ptr一根本性区别在于指针的所有权。auto_ptr特意被设计为指针的
    所有权是可转移的,可以在函数之间传递,同一时刻只能有一个auto_ptr管理指针。
    而scoped_ptr把拷贝构造函数和赋值函数都声明为私有的,拒绝了指针所有权的转让--
    除了scoped_ptr自己,其他任何人都无权访问被管理的指针,从而保证了指针的绝对
    安全。


    scoped_array弥补了标准库中没有指向数组的智能指针的缺憾。它包装了new[]操作符。
    但不推荐使用scoped_array,而推荐使用vector。


    3.4 shared_ptr
    shared_ptr是最重要的智能指针。它使用引用计数。它可以安全地放到标准容器中。弥补
    了auto_ptr因为转移语义而不能把指针作为STL容器元素的缺陷。
    shared_ptr的reset()函数的行为与scoped_ptr也不尽相同,它的作用是将引用计数减1
    停止对指针的共享,除非引用计数为0,否则不会发生删除操作。带参数的reset()则
    类似相同形式的构造函数,原指针引用计数减1的同时改为管理另一个指针。


    unique()在shared_ptr是指针的唯一所有者时返回true。use_count()返回当前指针的
    引用计数,但use_count()要小心使用,它应该仅仅用于测试或者调试,它不提供高效率
    的操作,而且有时候可能不可用的(极少数情形)。而unique()则是可靠的,任何时候
    都可用,而且比use_count() == 1速度更快。


    shared_ptr还支持相等或不相等,以及operator<比较大小,且都是基于内部保存的指
    针。这使用得shared_ptr可以被用于标准关联容器(set和map)。shared_ptr很好地消除
    了显式的delete调用。


    shared_ptr在头文件<boost/make_shared.hpp>中提供了一个自由工厂函数
    make_shared<T>(),来消除显式的new调用,它的名字提供了标准库的make_pair(),
    声明如下:
    template<class T, class... Args>
    shared_ptr<T> make_shared(Args &&... args);


    shared_ptr<string> sp = make_shared<string>("make_shared");
    shared_ptr<vector<int> > spv = make_shared<vector<int> >(10, 2);


    3.4.5 应用于标准容器
    有两种方式可以将shared_ptr应用于标准容器(或者容器适配器等其他容器)。
    一种用法是将容器作为shared_ptr管理的对象,如shared_ptr<list<T> >,使用容器可
    以被安全地共享,用法与普通shared_ptr没有区别。
    另一种用法是将shared_ptr作为容器的元素,如vector<shared_ptr<T> >。


    记住标准容器不能容纳auto_ptr和scoped_ptr等


    3.4.6 应用于桥接模式
    桥接模式bridge是一种结构型设计模式,它把类的具体实现细节对用户隐藏起一类。在
    具体编程实践中桥接模式也被称为pimpl或者handle/body惯用法。它可以将头文件的
    依赖关系降到最小,减少编译时间,而助可以不使用虚函数实现多态。它可以任意改变
    具体的实现而外界对此一无所知,也减少了源文件之间的编译依赖。


    3.4.8 定制删除器
    shared_ptr(Y* p, D d),它涉及shared_ptr的另一个重要概念:删除器。用d来删除,
    即把delete p换成d(p)。删除器d可以是函数对象或函数指针,使用得d(p)成立。


    shared_ptr提供一个自由函数get_deleter(shared_ptr<T> const&p),它能够返回删除
    器的指针。
    有了删除器的概念,我们就可以用shared_ptr实现管理任意资源。只要这种资源提供了
    它自己的释放操作,shared_ptr就能够保证自动释放。


    3.9 pool
    pool只能作为普通数据类型如int、double等的内存池,不能应用于复杂的类和对象,因
    为它只分配内存,不调用构造函数,这个时候,我们需要使用object_pool。




    第4章 实用工具
    4.1 noncopyable
    4.2 typeof
    4.3 optional
    optional库使用“容器”语义,包装了“可能产生无效值”的对象,实现了“未初始化”
    的概念。
    optional很像一个仅能存放一个元素的容器,它实现了“未初始化”的概念:如果元素
    未初始化,那么容器就是空的,否则,容器内就是有效的、已初始化的值。
    optional的比较是深比较。


    4.3.5 工厂函数
    optional提供一个类似make_pair()、make_shared()的工厂函数make_optional()。
    可以根据参数类型自动推导optional的类型,用来辅助创建optional对象。


    optional<T>要求类型T具有拷贝语义,因为它内部会保存值的拷贝。


    4.4 assign
    STL容器仅提供了容纳这些数据的方法,但填充的步骤却是相当地麻烦,必须重复调用
    insert()或push_back()等成员函数,这正是boost.assign出现的理由。


    assign库重载了赋值操作符operator+=、逗号操作符operator,和括号操作符operator()


    使用assign库时必须使用using指示符,只有这样才能让重载的+=,等操作符在作用域内
    生效,如:
    #include <boost/assigh.hpp>
    int main()
    {
    using namespace boost::assign; //很重要,启用assign库的功能
    vector<int> v;
    v += 1,2,3,4,6*6;


    set<string> s;
    s += "cpp", "java", "python";


    map<int, string> m;
    m += make_pair(1, "one"), make_pair(2, "two");
    }


    +=操作符后可以接若干个可被容器容纳的元素,元素之间使用逗号分隔。元素不一定是
    常量,表达式或者函数调用也是可以接受的,只要其结果能够转换成容器可容纳的类型。
    比较特别的是map容器,必须使用make_pair()辅助函数来生成容器元素。


    operator+=很好用,但有一点遗憾,它仅限应用于STL中定义的标准容器,vector,list,
    set等,对于其他类型的容器(如boost新容器)则无能为力。


    4.4.2 使用操作符()向容器增加元素
    assign库还提供了三个辅助函数insert()、push_front()、push_back()。这些函数可
    作用于拥有同名成员函数的容器,接受容器变量作为参数,返回一个代理对象
    list_inserter。如
    vector<int> v;
    push_back(v)(1)(2)(3);
    map<int, string> m;
    insert(m)(1, "one")(2, "two"); // 对于set和map,只能使用assign::insert(),如果
    括号中没有参数,则将调用容器元素的缺省构造函数填入一个缺省值,逗号操作符则不
    能这样做。


    括号操作符也可以与逗号等操作符配合使用,写法更简单,有时甚至看起来不像是合法
    的C++代码,例如:
    using namespace boost::assign;
    vector<int> v;
    push_back(v), 1, 2, 3, 4;
    push_back(v)(6), 7, 64/8, (9), 10;
    //v = [6,7,8,9,10];
    set<string> d;
    insert(d)() = "cpp", "java", "python";
    //d = ["", "cpp", "java", "python"]


    4.4.3 初始化容器元素
    assign库使用list_of()、map_list_of()/pair_list_of()和tuple_list_of()三个函数
    解决了在容器构造的时候就完成数据的填充。


    4.4.4 减少重复输入
    assign库提供repeat()、repeat_fun()和range()三个函数来减轻工作量。


    vector<int> v = list_of(1).repeat(3,2)(3)(4)(5);
    //v = 1,2,2,2,3,4,5;
    multiset<int> ms;
    insert(ms).repeat_fun(5, &read).repeat(2,1),10;
    //ms = x,x,x,x,x,1,1,10;
    deque<int> d;
    push_front(d).range(v.begin(), v.begin()+5);
    //d = 3,2,2,2,1


    4.4.5 与非标准容器工作
    assign库不仅支持全部八个STL标准容器vector,string, deque,list, set, multiset,
    map, multimap,也对STL中的容器适配器提供了适当的支持,包括stack,queue和
    priority_queue.
    因为stackt等容器适配器不符合容器的定义,没有insert、push_back等成员函数,所
    以不能使用赋值的方式填入元素,只能使用初始化的方式。并在list_of表达式最后
    使用to_adapter()成员函数来适配到非标准容器。如果使用逗号操作符还需要把整个
    表达式用括号括起来,才能使用点号调用to_adapter()。
    queue<string> q = (list_of("china")("us")("uk")).repeat(2, "russia").
    to_adapter();
    assign库也支持部分不在STL中定义的非标准容器,如STLport中的slist和hash_set/
    hash_map,因为它们符合容器的定义,故用法与标准容器没有什么区别。


    此外,assign库还支持大部分Boost库容器,如array, circular_buffer,unordered等。


    4.4.6 高级用法
    list_of()嵌套使用
    list_of()可以就地创建匿名列表,这一点很有用,它可以嵌套在assign库用法中。
    例如,下面的代码使用vector构造了了个二维数组,使用list_of(list_of())的嵌套
    形式来初始化:
    vector<vector<int> > v = list_of(list_of(1)(2))(list_of(3)(4));


    v += list_of(5)(6), list_of(7)(8);


    4.5 swap
    boost::swap是对标准库提供的std::swap的增强和泛化。
    为交换两个变量(可以是int等内置数据类型,或者是类实例、容器)的值提供了便捷
    的方法。


    std::swap()要求交换的对象必须是可拷贝构造和可拷贝赋值的,即重载operator=。但
    这种交换是最通用但也是效率最低的方法。


    解决方案有两种:一是直接利用函数重载; 二是使用ADL(argument dependent lookup,
    参数依赖查找)查找模板特化的swap。这两种方案就是boost::swap的工作原理。


    它查找有无针对类型T的std::swap()的特化swap()或者通过ADL查找模板特化的swap(),
    如果有则调用,如果两种查找都失败时则退化为std::swap()。此外,boost::swap还
    增加了对C++内建数组交换的支持(它要求两个数组大小相同)。


    如果读者担心在全局或std名字空间编写自由函数swap会造成名字“污染”,也可以把特化
    的swap加入到boost名字空间,或者其他ADL可以找到的名字空间。


    4.5.5 使用建议
    我们应该尽量使用boost::swap,它提供了比std::swap更好的优化策略。并且你自己写
    的类应该总是实现在高效的交换。或者想交换两个数组的内容。


    变量值交换是一个基础但很重要的操作,几乎所有boost库组件都实现了自己的swap
    成员函数,并且用boost::swap来提高交换的效率,


    4.6 singleton
    目前boost中并没有专门的单件库,而仅在其他库中有并不十分完善的实现。


    4.6.1 boost.pool的单件实现
    #include <boost/pool/detail/singleton.hpp>
    using boost::details::pool::singleton_default; //单件名字空间
    class point
    {
    public:
    point(int a = 0, int b = 0, int c = 0):x(a), y(b), z(c)
    {cout<<"point ctor"<<endl;}
    ~point()
    {cout<<"point dtor"<<endl;}
    ...
    };


    int main()
    {
    cout<<"main() start"<<endl;
    typedef singleton_default<point> origin; // 定义单件类
    origin::instance().print(); // 使用instance()获得单件对象
    cout<<"main() finish"<<endl;
    }
    singleton_default用法非常简单,只需要把想成为单件的类作为它的模板参数就可以了
    唯一的不方便之处就是过长的类型名,但可以用typedef来简化。


    4.6.2 boost.serialization的单件实现
    在序列化库serialization中有另一个单件实现类:singleton,它们于名字空间
    boost::serialization。需要包含头文件<boost/serialization/singleton.hpp>


    singleton与pool.singleton_default基本相同,对模板参数T也有同样的要求(具有
    缺省构造函数,构造析构不抛异常)。


    4.7 tribool
    在处理tribool的不确定状态时必须要小心,因为它既不是true也不是false,使用它
    进行条件判断永远都不会成立,判断不确定状态必须要与indeterminate值比较或者
    使用indeterminate()函数。


    4.7.3 tribool库默认采用indeterminate作为第三态的名字,很清晰明确但可能有些长。
    tribool库使用宏BOOST_TRIBOOL_THIRD_STATE就可以为第三态更名。像这样:
    BOOST_TRIBOOL_THIRD_STATE(unknown)。


    因为宏BOOST_TRIBOOL_THIRD_STATE实质上定义了一个函数,而C++不允许函数嵌套,所
    以这个宏最好在全局域使用,它将在定义后的整个源代码中都生效。


    4.7.4 输入输出,tribool需要包含头文件<boost/logic/tribool_io.hpp>就可以像
    bool类型一样进行流操作了。


    4.8 operators
    在C++98标准的std::rel_ops名字空间里提供了四个模板比较操作符!=, >, <=, >=
    只需要为类定义了==和<操作符,那么这四个操作符就可以自动实现了。
    例如:
    #include <utility>
    class demo_class
    {
    public:
    demo_class(int n):x(n){}
    int x;
    friend bool operator<(const demo_class& l, const demo_class&r)
    {return l.x < r.x;}
    };


    int main()
    {
    demo_class a(10), b(20);
    using namespace std::rel_ops; //打开std::rel_ops名字空间
    cout<<(a<b)<<endl;
    cout<<(b>=a)<<endl; //>=等操作符被自动实现
    }


    operators位于名字空间boost,为了使用operators组件,需要包含头文件<boost/
    operators.hpp>,


    4.8.1 基本运算概念
    operators库是由多个类组成,分别用来实现不同的运算概念,比如less_than_compparable
    定义了<系列操作符,left_shiftable定义了<<系列操作符。


    我们可以使用多重继承来获得多个操作符的重载。


    4.8.3 基类链
    4.8.5 相等与等价
    相等equality与等价equivalent是两个极易被混淆的概念。一个简单的解释是:相等是
    基于操作符==,即x==y;而等价基于<,即!(x<y)&&!(x>y)。
    但对于大多数复杂类型和自定义类型,==和<操作符是两个不同的运算。
    如p1(1,2,3)和p3(3,2,1)两者完全不相等,但等价。


    标准库中的关联容器set,map和排序算法使用的是等价关系<操作符,而各种查找算法
    find使用的是相等关系的==操作符。


    4.8.6 解引用操作符
    operators库使用dereferenceable提供了对解引用操作符*,->的支持,它的用法与之前
    介绍的算术操作符不太相同。


    dereferenceable类要求子类提供operator*,会自动实现operator->。与前面的算术
    操作符不同的是它不是使用的友元函数,因此在使用dereferenceable时必须使用
    public继承,否则operator->将会成为类的私有成员函数,外界无法访问。


    4.8.7 下标操作符
    operators库使用indexable提供下标操作符[]的支持,它也属于解引用的范畴。用法与
    dereferenceable很相似。
    indexable要求子类提供一个operator+(T, I)的操作定义,类似于一个指针的算术运算


    4.9 exception
    4.9.1标准库中的异常
    为了使用boost.exception,我们需要先了解C++98标准规定的异常体系。
    C++98标准中定义了一个异常基类std::exception和try/catch/throw异常处理机制,
    std::exception又派生出若干子类。用以描述不同种类的异常,如bad_alloc,
    bad_cast,out_of_range等等,共同构建了C++异常处理框架。


    C++允许任何类型作为异常抛出,但在std::exception出现后,我们应该尽量使用它,因
    为std::exception提供了一个很有用的成员函数what(),可以返回异常所携带的信息,
    这比简单地抛出一个整数错误值或者字符串更好、更安全。


    如果std::exception及其子类不能满足程序对异常处理的要求,我们也可以继承它,为
    它添加更多的异常诊断信息。


    4.9.2类摘要
    exception库提供两个类:exception和error_info,它们是exception库的基础。


    exception是一个抽象类,它的重要能力在于其友元操作符<<,可以存储error_info对象
    的信息,存入的信息可以用自由函数get_error_info<>()随时再取出来。


    应该总对异常类使用虚继承。这是由于异常的特殊处理机制决定的。从现在开始,对
    boost::exception应该总使用虚继承。


    因为exception被定义为抽象类,因此我们的程序必须定义它的子类才能使用它,如前
    所述exception必须使用虚继承的方式。通常,继承完成后自定义异常类的实现也就结
    束了,不需要再添加成员变量或成员函数,这些工作都已经由exception完成了。
    如:
    struct my_exception:
    virtual std::exception, // 虚继承
    virtual boost::exception
    {}; //空实现,不需要实现代码


    4.10 uuid
    uuid库是一个小的实用工具,可以表示和生成UUID.
    UUID是Universally Unique Identifier的缩写,它是一个128位的数字(16字节),不
    需要有一个中央认证机构就可以创建全球唯一的标识符
    UUID的另一个别名是GUID,在微软的COM中被广泛使用,用于标识COM组件接口。


    4.10.2用法
    uuid是一个很小的类,它特意被设计为没有构造函数,可以像POD数据类型一样使用。
    uuid内部使用一个16字节的数组data作为uuid值的存储,这个数组是public的,因此
    可以任意访问,比如拷贝或者赋值。
    可以把uuid看作是一个容量固定为16、元素类型为unsigned char的容器。


    UUID的生成有不同的算法,这些算法使用枚举version_type来标识,version()函数可以
    获得UUID的算法版本。uuid类可以识别现有的五种生成算法,分别是:
    一、基于时间的MAC的算法 version_time_based
    二、分布计算环境算法 dce_security
    三、MD5摘要算法 version_name_based_md5
    四、随机数算法 version_random_number_based
    五、SHA1摘要算法 version_name_based_sha1


    在数量宠大的UUID中有一个特殊的全零值nil,它表示一个无效的UUID,成员函数
    is_nil()可以检测uuid是否是nil。


    4.10.3 生成器
    uuid库提供了四种生成器,分别是Nil生成器、字符串生成器、名字生成器和随机生成
    器。它们都是函数对象,重载了operator(),可以直接调用生成uuid对象。


    Nil生成器
    只能生成一个无效的UUID值(即全零的UUID).
    Nil生成器的类名是nil_generator,另外有一个内联函数nil_uuid(),相当于直接调用
    了Nil生成器。
    如:
    uuid u = nil_generator()();
    asseert(u.is_nil());


    u = nil_uuid();
    assert(u.is_nil());
    注意,代码的第一行,在nil_generator类名后面出现了两对圆括号,不熟悉函数对象
    的读者可能会不太理解。它的语义解析是:第一对圆括号与nil_generator结合,结果
    是调用nil_generator的构造函数,生成一个临时对象,然后第二对圆括号是
    nil_generator对象的operator()操作符重载,就像是一个函数调用,产生了一个
    nil uuid对象。


    字符串生成器
    string_generator


    名字生成器
    name_generator使用基于名字的SHA1摘要算法。它需要先指定一个基准UUID,然后使用
    字符串名字派生出基于这个UUID的一系列UUID。名字生成器的典型的应用场景是为一个
    组织内的所有成员创建UUID标识,只要基准UUID不变,那么相同的名字总会产生相同的
    UUID。
    例如:
    uuid www_xxx_com = string_generator()("0123456789abcdef0123456789abcdef");
    name_generator ngen(www_xxx_com);


    uuid u1 = ngen("mario");


    uuid u2 = ngen("link");


    随机数生成器
    random_generator rgen;
    uuid u = rgen();


    4.10.4 增强的uuid类
    uuid类为了追求效率而没有提供构造函数,要生成一个UUID值必须要使用生成器。我们
    可以从uuid类派生一个可以自动产生UUID值的增强类,以简化UUID的使用。


    4.10.6
    uuid的名字生成器使用了SHA1摘要算法,该算法可以将任意长度的文本压缩成一个只有
    20字节(160位)的独一无二的摘要。


    sha1算法位于名字空间boost::uuids::detail,使用时需要包含头文件<boost/uuid/
    sha1.hpp>
    sha1类的用法很简单,使用成员函数process_byte()、process_block()和process_types()
    以不同的方式把数据“喂”给sha1对象,当输入所有数据后用get_digest()获得计算
    出的摘要值。


    4.11 config
    config库主要是提供给boost库开发者(而不是库用户)使用。它将程序的编译配置
    分解为三个正交的部分:平台、编译器和标准库。


    4.11.1 BOOST_STRINGIZE
    宏BOOST_STRINGIZE可以将任意字面量转换为字符串。它在头文件
    <boost/config/suffix.hpp>中
    但它是宏,不支持运行时转换。如果要在运行时转换数字或者其他变量到字符串,请
    使用之后的lexical_cast。


    4.11.2 BOOST_STATIC_CONSTANT
    C++98标准允许直接在类声明中为静态整形成员变量赋初始值。


    4.12 utility
    utility库不是一个有统一主题的Boost库,而是包含了若干个很小但有用的工具。


    本章开头介绍的noncopyable、swap都被归类在utility库里,此外utility还包括其他
    很多个实用类,如checked_delete、compressed_pair、base_from_memeber等。


    4.12.1 BOOST_BINARY
    BOOST_BINARY提供一组宏,用于实现简单的二进制常量表示。


    它使用boost.preprocessor预处理元编程工具将一组或多组01数字在编译期展开成为一
    个八进制数字。每个数字组之间可以用空格分隔,特别注意的是,数字组的长度一定不
    能超过八个,由于预处理器宏展开的限制,嵌套层次太深会导致无法通过编译,报出
    一大堆错误。


    4.12.2 BOOST_CURRENT_FUNCTION
    微软编译器VC在C89的__FILE__和__LINE__之外定义了一些扩展宏,其中的__FUNCTION__
    宏可以表示函数名称,GCC、intel C等编译器也定义有类似的宏。而C99标准则定义了
    __func__宏以实现同样的功能。但目前的C++98标准不能这样做。


    在STL中,std::bitset不能直接使用字符串构造,必须使用bitset<5>(string("10110"))
    这样的形式,导致生成一个string临时变量,效率较低。


    它在头文件#include <boost/current_function.hpp>
    只需要在代码中使用BOOST_CURRENT_FUNCTION宏,就可获得包含该宏的外围函数名称,
    它表现为一个包含完整函数声明的编译期字符串。


    应当总用boost::noncopyable的名字空间域限定形式来使用它,而不是用using语句,
    避免在头文件打开boost名字空间。


    在使用optional存储的元素之前,必须测试它的有效性。


    assign库对标准容器、容器适配器和Boost容器都提供了很全面的支持,这使得它具有
    很高的使用价值。


    如果想要自己的类安全高效,那么应该提供一个好的swap函数,它是很多实用功能的
    基础。如果你自己的类实现了高效的交换方法,那么boost::swap就会自动调用它。


    Boost库提供了两种实现singleton的方式:pool.singleton和serialization.singleton。
    两者都要求模板类型参数T具有缺省构造函数,而且构造的析构时不能抛出异常。


    tribool实现了三态布尔逻辑。


    operator库中的equality_comparable、less_than_comparable和totally_ordered是最
    常用的操作符重载类,它们提供比较运算,被用于支持标准容器。


    uuid组件实现了对UUID的表示和处理,提供了基于名字和随机数的生成算法生成全球
    唯一的标识符,可以用在很多地方来唯一地标识对象。uuid库里还附带了一个SHA1算法
    的实现,能够为任意长度的数据生成SHA1摘要。


    最后是config和utility库,它们提供了几个很有用的宏:BOOST_STRINGIZE实现编译期
    字符串转换;BOOST_STATIC_CONSTANT可以定义类的静态整型成员常量;BOOST_BINARY
    便利了二进制数字的书写方法;BOOST_CURRENT_FUNCTION能够输出函数名称字符串。
    还有头文件<boost/config/warning_disable.hpp>可以禁止编译器的C4996警告。




    第5章
    字符串与文本处理
    字符串与文本处理一直是C++的弱项。虽然C++98标准提供了一个标准字符串类std::string
    暂解燃眉之急,但C++仍然缺乏很多文本处理的高级特性,如正则表达式、分词等等,
    使得不少C++程序员不得不转向其他语言(如perl、python)。


    Boost中五个字符串与文件处理领域的程序库。首先是两个与C标准库函数功能类似的
    lexical_cast和format,它们关注于字符串的表示,可以将数值转化为字符串,对输
    出做精确的格式化。string_algo库提供了大量常用的字符串处理函数。剩下的可以
    使用tokenizer和xpressive,前者是一个分词器,而后者则是一个灵活且功能强大的
    正则表达式分析器,同时也是一个语法分析器。


    使用Boost,C++中文本处理的一切问题将迎刃而解。


    5.1 lexical_cast
    lexical_cast库进行“字面量”的转换,类似C中的atoi函数,可以进行字符串、整数/
    浮点数之间的字面转换。


    lexical_cast位于名字空间boost,为了使用lexical_cast组件,需要包含头文件
    <boost/lexical_cast.hpp>


    Unicode和区域字符编码转换逐渐成为了软件开发中无法回避的问题。C++98标准定义了
    wchar_t和locale等概念,C++0X还专门引入了<codecvt>。另外Boost的很多字符串的
    处理都需要标准库的<locale>。


    5.1.1用法
    C语言中的atoi()、atof()系列函数,它们可以把字符串转换成数值,但不存在如itoa()
    这样的返回转换(C语言标准未提供,但有的编译器厂商会提供非标准的如_itoa())。


    使用lexical_cast可以很容易地在数值与字符串之间转换,只需要在模板参数中指定
    转换的类型即可,


    lexical_cast不能转换如"133L"、"0x33"这样的C++语法许可的数字字面量字符串。而且
    lexical_cast不支持高级的格式控制,不能把数字转换成指定格式的字符串,如果需要
    更高级的格式控制,可使用std::stringstream或者boost::format。


    lexical_cast内部使用了标准库的流操作,因此,对于它的转换对象,有如下要求,
    下义了operator<<, operator>>和是可缺省构造的可拷贝构造的。
    C++中的内建类型都满足这些。


    5.2 format
    boost.format实现在类似于printf()的格式化对象,可以把参数格式化到一个字符串,
    而且是完全类型安全的。


    format组件位于名字空间boost,在头文件<boost/format.hpp>中。


    boost.range基于STL迭代器提出了“范围”的概念,是一个容器的半开空间。


    5.3 string_algo
    string_algo库主要的工作对象还是字符串string和wstring,它也可以工作在标准容器
    vector、deque、list和非标准容器slist、rope上,


    前缀i:
    后缀_copy
    后缀_if


    string_algo库提供的算法共分五大类,如下:
    大小写转换
    判断式与分类
    修剪
    查找与替换
    分割与合并


    5.3.3 大小写转换
    包括两组算法:to_upper()和to_lower()。
    还有前缀i和后缀_copy的版本。


    5.3.4 判断式(算法)
    starts_with
    ends_with
    contains
    equals
    lexicographical_compare
    all


    除了all,这些算法都有另一个i前缀的版本。由于它们不变动字符串,因此没有_copy
    版本。
    它们同时还有一种接受比较谓词函数对象的三参数版本,而没有使用_if后缀。


    5.3.5 判断式(函数对象)
    string_algo增强了标准库中的equal_to<>和less<>函数对象,允许对不同类型的参数
    进行比较,并提供大小写无关的形式。这些函数对象有
    is_equal
    is_less
    is_not_greater


    5.3.6 分类
    string_algo库的分类函数如下:
    is_space
    is_alnum
    is_alpha
    is_cntrl
    is_digit
    is_lower
    is_graph
    is_print
    is_punct
    is_upper
    is_xdigit
    is_any_of
    if_from_range: 字符是否位于指定区间内,即from<=ch<=to
    这些函数并不真正地检测字符,而是返回一个类型为detail::isclassifiedF的函数对象
    这个函数对象的operator()才是真正的分类函数(因此,它些函数都属于工厂函数)。
    函数对象is_classifiedF重载了逻辑运算符||,&&,和!,可以使用逻辑运算符把它们组
    合成逻辑表达式,以实现更复杂的条件判断。当然,我们也可以自定义判断式。


    5.3.7 修剪
    string_algo提供3个修剪算法:trim_left, trim_right和trim
    它有_if和_copy两种后缀,因此每个算法都有四个版本。


    5.3.8 查找
    string_algo使用了boost.range库的iterator_range返回查找到的整个区间。
    string_algo提供的查找算法包括:
    find_first
    find_last
    find_nth
    find_head
    find_tail
    这些算法都不变动字符串,因此没有_copy后缀版本。但其中前三个算法有i前缀版本。
    iterator_range可以像标准容器一样判断是否为空,也可以隐式转换为bool值。


    string_algo库还有另外三个查找算法:find_token,find_regex和通用的find算法。


    5.3.9 替换与删除
    replace/erase_first
    replace/erase_last
    replace/erase_nth
    replace/erase_all
    replace/erase_head
    replace/erase_tail
    前八个算法每个都有前缀i、后缀_copy和组合,有四个版本,后四个则只有后缀_copy
    的两个版本。


    5.3.10 分割
    string_algo提供两个字符串分割算法find_all和split
    find_all算法类似于普通的查找算法,它搜索所有匹配的字符串,加入到容器中,有一
    个忽略大小写的前缀i版本。


    split算法使用判断式Pred来确定分割的依据,如果字符ch满足判断式Pred(Pred(ch)
    ==true),那么它就是一个分割符,将字符串从这里分割。


    参数eCompress可以取值为token_compress_on或token_compress_off,如果值为前者,
    那么当分隔符连续出现时将被视为一个,如果为token_compress_off则两个连续的分隔
    符标记了一个空字符串。参数eCompress默认取值为token_compress_off。


    5.3.11 合并
    join还有一个后缀_if的版本,它接受一个判断式,只有满足判断式的字符串才能参与
    合并。


    5.3.12 查找(分割)迭代器
    string_algo库还提供两个查找迭代器find_iterator和split_iterator,它们可以在字
    符串中像迭代器那样遍历匹配,进行查找或者分割,无需使用容器来容纳。


    使用查找迭代器首先要声明迭代器对象find_iterator或split_iterator,它们的模板
    类型参数是一个迭代器类型a,例如string::iterator或char*


    为了获得迭代器的起始位置,我们需要调用first_finder()函数,它用于判断匹配的对
    象,然后再用make_find_iterator或make_split_iterator()来真正创建迭代器。同
    族的查找函数还有last_finder,nth_finder,token_finder等。
    例如:
    typedef split_iterator<string::iterator> string_split_iterator;
    string_split_iterator p, endp;
    for (p = make_split_iterator(str, first_finder("||", is_iequal()));
    p != endp; ++p)
    {
    cout<<"["<<*p<<"]";
    }


    5.4 tokenizer
    tokenizer库一个专门用于分词tiken的字符串处理库,可以使用简单易用的方法把一个
    字符串分解成若干个单词。
    <boost/tokenizer.hpp>
    using namespace boost;


    tokenizer接受三个模板类型参数,分别是:
    TokenizerFunc :tokenizer库专门的分词函数对象,默认是使用空格和标点分词
    Iterator: 字符序列的迭代器类型
    Type: 保存分词结果的类型


    tokenizer的构造函数接受要进行分词的字符串,可以以迭代器的区间形式给出,也可
    以是一个有begin()和end()成员函数的容器。
    assign()函数可以重新指定要分词的字符串,用于再利用tokenizer。
    tokenizer具有类似标准容器的接口,begin()函数使用tokenizer开始执行分词功能,
    返回第一个分词的迭代器,end()函数表明迭代器已经到达分词序列的末尾,分词结束。


    5.4.2 用法
    tokenizer的用法很像string_algo的分割迭代器,但要简单一些。可以像使用一个容器
    那样使用它,向tokenizer传入一个欲分词的字符串构造,然后用begin()获得迭代器
    反复迭代。


    tokenizer默认把所有的空格和标点符号作为分隔符,因此分割出的只是单词。


    5.5 xpressive
    许多编程语言都提供了内置的正则表达式,如Perl,Python,Java,但C++98中没有。
    静态方式 <boost/xpressive/xpressive_static.hpp>
    动态方式 <boost/xpressive/xpressive_dynamic.hpp>
    两种方式 <boost/xpressive/xpressive.hpp>


    \d匹配数字(相当于[0-9]),\w匹配字母(相当于[a-z]),\s匹配空格等。


    由于C++没有如C#和Python那样提供“原始字符串”的表达式,因此在C++代码中要使用
    双斜杠。建议在代码中使用正则表达式时,一定要在语句前用注释说明其原始表达式
    以方便将来调试和维护。




    5.5.3 类摘要
    介绍xpressive库提供的3个重要的类,分别是:basic_regex, match_results和
    sub_match。


    typedef basic_regex<std::string::const_iterator> sregex;
    typedef basic_regex<char const *> cregex;


    sregex用于操作标准字符串类std::string,cregex用于操作字符数组(C风格字符串)


    match_results为正则表达式的匹配结果提供一个类似容器的视图,可以用size()和
    empty()判断匹配结果中子表达式的数量,operator[]返回第i个子表达式。如果i==0
    则返回整个表达式的匹配对象。


    sub_match
    模板类sub_match继承自std::pair。可以把它当作一个字符串的区间表示。


    5.5.4 匹配
    自由函数regex_match()用来检查一个字符串是否完全匹配一个正则表达式,返回一个
    bool结果。


    最常用的有两种形式:
    bool regex_match(String, basic_regex const &re);
    bool regex_match(String, match_results& what, basic_regex const &re);


    cregex reg = cregex::compile("a.c");
    使用cregex类的工厂方法compile()创建了一个正则表达式对象reg。


    using namepsace boost::xpressive;
    cregex reg = cregex::compile("\\d{6}((1|2)\\d{3}))", icase);
    cmatch what;
    assert(regex_match("9995551970010199", what, reg));
    for (BOOST_AUTO(pos, what.begin()); pos != what.end(); ++pos)
    {
    cout<<"["<<*pos<<"]";
    }
    cout<<endl;
    cout<<what[1]<<what[3]<<endl;


    regex_match()还可以替代部分string_algo库的判断式算法的功能,例如下面的代码
    使用sregex实现了starts_with和ends_with算法。
    string str("readme.txt");
    sregex start_reg = sregex::compile("^re.*");
    sregex end_reg = sregex::compile(".*.txt$"); //匹配末尾
    assert(regex_match(str, start_reg));
    assert(regex_match(str, end_reg));


    5.5.5 查找
    为了使用正则表达式的查找功能,我们要用到regex_search()。
    它与regex_match()的区别是
    regex_match()要求输入字符串必须要与正则表达式完全匹配,而regex_search()则检测
    输入表达式中是否包含正则表达式,即存在一个匹配正则表达式的子串。


    因此,regex_search()比regex_match()使用起来更加灵活。


    sting str("readme.TXT");


    sregex start_reg = sregex::compile("^re")
    sregex end_reg = sregex::compile("txt$", icase);


    assert(regex_search(str, start_reg)); // starts_with
    assert(regex_search(str, end_reg)); // ends_with
    assert(regex_search(str, cregex::compile("me"))); // contains


    5.5.6 替换
    xpressive的替换功能使用的函数是regex_replace(),它先使用正则表达式查找匹配的
    字符串,然后再用指定的格式替换。
    string regex_replace(string, basic_regex const &re, Format);
    第三个参数Format可以是一个简单字符串,也可以是一个符合ECMA_262定义的带格式的
    字符串,它可以用$N引用正则表达式匹配的子表达式,$&引用全匹配。在替换完成后,
    regex_replace()返回一个字符串的拷贝。


    5.5.7 迭代
    xpressive库提供一个强大的迭代器模板类regex_iterator<>,它类似string_algo的
    查找迭代器和tokenizer,提供一个类似迭代器的视图(不是容器)。在具体应用时,我
    们应当使用它的typedef,如sregex_iterator和cregex_iterator。


    示范regex_iterator<>用法的代码如下:
    using namespace boost::xpressive;
    string str("Power-bomb, power-suit, pOWER-beam all items\n");
    sregex reg = sregex::compile("power-(\\w{4})", icase);
    sregex_iterator pos(str.begin(), str.end(), reg);
    sregex_iterator end;
    while(pos != end)
    {
    cout<<"["<<(*pos)[0]<<"]";
    ++pos;
    }
    cout<<endl;
    }


    5.5.8 分词
    xpressive库使用模板类regex_token_iterator<>提供了强大的分词迭代器,要比
    string_algo和tokenizer的分词能力强大,灵活得多。


    regex_token_iterator<>同样不能直接使用,你需要用它的两个typedef: 
    sregex_token_iterator和cregex_token_iterator。
    regex_token_iterator<>的用法大致与regex_iterator<>相同,但它的变化在于匹配
    对象的使用方式。


    默认情况下regex_token_iterator<>同regex_iterator<>,返回匹配的字符串。如果
    构造时传入-1作为最后一个args参数的值,它将把匹配的字符串视为分隔符。如果args
    是一个正数,则返回匹配结果的第args个字串。


    还有一点与regex_iterator<>不同的是它解引用返回的是一个sub_match对象,而不是
    match_results对象。


    char * str = "*Link*||+Mario+||Zelda!!!||Metroid";
    // 使用分隔符正则表达式,分隔符是"||"
    cregex split_reg = cregex::compile("\\|\\|");
    cregex_token_iterator pos(str, str+strlen(str), split_reg, -1);


    while(pos != cregex_token_iterator())
    {
    cout<<"["<<*pos<<"]";
    ++pos;
    }
    cout<<endl;
    }




    5.5.9 与regex的区别
    boost.regex是Boost库中另一个正则表达式解析库,xpressive的形式受到了它的很大
    影响,接口几乎与boost.regex完全相同。


    5.5.10 高级议题
    工厂类
    除了可以使用sregex::compile()创建正则表达式对象外,xpressive还提供一个专门
    的工厂类regex_compiler<>。


    同样,我们不能直接使用regex_compiler,而是使用它预定义的typedef,如
    sregex_compile和cregex_complier。


    regex_compiler也可以创建正则表达式对象,但比regex::compile()有更多的功能,可
    以传入特定的locale支持本地化,并重载operator[]实现了flyweight模式,可以把
    它当作一个正则表达式对象池。


    cregex_compiler rc; // 一个正则表达式工厂
    rc["reg1"] = rc.compile("a|b|c"); // "reg1"关联一个正则表达式
    rc["reg2"] = rc.compile("\\d*");  // "reg2"关联一个正则表达式


    assert(!regex_match("abc", rc["reg1"]));
    assert(regex_match("123", rc["reg2"]));


    异常
    当xpressive在编译不正确的正则表达式或者执行其他操作出错时会抛出regex_error异
    常。


    在使用xpressive时应当总是使用try-catch块,以防止异常。


    格式化器
    在使用regex_replace()进行替换时,除了使用简单字符串和格式字符串,还可以使用
    格式化器。格式器是一个具有operator()的可调用对象,函数指针、函数对象都可以,
    它必须能够处理查找到的match_results对象。


    下面的代码定义了一个函数对象formater,它将查找到的cmatch对象全部改为大写,使用
    了string_algo库的to_upper_copy算法。


    struct formater
    {
    string operator()(cmatch const &m) const
    {
    return boost::to_upper_copy(m[0].str());
    }
    };
    char *str = "*Link*||+Mario+||Zelda!!!||Metroid";
    cregex reg = sregex::compile("\\w+", icase);
    cout<<regex_replace(str, reg, formater())<<endl;




    让正则表达式执行得更快
    与format库类似,编译一个正则表达式需要很多的运行时开销,应当少做sregex/cregex
    对象的创建工作,尽量重用。


    同样,smatch/cmatch也应当尽量重用。它们缓存在动态分配的内存,不至于每次重新
    分配内存。
    下面代码定义了一个静态正则表达式,它等价于"^\d*\w+":
    cregex reg = '^'>>*_d>>+_w; // 使用operator>>连接表达式




    第6章 正确性与测试
    但C\C++只提供了很有限的正确性验证/测试支持---assert宏(没错,它是一个宏,虽然
    它违背常识使用了小写的形式),这是很不够的。C++98标准中的std::exception能够
    处理运行时异常、但并不能检查代码的逻辑。


    Boost在这方面前进了一大步:boost.assert库增强了原始的运行时assert宏,
    static_assert库提供了静态断言(编译期诊断),而boost.test库则构建了完整的单元
    测试框架。


    6.1.1 基本用法
    默认情况下BOOST_ASSERT宏等同于assert宏,断言表达式为真。


    BOOST_ASSERT宏仅会在debug模式下生效,在release模式下不会进行编译,不会影响
    到运行效率,所以可以放心大胆地在代码中使用BOOST_ASSERT断言。


    例如下面的用法:
    BOOST_ASSERT(x != 0 && "divided by zero"); // 断言参数非0


    上面宏BOOST_ASSERT的表法,除了要检查表达式x != 0,还使用逻辑与操作符增加了
    断言的描述信息。当断言操失败时,可以给出更具描述性的文字,有助于排错。


    6.1.2 禁用断言
    如果在头文件<boost/assert.hpp>之前定义了宏BOOST_DISABLE_ASSERTS,那么
    BOOST_ASSERT将会定义为((void)0),自动失效。但标准的assert宏并不会受影响,这
    可以让程序员有选择地关闭BOOST_ASSERT。


    6.1.3 扩展用法
    如果在头文件<boost/assert.hpp>之前定义了宏BOOST_ENABLE_ASSERT_HANDLER,这将导
    致BOOST_ASSERT的行为发生改变:


    它将不再等同于assert宏,断言的表达式无论是在debug还是release模式下都将被求
    值,如果断言失败,会发生一个断言失败的函数调用boost::assertion_failed()---这
    相当于提供了一个错误处理handle。


    函数assertion_failed()被声明在boost名字空间里,但被特意设计为没有具体实现。
    用户需要自己实现assertion_failed()函数。以恰当的方式处理错误--通常是记录日志
    或者抛出异常。


    void assert_failed(char const *expr, char const *function, char const *file,
    long line)
    {
    boost::format fmt("Assertion failed! \n Expression: %s \n Function: %s \n 
    File: %s \n Line: %ld\n\n");
    fmt % expr% function% file% line;
    cout<<fmt;
    }


    6.1.4 BOOST_VERIFY
    它具有与BOOST_ASSERT一样的行为,仅有一点区别:断言的表达式一定会被求值。
    使用BOOST_VERIFY需要注意,它在release模式下同样会失效。程序最好不应该依赖于
    它的副作用。


    6.2 static_assert
    使用BOOST_STATIC_ASSERT时要小心,断言的表达式必须能够在编译期求值。
    BOOST_ASSERT(assert)必须是一个能够执行的语句,它只能在函数域里出现,而
    BOOST_STATIC_ASSERT则可以出现在程序的任何位置:名字空间域、类域或函数域。


    BOOST_STATIC_ASSERT(sizeof(empty_class) == 1);
    空类其实并不空,因为C++不允许大小为0的类或对象存在,通常的“空类”会由编译器
    在里面安插一个类型为char的成员变量,令它在一个确定的大小。


    6.2.2 使用建议
    BOOST_STATIC_ASSERT主要在泛型编程或模板元编程中有于验证编译期常数或者模板类型
    参数。


    6.3 test
    BOOST_ASSERT


    6.2 static_assert
    assert宏和BOOST_ASSERT宏是用于运行时的断言,应该在程序中被广泛应用。


    static_assert库把断言的诊断时刻由运行期提前到编译期,让编译器检查可能发生的
    错误。


    static_assert库定义了宏BOOST_STATIC_ASSERT,用来进行编译期断言。
    BOOST_STATIC_ASSERT是一个编译期断言,使用了typedef和模板技术实现,它和
    BOOST_ASSERT很相似,最重要的区别是使用范围,BOOST_ASSERT(assert)必须是一个
    能够执行的语句,它只能在函数域里出现,而BOOST_STATIC_ASSERT则可以出现在程序
    的任何位置:名字空间域、类域或函数域。


    在C++中“空类”其实并不空,因为C++不允许大小为0的类或对象的存在,通常的“空类”
    会由编译器在里面安插一个类型为char的成员变量,令它有一个确定的大小。


    BOOST_STATIC_ASSERT主要在泛型编程或模板元编程中用于验证编译期常数或者模板类型
    参数。
    使用BOOST_STATIC_ASSERT时一定要小心,断言的表达式必须能够在编译期求值。这也正
    是迈向C++泛型编程和模板元编程的第一步。


    6.3 test
    <boost/test/unit_test.hpp>


    6.3.2 最小化的测试套件
    test库提供一个最小化的测试套件minimal test,只需包含文件<boost/test/minimal.hpp>
    它只提供最基本的单元测试功能,没有UTF那么强大,不支持多个测试用例,能够使用
    测试断言也很少。
    BOOST_CHECK(predicate)
    BOOST_REQUIRE(predicate)
    BOOST_ERROR(message)
    BOOST_FAIL(message)


    6.3.3 单元测试框架简介
    test库提供了强有力的单元测试框架(UTF),


    6.3.4 测试断言
    test库中的断言形式是BOOST_XXX_YYY,具体命名规则如下:
    BOOST_:遵循Boost库的命名规则,宏一律以大写的BOOST开头。
    XXX:断言的级别。WARN是警告级,不影响程序的运行,也不增强错误数量;CHECK是检查
    级别,会增加错误数量,但不影响程序运行;REQUIRE是最高级别,如果断言
    失败将增加错误数量并终止程序运行。最常用的是CHECK。
    YYY:各种具体的测试断言,如断言相等/不等、抛出/不抛出异常、大于或小于等。


    test库中最常用的几个测试断言如下:
    BOOST_XXX_EQUAL(1,r): 检查l==r,当测试失败时给出详细的信息。它不能用于浮点数
    的比较,浮点数的相等比较应使用BOOST_XXX_CLOSE;
    BOOST_XXX_GE(l,r):检查l>=r,同样还有GT(L>R),LT(l<r),LE(l<=r)和NE(l!=r),它们
    用于测试各种不等性。


    BOOST_XXX_THROW(expr, exception):检测表达式expr抛出指定的exception异常


    BOOST_XXX_NO_THROW(expr, exception):检测表达式expr不抛出指定的exception
    异常。


    BOOST_XXX_MESSAGE(expr, message):它与不带MESSAGE后缀的断言功能相同,但测试
    失败时给出指定的消息


    BOOST_TEST_MESSAGE(message):它仅输出通知用的信息。默认情况不会显示。


    6.3.5 测试用例与套件
    test库将测试程序定义为一个测试模块,由测试安装、测试主体、测试清理和测试运行
    器四个部分组成。测试主体是测试模块的实际运行部分,由测试用例和测试套件组
    织成测试树的形式。


    测试用例是一个包含多个测试断言的函数,它是可以被独立执行测试的最小单元,各个
    测试用例之间是无关的,发生的错误不会影响到其他测试用例。


    要添加测试用例,需要向UTF注册。在test库中,可以采用手工或者自动两种形式,
    通常我们使用自动和方式。
    我们使用BOOST_AUTO_TEST_CASE声明函数一样创建测试用例。
    例如
    BOOST_AUTO_TEST_CASE(t_case1)
    {
    BOOST_CHECK_EQUAL(1,1);
    ...
    }


    测试套件是测试用例的容器,它包含一个或多个测试用例,可以将繁多的测试用例分组
    管理,共享/清理代码,更好地组织测试用例。测试套件可以嵌套,并且没有嵌套层数
    的限制。


    测试套件同样可以有手工和自动两种使用方式,自动方式使用两个宏BOOST_AUTO_TEST
    _SUITE和BOOST_AUTO_TEST_SUITE_END。


    这两个宏必须成对使用,宏之间的所有测试用例都属于这个测试套件。一个c++源文件中
    可以有任意多个测试套件,测试套件也可以任意嵌套,没有深度的限制。测试套件的名
    字一般以s开头。例如:
    BOOST_AUTO_TEST_SUITE(s_suite1) // 测试套件开始
    BOOST_AUTO_TEST_CASE(t_case1)
    {
    BOOST_CHECK_EQUAL(1,1);
    ...
    }


    BOOST_AUTO_TEST_CASE(t_case2)
    {
    BOOST_CHECK_EQUAL(5, 10/2);
    ...
    }
    BOOST_AUTO_TEST_SUITE_END() // 测试套件结束


    任何一个UTF单元测试程序都必须存在一个主测试套件,它是整个测试树的根节点,其他
    测试套件都是它的子节点。


    主测试套件的定义可以使用宏BOOST_TEST_MAIN或者BOOST_TEST_MODULE,定义了这个宏
    的源文件中不需要再有宏BOOST_AUTO_TEST_SUITE和BOOST_AUTO_TEST_SUITE_END,所
    有的测试用例都自动属于主测试套件。


    6.3.6 测试实例
    首先要保证我们已经编译了test库,如前,建立一个test_main.cpp,包含有test库
    的编译源码。这个文件就是test库的预编译源程序,含有一个空的测试套件。一旦写
    好,不应用再变动它。


    6.3.7 测试夹具
    测试用例和测试套件构成了单元测试的主体,可以满足大部分单元测试的功能需求,但
    有的时候这些还不够,因为它们不能完成测试安装和测试清理的任务。


    测试安装和测试清理的动作很像C++中的构造函数和析构函数,因此可以定义一个辅助类
    它的构造函数和析构函数分别执行测试安装和测试清理,之后,我们就可以在每个测试
    用例最开始声明一个对象,它将自动完成测试安装和测试清理。


    基于这个基本原理,UTF中定义了“测试夹具”的概念,它实现了自动的测试安装和测
    试清理。就像是一个夹在测试用例和测试套件两端的夹子。测试夹具不仅可以用于测试
    用例,也可以用于测试套件和单元测试全局。


    使用测试夹具,必须要定义一个夹具类,它只有构造函数和析构函数,用于执行测试
    安装和测试清理。基本形式如下:
    struct test_fixture_name
    {
    test_fixture_name(){} // 测试安装工作
    ~test_fixture_name(){} // 测试清理工作
    };


    指定测试用例和测试套件的夹具类需要使用另外两个宏:
    #define BOOST_FIXTURE_TEST_SUITE(suite_name, F)
    #define BOOST_FIXTURE_TEST_CASE(test_name, F)
    它们替代了之前的BOOST_AUTO_TEST_CAST和BOOST_AUTO_TEST_SUITE宏,第二个参数指
    定了要使用的夹具类。


    6、3、8 测试日志
    测试日志是单元测试在运行过程中产生的各种文本信息,包括警告、错误和基本
    信息,默认情况下这些测试日志都被定向到标准输出stdout。测试日志不同于测试报告
    后者是对测试日志的总结。


    默认情况下,UTF的日志级别是warning,会输出大部分单元测试相关的诊断信息,但
    BOOST_TEST_MESSAGE宏由于是message级别,它的信息不会输出。
    日志级别可以通过接下来介绍的单元测试程序的命令行参数改变。


    6.3.9 运行参数
    UTF的命令行参数基本格式是:
    --arg_name=arg_value
    参数名称和参数值都是大小写敏感的,并且==两边和--右边不能有空格。


    测试泛型代码
    UTF也能够测试模板函数和模板类,这对于泛型库开发是非常有用的。
    如果采用自动测试方式,可使用BOOST_AUTO_TEST_CASE_TEMPLATE,它需要使用模板元
    编程库mpl,声明如下:
    #define BOOST_AUTO_TEST_CASE_TEMPLATE(test_name, type_name, TL)


    VC下使用test库的一个小技巧
    test库在文档中向库用户提供了一个在VC系列开发环境使用test库的一个小技巧,可以
    在工程选项build-event中设置post-build,加入命令:
    "$(TargetDir)\$(TargetName).exe" --result_code=no --report_level=no
    这样在VC编译完成后可以立刻运行单元测试,并且在Output窗口显示出未通过的测试
    断言,可以用双击的方式快速跳到断言的位置,提高测试的工作效率。


    6.4 总结
    很多软件方法学如敏捷开发、极限编程都非常强调测试的重要性。




    第7章 容器与数据结构
    容器是STL中最引人注意的部分,vector,deque, list, set和map分别实现了最常用的
    动态数组(向量)、双端队列、双向链表、集合和映射五种数据结构,它们以泛型的
    形式提供,可以容纳任意的类型(但也有例外,比如auto_prt)。


    Boost程序库基于STL同样的设计理念,实现了STL没有来得及实现的新型容器,如散列
    容器、循环队列、多维数组。


    本章总共介绍10个容器(数据结构),首先是array,dynamic_bitset, unordered,
    bimap和circular_buffer,它们都是对STL原有容器在概念上的扩展,与STL容器的接口
    非常相似。随后是tuple,any,variant,它们三个既是数据结构也是特殊的容器,展示
    了泛型编程的精妙用法。最后介绍新型的容器multi_array和property_tree。


    这些容器都对容纳的元素类型有一个基本要求:析构函数不能抛出异常,有的容器
    还要求元素具有缺省构造函数或者拷贝构造函数。


    7.1 array
    array包装了C++语言内建的数组,为基提供标准的STL容器接口,如begin(), front()等。
    array已经被放入C++11的新特性中了。


    成员函数at()类似operator[],根据索引值访问内部元素,但at()有范围检查的功能,
    如果索引值超过容器大小会抛出异常。


    array的data()和c_array()以C数组的形式返回内部数组指针,用于需要原始指针的场合
    两者的功能基本相同,但data()在需要的时候可以返回一个数组的常量指针,不允许
    变动容器元素。


    为了方便操作array对象,array还提供了operator==,swap()和assign()函数:
    operator=使用std::copy算法实现了赋值操作;
    swap()使用boost::swap可以高效地交换两个array对象
    assign()使用标准算法std::fill_n将容器内所有元素赋值为value。


    此外array还重载了==, <, >等比较操作符,使得字典序比较两个array对象。(但没有
    使用operators库来简化操作符重载定义)。


    7.1.3 用法
    array需要在模板参数列表中指明数组的类型和大小,声明的形式与vector和普通数组
    都很相像,它的使用方法与普通数组没有太大的区别。


    基本上,读者可以把array看做是普通数组,只是多了一些STL风格的成员函数,可以
    方便地搭配STL算法,是一个“更好的”数组。


    7.1.4 能力限制
    array的能力缺陷主要是:
    没有构造函数,不能指定大小的初始值(只能使用模板参数指定大小);
    没有push_back()和push_front(),因为它不能动态增长。
    不能搭配插入迭代器适配器功能,同样因为它不能动态增长。


    因此,array的功能相当有限,只能应用在已知数组大小,如果需要可动态变动的容量
    的数组,请使得std::vector或boost::scoped_array。


    7.1.5 array的初始化
    虽然arrray没有构造函数,但它可以使用与普通数组一样的风格进行初始化,把数组元素
    以逗号分隔放进花括号中,例如:
    array<string, 3> ar = {"alice", "bob", "carl"};
    也可以在花括号中初始化部分元素,剩余的元素将调用缺省构造函数完成初始化。


    array也可以与assign库的list_of配合工作。但因为没有insert()、push_back()、
    push_front()等成员函数,不能对它调用这些同名的assign库函数。
    如:
    array<int, 3> arr(list_of(2)(4)(6)); // 只能用list_of


    7.2 dynamic_bitset
    C++98标准为处理二进制数值提供了两个工具:vector<bool>和bitset。
    vector<bool>是对元素类型为bool的vector特化,它内部并不真正存储 bool值,而是
    以bit来压缩保存、使用代理技术来操作bit,造成的后果就是它很像容器。大多数
    情况下的行为与标准容器一致,但它不是容器,不满足容器的定义。


    bitset与vector<bool>类似,同样存储二进制位,但它的大小固定,而且比vector<bool>
    支持更多的位运算。


    vector<bool>可以动态增长,但不能方便地进行位运算;bitset则正好相反,可以方便
    地对容纳的二进制位做位运算,但不能动态增长。


    boost.dynamic_bitset的出现恰好填补了这两者之间的空白,它类似标准库的bitset,
    同时长度又是动态可变的。


    7.3.2 散列集合的用法
    hash_set/unordered_set具有与std::set相同的功能。可以用size()获得容器大小,用
    empty()判断空,支持比较操作符,可以用count()、find(),大多数应用std::set的
    场景都能用hash_set/unordered_set替换。


    我们唯一需要注意的是散列容器的无序性,我们不能在散列容器上使用binary_search、
    lower_bound和upper_bound这样用于已序区间的算法,散列容器自再也不提供这样的
    成员函数。


    7.3.3 散列映射简介
    unordered库提供两个散列映射类unordered_map和unordered_multimap。
    它们的接口、用法与STL里的标准关联容器map/multimap相同,只是内部使用散列表代替
    了二叉树实现、模板参数多了散列计算函数,比较谓词使用equal_to<>。


    7.3.4 散列映射的用法
    unordered_map/hash_map属于关联式容器,采用std::pair保存key-value形式的数据。
    提供operator[]重载,用法与标准容器map完全相同。


    unordered_multimap用法与之类似,但因为它允许有重复的key-value映射,因此不
    提供operator[]。


    7.3.5 性能比较
    一般情况下散列容器的性能表现要比标准容器更好。


    内部数据结构
    unordered库使用“桶”(bucket)来存储元素,散列值相同的元素被放入同一个桶中。
    当前散列容器的桶的数量可以用成员函数bucket_count()来获得,bucket_size()返回
    桶中的元素数量。


    为了提高散列容器的性能,unordered库会在插入元素时自动增加桶的数量。用户不
    能直接指定桶的数量(由散列容器自己管理会更好),但可以在构造函数或者rehash()
    函数指定最小的桶的数量。


    支持自定义类型
    unordered库支持C++内建类型和大多数标准库容器,但不支持用户自定义的类型,因为
    它无法计算自定义类型的散列值。


    如果要使用unordered支持自定义类型,需要定制类模板的第二个和第三个参数,也就
    是提供散列函数和相等比较谓词。


    boost.unordered基本上是依据C++0X标准草案来实现的,它有个小的扩充,增加了比较
    操作符operator==和operator!=,可以比较两个容器包含的元素是否相等。


    7.4 bimap
    C++标准提供了映射型容器map和multimap,它们就像是一个关联数组,把一个元素key
    映射到另一个元素value,但这种映射关系是单向的,只能是key到value,而不能反过
    来。


    boost.bimap扩展了标准库的映射型容器,提供双向映射的能力,功能强大,其接口被特
    特意设计为符合STL规范。


    名字空间boost::bimaps,但被using语句引入到了名字空间boost。头文件
    <boost/bimap.hpp>


    7.4.3 基本用法
    bimap可以容纳两个类型的元素,是这两种元素关系的集合,这是bimap的基本视图。此
    外,这个关系还可以有两个视图:左视图和右视图,分别用两个成员变量left和right
    访问。相当于两个不同方向的std::map。其用法也同std::map一样。


    对于bimap<X,Y>, bimap.left相当于map<X,Y>, bimap.right相当于map<Y,X>。


    因为它的双向性,key/value值对必须都是唯一的,插入任何一个重复的值都将是无效
    操作。


    其次,bimap的两个视图的迭代器返回的值都是常量对象。相当于pair<const, const>。
    不能如std::map那样修改value的值。


    最后一点区别是bimap不能使用operator[]和at().


    set_of  有序且唯一,相当于map
    multiset_of, 有序,相当于multimap
    unordered_set_of  无序且唯一,相当于unordered_map
    unordered_multiset_of:
    list_of
    vector_of
    unconstrained_set_of:


    7.4.6 使用assign库
    bimap的接口兼容STL,因此可以很容易地使用assign库为它赋初值。
    typedef bimap<multiset_of<int>, vector_of<string> > bm_t;
    那么可以直接使用assign::list_of为它初始化:
    bm_t bm = assign::list_of<bm_t::relation>(1, "111")(2, "222");


    bm的左视图是个multiset,可以使用assign库的insert()函数赋值,这里不需要使用
    assign名字空间限定。
    insert(bm.left)(3, "333")(4, "444");


    bm的左视图是个vector,可以使用push_back()函数赋值,如果右视图是list_of定义的,
    那么还可以使用push_front()函数。
    push_back(bm.right)("555", 5)("666", 6);


    7.4.7 查找与替换
    当bimap的左右视图是set时,可以如std::map一样调用成员函数find(),以键值这索引
    查找元素。


    7.4.7 查找与替换
    当bimap的左右视图是set时,可以如std::map一样调用成员函数find(),以键值为索引
    查找元素。


    bimap还通过视图提供std::map所没有的替换功能,可以直接修改key或者value。
    视图的成员函数replace_key()可以替换键,replace_data()可以替换value,它们接受
    一个指示位置的迭代器和值作为参数。返回bool表示替换是否成功。


    除了使用replace_key()和replace_data()函数,bimap还使用了boost.lambda库提供更
    快速的修改函数modify_key()和modify_data()。如果因为集合类型的约束导致修改失败
    将会删除修改的元素,而不是抛出异常通知用户,所以在使用modify时要小心。


    7.4.8 投射
    bimap提供三个成员函数project_left()、project_right()和project_up(),可以把
    bimap的迭代器分别投射到左视图、右视图和关系视图上。这允许用户以一种视图查找
    然后很容易在转换到其他视图再进行其他操作。




    7.5 circular_buffer
    circular_buffer实现了循环缓冲区的的数据结构,支持标准的容器操作如push_back,
    但大小是固定的,当到达容器末尾时将自动循环利用容器另一端的空间。


    7.5.2 用法
    circular_buffer被设计成一种可以与STL共同使用的容器,实现了一个大小固定的循环
    队列,就像是一个deque和stack。可以像普通双端队列一样执行push_back()、
    push_front()、insert()等操作来增加元素,也可以像栈一样用pop_back()、
    pop_front()来弹出元素。




    7.5.4 空间优化型缓冲区
    circular_buffer在创建时一次性分配所需的内存,这是标准容器的通常做法,但对于
    循环缓冲区数据结构可能不一定最合适。因此,circular_buffer库还提供了一个
    circular_buffer_space_optimized类,它是circular_buffer的适配器,只有在确实
    需要时才分配内存空间,而且当容器内元素减少时也会自动释放内存。


    当程序需要容纳大量的元素,但多数情况下仅保存较少数量元素的时候就可以使用
    circular_buffer_space_optimized,它可以有效地减少内存的占用。


    7.6 tuple
    tuple(元组)定义了一个有固定数目元素的容器,其中的每个元素类型都可以不相同,
    它是std::pair的泛化,可以从函数返回任意数量的值,也可以代替struct组合数据。


    7.6.1 最简单的tuple:pair
    标准库中提供的模板类std::pair是tuple的特例。


    tuple要求元素类型支持缺省构造或者赋值操作。
    如果tuple的元素类型是引用,那么在初始化时必须给予初值。


    make_tuple
    为了方便创建tuple对象,tuple库提供与make_pair()类似的make_tuple()函数,


    如果想让make_tupe生产的tuple数据类型是引用,那么要用到ref库的ref()和cref()函
    数,它们可以包装变量,使tuple的类型为T&或者从const T&,这在用于不可拷贝的数据
    类型时是很有用的。


    tuple<const int&, string &> t2 = make_tuple(cref(i), ref(s));


    7.6.4 访问元素
    tuple提供成员函数get<>()访问内部的值。它是一个模板函数。
    例如:
    BOOST_AUTO(t, make_tuple(1, "char[]", 100.0));
    assert(t.get<0>() == 1);
    cout<<++t.get<0>();
    因为get<>()是一个模板函数,所以模板实参<int N>必须是一个编译期可确定的,除
    了成员函数get<>(),tuple库还提供一个自由函数boost::get<>(),它的用法与同名
    成员函数完全相同,只是需要接受一个tuple变量作为操作的对象。例如:
    get<0>(t);


    7.6.4 比较操作
    它将比较操作符转发到内部的各个元素进行比较,因此要求tuple的元素必须能够执行
    比较操作,作为比较对象的两个tuple也必须要相同的元素个数,不则也会引发编译错误


    7.6.6 输入输出
    对tuple内的所有元素逐个调用operator<<或operator>>,因此要求每个元素都支持输
    入输出。输入输出格式默认是:整个tuple用圆括号包围,元素值间用空格分隔。


    7.6.7 连续变量
    tuple库提供一个类似make_tuple()的函数tie()。它可以把变量连结到tuple上,生成
    一个元素类型全是引用的tuple。相当于make_tuple(ref(a), ref(b),...),可以被用于
    左值。
    因此可以对tuple执行“解包操作”,这在接收返回tuple的函数返回值时特别有用。


    它还可以应用于std::pair:
    int i, string s;
    tie(i, s) = make_pair(200l, "adc");


    关于tie()还有一个特殊的对象tuples::ignore,它相当于一个点位符,可以对赋值时
    “忽略”某些对象。


    7.6.8 应用于assign库
    assign库提供了一个初始化工具tuple_list_of,它可以初始化元素类型为tuple的容器
    用法类似于map_list_of和pair_list_of。例如:
    typedef tuple<int, double, string> my_tuple;
    using namespace boost::assign;


    vector<my_tuple> v = tuple_list_of(1, 1.0, "123")(2, 2.0, "456");
    其他的assign库操作函数需要使用make_tuple()或者tie()函数来创建可插入容器的
    tuple对象。例如:
    v += make_tuple(3, 3.0, "789"), make_tuple(4, 4.0, "abc");


    我们无法使用循环来处理tuple,可以使用递归方法。


    7.7 any
    any不是一个模板类
    但它的构造函数any(const ValueType &)和赋值函数operator=(const ValueType&)
    是模板函数,可以接受任意的类型,将值存入内部的一个模板类holder。所以any实际
    上可以说是一个包装类。


    any不会对指针执行delete操作,因此,如果应使用any来保存智能指针。


    7.7.2
    any的类接口很小很简单,它的出现让C++的强类型语法检查失去了作用,C++仿佛变成了
    一种弱类型的动态语言。
    它可以被初始化或者赋值任意类型的数据。


    在any中存储字符串的时候我们必须用std::string,如果直接使用C字符串会引发编译
    错误:这是因为C字符串是一个普通数组,它没有拷贝构造函数,不符合any对类型的
    要求。


    但我们在用any_cast()取回存入的值的时候,我们必须知道any内部值的确切类型才能
    调用成功。


    在用any中保存指针时要特别小心,因为空指针对于any也是有效的值,在用any_cast取
    出来之后必须要进行测试。例如:
    string *ps = new string("abc");
    a = ps;
    if (!a.empty() && any_cast<string*>(a))
    { cout<<*any_cast<string*>(a)<<endl;}
    但这样的代码很麻烦,而且还有内存泄漏的危险,所以除非必要,应该使用智能指针
    包装原始指针再放入any。


    7.7.7 应用于容器
    当容器的元素类型是any时,容器的表现就像是一个可持有多个不同类型对象的动态
    tuple。
    也可以对any容器使用assign库来初始化或赋值:例如:
    vector<any> v2 = list_of<any>(10)(0.644)(string("char"));


    如果希望一种数据结构具有tuple那样容纳任意类型的能力,又可以在运行时动态变化
    大小,那么我们可以用any作为元素类型搭配容器。


    7.8 variant
    variant与any有些类似,是一种可变类型,是对C/C++中union概念的增强和扩展。
    普通的union只能持有POD(普通数据类型),而不能持有如string,vector等复杂类型,
    variant则没有这个限制。


    variant提供了一个外界自由函数get()来访问内部元素。这是因为variant的设计出发
    点与any不同,它的目的是存储多个数据的联合,而不是任意类型的容器。


    7.8.3 用法
    variant必须要在模板参数中指定它所能容纳的类型。它的写法与tuple很相似。


    7.9 multi_array
    多维数组在C++98中除了原始数组,只能用vector<vector<T> >来代替,但很不方便。
    multi_array库解决了这个问题,它是一个多维容器,高效地实现了STL风格的多维数组,
    比使用原始多维数组或者vector of vector更好。


    7.9.3 用法
    multi_array的模板参数很像标准容器,除了容纳的元素类型外,它多了一个维数的模板
    参数,用来指定多维数组的维数。例如,下面代码:
    multi_array<int, 3> ma; // 相当于int ma[X][Y][Z];
    下面代码声明了一个维度为2/3/4的三维数组:
    multi_array<int, 3> ma(extentsp[2][3][4]);
    multi_array的总维数可以用成员函数num_dimensions()获得,它的返回值就是模板参数
    中NumDims。函数shape()返回一个常量指针(数组),里面有NumDims个元素,表明了具
    体各个维度的值。


    multi_array重载了operator[],可以像普通数组那样访问内部的元素,operator[]在
    debug模式下执行范围检查,如果访问索引超过了多维数组的范围会引发BOOST_ASSERT
    断言失败从而退出程序。但在release模式下的行为则未定义。


    multi_array另外提供一个data()成员函数,以指针的形式返回内部的数组,它包含了所
    有的元素,成员函数num_elements()可以获得元素的总数。


    7.9.4 改变形状和大小
    multi_array可以在运行时使用成员函数reshape()改变多维数组的形状,即变动各个维
    度的大小,但总维数的元素数量保持不变,变动前的维度乘积与变动后的维度乘积必须
    相同。


    reshape()接受一个维度序列作为参数,std::vector或者boost::array都可以,能常
    array会更快。


    例如,把一个三维数组由[2][3][4]变成[4][2][3]可以这样:
    multi_array<int,3> ma(extents[2][3][4]);
    assert(ma.shape()[2] == 2);
    array(std::size_t, 3> arr = {4,3,2};
    ma.reshape(arr);
    ma.reshape(arr);
    assert(ma.shape()[4] == 4);


    multi_array也可以在运行时使用成员函数resize()改变多维数组的大小,动态增长或
    缩减元素的数量,但总维数不能变(它已经在模板参数中固定了)。resize()函数既
    可以接受维度序列,也可以接受extents表达式。


    在变动大小时multi_array将重新分配内存,原有的元素将被复制到新内存,然后被
    析构,新增的元素使用缺省构造函数构造。如果multi_array的元素总数变少,那么原
    有的多余元素将被删除。


    7.9.5 创建子视图
    多维数组的操作是比较复杂的,multi_array库允许用户为多维数组创建一个只查看
    其中一部分数据的子视图view,子视图既可以与原数组拥有相同的维数,也可以少于
    原来的维数。后一种情况被称为多维数组的“切片”(slice),它可以降低数组的维数,
    使之更容易处理。


    我们需要使用index_gen类的预定义实例indices来定义子视图的索引范围。
    indices用法类似extents对象,重载了operator[]来限定范围,它的参数是一个
    multi_array<T,N>::index_range对象,它用来指定从多维数组中抽取的维度范围。


    而multi_array的operator[]接受indices对象返回子视图,子视图的类型是
    multi_array<T, N>::array_view<N>::type。
    获取一个2*2的子视图可以是这样的:
    BOOST_AUTO(view, ma[indices[range(0,2)][range(0,2)]]);


    子视图也是一个multi_array,拥有相同的接口,可以用operator[]、num_dimensions()
    等成员函数操作,但不能改变子视图的形状的大小。


    7.9.6 适配普通数组
    multi_array是多维数组最常用的类,它自己管理内存,可以动态增长,但有的时候我们
    需要将一个一维数组适配成多维数组进行处理。
    multi_array库提供了另外两个类multi_array_ref和const_multi_array_ref来满足这
    一需求。它们可以把一段连续的内存(原始数组)适配成多维数组,用法类似
    smart_ptr库中的scope_array。


    multi_array_ref和const_multi_array_ref都通过构造函数接受一块外界的内存,把它
    适配成多维数组的接口,适配后的用法除了不能动态增长外与multi_array完全相同,
    包括operator[]访问和reshape()改变维度。const_multi_array_ref的功能要更少一些
    它是只读的,适配后不能修改数组元素的值。
    例如:
    int arr[12];
    multi_arrray_ref<int, 2> ma(arr, extents[3][4]);


    用序列指针维数
    除了使用extents对象,multi_array还支持另一种创建对象的方法,直接向构造函数
    传入一个维度序列,例如:
    array<std::size_t, 3> arr = {2, 3, 4};
    multi_array<int, 3> ma(arr);
    这里必须使用array或者vector,通常array会更快。


    我们不能直接使用assign库的list_of,因为list_of生成的匿名数组不满足multi_array
    的序列概念要求,但可以把array或vector和list_of组合起来使用,这样就可以在一
    句话中完成multi_arrray的构造,例如:
    multi_array<int, 3> ma(vector<int>(assign::list_of(2)(2)));


    7.10 property_tree
    property_tree是一个保存了多个属性值的树形数据结构,可以用类似路径的简单方式
    访问任意节点的属性,而且每个节点都可以用类似STL的风格遍历子节点。property_tree
    特别适合于应用程序的配置数据处理,可以解析xml, ini, json和info四种格式的文本
    数据。


    property_tree库的核心类是basic_ptree。
    basic_ptree的接口很像标准容器std::list,可以执行很多基本的元素操作,如使用
    begin()和end()遍历当前属性树的所有子节点。此外它还增加了操作属性树的get()、
    get_child()、get_value()、data()等额外的操作。


    basic_ptree有两个重要的内部类型定义self_type和value_type。self_type是
    basic_ptree模板实例化后自身的类型,它也是子节点的类型。value_type是节点的
    数据结构,它是一个std::pair,含有节点的属性名(first)和节点自身(second)。
    同标准字符串类basic_string一样,通常我们不直接使用basic_ptree,而是使用预定
    义好的typedef:ptree、wptree、iptree、wiptree,这些名字中的前缀i表示忽略大小
    写,前缀w表示支持宽字符。




    第8章 本章介绍了两个Boost算法库:foreach和minmax




    第9章 数学与数字
    C++98涵盖了C89所定义的数学运算函数,例如sqrt()、abs()、log()等等,标准库还提
    供了复数complex和数值序列valarray,但对于近代数学来说这些工具还很不够用。


    Boost库对标准库进行了补充,增加了C99标准中引入的大量特殊数学函数和复数函数,
    还有四元数、八元数、拉格朗日多项式、贝塞尔曲线等许多扩充,几乎覆盖了近现代
    数学的大部分领域,包括高等代数、线性代数和统计学。
    本章介绍Boost数学领域的四个库:integer、rational、crc和random。


    9.1.1 integer_traits
    C++98在标准头文件<limits>里定义了模板类std::numeric_limits<>,它使用模板特化
    技术给出了int,double等数据类型的相关特性,如最大最小值、是否有符号等等。这些
    特性大部分是编译期的常量,但最大最小值函数却不是常量,只能在运行时使用,这在
    泛型编程时会带来一些不方便。


    <boost/cstdint.hpp>还有两个最大宽度整数类型:intmax_t和uintmax_t有来表示编译
    器支持的最大整数,可以兼容其他任意整数类型。


    9.1.3 整数类型模板类
    头文件<boost/integer.hpp>与<boost/cstdint.hpp>功能类似,也提供一系列的整数
    类型定义,但它不使用typedef的形式,而是用模板类,可以自动地选择最合适的整数
    类型。


    <boost/integer.hpp>包含两组模板类。最容易使用的模板类是int_fast_t<>,它的
    声明如下:
    例如:
    int_fast_t<char>::fast a; //char类型的最快类型
    cout<<typeid(a).name()<<endl;


    整数类型模板类与普通类型一样容易使用,只需要多写一点点模板类代码,它就会根据
    所需要的整数宽度或者处理的整数值自动为你选择最合适的整数类型。
    由于整数类型模板类内仅有typedef,没有其他数据成员或成员函数,因此这些类不会
    产生任何运行时开销,与使用内置整数类型或者Boost整数类型同样高效。
    示例代码:


    #include <boost/integer.hpp>
    #include <boost/format.hpp>
    #include <typeinfo>


    int main()
    {
    format fmt("type:%s, size=%dbit\n"); //一个format对象
    uint_t<15>::fast a; //可容纳15位的无符号最快整数
    cout<<fmt%typeid(a).name%(sizeof(a)*8);
    int_max_value_t<32700>::fast c; // 可处理32700的最快整数
    cout<<fmt%typeid(c).name()%(sizeof(c)*8);


    9.2 rational
    科学计算的基础是数字运算,C++语言内建了int和float/double,分别用于支持有
    限精度的整数和实数(小数).C++98标准库提供了complex,支持复数的概念。


    9.2.2 创建与赋值
    rational有三种形式的构造函数,缺省构造函数(无参)创建一个值为零的有理数,单
    参的构造函数创建一个整数,双参数的构造函数创建一个经规范化的分数。
    rational重载了operator=,可以直接从另一个整数赋值,也可以使用成员函数assign()
    接受分子/分母形式的两个整数参数赋值。
    但ration不能多浮点数(float/double)构造或者赋值。


    除了bool以外,ratinal不能隐式转换到其他任何类型(即使是分母为1的有理数转换到
    整数),而必须使用ration_cast<>函数,这个函数模仿了标准库的转型操作符,可以
    显式地转换到其他数字类型。


    ration重载了流输入输出操作符,可以直接与IO流配合工作,格式为用斜杠分隔两个
    整数。


    9.2.6 分子与分母
    rational的成员函数numerator()和denominator()可以直接访问有理数对象的分子和
    分母,但返回的是一个右值,只能读而不可写


    9.2.7 与数学函数配合工作
    rational没有实现到float/double的隐式转换,故不能直接使用sqrt(),pow()等标准C
    函数,但取绝对值的abs()函数是一个例外,因为rational实现了对它的重载。我们必
    须使用ration_cast<>转型成float或者double值才可以。


    9.2.11 最大公约数的最小公倍数
    rational库还提供了两个工具函数:最大公约数gcd()和最小公倍数lcm()。这两个函数
    实际是另一个库math()的组成部分,位于名字空间boost::math。被rational库引入了
    名字空间boost。




    9.3 CRC
    CRC(循环冗余校验码)是一种被广泛应用的错误验证机制,它使用一定的规则处理数据,
    计算出一个校验和,并附在数据末尾发送给接收方。接收方使用同样的规则计算CRC,
    如果两个计算结果一致则说明传输正确,否则表明传输过程发生了错误。


    CRC库提供了两个类用于计算循环冗余校验码:一个是crc_basic,以bit为单位进行计算,
    速度慢,仅供理论研究;另一个是crc_optimal,是优化过的处理机,以byte字节为单位
    进行计算,速度很快,适合于实际应用,crc库的所有实现均基于crc_optimal。


    9.3.2 预定义的实现类
    crc_optimal类的模板参数过多,实际使用时很不方便,因此crc库基于crc_optimal预
    定义了四个实现类:crc_16_type、crc_ccitt_type、crc_xmodem_type和crc_32_type.
    这四个类代表了历史上被广泛使用的CRC计算方法,前三个是16位的CRC码,第四个是
    32位的CRC码。


    较常用的是crc_32_type,它使用的算法被用于PKZip计算CRC值。定义如下:
    typedef crc_optimal<32, 0x04C11DB7,0xFFFFFFFF,0xFFFFFFFF,true, true>
    crc_32_type;


    9.3.3 计算CRC
    为了优化速度,crc_optimal只能以byte为单位处理数据,crc_basic可以处理bit.
    成员函数process_byte()一次只能处理一个字节。process_bytes()和process_block()
    函数则可以处理任意长度的数据块,两者的输入参数不同,process_bytes()用数据块
    +数据块长度的形式,而process_block()接受数据块的开始和结束指针。
    在任何时候crc_optimal都可以调用checksum()函数获得当前已计算出的CRC值,或者调
    用reset()重置计算结果。


    crc_optimal还重载了括号操作符operator(),这有两种用法。不带参数的形式直接返回
    CRC值,相当于调用checksum(),带参数的形式接受一个字节,相当于调用process_byte()
    因此可以把crc_optimal对象当作函数对象传递给STL算法。
    例如:
    crc_32_type crc32;
    string str = "123456789";
    crc32.reset();
    //把crc对象作为函数对象传递给for_each算法处理字符串
    cout<<for_each(str.begin(), str.end(), crc32)()<<endl;
    cout<<crc32()<<endl;


    for_each函数存储的函数对象的副本。


    9.3.4 CRC函数
    crc_optimal模板类及其预定义实现类可以非常容易地计算CRC值,但crc库还提供了两个
    自由函数:crc()和augmented_crc()。


    crc()函数使用模板参数在内部构造crc_optimal对象,直接计算CRC值并立即返回,例如
    crc<16, 0x8005,0,0,true, true>("1234567890", 10);
    等同于crc_16_type().process_bytes("1234567890", 10);
    augmented_crc()的用法与crc()类似,只需要传递较少的参数,但它的计算规则与crc()
    和crc_optimal模板类有所不同,主要是为了方便计算附加crc码的数据。




    9.4 random
    random库专注于伪随机数的实现,有多种算法可以产生高质量的伪随机数,并提供随机
    数发生器、分布等很多有用的数学、统计学相关概念。


    9.4.1伪随机数发生器
    random库提供了26个伪随机数发生器,使用的算法包括线性同余算法、逆同余算法、
    Mersenne Twister算法、fibonacci算法、ranlux算法及它们的混合。


    在这里作者推荐三个伪随机数类:rand48、mt19937和lagged_fibonacci19937,它们产
    生随机数的速度由高到低,产生的随机数质量和所需内存则低到高。


    虽然这些随机数发生器使用的算法不同,实现也有较大的差异,但基本接口是类似的,
    像这样:
    class random_demo
    {
    random_demo(int 0x) //使用种子值的构造函数
    int seed(int x0); //设置种子值
    int operator()(); // 调用操作符,产生随机器
    };


    9.4 伪随机数发生器的构造
    伪随机数发生器在程序中应尽量少发生构造操作,最好是实现为单件。原因有两个,一是
    伪随机器发生器的构造成本相当昂贵,越是高质量的伪随机数发生器越需要初始化大量
    的内部状态。二是由于构造发生器提供的种子一般是使用系统时间,如果两个发生器
    构造的时间间隔很短,那很可能种子值是相同的,从而得到两个行为相同的伪随机数
    发生器,令随机器失去意义。


    9.4.4 随机数分布器
    伪随机数发生器能够产生高质量的随机数,但产生的随机数都是整数类型,均匀分布在
    整数域。
    random库为些提出了随机数分布器的概念,把伪随机数发生器产生的整数域随机数映射
    到另一种分布,提高了代码的复用性。
    random库提供了15个分布的映射类:
    uniform_smallint: 在小整数域内的均匀分布
    uniform_int: 在整数域上的均匀分布
    uniform_01: 在区间[0,1]上的实数连接均匀分布
    uniform_real: 在区间[min, max]上的实数连续均匀分布
    bernoulli_distribution: 伯努利分布
    binomial_distribution: 二项分布
    cauchy_distribution: 柯西(洛伦兹)分布
    gamma_distribution: 伽马分布
    poisson_distribution: 泊松分布
    geometric_distribution: 几何分布
    triangle_distribution: 三角分布
    exponential_distribution: 指数分布
    normal_distribution: 正态分布
    lognormal_distribution: 对数正态分布
    uniform_on_sphere: 球面均匀分布


    9.4.6 随机数分布器用法
    随机器分布器必须搭配随机数发生器才能工作,因此必须先定义一个随机数发生器作为
    随机数源,然后再选择恰当的模板参数定义一个分布器(如uniform_smallint<>),把随
    机数映射到某个区间的分布,最后调用分布器的operator()获得所需的随机数。


    9.4.7 变量发生器(随机数引擎)
    有了随机数发生器和分布器,已经基本可以解决大部分与随机数相关的问题了。但为了
    方便使用,random库还提供了一个模板类variate_generator<>(变量发生器),组合了
    随机数发生器和分布器,使产生随机数更加容易便捷。


    variate_generator的模板参数是随机数发生器Engine和分布器Distribution,Engine
    的类型可以是T(原始类型)、T*(指针类型)或者T&(引用类型),Distribution则必
    须是T(原始类型),不能是指针或引用。


    variate_generator在构造函数中指定随机数发生器和分布器的实例,可以用engine(),
    distribution()获得随机数发生器和分布器的引用,operator()产生随机数。


    variate_generator也是可以拷贝和赋值的,这意味着它同伪随机数发生器一样可以保存
    状态以备后用,但如果模板参数的Engine是引用类型则无法拷贝、赋值。


    variate_generator必须组合随机数发生器和分布器。


    C++11库提供了引用包装器reference_wrapper<T>。




    第10章 操作系统相关
    Boost提供了数个操作系统相关的库,其中的两个库system和filesystem即将成为C++
    将标准的一部分。


    io_state_savers库,它可以保存输入输出流的状态并自动恢复;第二个是system库,
    第三个是filesystem库,提供跨平台的文件系统处理能力。最后是program_options库
    它提供了强大的命令行参数解析和程序运行选项配置。


    本章的库除io_state_savers外都需要编译,这是由其平台相关特性所决定的。


    ios_savers有两个内部类型定义:state_type和aspect_type,它们标记了ios_saver的基
    本属性:
    state_type是ios_saver要保存的流类型,如ios_base、basic_ios<>;
    aspect_type是ios_saver要保存的具体流状态,如fmtflags、iostate.


    ios_savers的构造函数接受一个流对象的引用,同时保存该流的aspect_type值。另一种
    形式的构造函数则在保存的同时变更流的状态为new_value。
    ios_saver在析构时会自动把保存的状态恢复到流对象。我们也可以在任何时刻使用
    restore()函数手工恢复流状态。


    ios_saver把赋值操作符operator=声明为私有的,因此都是不可赋值的,但允许拷贝
    构造,可以在程序中拷贝一个ios_saver以备后用。


    10.1.2 用法
    io_state_savers库里有很多用于保存并恢复流状态的类,它们被分成四组,分别是:
    基本的标准属性保存器,如ios_flags_saver、ios_width_saver;
    增强的标准属性保存器,如ios_iostate_saver、ios_rdbuf_saver;
    自定义的属性保存器, 如ios_iword_saver、ios_pword_saver;
    组合的属性保存器,如ios_all_saver。


    最简单也是最常用的类是ios_all_saver和wios_ass_saver,它们可以保存流所有的状态
    wios_all_saver是ios_all_saver的宽字符形式,用来配合宽字符wcout使用。


    我们可以使用流的重定向功能,把日志的输出转到一个文件流中,
    但下面代码将发生崩溃的错误:
    int main()
    {
    string filename("c:/test.txt");
    cout<<"log start"<<endl;
    if(!filename.empty())
    {
    ofstream fs(filename.c_str()); // 打开一个文件流
    cout.rdbuf(fs.rdbuf()); // 标准输出重定向到文件流
    logging("fatal msg"); // 重定向输出日志
    } // 文件流被自动析构
    cout<<"log finish"<<endl; // cout无法输出,运行错误
    }
    出错的原因在于流的重定向。当文件流fs被设置为cout的缓冲区后,cout将总向它输出
    数据。但fs是一个局部变量,当离开if语句的作用域后它被自动销毁,导致缓冲区失效
    但cout对此并不知情,仍然向一个无效缓冲区写入数据,从而发生了严重的运行错误。


    是修复这个bug非常容易,只需要在重定向或任何可能改变流状态的操作前用
    ios_all_saver保存cout的状态。那么在离开作用域时它就会自动把cout恢复到最初的
    状态。如下所示:
    ios_all_saver ifs(cout); // 保存流的所有状态
    cout.rdbuf(fs.rdbuf());
    // 离开作用域,导致保存器析构,自动恢复流的状态。


    10.2 system库
    system库使用轻量级的对象封装了操作系统底层的错误代码和错误信息,它作为基础部
    件已经被filesystem和asio等库调用,并且被接受为C++0x TR2.


    10.3 filesystem
    目录、文件处理是脚本语言如shell、python、perl所擅长的领域,但C++语言缺乏对
    操作系统中文件的查询和操作能力,因此C++程序员经常需要再掌握另外一门脚本语言
    以方便自己的工作。


    filesystem库需要system库支持,因此必须先编译system库。
    filesystem库需要编译才能使用。
    如果使用嵌入源码的方式,需要在包含头文件之前定义宏BOOST_FILESYSTEM_NO_LIB、
    BOOST_SYSTEM_NO_LIB或者直接使用BOOST_ALL_NO_LIB。


    filesystem库的核心类是basic_path,它屏蔽了不同文件系统的差异,使用可移植的
    POSIX语法提供了通用的目录、路径表示,并且支持POSIX的符号连接概念。


    但通常我们不直接使用basic_path,而是使用预定义的typedef:path和wpath。


    10.3.3 路径表示
    path的构造函数可以接受C字符串和string,也可以是一个指定首末迭代器字符串序列
    区间。path使用标准的POSIX语法提供可移植的路径表示,它也是unix、linux的原生
    路径。POSIX语法使用斜杠/来分隔文件名和目录名,点号.表示当前目录,双点号表示
    上一级目录。


    path也支持操作系统的原生路径表示,比如在windows下使用盘符,分隔符使用反斜杠\
    因上反斜杠被C++定义为转义符,在字符串中使用反斜杠表示路径的时候必须连续写\\
    才能被识成一个\,因此这种方式通常没有unix风格的斜杠方式方便。


    path重载了operator/=,可以像使用普通路径一样用/来追加路径,成员函数append()
    也可以追加一个字符串序列。


    自由函数system_complete()可以返回路径在当前文件系统中的完整路径(也就是通常
    所说的绝对路径)。


    10.3.4 可移植的文件名
    filesystem库提供了一系列的文件名(或目录名)检查函数,可以根据系统的命名规则
    判断一个文件名字符串的有效性。


    自由函数portable_posix_name()和windows_name()分别检测文件名字符串是否符合
    POSIX规范和Windows规范。


    portable_name()判断名字是否是一个可移植的文件名,相当于portable_posix_name()
    &&windows_name(),


    filesystem库提供了个native()函数,它判断文件名是否符合本地文件系统命名规则,
    在windows下它等同于windows_name(),而在其他操作系统下则只是简单地判断文件名
    不是空格且不包含斜杠。


    10.3.5 路径处理
    path类提供了丰富的函数用于处理路径,可以获取文件名、目录名、判断文件属性等等,
    path的成员函数string()返回标准格式的路径表示,directory_string()返回文件系统
    格式的路径表示,parent_path()、stem()、filename()和extension()分别返回路径
    中的父路径、不含扩展名的全路径名、文件名和扩展名。


    is_complete()用于检测path是否是一个完整(绝对)路径,这需要依据具体的文件系统
    的表示,例如在windows系统的完整路径需要包括盘符。


    root_name()、root_directory()、root_path()这三个成员函数用于处理根目录,如果
    path中含有根,那么它们分别返回根的名字、根目录和根路径。


    relative_path()返回path的相对路径,相当于去掉了root_path()。


    根路径的相对路径的这四个函数都有对应的has_xxx()的形式,用来判断是否存在对应的
    路径。同样,has_filename()和has_parent_path()用于判断路径是否有文件名或者父
    路径。


    remove_filename()删除路径中最后的文件名,把path变为纯路径表示
    replace_extension()可以变更文件的扩展名。


    path类还提供迭代器begin()和end(),可以迭代路径中的字符串。


    当然了,我们还可以使用boost中的字符串处理库,如sting_algo、xpressive。


    我们通常应该使用下面两个异常类:
    typedef basic_filesystem_error<path> filesystem_error;
    typedef basic_filesystem_error<wpath> wfilesystem_error;


    示例,下面的代码检查文件的大小,如果文件不存在,则抛出异常:
    path p("c:/test.txt");
    try
    {
    file_size(p);
    }
    catch(filesystem_error &e)
    {
    cout<<e.path1()<<endl;
    cout<<e.what()<<endl;
    }
    由于文件系统位于程序之外,是不可控具全局共享的,因此访问目录或文件随时都有可
    能发生异常,例如文件已经删除、文件已存在等。


    应总使用try-catch块来保护文件访问代码。


    10.3.7 文件状态
    filesystem库提供一个文件状态类file_status及一组相关函数,用于检查文件的各种
    属性,如是否存在、是否是目录、是否是符号链接等。


    10.3.8 文件属性
    函数initial_path()返回程序启动时(进行main()函数)的当前路径
    函数current_path()返回当前路径。它和initial_path()返回的都是一个完整路径(绝
    对路径);
    函数file_size()以字节为单位返回文件的大小;
    函数last_write_time()文件的最后修改时间,是一个std::time_t。
    last_write_time()还可以额外接受一个time_t参数,修改文件的最后修改时间。就像是
    使用linux下的touch命令。


    这些函数都要求操作的文件必须存在,否则会抛出异常,file_size()还要求文件必须是
    个普通文件is_regular_file(name) == true


    此外,函数space()可以返回一个space_info结构,它表明了该路径下的磁盘空间分配
    情况,space_info的结构的定义如下:
    struct space_info
    {
    uintmax_t capacity;
    uintmax_t free;
    uintmax_t available;
    };
    space()函数可以这样使用:
    const int GBYTES =  1024*1024*1024;
    space_info si = space("d:/");
    cout<<si.capacity/GBYTES<<endl;
    cout<<si.available/GBYTES<<endl;
    cout<<si.free/GBYTES<<endl;


    10.3.9 文件操作
    filesystem库基于path的路径表示提供了基本的文件操作函数,如创建目录
    create_directory、文件改名rename、文件删除remove、文件拷贝copy_file等。。


    remove只能一次删除一个目录或文件,remove_all可以递归删除。
    create_directory只能创建一级目录,create_directories可以一次创建多级目录。


    10.3.10 迭代目录
    filesystem库使用basic_directory_iterator提供了迭代一个目录下所有文件的功能,
    它预定义了两个typedef:directory_iterator和wdirectory_iterator。


    directory_iterator用法有些类似string_algo库的find_iterator、split_iterator
    或xpressive库的regex_token_iteraator,空的构造函数生成一个逾尾end迭代器,
    传入path对象构造将开始一个迭代操作,返回调用operator++即可迭代目录下的所有
    文件,例如:director_iterator end;
    for(director_iterator pos("d:/boost/"; pos != end; ++pos)
    {
    cout<<*pos<<endl;
    }


    注意,basic_director_iterator迭代器返回的对象并不是path,而是一个
    basic_directory_entry对象,但basic_directory_entry类定义了一个到path的类型
    转换函数,因此可以在需要path的语境中隐式转换到path.


    basic_directory_entry可以用path()方法返回路径,status()返回路径的状态,
    directory_iterator只能迭代本层目录,不支持深度遍历目录,可以使用递归来实现
    这个功能。


    由于递归遍历文件系统目录的功能非常有用也很有必要,因此filesystem库专门提供了
    另一个类basic_recursive_directory_iterator,它遍历目录的效率要比递归的
    directory_iterator高很多。


    通常我们使用basic_recusive_directory_iterator的两个typedef:
    recursive_directory_iterator和wrecurisive_directory_iterator。


    recursive_directory_iterator能够迭代当前及子目录下的所有文件。
    成员函数level()返回当前的目录深度m_level。每深入一层子目录则m_level增加,退出
    时减少。成员函数pop()用于退出当前目录层次的遍历,同时--m_level。当迭代到一
    个目录时,no_push()可以让目录不参与遍历,使用recursive_directory_iterator
    和行为等价于directory_iterator。




    第11章 函数与回调
    首先我们学习两个小的工具类result_of和ref,result_of使用了复杂的技巧来自动推
    导函数的返回值类型,ref可以包装对象的引用,在传递参数时消除对象拷贝的代价
    或者将不可拷贝的对象变为可以拷贝。


    bind是C++98标准库中函数适配器的增强,可以适配任意的可调用对象,包括函数指针、
    函数引用和函数对象,把它们变成一个新的函数对象。function库则是对C++中函数
    指针类型的增强,它能够容纳任意的可调用对象,可以配合bind使用。


    最后是signals2库,它实现了威力强大的观察都模式。
    如同C#的event/delegate或者java的observable/observer。


    11、1 result_of


    11.1.1 原理
    所谓“调用表达式”,是指一个含有operator()的表达式,函数调用或函数对象调用
    都可以称为调用表达式,而result_of可以确定这个表达式所返回的类型。
    有点类似typeof库,typeof可以确定一个表达式的类型,但它不具备推演调用表达式
    的能力。


    result_of用到了很多C++的高级特性,如模板偏特化和SFINAE,并且部分依赖于编译
    器的能力。它不仅可以用于函数指针,更重要的是用于函数对象进行泛型编程。


    给定一个调用表达式,可以通过内部类型定义result_of<...>::type获得返回值的类型。


    假设我们有一个类型Func,它可以是函数指针、函数引用或者成员函数指针,当然也可
    以是函数对象类型,它的一个实例是func,Func有一个operator(),参数是(T1 t1,
    T2 t2),这里T1,T2是两个模板类型,那么
    result_of<Func(T1,T2)>::type
    就是func(t1, t2)的返回值类型。




    设类型Func可被调用(具有operator()),func是Func的一个左值,那么
    typeid(result_of<Func(T1, T2...,TN)>::type)
    必然等于
    typeid(func(t1, t2, ...tN))。


    11.2 ref
    STL和Boost中的算法和函数大量使用了函数对象作为判断式或谓词参数,而这些参数
    都是传值语义,算法或函数在内部保留函数对象的拷贝并使用。


    一般情况下传值语义都是可行的,但也有很多特殊情况,作为参数的函数对象拷贝
    代价过高(具有复杂的内部状态),或者不希望拷贝对象(内部状态不应该被改变),
    甚至拷贝是不可行的(noncopyable、单件)。
    boost.ref应用代理模式,引入对象引用的包装器概念解决了这个问题。


    ref库定义了一个很小很简单的引用类型的包装器。名字叫reference_wrapper。
    reference_wrapper的名字过长,声明引用包装对象很不方便,因而ref库提供了两个
    便捷的工厂函数ref()和cref()。可以通过参数类型推导很容易地构造reference_
    wrapper对象。
    因为reference_wrapper支持拷贝,因此ref()和cref()可以直接用在需要拷贝语义
    的函数参数中,而不专门使用一个reference_wrapper来暂存。例如:
    double x = 2.0;
    cout<<std::sqrt(ref(x))<<endl;


    11.2.4 操作包装
    ref库运用模板元编程技术提供两个特征类is_reference_wrapper和unwrap_reference
    用于检测reference_wrapper对象:
    is_reference_wrapper<T>的bool成员变量value可以判断T是否为一个reference_wrapper
    unwrap_reference<T>的内部类型定义type表明了T的真实类型,无论它是否经过
    reference_wrapper包装。


    自由函数unwrapper_ref()为解开包装提供了简便的方法,它利用unwrap_reference<T>
    直接解开reference_wrapper的包装,返回被包装对象的引用。


    unwrap_ref()的这个功能很有用,可以把unwrap_ref()安全地用在泛型代码中,从而不
    必关心对象的包装特性,总能够正确地操作对象。


    struct square
    {
    typedef void result_type;
    void operator()(int &x)
    {
    x = x*x;
    }
    };


    int main()
    {
    typedef double(*pfunc)(double);
    pfunc pf = sqrt;
    cout<<ref(pf)(5.0); // 包装函数指针


    square sq;
    int x = 5;
    ref(sq)(x);
    cout<<x;


    vector<int> v = (list_of(1), 2, 3, 4, 5);
    for_each(v.begin(), v.end(), ref(sq));
    }


    11.3 bind
    bind是C++98标准库中函数适配器bind1st/bind2nd的泛化和增强,可以适配任意的可
    调用对象,包括函数指针、函数引用、成员函数指针和函数对象。bind远远地超越了
    STL中的函数绑定器(bind1st/bind2nd),可以绑定最多9个函数参数,而且对被绑定
    对象的要求很低,可以在没有result_type内部类型定义的情况下完成函数对象的绑定。


    11.3.1工作原理
    bind并不是一个单独的类或函数,而是非常庞大的家族,依据绑定的参数个数和要绑定
    的调用对象类型,总共有数十个不同的形式,但它们的名字都叫做bind。


    bind接受的第一个参数必须是一个可调用对象f,包括函数、函数指针、函数对象和成员
    函数指针,之后bind接受最多九个参数,参数的数量必须与f的参数数量相等,这些参数
    将被传递给f作为入参。


    绑定完成之后,bind会返回一个函数对象,它内部保存了f的拷贝,具有operator(),
    返回类型被自动推导为f的返回值类型。在发生调用时,这个函数对象将之前存储的
    参数转发给f完成调用。


    例如,如果有一个函数func,它的形式是:
    func(a1, a2)
    那么,它将等价于一个具有无参operator()的bind函数对象调用:
    bind(func, a1, a2)()


    bind的真正威力在于它的占位符,它们分别被定义为_1, _2, _3一直到_9,位于一个匿名
    名字空间,占位符可以取代bind中参数的位置,在发生函数调用时才接受真正的参数。


    占位符的名字表示它在调用式中的顺序,而在绑定表达式中没有顺序的要求,_1不一
    定必须第一个出现,也不一定只出现一次。


    注意,必须在绑定表达式中提供函数要求的所有参数,无论是真实参数还是占位符均
    可以。占位符可以出现也可以不出现,出现的顺序和数量也没有限定。


    bind完全可以代替标准库中的bind1st和bind2nd,使用bind(f,N,_1)和bind(f,_1,N)。


    11.3.3 绑定成员函数
    bind也可以绑定类的成员函数
    类的成员函数不同于普通函数,因此成员函数指针不能直接调用operator(),它必须
    被绑定到一个对象或者指针,然后才能得到this指针进而调用成员函数。因此bind
    需要“牺牲”一个占位符的位置,要求用户提供一个类的实例、引用或者指针,通过
    对象作为第一个参数来调用成员函数。
    例如:
    bind(&X::func, x, _1, _2,...);


    注意,我们必须在成员函数前加上取地址操作符&,表明这是一个成员函数指针,否
    则会无法通过编译,这是与绑定函数的一个小小的不同。


    bind能够绑定成员函数,这样,它可以替代标准库中令人迷惑的mem_fun和mem_fun_ref
    绑定器,用来配合标准算法操作容器中的对象。


    下面的代码使用bind搭配标准算法for_each用调用容器中所有对象的print()函数:
    for_each(v.begin(), v.end(), bind(&point::print, _1));
    bind同样支持绑定虚拟成员函数,用法与非虚函数相同,虚函数的行为将由实际调用
    发生时的实例来决定。


    11.3.4 绑定成员变量
    bind的另一个对类的操作是它可以绑定public成员变量,就像是一个选择器,用法
    与绑定成员函数类似,只需要把成员变更名像一个成员函数一样去使用。


    仍然以point类为例子,假设我们已经在vector中存储了大量的point对象,而我们想要
    得到它们的x坐标值,那么bind可以这样使用:
    transform(v.begin(), v.end(), v2.begin(), bind(&point::x, _1));
    代码中bind(&point::x, _1)取出point对象的成员变量x。


    使用bind,可以实现SGISTL/STLport中的非标准函数适配器select1st和select2nd的功
    能,直接选择出pair对象的first和second成员。


    11.3.5 bind绑定函数对象
    bind不仅能够绑定函数和函数指针,也能够绑定任意的函数对象,包括标准库中的所有
    预定义的函数对象。
    如果函数对象有内部类型定义的result_type,那么bind可以自动推导出返回值类型,用
    法与绑定普通函数一样。但如果函数对象没有定义result_type,则需要在绑定形式上
    做一点改动,用模板参数指明返回类型。


    标准库和Boost库中的大部分函数对象都具有result_type定义,因此不需要特别的形式
    就可以直接使用bind。例如:
    bind(plus<int>(), _1, _2);


    对于自定义函数对象,如果没有result_type类型定义,例如:
    struct f
    {
    int operator() (int a, int b)
    {
    return a+b;
    }
    };


    那么我们必须指明bind的返回值类型,像这样。
    cout<<bind<int>(f(), _1, _2)(10, 20)<<endl;


    最在遵循规范为它们增加内部typedef result_type,这将使函数对象与许多其他标准
    库和Boost库组件良好配合工作。


    11.3.6 使用ref库
    bind采用拷贝的方式存储绑定对象和参数,这意味着绑定表达式中的每个变量都会有一
    份拷贝,如果函数对象或值参数很大、拷贝代价很高,或者无法拷贝,那么bind的使用
    就会受到限制。
    因此bind库可以搭配ref库使用。


    11.4 function
    function是一个函数对象的“容器”,概念上像是C/C++中函数指针类型的泛化,是一
    种“智能函数指针”。它以对象的形式封装了原始的函数指针或函数对象,能够容纳
    任意符合函数签名的可调用对象。因此,它可以被用于回调机制,暂时保管函数或
    函数对象,在之后需要的时机再调用,使回调机制拥有更多的弹性。


    function可以配合bind使用,存储bind表达式的结果,使bind可以被多次调用。


    11.4.2 function的声明
    function是一个模板类。
    function使用result_of的方式,用类似函数声明的形式一次给出所有函数的类型信息,
    这被称为“首选语法”。例如:
    function<int()> func;
    将声明一个可以容纳返回值为int、无参数函数的function对象。


    11.4.3 操作函数
    function的构造函数可以接受任意符合模板中声明的函数类型的可调用对象,如函数
    指针和函数对象,也可以是另一个function对象的引用,之后在内部存储一份它的拷贝。


    无参的构造函数或者传入空指针构造将创建一个空的function对象,不持有任何可调用
    物,调用空的function对象将抛出bad_function_call异常,因此在使用function前
    最好检测一下它的有效性。可以用empty()测试function是否为空,或用重载操作符
    operator!来测试。它是类型安全的。


    如果function存储的是函数对象,那么要求函数对象必须重载了operator==,是可比较
    的。


    两个function对象不能使得!=和==直接比较,这是特意的。因为function存在到bool的
    隐式转换,function定义了两个function对象的operatorr==但没有实现,企图比较两
    个function对象会导致编译错误。


    11.4.5 用法
    function就像是一个函数的容器,也可以把function想像成一个泛化的函数指针,只要
    符合它声明中的函数类型,任何普通函数、成员函数、函数对象都可以存储在function
    对象中,然后在任何需要的时候被调用。


    function的这种能够容纳任意可调用对象的能力是非常重要的,在编写泛型代码的时候
    尤其有用,它使我们可以接受任意的函数或函数对象,增加程序的灵活性。


    存储成员函数时可以直接在function声明的函数签名式中指定类的类型,然后用bind
    绑定成员函数:
    function<int(demo_class&, int, int)> func1;
    func1 = bind(&demo_class::add, _1, _2, _3);
    demo_class sc;
    cout<<func1(sc, 10, 20);


    11.4.8 使用ref库
    function使用拷贝语义保存参数。
    function能够直接调用被ref库包装的函数对象,这个功能可以部分地弥补boost.ref
    没有operator()的遗憾。


    如果ref库不提供operator(),那么它将无法用于标准库算法,因为标准库算法总使用
    拷贝语义,算法内部的改变不能影响原对象。


    11.4 function
    function是一个函数对象的“容器”,概念上像是C/C++中函数指针类型的泛化,是一种
    ”智能函数指针”,它以对象的形式封装了原始的函数指针或函数对象,能够容纳任意
    符合函数签名的或调用对象。因此,它可以被用于回调机制,暂时保管函数或函数 
    对象。


    function可以配合bind或ref使用。


    11.4.2 function的声明
    function是一个模板类,function使用result_of的方式,用类似函数声明的形式一
    次给出所有函数的类型信息,这被称为“首选语法”,例如:
    function<int()> func;
    将声明一个可以容纳返回值为int、无参函数的function对象。


    11.4.3 操作函数
    function的构造函数可以接受任意符合模板中声明的函数类型的可调用对象,如函数
    指针和函数对象,也可以是另一个function对象的引用,之后在内部存储一份它的
    拷贝。


    无参的构造函数或者传入空指针构造将创建一个空的function对象,不持有任何可
    调用物,调用空的function对象将抛出bad_function_call异常,因此在使用
    function前最好检测一下它的有效性。可以用empty()测试function是否为空,或
    者用重载操作符operator!来测试。function对象也可以在一个bool上下文中直接
    测试它是否为空,它是类型安全的。


    11.4.4 比较操作
    function重载了比较操作符operator==和operator!=,可以与被包装的函数或函数
    对象进行比较。如果function存储的是函数指针,那么比较相当于
    function.target<Functor>() == func_pointer
    例如:
    function<int(int, int)> func(f);
    assert(func == f);
    如果function存储的是函数对象,那么要求函数对象必须重载了operator==,是
    可比较的。
    两个function对象不能使用==和!=直接比较,因为function存在到bool的隐式转换


    function能够直接调用被ref库包装的函数对象,这个功能可以部分地弥补
    boost.ref没有operator()的遗憾。


    例如,下面代码定义了一个求总和的函数对象,它具有内部状态:
    template<typename T>
    struct summary
    {
    typedef void result_type;
    T sum;
    summary(T v = T()):sum(v){}
    void operator()(T const &x)
    { sum += x;}
    };


    如果ref库不提供operator(),那么它将无法用于标准库算法,因为标准库算法部使
    用拷贝语义,算法内部的改变不能影响原对象,function可以提供一个略显麻烦但
    可用的解法:
    summary<int> s;
    function<void(int const&)> func(ref(s)); // function包装引用
    for_each(v.begin(), v.end(), func); //使用标准库算法


    当需要存储一个可调用物用于回调的时候,最好使用function,它具有更多灵活性,
    特别是把回调作为类的一个成员的时候我们只能使用function。


    11.5 signals2
    signals2基于Boost的另一个库signals,实现了线程安全的观察者模式。在signals2
    库中,观察者模式被称为信号/插槽signals and slots,它是一种函数回调机制,一
    个信号关联了多个插槽,当信号发出时,所有关联它的插槽都会被调用。


    许多成熟的软件系统都用到了这种信号/插槽机制(另一个常用的名称是事件处理
    机制:event/event handler),它可以很好地解耦一组互相协作的类,有的语言甚至
    直接内建了对它的支持,如C#.


    signal的模板参数列表相当长,总共有七个参数,而且除了第一个是必须的外,其
    他的都可以使用默认值。


    第一个模板参数Signature的含义与function的一模一样,也是一个函数类型签名,
    表示可被signal调用的函数(插槽、事件处理handler)。例如:
    signal<void(int, double)>


    第二个模板参数combiner是一个函数对象,它被称为“合并器”,用来组合所有插槽
    的调用结果,默认是optional_last_value<R>,它使用optional库返回最后一个
    被调用的插槽的返回值。


    第三个模板参数Group是插槽编组的类型,缺省使用int来标记组号,也可以改为
    std::string等类型,但通常没有必要。


    第四个模板参数GroupCompare与Group配合使用,用来确定编组的排序准则,默认是
    升序std::less<Group>,因此要求Group必须定义了operator<。


    signal继承自signal_base,而signal_base又继承自noncopyable,因此signal是
    不可拷贝的,如果把signal作为自定义类的成员变量,那么自定义类也将是不可
    拷贝的,除非使用shared_ptr来包装它。


    11.5.2 操作函数
    signal最重要的操作函数是插槽管理connect()函数,它把插槽连接到信号上,相当
    于为信号(事件)增加了一个处理的handler.


    插槽可以是任意的可调用对象,包括函数指针、函数对象、以及它们的bind表达式
    和function对象,signal内部使用function作为容器来保存这些可调用对象。
    连接时可以指定组号也可以不指定组号,当信息发生时将依据组号的排序准则依次
    调用插槽函数。


    如果连接成功,connect()将返回一个connection对象,表示了信号与插槽之间的连
    这是一个轻量级的对象,可以处理两者间的连接,如断开、重连接、或者测试连接
    状态。


    成员函数disconnect()可以断开插槽与信号的连接,它有两种形式:传递组号将
    断开该组的所有插槽,传递一个插槽对象将仅断开该插槽。函数
    disconnect_all_slots()可以一次性断开信号的所有插槽连接。


    当前信号所连接的插槽数量可以用num_slots()获得,成员函数empty()相当于
    num_slots()==0,但它执行效率比num_slots()高。disconnect_all_slots()的
    后果就是令empty()返回true;


    signal提供operator(),可以接受最多9个参数,当operator()被外界调用时
    意味着产生一个信号(事件),从而导致信号所关联的所有插槽被调用。插槽调用
    的结果使用合并器处理后返回,默认情况下是一个optional对象。


    成员函数combiner()和set_combiner()分别用于获取和设置合并器对象,通过
    signal的构造函数也可以在创建的时候就传入一个合并器的实例。但通常我们可
    以直接使用缺省构造函数创建模板参数列表中指定的合并器对象,除非像不想改用
    其他的合并方式。


    当signal析构时,将自动断开所有插槽连接,相当于调用disconnect_all_slots()。


    11.5.3 插槽的连接与调用
    signal就像是一个增强的function对象,它可以容纳(使用connect()连接)多个符
    合模板参数中函数签名类型的函数(插槽),形成一个插槽链表,然后在信号发生时
    一起调用。
    除在类名字不同,signal的声明语法与function几乎一模一样。


    然后我们就可以使用connect()来连接插槽,最后用operator()来产生信号。


    void slots1()
    {
    cout<<"slot1 called"<<endl;
    }


    void slot2()
    {
    cout<<"slot2 called"<<endl;
    }


    int main()
    {
    signal<void()> sig; // 一个信号对象
    sig.connect(&slots1); // 连接插槽1
    sig.connect(&slots2); // 连接插槽2
    sig(); // 调用operator(),产生信号(事件),触发插槽调用
    }


    在连接插槽时我们省略了connect()的第二个参数connect_position,它的缺省值是
    at_back,表示插槽将插入到信号插槽链表的尾部,因此slots2将在slots1之后被
    调用。我们还要可以使用at_front位置标志。
    sig.connect(&slots2, at_front);
    那么slots2将在slots1之前被调用。


    使用组号:
    connect()函数的另一个重载形式可以在连接时指定插槽所在的组号,缺省情况下组
    号是int类型。组号不一定要从0开始连续编号,它可以是任意的数值、离散的、负值
    都允许。


    如果在连接的时候指定组号,那么每个编组的插槽将是又一个插槽链表,形成一个
    略微有些复杂的二维链表。它们的顺序规则如下:
    各编组的调用顺序由组号从小到大决定(也可以在signal的第四个模板参数改变
    排序函数对象);
    每个编组的插槽链表内部的插入顺序用at_back和at_front指定;
    未被编组的插槽如果位置标志是at_front,将在所有编组之前调用;
    未被编组的插槽如果位置标志是at_back,将在所有编组之后调用。


    我们使用一个新的函数对象slots来演示一下signal的编组,它是一个模板类:
    template <int N>
    struct slots
    {
    void operator()()
    {
    cout<<"slot"<<N<<"called"<<endl;
    }
    };


    signal的连接代码如下:
    sig.connect(slots<1>(), at_back); // 最后被调用
    sig.connect(slots<100>(), at_front); // 第一个被调用


    sig.connect(5, slots<51>(), at_back); //组号5,该组最后一个
    sig.connect(5, slots<44>(), at_front); // 组号5, 该组第一个


    sig.connect(10, slots<10>()); // 组号10,该组仅有一个


    11.5.4 信号的返回值
    signal如function一样,不仅可以把输入参数转发给所有插槽,也可以传回插槽的
    返回值。默认情况下signal使用合并器optional_last_value<R>,它将使用optional
    对象返回最后被调用的插槽的返回值。


    让signal支持拷贝
    signal是noncopyable的子类,这意味着它不能被拷贝或者赋值。如果出于某种理由,
    确实需要在多个对象之间共享signal对象,那么可以考虑使用
    shared_ptr<signal<Signature> >作为类的成员,shared_ptr可以很好地管理
    signal的共享语义。


    插槽调度
    因为signal会自动把解引用操作转换为插槽调用,所以自定义合并器某种程序上也相
    当于一个插槽调度器。程序可以不需求所有的插槽被调用,只选择那些符合特定条件
    的插槽,比如当一个插槽的返回值满足要求后就终止迭代,不再调用剩余的插槽。


    线程安全
    signal模板参数列表的最后一个类型参数是互斥量Mutex,默认值是signals2::mutex,
    它会自动检测编译器的线程支持程度,根据操作系统自动决定要使用和的系统互斥
    量对象(windows下使用临界区,UNIX下使用pthread_mutex),通常mutex都工作的很
    好,不需要改变它。


    signal对象在创建时会自动创建一个mutex保护内部状态,每一个插槽连接时也会创建
    出一个新的mutex,当信号或插槽被调用时mutex都会自动锁定,因此signal可以很好
    地工作于多线程环境。


    同样地,connection和shared_connection_block也是线程安全的,但用于自动连接
    管理的slot类不是线程安全的。


    signals2库中还有一个dummy_mutex,它是一个空的mutex类,把它作为模板参数可以
    使用signals2变成非线程安全的版本。


    由于mutex是signal的最后一个模板参数,要指定它需要写出很多缺省的类型,signals2
    使用元函数的方式可以便利地完成这个工作。


    typedef signal_type<int(int), keywords::mutex_type<dummy_mutex> >::type 
    signal_t;


    关于function
    signal内部使用function来存储可调用物,它的声明也与function很像,同样提供了
    operator()。在signal只连接了一个插槽的时候基本上可以与function替换。


    但需要注意它们的返回值,function对象直接返回被包装函数的返回值,而signal则
    使用optional对象作为返回值,真正的返回值需要使用解引用操作符*才能取得。


    与signals的区别
    signals是Boost库中另一个信号/插槽库,实际上signals2的实现是基于signals的。
    signals2与signals的最大区别是具有线程安全,能够用于多线程环境,而且不需要
    编译就可以使用。


    与C#的区别
    signals2中的信号/插槽机制原理上类似于C#语言的event/delegate机制。
    但C#的delegate的功能要比signals2弱,它要求精确的类型匹配,也没有合并器的概
    念,只能返回一个结果。


    delegate使得operator+=来连接event与delegate,signals2则使用connect()函数


    11.6 总结
    本章我们讨论了Boost库中五个用于函数和回调编程的组件。


    result_of库,它很小但功能很强大,使用了模板元编程技术,可以帮助确定一个调用
    表达式的返回类型,类似typeof库,主要用于泛型编程。


    ref也是一个很小的库。它最初是tuple库的一部分,后来由于其重要性而被移出。
    它能够包装对象的引用,变成一个可以拷贝、赋值的普通对象,因此减少了昂贵的
    复制代价,标准库算法、tuple,bind,function等许多库都可以从ref库受益。


    但Boost的ref库实现有个较大的缺陷,不支持operator()重载(函数调用)。


    bind是一个功能强大的函数绑定器,它远远地超越了历史上出现过的各种函数绑定器,
    包括标准库中的bind1st, bind2nd, mem_fun和非标准的select1st, select2nd,
    compose_f_gx等等。它可以绑定任何可调用对象,搭配标准算法可以获得灵活操作
    容器内元素的强大功能。bind还支持嵌套和操作符重载。


    function库是函数指针的泛化,可以存储任意可调用的对象,因此function库经常
    配合bind使用,它可以存储bind表达式的结果,以备之后调用。function具有很多优
    点,它是泛型的,比普通的函数指针能够接受更多的可调用对象。function也可以
    配合ref库使用,存储有内部状态的函数,


    最后介绍的是signals2库,它综合运用了前四个组件,使用了信号/插槽机制,
    是观察者设计模式的一个具体应用,也是一个功能强大的回调框架。使用signals2库
    可以简化对象间的通信关系,降低它们的耦合性,只需要在程序开始时把它们连接
    起来,之后的一切都会自动处理。


    signals2还有许多的高级用法,可以使用合并器任意处理插槽的返回值,可以自动
    跟踪插槽的生命周期。它也是线程安全的,能够被安全地应用在多线程程序中,而且
    不需要预告编译。




    第12章 并发编程
    Boost在头文件<boost/detail/atomic_count.hpp>也提供了一个原子计数器---atomic
    _count,它使用long进行线程安全的递增递减计数。


    12.1.5 线程对象
    thread类是thread库的核心类,负责启动和管理线程对象。


    在使用thread对象时需要注意它是不可拷贝的,虽然它没有从boost::noncopyable继承
    但thread内部把拷贝构造函数和赋值操作都声明为私有的,不能对它进行赋值或者
    拷贝构造。


    thread通过特别的机制支持转移语义,因此我们可以编写创建线程的工厂函数,封装
    thread的创建细节,返回一个thread对象。


    12.1.6 创建线程
    线程的创建需要传递给thread对象一个可调用物(函数或函数对象),它必须具
    有operator()以供线程执行。


    如果可调用物不是无参的,那么thread的构造函数也支持直接传递所需的参数,这些
    参数将被拷贝并在发生调用时传递给函数,thread的构造函数支持最多传递九个参数。


    在传递参数是需要注意,thread使用的是参数的拷贝,因此要求可调用物和参数类型
    都支持拷贝。如果希望传递给线程引用值就需要使用ref库进行包装,同时必须保证
    被引用的对象在线程执行期间一直存在,否则会引发未定义的行为。


    启动线程
    当成功创建了一个thread对象后,线程就立刻开始执行,thread不提供类似start(),
    begin()那样的方法、


    如下函数,它向标准输出流打印字符串:
    mutex io_mu; // io流是个共享资源,不是线程安全的,需要锁定
    void printing(atom_int& x, const string& str) 
    {
    for(int i = 0; i < 5; ++i)
    {
    mutex::scoped_lock lock(io_mu); // 锁定io流操作
    cout<<str<<++x<<endl;
    }
    }


    那么我们可以这样使用thread对象:
    int main()
    {
    atom_int x; // 原子操作的计数器
    // 使用临时thread对象启动线程
    thread(pirnting, ref(x), "hello"); // 向函数传递多个参数
    thread(printing, ref(x), "boost"); // 使用ref库传递引用


    this_thread::sleep(posix_time::seconds(2)); // 等待2秒钟
    }
    在当线程启动后我们必须调用sleep()来等待线程执行结束,否则会因为main()的
    return语句导致主线程结束,而其他的线程还没有机会运行而一并结束。


    join和timed_join
    thread的成员函数joinable()可以判断thread对象是否标识了一个可执行的线程体。
    如果joinable()返回true,我们就可以调用成员函数join()或者timed_join()来
    阻塞等待线程执行结束。两者的区别如下:
    join()一直阻塞等待,直到线程结束。
    timed_join()阻塞等待线程结束,或者阻塞等待一定的时间段,然后不管线程是否
    结束都返回。注意,这不必阻塞等待指定的时间长度,如果在这段时间里线程运行
    结束,即使时间未到它也会返回。


    使用join()可以这样操作thread对象:
    atom_int x;
    thread t1(printing, ref(x), "hello");
    thread t2(printing, ref(x), "boost"); 
    t1.timed_join(posix_time::seconds(1)); // 最多等待1秒然后返回
    t2.join(); // 等待t2线程结束再返回,不管执行多少时间。


    与线程执行体分离
    可以使用成员函数detach()将thread与线程执行体手动分离,此后thread对象不代表任
    何线程体,失去对线程体的控制。
    thread t1(printing, ref(x), "hello"); // 启动线程
    t1.detach(); // 与线程执行体分离,但线程继续执行
    当thread与线程执行体分离时,线程执行体将不受影响地继续执行,直到运行结束,或
    者随主线程一起结束。
    当线程执行完毕或者thread对象被销毁时,thread对象也会自动与线程执行体分离,因
    此,当不需要操作线程体时,我们可以使用临时对象启动一个线程。


    使用bind和function
    有时在thread的构造函数中写传递给调用函数的参数很麻烦,尤其是在使用大量线程
    对象的时候。这时候我们可以使用Boost的bind和function库:bind库可以把函数所
    需的参数绑定到一个函数对象,而function则可以存储bind表达式的结果,供程序以后
    使用:
    例如:
    thread t3(bind(printing, ref(x), "thread")); // bind表达式
    function<void()> f = bind(printing,5, "mutex"); // 使用function对象
    thread(f);


    12.1.7 操作线程
    通常情况下一个非空的thread对象唯一地标识了一个可执行的线程体,是joinable()的
    成员函数get_id()可以返回线程id对象。


    线程id提供了完整的比较操作符和流输出操作,因此可以被放入标准容器用来管理线程
    thread类也通过线程id支持线程间的比较操作。


    thread类还提供了三个很有用的静态成员函数:yield()、sleep()和hardware
    _concurrency(),它们用来在线程中完成一些特殊的工作。
    yield()函数指示当前线程放弃时间片,允许其他的线程运行。
    sleep()让线程睡眠等待一小段时间,注意它要求参数是一个system_time UTC时间点
    而不是时间长度
    haredware_concurrency()可以获得硬件系统可并行的线程数量,即CUP数量或者CPU内
    核数量,如果无法获取信息则返回0。


    例如,下面代码令当前线程睡眠1秒钟,然后输出可并行的线程数量:
    thread::sleep(get_system_time()+posix_time::seconds(1));
    cout<<thread::haredware_concurrency()<<endl;


    thread库也在子名字空间this_thread里提供了3个自由函数---get_id()、yield()和
    sleep()用于操作当前线程。它们的功能同thread类的同名函数,分别用来获得线程
    id、放弃时间片和睡眠等待,但this_thread的sleep()函数不仅可以使用绝对的UTC
    时间点,也可以使用时间长度。
    例如:
    this_thread::sleep(posix_time::seconds(2)); // 睡眠2秒钟
    cout<<this_thread::get_id();
    this_thread::yield();


    12.1.8 中断线程
    thread的成员函数interrupt()允许正在执行的线程被中断,被中断的线程会抛出一个
    thread_interrupted异常,它是一个空类,不是std::exception或者boost::exception
    的子类。thread_interrupted异常应该在线程执行函数里捕获并处理,如果线程不处理
    这个异常,那么默认的动作是中止线程。


    下面函数使用this_thread::sleep()睡眠1秒钟再输出字符串:
    void to_interrupt(atom_int &x, const string &str)
    try
    {
    for(int i = 0; i < 5; ++i)
    {
    this_thread::sleep(posix_time::seconds(1)); // 睡眠1秒钟
    mutex::scoped_lock lock(io_mu); // 锁定io流操作
    cout<<str<<++x<<endl; 
    }
    }
    catch(thread_interrupted &) // 捕获中断异常
    {
    cout<<"thread_interrupted"<<endl; // 显示消息
    }


    main()启动这个线程,等待两钞钟,然后中断线程的执行:
    int main()
    {
    atom_int x;
    thread t(to_interrupt, ref(x), "hello");
    this_thread::sleep(posix_time::seconds(2)); // 睡眠2秒钟
    t.interrupt(); // 要求线程中断执行
    t.join(); // 因为线程已经中断,所以join()立刻返回
    }


    线程的中断点
    线程不是在任意时刻都可以被中断的。如果我们将to_interrupt()函数中的sleep()
    睡眠等待去掉,那么即使在主线程中调用interrupt()线程也不会被中断。


    thread库预定义了若干个线程的中断点,只有当线程执行到中断点的时候才能被中断,
    一个线程可以拥有任意多个中断点。


    thread库预定义了共9个中断点,它们都是函数,如下:
    thread::join()
    thread::timed_join();
    condition_variable::wait()
    condition_variable::timed_wait()
    condition_variable_any::wait()
    condition_variable_any::timed_wait()
    thread::sleep()
    this_thread::sleep()
    this_thread::interuption_point()


    这些中断点的前8个都是某种形式的等待函数,表明线程在阻塞等待的时候可以被中断。
    而最后一个位于子名字空间this_thread的interruption_point()则是一个特殊的中断
    点函数,它并不等待,只是起到一个标签的作用,表示线程执行到这个函数所在的
    语句就可以被中断。


    启用/禁用线程中断
    缺省情况下线程都是允许中断的,但thread库允许控制线程的中断行为。
    thread库在子名字空间this_thread提供了一组函数和类来共同完成线程的中断启用和
    禁用:
    interruption_enabled()函数检测当前线程是否允许中断。
    interruption_requested()函数检测当前线程是否被要求中断。
    类disable_interrruption是一个RAII类型对象,它在构造时关闭线程的中断,析构时
    自动恢复线程的中断状态。


    在disable_interruption的生命期内线程始终是不可中断的,除非使用了restore_
    interruption对象。


    restore_interruption只能在disable_interruption的作用域内使用,它在构造时
    临时打开线程的中断状态,在析构时又关闭中断状态。


    void to_interrupt(atom_int &x, const string& str)
    try
    {
    using namespace this_thread; // 打开this_thread名字空间
    assert(interruption_enabled()); // 此时允许中断
    for(int i = 0; i < 5; ++i)
    {
    disable_interruption di; // 关闭中断
    assert(!interruption_enabled()); // 此时中断不可用
    mutex::scoped_lock lock(io_mu); // 锁定io流操作
    cout<<str<<++x<<endl;
    cout<<this_thread::interruption_requested()<<endl;
    this_thread::interruption_point(); // 中断点被禁用


    restore_interruption ri(di); // 临时恢复中断
    assert(interruption_enabled()); // 此时中断可用
    cout<<"can interrupted"<<endl;
    cout<<this_thread::interruption_requested()<<endl;
    this_thread::interruption_point(); // 可中断
    } // 离开作用域,di/ri都被析构
    assert(interruption_enabled()); // 此时允许中断
    }
    catch(thread_interrupted &)
    {...}


    12.1.9 线程组
    thread库提供类thread_group用于管理一组线程,就像是一个线程池,它内部使用
    std::list<thread*> 来容纳创建的thread对象。


    thread_group的接口很小,用法也很简单.
    成员函数create_thread()是一个工厂函数,可以创建thread对象并运行线程,同时加
    入内部的list。我们也可以在thread_group外部创建线程对象,使用add_thread()
    加入到线程组。


    如果不需要某个线程,remove_thread()可以删除list里的thread对象。
    join_all()和interrupt_all()用来对list里的所有线程对象操作,等待或中断这些
    线程。
    例如:
    thread_group tg;
    tg.create_thread(bind(printing, ref(x), "c++"));
    tg.create_thread(bind(printing, ref(x), "boost"));
    tg.join_all();
    使用thread_group,我们可以为程序建立一个类似全局线程池的对象,统一管理程序中
    使用的thread。这个可以使用前面的单件库变成一个单件,以提供一个全局的访问点,
    例如:
    typedef singleton_default<thread_group> thread_pool;
    thread_pool::instance().create_thread(...);


    不过Boost的单件库不提供完全的线程安全。


    12.1.10 条件变量
    条件变更是thread库提供的另一种用于等待的同步机制,可以实现线程间的通信,它
    必须与互斥量配合使用,等待另一个线程中某个事件的发生(满足某个条件),然后
    线程才能继续执行。


    thread库提供两种条件变量对象condition_variable和condition_varriable_any,
    一般情况下我们应该使用condition_vairable_any,它能够适应更广泛的互斥量
    类型。


    条件变量的使用方法很简单:
    拥有条件变量的线程先锁定互斥量,然后循环检查某个条件,如果条件不满足,那么
    就调用变量的成员函数wait()等待直至条件满足。其他线程处理条件变更要求的条件,
    当条件满足时调用它的成员函数notify_one()或notify_all(),以通知一个或者所
    有正在等待条件变更的线程停止等待继续执行。


    其他用法
    条件变量的wait()函数有一个有用的重载形式:wiat(lock_type& lock, predicate_type
    predicate),它比普通的形式多接受一个谓词函数(或函数对象),当谓词predicate不
    满足时持续等待。


    使用这个重载形式或以写出更简洁清晰的代码,通常需要配合bind来简化谓词函数的
    编写。
    例如,buffer类的两个条件变量的等待可以改写成:
    cond_put.wait(mu, !bind(&buffer::is_full, this));
    cond_get.wait(mu, !bind(&buffer::is_empty, this));


    12.1.11 共享互斥量
    共享互斥量shared_mutex不同于mutex和recursive_mutex,它允许线程获取多个共享
    所有权和一个专享所有权,实现了读写锁的机制,即多个读线程一个写线程。


    shared_mutex具有mutex的全部功能,可以把它像mutex一样使用lock()和unlock()
    来获得专享所有权,但它代价要比mutex高很多。如果要获得共享所有权需要使用
    lock_shared()或try_lock_shared(),相应地要使用unlock_shared()来释放共享
    所有权。


    shared_mutex没有提供内部的lock_guard类型定义,因此在使用shared_mutex时我们
    必须直接使用lock_guard对象,读锁定时使用shared_lock<shared_mutex>,写锁定
    时使用unique_lock<shared_mutex>。


    以下使用shared_mutex实现多个读者一个作者:
    class rw_data
    {
    private:
    int m_x; // 用于读写的数据
    shared_mutex rw_mu; // 共享互斥量
    public:
    rw_data():m_x(0){} // 构造函数
    void write()// 写数据
    {
    unique_lock<shared_mutex> ul(rw_mu); // 写锁定
    ++m_x; 
    }


    void read(int *x) // 读数据
    {
    shared_lock<shared_mutex> sl(rw_mu); // 读锁定
    *x = m_x;
    }
    };


    12.1.12 future
    很多情况下线程不仅仅要执行一些工作,它还可能要返回一些计算结果。


    thread库使用future范式提供了一种异步操作线程返回值的方法,


    future使用packaged_task和promise两个模板类来包装异步调用,用unique_future
    和shared_future来获取异步调用结果(即future值)。


    package_task和unique_future
    packaged_task用来存储packaged_task异步计算得到的future值,它只能持有结果的
    唯一的一个引用。成员函数wait()和timed_wait()的行为类似thread.join(),可以
    阻塞等待packaged_task的执行,直至获得future值。成员函数is_ready()、has_value()
    和has_exception()分别用来测试unique_future是否可用。


    下面代码示范了future特性的用法,使用packaged_task和unique_future:
    int fab(int n)
    {
    if (n == 0 || n ==1)
    {return 1;}
    return fab(n-1) + fab(n-2);
    }


    int main()
    {
    // 声明packaged_task对象,用模板参数指明返回值类型
    // packaged_task只接受无参函数,因此需要使用bind
    packaged_task<int> pt(bind(fab, 10));


    // 声明unique_future对象,接受packaged_task的future值
    // 同样要用模板参数指明返回值类型
    unique_future<int> uf = pt.get_future();


    // 启动线程计算,必须使用boost::move()来转移package_task对象
    // 因为packaged_task是不可拷贝的
    thread(boost::move(pt));
    uf.wait(); // unique_future等待计算结果
    assert(uf.is_ready() && uf.has_value()); 
    cout<<uf.get(); // 输出计算结果99
    }


    使用多个future对象
    为了支持多个future对象的使用,future还提供wait_for_any()和wiat_for_all()两个
    自由函数,它们可以阻塞等待多个future对象,直到任意一个或者所有future对象都
    可以(is_ready())。这两个函数有多个重载形式,可以接受一对表示future容器区间
    的迭代器或者最多5个future对象。


    promise
    promise也用于处理异步调用返回值,但它不同于packaged_task,不能包装一个函数,
    而是包装一个值,这个值可以作为函数的输出参数,适用于从函数参数返回值的函数。


    promise的用法与packaged_task类似,在线程中用set_value()设置要返回的值,用
    成员函数get_future()获得future值赋给future对象。




    12.2 asio
    asio库基于操作系统提供的异步机制,采用前摄器设计模式proactor实现了可移植的
    异步(或者同步)IO操作,而且并不要求使用多线程和锁定,有效地避免了多线程
    带来的诸多有害副作用(如条件竞争、死锁等)。


    目前asio主要关注于网络通信方面,使用大量的类和函数封装了socket API,提供了
    一个现代C++风格的网络编程接口,支持TCP、ICMP、UDP等网络通信协议。但asio的
    异步操作并不局限于网络编程,它还支持串口读写、定时器、SSL等功能。而且
    asio是一个很好的富有弹性的框架,可以扩展到其他有异步操作需要的领域。


    使用asio不需要编译,但它依赖于其他一些Boost库组件,最基本的是boost.system
    和boost.datetime库,用来提供系统错误和时间支持。其他的可选库有regex、thread
    和serialization,如果支持SSL,还要额外安装OpenSSL.


    12.2 asio
    asio库基于操作系统提供的异步机制,采用前摄器设计模式proactor实现了可移植的
    异步(或者同步)IO操作,而且并不要求使用多线程和锁定,有效地避免了多线程编程
    带来的诸多有害副作用(如条件竞争、列锁等)。


    目前asio主要关注于网络通信方面,使用大量的类和函数封装了socket API,提供了一
    个现代C++风格的网络编程接口,支持TCP、ICMP、UDP等网络通信协议。但asio的异步
    操作并不局限于网络编程,它还支持串口读写、定时器、SSL等功能,而且asio是
    一个很好的富弹性的框架,可以扩展到其他有异步操作需要的领域。


    本章下面对asio的介绍将以基本功能为主,即不使用system和datetime以外的Boost
    组件。




    12.2.1 概述
    asio库基于前摄器模式proactor封装了操作系统的select、poll/epoll、kqueue、
    overlapped I/O等机制,实现了异步IO模型。它的核心类是io_service,相当于前
    摄器模式中的proactor角色,asio的任何操作都需要有io_service的参与。


    在同步模式下,程序发起一个IO操作,向io_service提交请求,io_service把操作
    转交给操作系统,同步地等待。当IO操作完成时,操作系统通知io_service,然后
    io_service再把结果发回给程序,完成整个同步流程。这个处理流程与多线程的
    join()等待方式很相似。


    在异步模式下,程序除了要发起的IO操作,还要定义一个用于回调的完成处理函数。
    io_service同样把IO操作转交给程序系统执行,但它不同步等待,而是立即返回。
    调用io_service的run()成员函数可以等待异步操作完成,当异步操作完成时io_service
    从操作系统获取执行结果,调用完成处理函数。


    asio不直接使用操作系统提供的线程,而是定义了一个自己的线程概念:strand,它保证
    在多线程的环境中代码可以正确的执行,而无需使用互斥量。io_service::strand::
    wrap()函数可以包装一个函数在strand中执行。


    IO操作会经常使用到缓冲区,asio库专门用两个类mutable_buffer和const_buffer
    来封装这个概念,它们可以被安全地应用在异步的读写操作中,使用自由函数buffer()
    能够包装常用的C++容器类型,如数组、array、vector、string等,用read()、write()
    函数来读取缓冲区。


    asio库使用system库的error_code和system_error来表示程序运行的错误。基本上所
    有的函数都有两种重载形式,一种形式是有一个error_code的输出参数,调用后必
    须检查这个参数验证是否发生了错误;另一种形式没有error_code参数,如果发生了
    错误会抛出system_error异常,调用代码必须使用try_catch块来捕获错误。


    12.2.2 定时器
    定时器是asio库里最简单的一个IO模型示范,提供等候时间终止的功能。
    定时器deadline_timer有两种形式的构造函数,都要求有一个io_service对象,用于
    提交IO请求,第二个参数是定时器的终止时间。可以是posix_time的绝对时间点或者
    是自当前时间开始的一个时间长度。


    一旦定时器对象创建,它就会立即开始计时,可以使用成员函数wait()来同步等待
    定时器终止,或者使用async_wait()异步等待,当定时器终止时会调用handler函数。


    如果创建定时器时不指定终止时间,那么定时器不会工作,可以使用成员函数
    expires_at()和expires_from_now()分别设置定时器终止的绝对时间和相对时间,然后
    再调用wait()和async_wait()等待。这两个函数也可以用于获得定时器的终止时间,只
    需要使用它们的无参重载形式。


    定时器还有一个cancel()函数,它的功能是通知所有异步操作取消,转而等待定时器
    终止。


    同步定时器
    下面示范了deadline_timer的用法
    #include <boost/asio.hpp>
    #include <boost/date_time/posix_time/posix_time.hpp>
    using namespace boost::asio; // 打开asio名字空间
    int main()
    {
    io_service ios; // 所有的asio程序必须要有一个io_service对象
    deadline_time t(ios, posix_time::seconds(2)); // 定时器,io_serice作为构造函数
    的参数, 两秒钟后定时器终止
    cout<<t.expires_at()<<endl; // 查看终止的绝对时间 
    t.wait(); // 调用wait()同步等待
    cout<<"Hello asio"<<endl; // 输出一条消息
    }


    读者可以把它与thread库的sleep()函数对象研究一下,两者虽然都是等待,但内部
    机制完全不同:thread库的sleep()使用了互斥量和条件变量,在线程中等待,而asio
    则是调用了操作系统的异步机制,如select、epoll等完成的。


    上面演示了asio程序的基本结构和流程:一个asio程序首先要定义一个io_service对象
    它是前摄器模式中最重要的proactor角色,然后我们声明一个IO操作(在这里是定时
    器),并把它挂接在io_service上,再然后就可以执行后续的同步或异步操作。


    异步定时器
    代码与同步定时器大致相同,增加了回调函数,并使用io_service.run()和定时器的
    async_wait()方法。


    首先我们需要定义一个回调函数,asio库要求回调函数只能有一个参数,而且这个
    参数必须是const asio::error_code& 类型:
    void print(system::error_code &/*e*/)
    {
    cout<<"hello asio"<<endl;
    }


    随后的异步定时器代码也同样很简单:
    int main()
    {
    io_service ios;
    deadline_timer t(ios, posix_time::seconds(2)); // 定时器
    t.async_wait(print); // 异步等待,传入回调函数
    cout<<"it show before t expired."<<endl;
    ios.run(); // 很重要,异步IO必须
    }
    当定时器时间终止时io_service将调用被注册的print(),输出一条消息,然后程序
    结束。




    异步定时器使用bind
    由于async_wait()接受的回调函数类型是固定的,必须使用bind库来绑定参数以适
    配它的接口。


    下面是可以定时执行任意函数的定时器。它持有一个asio定时器对象和一个计数器
    还有一个function()对象用来保存回调函数。


    12.2.4 asio库支持TCP,UDP和TCMP通信协议,它在名字空间boost::asio::ip里
    提供了大量的网络通信方面的函数和类,很好地封装了原始的Berkeley Socket API,
    展现给asio用户一个方便易用且健壮的网络通信库。




    12.2.8 查询网络地址
    之前关于TCP通信的所有论述我们都是使用直接的IP地址,但在实际中大多数只有一个
    域名,这时候我们就需要使用resolver类来通过域名获得可用的IP,它可以实现与
    IP版本无关的网址解析。


    串口通信
    asio除网络通信处,也支持串口通信,需要使用serial_port类。串口通信的基本处理
    流程与网络通信类似,但要在通信前设置好波特率、奇偶校验位等串口通信参数。
    int main()
    {
    io_service ios;
    serial_port sp(ios, "COM1"); // 打开串口COM1


    // 设置串口参数
    sp.set_option(serial_port::baud_rate(9600));
    sp.set_option(serial_port::flow_control(serial_port::flow_control::none));
    sp.set_option(serial_port::parity(serial_port::parity::none));
    sp.set_option(serial_port::stop_bits(serial_port::stop_bits::one));
    sp.set_option(serial_port::character_size(8)); 
    size_t len = sp.write_some(buffer("hello serial")); // 向串口写数据
    cout<<len<<endl;


    vector<char> v(100);
    sp.async_read_some(buffer(v), bind(read_handler, placeholders::error));
    // 异步接收数据
    deadline_timer t(ios, posix_time::seconds(2)); // 处理超时
    t.async_wait(bind(time_exipred, placeholders::error, &sp)); 
    ios.run(); // 启动事件等待循环
    }




    第13章 编程语言支持
    boost中的python库可以更加方便和容易地在python和C++之间自由转换,而且功能更强
    大。python库全面支持C++和python的各种特性,包括C++到python的异常转换、默认
    参数、关键字参数、引用和指针等等,让C++与python可以近乎完美地对接:用C++很
    容易地为python编写扩展模块,也可以很容易地在C++代码中执行python程序。


    python是一种简洁的、功能强大的、动态强类型的解释型语言。
    python具有比C/C++语言还要简洁的语法,使用代码缩进而不是分号来分隔语句,同时
    简化了许多传统的语法结构,从而具有优雅的代码格式。


    python也有具有强大的功能,内建了多种高级数据结构,如列表、集合、元组和字典,
    python完全支持面向过程编程和面向对象编程(实际上,python里的一切几乎都是对象)
    支持异常、名字空间、操作符重载等现代编程机制。它还有一个强大的标准库和许多
    第三方库,如正则表达式、数据库、XML、电子邮件、测试、图形界面。。。如果想
    要定制功能也很容易,python可用C语言扩展底层,增添新模块。


    python是一个动态语言,类似php、perl,无需声明变量类型就可以使用。但它又是
    强类型的,变量一旦初始化,就不能随意改变类型。python不需要编译,它可以动态
    地解释交互运行,类似BASIC,这大大缩短了程序的开发周期。但python也能够像java
    一样编译成字节码后运行在虚拟机(解释器)上以获得更高的效率。


    python是可移植的,在各种操作系统上都有免费的python解释器。在这些平台上python
    可以代替批处理、shell、或者perl,编写脚本程序方便日常工作,也可以开发非常
    复杂的应用程序或者服务程序,python能够做其他语言所能做的所有事情。


    最初的python是用C语言写成的,后又逐渐出现了其他语言编写的python,如java的
    Jython和C#的IronPython,它们不仅具有python的功能,还可以调用宿主语言,更为
    强大。因此,传统的python有时又被称为C-Python。


    13.1.3 编译python库
    boost.python库需编译才能使用,要求前提是已经安装了python环境。


    13.1.4 使用python库
    python库位于名字空间boost::python,为了使用python库,需要包含头文件<boost/
    python.hpp>。


    13.2 嵌入python
    我们先从python库最简单的用法---嵌入python语句开始。这种使用方式可以调用python
    语言的标准库和第三方库,就像拥有了一群数量庞大的库函数,让C++不费任何力气就
    拥有了脚本语言的操纵能力。


    嵌入python语言需要链接python的运行库python26.lib。它在c:/Python26/Libs目录下,
    可以在VC的工程属性中设置链接库选项,但最好是使用预处理指令放在源代码中,如:
    #pragma comment(lib, "python26.lib") // vc系列编译器支持这个指令
    #define BOOST_PYTHON_SOURCE
    #include <boost/python.hpp>
    using namespace boost::python;


    13.2.1 初始化解释器
    在C程序中执行python语句有一个标准流程:
    首先要调用python API函数Py_Initialize()启动Python解释器;
    解释器启动后,可以使用Py_IsInitialized()来检查解释器是否已经成功启动;
    在完成所有的Python调用后,使用Py_Finalize()来清除解释器环境。


    目前的boost.python库不完全遵循上面的流程,库文档建议不执行Py_Finalize()来清除
    环境,因此在C++中只需要调用Py_Initialize()就可以了。


    Python API相当的简陋,而python库并没有对它进行封装,读者可以自已对python库
    的API进行封装,使它使用更方便。


    13.2.2 封装python对象
    python库使用模板类handle和object封装了Python API中的PyObject类型,handle是一
    个智能指针,一般情况下我们应当优先使用object。


    object类封装了PyOjbect,内部也使用了引用计数,使用起来就像Python语言中的
    原生变量,或者是C++中的auto和boost.any。




    13.2.3 执行Python语句
    启动python解释器后,可以使用python库提供的exe()系列函数执行python语句,这些
    函数的声明如下:
    object eval(str expression, object globals, object locals)
    object exec(str code, object globals, object locals)
    object exec_file(str filename, object globals, object locals)
    这三个函数的功能类似,都可以执行python语句,但有小的不同:eval()函数计算表达
    式的值并返回结果,exec()执行python语句并返回结果,而exec_file()则执行一个文件
    中的python代码。


    函数接口中的globals和locals参数是python中的字典结构,是语句运行的全局和局部
    场景,通常这两个参数可以忽略,或者取__main__模块的名字空间字典。


    例如:
    cout<<extract<int>(eval("3**3"))<<endl; // 计算3的立方
    exec("print "hello python""); // 输出语句


    如果在python语句使用了变量,那么必须要指定globals参数,使用import()函数可以
    导入__main__模块,用成员函数attr()获取属性。


    #pragma comment(lib, "python26.lib")
    #define BOOST_PYTHON_SOURCE
    #include <boost/python.hpp>
    using namespace boost::python;


    int main()
    {
    Py_Initialize();


    // 获取运行所需的名字空间
    object main_ns = import("__main__").attr("__dit__");


    // 执行for 循环
    string str = "for x int range(1, 5):\n"
    "\tprint x";
    exec(str.c_str(), main_ns); // 输出1,2,3,4


    // 定义一个python函数,计算x的y次方
    char *funcdef = "def power(x, y):\n"
    "\t return x**y\n"
    "print power(5, 3)\n"; 
    exec(funcdef, main_ns); // 输出125
    object f = main_ns["power"]; // 使用名字空间字典获得函数对象
    cout<<extract<int>(f(4,2))<<endl; // 用operator()执行


    // 导入re模块,执行正则表达式功能,输入true
    exec("import re", main_ns);
    exec("print re.match('c*', 'ccc') != None", main_ns);
    }


    异常处理
    如果执行python语句发生错误,python库会抛出error_already_set异常,但不含有
    任何信息,需要调用API函数PyErr_Print()向标准输出打印具体的错误信息。


    13.3 扩展Python
    python库能够在C++程序中调用Python语言,但它更重要的功能在于用C++编写Python
    扩展模块,嵌入到Python中解释器中调用,提高Python的执行效率。


    扩展Python我们同样需要编译python库,并包含头文件<boost/python.hpp>,但不必
    指明Python的运行库即可自动链接(需设定库文件搜索路径,默认是C:\Python26\libs)


    首先我们要在VC中建立一个DLL工程,名字叫boostpy,是一个不使用预编译头的空工程,
    不要忘记设置工程字符集的Not Set和运行多线程的MT或MTD属性,并加入宏"_STLP_DEBUG"
    和"__STL_DEBUG"(如果使用了STLport)。


    接下来我们在boostpy.cpp中编写函数的导出代码,使用BOOST_PYTHON_MODULE宏定义
    Python模块名,模块名必须与dll的名字相同,当然也可以在编译后改dll的名字。


    // 需要导出的函数
    string hello_func()
    {
    return "hello python";
    }


    在宏BOOST_PYTHON_MODULE定义的模块内部我们使用def()函数定义要导出的函数,需要
    指定导出的名字和C++的函数名字,它的语法一定程序上模仿了Python语言的函数定义
    关键字def。


    #define BOOST_PYTHON_SOURCE // 源码嵌入工程编译的方式
    #include <boost/python.hpp> 
    using namespace boost::python; // 打开名字空间
    string hello_func(){...} // C++函数定义


    BOOST_PYTHON_MODULE(boostpy)
    {
    // 导出一个只字为hello的函数,其doc string是“函数说明字符串”
    def("hello", hello_func, "函数说明字符串");
    }


    不需要担心这个简短的程序中没有DllMain(),WINAPI等Windows编程中常见的dll导出
    元素,也不需要写def或者exp文件,python库为我们在幕后做了一切,将自动生成一个
    完全可用的动态链接库boostpy.dll。


    但这个dll不能直接被python环境识别,必须把后缀名改成标准的python模块后缀名pyd
    即boostpy.pyd(也可以修改VC工程设置,直接生成后缀是pyd的dll文件),然后放置
    到python环境可以找到的路径下---通常是python主目录或主目录下的lib目录.
    这样,我们就可以在python交互解释器IDLE测试这个最小的python扩展模块。


    boostpy模块也可以在python脚本中运行,使用脚本运行扩展模块时不要求pyd模块必
    须在python主目录下,只要和脚本在同一个目录即可。
    例如下面的test.py脚本:
    #coding:uft-8
    #file test.py
    import boostpy  #导入boostpy模块
    print boostpy.hello() #使用print语句输出结果


    13.3.2 导出函数
    python库使用模板函数def()来导出C++函数到python,它有多个重载形式,声明是:
    template <class F>
    void def(char const* name, F f,...);
    def()函数要求导出的名字必须是C字符串(以0结束的字符数组),不能是std::string
    对象,第二个参数是类型为F的函数指针,这之后可以添加文档字符串以及函数的参数
    列表等函数的附加信息。


    向python导出函数的参数需要使用python库中的参数关键字类arg:


    为了支持C++和Python的缺省参数特性,arg重载了operator=,可以指定参数的缺省值。
    它还重载了逗号操作符,可以使用逗号把arg参数连接起来(很像assign库的用法)。但
    在def()函数中使用时,为了不与函数参数分隔的逗号混淆,我们需要把arg逗号表达
    式用圆括号括起来。


    导出带有参数的C++函数:
    string hello_to(const string& str) // 接收一个字符串参数
    {return "hello" + str;}


    string hello_x(const string& str, int) // 接收字符串和整数参数
    {
    string tmp += "hello";
    for (int i = 0; i < x; ++i)
    {
    tmp += "str" +" ";
    }
    return tmp;
    }


    然后我们在宏BOOST_PYTHON_MODULE定义的导出模块中导出它们,并使用arg类定义它
    们在python中的参数。
    def("helloto", hello_to, arg("str")); // 定义一个参数
    def("helloto", hello_x, (arg("str"), "x")); // 使用逗号操作符


    在导出函数时参数名不一定非要与C++中的一致,可以是任意的名字,只要它符合
    python的命名规则即可。


    编译生成新的pyd文件后,可以使用下面的python脚本调用验证。


    print boostpy.hello_to('boost')
    print boostpy.hello_x('C++', 5);


    python库另外提供了一个便捷的函数args(),可以用在不需要指定参数值的时候直接
    使用参数名字符串生成多个arg对象,例如:
    def("hellox", hello_x, args("str", "x"));


    13.3.3 导出重载函数
    C++和python中都有重载函数的概念,它们可以名字相同但参数和返回值不同。
    python导出C++重载函数的时候不能使用之前的def()形式,因为无法从函数名字区分
    出重载函数,必须使用函数指针的类型定义


    使用宏自动导出
    如果程序中有大量的重载函数,那么手工定义函数指针的工作将会很繁琐,因此
    python库特意提供了一个方便的宏BOOST_PYTHON_FUNCTION_OVERLOADS,专门用于简化
    重载函数的定义,它可以自动产生重载函数说明,声明如下:
    #define BOOST_PYTHON_FUNCTION_OVERLOADS(generator_name, fname, min_args, 
    max_args)


    使用BOOST_PYTHON_FUNCTION_OVERLOADS有一点限制,要求重载函数必须具有顺序相同
    的参数序列,即少的参数序列是多的参数序列的子集。
    宏BOOST_PYTHON_FUNCTION_OVERLOADS 可以这样使用:
    BOOST_PYTHON_FUNCTION_OVERLOADS(hello_overloads, hello_func, 0, 2)
    其中hello_overloads是我们为重载函数指定的辅助类名,hello_func是重载函数的名
    字,数字0和2表示有三个重载形式,参数最少是0个,最多是3个。


    在使用def()导出前,我们还必须定义最多参数的重载函数指针类型,即:
    typedef string (*hello_ft) (const string&, int);
    然后我们就可以向Python一次性导出全部重载函数:
    def("hello", (hello_ft)0, hello_overloads());
    注意,上面def()函数中第二个参数,即函数指针参数的用法,我们把一个空指针
    转换成了hello_ft函数指针类型,然后再用辅助类的临时对象hello_overloads()以
    导出所有函数。


    我们也可以仍然使用函数指针,但需要用最多参数的那个函数指针:
    def("hello", fp3, hello_overloads());


    BOOST_PYTHON_FUNCTION_OVERLOADS的另一个用法是导出具有缺省参数值的函数,这种
    函数就像是有N个重载形式的函数,例如:
    string hello_func(const string& str = "boost", int x = 5);
    typedef string(*hello_ft)(const string&, int);
    def("hello", (hello_ft)0, hello_overloads());
    对于有相同参数序列或者缺省参数的函数使用宏BOOST_PYTHON_FUNCTION_OVERLOADS,
    其他的则使用手工定义函数指针的方式。


    13.3.4 导出类
    python库的另一个强大的功能是可以方便地把C++类导出为Python类,这在python库
    出现前是一件非常烦琐且容易出错的工作。python库使用一个类似python语法的模板
    类“Class"封装了这项工作。


    class_类的用法与def()函数基本相同,它导出模板参数T类型为python类,再用成员
    函数def()、def_readonly()等分别导出T的成员函数和成员变量。


    例如,我们把之前的hello()系列函数改为一个简单的C++类:
    class demo_class
    {
    private:
    string msg;
    public:
    static string s_hello;
    demo_class():msg("boost"){} // 缺省构造函数
    string hellox(int x = 1)
    {
    string tmp = s_hello;
    for (int i = 0; i < x; ++i)
    {
    tmp += msg + "";
    }
    return tmp;
    }
    };
    string demo_class::s_hello = "hello"; // 静态成员变量初始化


    那么使用class_可以这样导出类:
    BOOST_PYTHON_MODULE(boostpy)
    {
    class_<demo_class>("demo", "doc_string")
    .def("hello", &demo_class::hellox, arg("x")=1)
    .def_readwrite("shello", &demo_class::s_hello);
    }




    构造函数
    我们不能使用def()来导出构造函数,因为C++中的构造函数不同于普通的成员函数,最
    重要的区别是不能取它的地址,即没有这样的语法:
    &demo_class::demo_class
    因此,python库使用模板类init<...>和optional<...>来共同定义构造函数和构造函数
    中的缺省参数。它们的模板参数都是构造函数的参数类型,init中的参数是必须出现的,
    而optional中的参数是有缺省值可以不出现的,它们的用法很像定义重载构造函数。


    导出property
    使用def_readonly()和def_readwrite()我们可以导出C++类的成员函数,同时指定它
    的读写属性,但这两个函数要求类的成员变量必须是public的。




    第14章 其他Boost组件
    14.1 字符串和文件处理
    regex
    spirit:
    spirit是一个面向对象的递归下降解析器的生成框架,它使用EBNF语法,是一个比正则
    表达式更强大的语法分析器。


    14.2 容器与数据结构
    gil
    它是一个通用图像库,它为像素、色彩、通道等图像处理概念提供了泛型的、STL式
    的容器和算法,可以对图像做灰度化、梯度、均值、旋转等许多运行,支持JPG、
    PNG,TIFF等文件格式。


    graph库处理离散数学中的图结构,并提供图、矩阵等数据结构上的泛型算法,例如
    Dijkstra最短路径、Prim最小生成树、连通分支等,可以看作是STL在非线性容器
    领域的扩展。


    intrusive


    pointer container提供了与STL类似的若干种指针容器,包括ptr_vector、ptr_list、
    ptr_map等,性能较好且异常安全。


    multi_index


    iterator


    range


    14.4 函数对象与高级编程
    mem_fn 是STL中函数对象适配器mem_fun和mem_fun_ref的扩展,类似bind与bind1st,
    bind2nd的关系,可以完全取代C++98中的成员函数适配器,能够把成员函数适配为
    可用于STL算法的函数对象。


    mem_fn的用法与bind很类似,仅仅是少传递了一个类实例参数,在大多数情况下
    bind可以完全替代mem_fn。


    functional


    hash


    lambda


    signals实现了观察都模式,功能和用法与signals2基本相同,但不支持线程安全,而
    且需要编译,应该使用signals2库。


    parameter




    14.5 泛型编程
    enable_if为允许模板函数或者模板类在偏特化时针对某些特定类型有效,即启用或
    禁用某些特化形式,它依赖于SFINAE原则。


    call_traits
    class_traits<T>封装了可能是最好的传递参数给函数的方式,它会自动推导出最高
    效的传递参数的类型:内建类型传值,类实例则传引用,而且保证不会出现”引用的
    引用“这个非法的错误。


    type_traits
    type_traits提供了一组特征(trait)类,用以在编译期确定类型是否具有某些特征,
    例如类型T是否是一个指针,是否是个抽象类,是否重载了new操作符等,它还包含一
    套可以对某个类型执行特定转换的工具类。使用type_traits可以编写出更高效的泛型
    代码。
    type_traits已被收入TR1中。


    concept check主要被用来编写实现泛型算法或泛型库。


    function_types这个库提供了对函数、函数指针、函数引用和成员指针等类型进行分类、
    分解和合成的功能。


    in_place_factory库是工厂设计模式的一种实践,允许就地直接构造对象而不需要一
    个临时对象的拷贝。


    proto库允许在C++中构建专用领域嵌入式语言,


    property_map是一个概念库,提供了key_value映射的属性概念定义,为从键到值的
    映射定义了一个通用接口


    14.6 模板元编程
    fusion库提供基于tuple的编译期容器(vector, set, map等)和算法,是模板元编程
    的强大工具,可以与mpl很好地协同工作。


    map是一个模板元编程框架,包含有编译期的算法、容器和函数等完整的元编程工具。
    运用mpl,很多运行时的工作都可以在编译期完成,甚至编译结束就意味着程序的
    运行结束。




    14.7 预处理元编程
    preprocessor
    wave


    14.8 并发编程
    interprocess实现在可移植的进程间通信IPC的功能,包括共享内存、内存映射文件、
    信号量、文件锁、消息队列等许多现代操作系统的IPC机制,并提供了简洁易用的
    STL风格接口,大大简化了IPC编程工作。


    MPI库可用于高性能分布式并行计算应用的开发,它封装了标准的MPI(消息传送
    接口)以更好地支持现代C++编程风格。


    14.9 数学与数字
    accumulators


    interval库处理“区间”概念相关的数学问题。


    map库包括common_factor,octonion,quaternion等六个组件,包含了大量数学领域
    的模板类和算法,如复数的反三角函数asin, acos、最大公约数和最小公倍数(lcm,
    gcd)、由元数、八元数,还有许多特殊数学函数和统计分布(拉格朗日多项式、
    椭圆积分、X方分布,伯努得分布等)。


    uBLAS是一个用于线性代数领域的数学库。它支持单位向量、稀疏向量、密集矩阵、
    稀疏矩阵、三角矩阵等许多线性代数概念,可以对向量或矩阵进行加法和减法,与
    标量做乘法,执行内积、外积等许多矩阵运行,这些接口被设计成与STL类似的风格。


    14.10 TR1
    TR1(Technical Report1)C++库扩展技术报告。




    第15 章 Boost与设计模式
    23个设计模式,分为创建型模式、结构型模式和行为模式三个大类。

    展开全文
  • 对于EditBox类,在2.2.6版本之前,在iOS 8上,如果单击输入,键盘弹出时,会发生界面偏移错误,出现黑色背景部分。关闭键盘时,界面偏移不回来了。 引擎3.5/2.2.6解决了这个Bug。 2.2.6版本中修复了iPhone6和iPhone6...

    20170326添加:
    尽量使用最新的类,因为被遗弃的类一般都有功能和性能上的缺陷,所以不建议使用。
    对于EditBox类,在2.2.6版本之前,在iOS 8上,如果单击输入,键盘弹出时,会发生界面偏移错误,出现黑色背景部分。关闭键盘时,界面偏移不回来了。
    引擎3.5/2.2.6解决了这个Bug。
    2.2.6版本中修复了iPhone6和iPhone6 Plus的一些BUG,支持苹果的64位架构。
    对于游戏中用到文字的部分,尽量使用Label类和ui命名空间下Text类型的类来实现,LabelTTF等2.x中的类都被遗弃了。


    TableView控件即表格控件
    // 创建一个TableView对象,并返回其指针
    m_table = TableView::create(this, cocos2d::Size(SCREEN_WIDTH, 604));
    // 设置表格委托,pDelegate参数表示指向委托对象的指针 
    m_table->setDelegate(this);
    // 设置滚动方向
    m_table->setDirection(TableView::Direction::VERTICAL);
    // 设置网格垂直填充顺序,TOP_DOWN代表从上到下排列,BOTTOM_UP代表从下到上排列
    m_table->setVerticalFillOrder(TableView::VerticalFillOrder::TOP_DOWN);
    this->addChild(m_table, 0);
    // 加载数据刷新表格绘制
    m_table->reloadData();

    表格控件涉及3个非常重要的辅助类:
    TableViewCell类为表格行对象类
    TableViewDataSource类为表格数据源类
    TableViewDelegate类为表格的委托类

    //用来响应表格触摸事件的方法,table参数为指向表格对象的指针,cell参数为指向行对象的指针
    void LotteryKindScrollView::tableCellTouched(TableView* table, TableViewCell* cell)
    {
     // getIdx得到此TableViewCell对象对应的行索引
            int tag = cell->getIdx();
     //if (tag < 0) return; 
     //
     //pCell = cell;
    }
    // 返回表格总行数,table参数为指向表格对象的指针
    ssize_t LotteryKindScrollView::numberOfCellsInTableView(TableView *table)
    {
     int result = m_Count / 4;
     int tempLast = m_Count % 4; //除4有余数,则加1
     if (tempLast != 0) result ++;
     return result;
    }

    // 返回给定表格的行对象尺寸,table参数为指向表格对象的指针
    cocos2d::Size LotteryKindScrollView::cellSizeForTable(TableView *table)
    {
     cocos2d::Size size = cocos2d::Size(SCREEN_WIDTH, 201); //高度定死
     return size;
    }

    // 返回表格每一行内容,table参数为指向表格对象的指针,idx参数为行对象的索引值
    TableViewCell* LotteryKindScrollView::tableCellAtIndex(TableView *table, ssize_t idx)
    {
     TableViewCell* cell = new TableViewCell();

     if(m_Data.size() < 1) return cell;

     for(int i = 0 ; i < 4 ; i ++)
     {  
      int data_index = idx * 4 + i + 1;
      if (data_index > m_Count) break;
      int kindId = m_Data.at(data_index - 1); 
      string nameStr = String::createWithFormat("%s%02d.png",m_name.c_str(),data_index)->getCString();

      if(data_index > 18)
      {   
       nameStr = String::createWithFormat("%s%02d_2.png",m_name.c_str(),data_index)->getCString();
      } 

      if (m_Count < 9) //游戏为4个,这里用9作参考
      {
       nameStr = String::createWithFormat("%s%02d_2.png",m_name.c_str(),data_index)->getCString();
       if(data_index == 1)
       nameStr = String::createWithFormat("%s%02d.png",m_name.c_str(),data_index)->getCString();
       if(data_index == 2)
       nameStr = String::createWithFormat("%s%02d.png",m_name.c_str(),data_index)->getCString();
       if(data_index == 4)
       nameStr = String::createWithFormat("%s%02d.png",m_name.c_str(),data_index)->getCString();
       if(data_index == 3)
        nameStr = String::createWithFormat("%s%02d.png",m_name.c_str(),data_index)->getCString();
       if(data_index == 5)
        nameStr = String::createWithFormat("%s%02d.png",m_name.c_str(),data_index)->getCString();
      } 

      Sprite *kindSprite = Sprite::createWithSpriteFrame(spriteFrame(nameStr));
      kindSprite->setPosition(Vec2(SCREEN_WIDTH * (i * 2 + 1) / 8, 100));
      kindSprite->setTag(LOTTERY_KIND_SPRITE_TAG+kindId);
      cell->addChild(kindSprite);  
     }

     return cell;
    }

    //辅助类——编辑文本框的委托类EditBoxDelegate
    class KongJianLayer : public Layer,public EditBoxDelegate

    {
    //开始编辑回调方法
    virtual void editBoxEditingDidBegin(EditBox* editBox);
    //结束编辑回调方法
    virtual void editBoxEditingDidEnd(EditBox* editBox);
    //内容变化回调方法
    virtual void editBoxTextChanged(EditBox* editBox,const std::string& text);
    //编辑返回回调方法
    virtual void editBoxReturn(EditBox* editBox);
    CREATE_FUNC(KongJianLayer);
    EditBox* m_peditAccount;
    EditBox* m_peditPW;

    };
     static EditBox* create(EditBoxDelegate* pDelegate, CCRect rcEditBox, const char *file, const char *PlaceHolder, int MaxLength,
      const char* pFontName="微软雅黑", int fontSize=20,
      //5种回显模式
      EditBox::InputFlag inputFlag=EditBox::InputFlag::PASSWORD,//密码模式
      EditBox::InputFlag inputFlag=EditBox::InputFlag::SENSITIVE,//敏感数据模式
      EditBox::InputFlag inputFlag=EditBox::InputFlag::INITIAL_CAPS_WORD,//单词首字母大写模式
      EditBox::InputFlag inputFlag=EditBox::InputFlag::INITIAL_CAPS_SENTENCE,//句子首字母大写模式
      EditBox::InputFlag inputFlag=EditBox::InputFlag::INITIAL_CAPS_ALL_CHARACTERS,//所有字母大写模式
      //7种输入模式
      EditBox::InputMode inputMode=EditBox::InputMode::ANY,//任何类型
      EditBox::InputMode inputMode=EditBox::InputMode::EMAIL_ADDRESS,//email地址类型
      EditBox::InputMode inputMode=EditBox::InputMode::NUMERIC,//数字类型
      EditBox::InputMode inputMode=EditBox::InputMode::PHONE_NUMBER,//电话号码类型
      EditBox::InputMode inputMode=EditBox::InputMode::URL,//URL类型
      EditBox::InputMode inputMode=EditBox::InputMode::DECIMAL,//小数类型
      EditBox::InputMode inputMode=EditBox::InputMode::SINGLELINE,//单行输入类型
      EditBox::KeyboardReturnType returnType=EditBox::KeyboardReturnType::Default)
     {
      CCEditBox *pEditBox = CCEditBox::create(rcEditBox.size, CCScale9Sprite::create( file ));
      pEditBox->setPosition(CCPoint(rcEditBox.getMinX(), rcEditBox.getMinY()));
      //setFont设置编辑文本框显示文本所采用的字体样式和字体尺寸,pFontName参数为目标样式字体文件路径,fontSize表示字体尺寸值
      //setFontName设置字体样式;setFontSize设置字体尺寸
      pEditBox->setFont(pFontName, fontSize);
      pEditBox->setPlaceholderFont(pFontName, fontSize);
      //setFontColor设置编辑文本框显示文本颜色
      pEditBox->setFontColor(ccBLACK);
      //setPlaceHolder设置输入为空时编辑文本框的显示内容
      pEditBox->setPlaceHolder(PlaceHolder);
      //setPlaceholderFontColor设置输入为空时编辑文本框显示内容的颜色
      pEditBox->setPlaceholderFontColor(ccWHITE);
      //setMaxLength设置编辑文本框输入文本最大长度,MaxLength参数为输入文本的最大长度
      pEditBox->setMaxLength(MaxLength);
      //setReturnType设置此文本编辑控件的返回类型,returnType参数为要设置的返回类型
      pEditBox->setReturnType(returnType);
      //setInputFlag设置输入回显模式,inputFlag参数为要设置的回显模式
      pEditBox->setInputFlag(inputFlag);
      //setInputMode设置编辑文本框的输入模式,inputMode参数为要设置的输入模式
      pEditBox->setInputMode(inputMode);
      //setDelegate设置编辑文本框的委托,pDelegate表示指向委托对象的指针
      pEditBox->setDelegate(pDelegate);
      return pEditBox;
     }

    chap22 GUI框架概述
    Cocos2d-x引擎自带的基础UI控件Menu、MenuItem、Label、TextFieldTTF等,以及2.x本意欲一统GUI框架的CCControlXXX系列控件。它们都没能形成系统的UI框架,直到CocosStudio的出现,以CocosStudio为基础的

    Widget系列UI控件才逐渐形成了系统的UI框架。

    在旧版本引擎当中,不存在动画帧的类AnimationFrame。
    精灵帧缓冲类的函数
    addSpriteFramesWithFile通过plist文件来添加精灵帧
    addSpriteFramesWithFile通过plist文件和纹理图片的名字来添加精灵帧
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pic_show.plist","pic_show.png");
    SpriteFrameCache::getInstance()->removeSpriteFramesFromFile("pic_show.plist");
    3.3 百分比动作特效
    百分比动作的实现类
    ProgressTo(从初始百分比为0%开始的动作)和ProgressFromTo这两个类都直接继承了ActionInterval类
    duration参数表示动作持续的时间
    percent参数表示目标百分比
    reverse方法表示获取当前动作的逆动作,并返回其指针
    fromPercentage表示初始百分比
    toPercentage表示目标百分比
    与普通动作不同,百分比动作不应该直接被精灵等可见物体执行,而是需要将希望执行这些动作的精灵包裹进ProgressTimer对象来执行。
    //sp参数表示指向其他包裹的精灵对象的指针
    Sprite* sp=Sprite::createWithSpriteFrame(spriteFrame((i==2||i==3||i==6||i==7)?texture_name::s_cd_bg_other_h:texture_name::s_cd_bg_other_v));
    //创建ProgressTimer对象,返回其指针
    m_pCountDown[i]=ProgressTimer::create(sp);
    m_pCountDown[i]->setPosition(ccp(myConfig.m_CountDownPos[i][0],myConfig.m_CountDownPos[i][1]));
    //设置工作模式,RADIAL代表半径模式,BAR代表水平/垂直模式
    m_pCountDown[i]->setType(ProgressTimer::Type::RADIAL);
    //设置动作是否逆序执行,true为逆序执行,false为非逆序执行 
    m_pCountDown[i]->setReverseDirection(true);
    m_pCountDown[i]->setVisible(true);
    addChild(m_pCountDown[i],2);
    CCLOG("m_pCountDown Pos%d=\"%f,%f\" ",i,m_pCountDown[i]->getPositionX(),m_pCountDown[i]->getPositionY());
    if (iViewID == MY_VIEW_CHAIR_ID)
    {
    m_pCountDown[iViewID]->runAction(CCSequence::create(CCShow::create(),ProgressFromTo::create(45,95,5),CCHide::create(),CCCallFunc::create(this,callfunc_selector(DZPKLayer::selfTimeOut)),NULL));
    }
    else
    {
    m_pCountDown[iViewID]->runAction(CCSequence::create(CCShow::create(),ProgressFromTo::create(45,95,5),CCHide::create(),NULL));
    }

    OpenGL坐标系是指以屏幕左下角为原点的坐标系
    屏幕坐标系是指以屏幕左上角为原点的坐标系
    //getLocation获取触控点在OpenGL坐标系中的当前坐标
    bool LotteryKindScrollView::onTouchBegan(Touch *pTouch, Event *pEvent)
    {
        if(pTouch->getLocation().y > 150 + 604 || pTouch->getLocation().y < 150)
           return false; 
        start_pos = pTouch->getLocation();
        return true;
    }
    //getLocationInView获取触控点在屏幕坐标系中的当前坐标
    void Player::ccTouchesBegan(CCSet* touches, CCEvent* event)
    {
     if ( !m_MySelf )
      return;

     CCSetIterator iter = touches->begin();
     for(; iter != touches->end(); iter++)
     {
      CCTouch* pTouch = (CCTouch*)*iter;
      EmitGun(CCDirector::sharedDirector()->convertToGL( pTouch->getLocationInView() ));
     }
    }

    Vec2类一些常用的求距离与夹角的方法  
    //getDistance获取自身与另一个Vec2对象所代表的二维向量坐标点之间的距离
    Vec2 pos = pTouch->getLocation(); 
    if(pos.getDistance(start_pos) > 10) return;


     

    20170322问题:
    Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
    解决方法:
    schedule_selector声明的回调函数要带一个float参数
    20170305添加:
    7.1.1按钮类ControlButton
    示例:测试按钮——ControlButtonTest 
    // 背景
    CCScale9Sprite *pBackground;
    // 确定
    CCControlButton *pSubmit;
    // 取消
    CCControlButton *pCancel;
    // 关闭
    CCControlButton *pClose;
    // 按钮的标题字
    Label *titlebutton;
    // 按钮的标题字
    Scale9Sprite *pBackground=Scale9Sprite::create("button.png");
    // 创建按钮对象
    ControlButton *pSubmit=ControlButton::create(titlebutton,pBackground);
    // 背景九宫格
     void backgroundWithSpriteFrameName(const char *pszSpriteFrameName);
     void backgroundWithFile(const char *file);
     
     // 按钮九宫格
     // 从帧名加载
     void buttonSubmitWithSpriteFrameName(const char *pszSpriteFrameName);
     void buttonCancelWithSpriteFrameName(const char *pszSpriteFrameName);
     void buttonCloseWithSpriteFrameName(const char *pszSpriteFrameName);
     // 从文件加载
     void buttonSubmitWithFile(const char *file,const char *text=0,const char *fontName="微软雅黑",int fontSize=12);
     void buttonCancelWithFile(const char *file,const char *text=0,const char *fontName="微软雅黑",int fontSize=12);
     void buttonCloseWithFile(const char *file,const char *text=0,const char *fontName="微软雅黑",int fontSize=12);
     // 从文件加载并均分,实现四态按钮
     void buttonSubmitWithFileCell(const char *file,int cell,bool Hor=true);
     void buttonCancelWithFileCell(const char *file,int cell,bool Hor=true);
     void buttonCloseWithFileCell(const char *file,int cell,bool Hor=true);

    适配功能函数
    // 获得设计分辨率大小
     winSize = CCDirector::sharedDirector()->getWinSize();
    void DZPKLayer::dzpk_ToPortrait()
    {
    #if (CC_TARGET_PLATFORM ==CC_PLATFORM_WIN32)
     GLView* eglView = Director::getInstance()->getOpenGLView(); 
     eglView->setViewName("QiXing");
     eglView->setFrameSize(WINDOW_WIDTH,WINDOW_HEIGHT);
     eglView->setDesignResolutionSize(SCREEN_WIDTH, SCREEN_HEIGHT, kResolutionExactFit);
    #endif
     //切换竖屏代码
    #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
     GLView *pEGLView = Director::getInstance()->getOpenGLView();
            // 获得屏幕分辨率大小
     cocos2d::Size frameSize = pEGLView->getFrameSize();
     JniMethodInfo minfo;
     if( JniHelper::getStaticMethodInfo(minfo,"org.cocos2dx.cpp.AppActivity","changedActivityOrientation","(I)V") )
     {
      minfo.env->CallStaticVoidMethod(minfo.classID,minfo.methodID,2);
     }
     pEGLView->setFrameSize(frameSize.height,frameSize.width);
            // 设置设计分辨率和适配方式
     pEGLView->setDesignResolutionSize(SCREEN_WIDTH, SCREEN_HEIGHT,kResolutionExactFit);
    #endif
    }

    void DZPKLayer::dzpk_ToLandscape()
    {
    #if (CC_TARGET_PLATFORM ==CC_PLATFORM_WIN32)
     MyConfig::Instance().LoadData("xml/MyConfig.xml");
     GLView* eglView = Director::getInstance()->getOpenGLView();
     eglView->setViewName("dzpk");
     eglView->setFrameSize(SCREEN_HEIGHT*0.8,SCREEN_WIDTH*0.8);
     eglView->setDesignResolutionSize(SCREEN_HEIGHT,SCREEN_WIDTH,kResolutionExactFit);
    #endif
     //切换横屏代码
    #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
     GLView *pEGLView = Director::getInstance()->getOpenGLView();
            // 获得屏幕分辨率大小
     cocos2d::Size frameSize = pEGLView->getFrameSize();
     JniMethodInfo minfo;
     if( JniHelper::getStaticMethodInfo(minfo,"org.cocos2dx.cpp.AppActivity","changedActivityOrientation","(I)V") )
     {
      minfo.env->CallStaticVoidMethod(minfo.classID,minfo.methodID,1);
     }
     pEGLView->setFrameSize(frameSize.height,frameSize.width);
            // 设置设计分辨率和适配方式
     pEGLView->setDesignResolutionSize(SCREEN_HEIGHT,SCREEN_WIDTH,kResolutionExactFit);
    #endif
    }

    chap11数据结构和常用类
    deprecated文件夹
    CCDeprecated.h对这些废弃类重新做了类型定义
    整数类型封装类、整型数的封装
    __Integer/Integer/CCInteger
    继承自Ref,Clonable
    使用create创建对象,使用getValue获取值
    例如:
    NotificationCenter::sharedNotificationCenter()->postNotification(MSG_UI_ANS_GAMELINK,Integer::create(1));

    typedef __Array Array;
    typedef __Array CCArray;
    __Array多继承自Ref,Clonable
    removeObjectAtIndex函数删除元素
    getRandomObject函数获得一个随机元素对象
    containsObject函数判断是否包含某个对象
    void DZPKLayer::updateActionName(Object* obj)
    {
     __Array *arr=(__Array *)obj;
     // count函数获得数组中的元素个数
     if(arr->count()>=2)
     {
      // getObjectAtIndex函数根据索引获得某个元素对象
      int iViewID=((Integer *)arr->getObjectAtIndex(0))->getValue();
      const char* szName=((__String *)arr->getObjectAtIndex(1))->getCString();
      m_pPlayerInfoManager->updateActionName(iViewID,szName);
     }
    }
    string str=String::createWithFormat("胜利:手牌数=%d,公共牌数=%d,牌型=%d",bTempCount1,bTempCount2,ct)->getCString();
    MyNSString::GBKToUTF8(str);

    // 静态函数create创建一个数组对象  
    __Array *arr=__Array::create();

    // addObject添加元素 
    arr->addObject(Integer::create(wWinnerID));
    arr->addObject(String::create(str));
    NotificationCenter::getInstance()->postNotification("updateActionName",arr);


    字典CCDictionary\__Dictionary\Dictionary是一个键值对容器,键不可以重复,值可以重复
    //创建
      CCDictionary *dictionary = CCDictionary::create();
    //添加
      dictionary->setObject(CCInteger::create(dwUserID), "dwUserID");
      dictionary->setObject(CCInteger::create(wFaceID), "wFaceID");
      dictionary->setObject(CCInteger::create(cbGender), "cbGender");
      dictionary->setObject(CCInteger::create(cbMemberOrder), "cbMemberOrder");
      dictionary->setObject(CCInteger::create(lScore), "lScore");
      dictionary->setObject(CCInteger::create(lWinCount), "lWinCount");
      dictionary->setObject(CCInteger::create(lLostCount), "lLostCount");
      dictionary->setObject(CCInteger::create(lDrawCount), "lDrawCount");
      dictionary->setObject(CCInteger::create(lFleeCount), "lFleeCount");
      dictionary->setObject(CCInteger::create(lWinCount+lLostCount+lDrawCount+lFleeCount), "lTotalCount");
      dictionary->setObject(CCInteger::create(lExperience), "lExperience");
      dictionary->setObject(CCString::create(szNickName), "szNickName");
     
    CCDictionary *dic = (CCDictionary *)friendData->objectAtIndex(i);
    //根据键获得值 
    DWORD userID = ((CCInteger *)dic->objectForKey("dwUserID"))->getValue();

    3.5.6 SpriteBatchNode精灵表单类
    如果能尽量减少OpenGL ES的调用,游戏的运行就会变得非常顺畅。
    SpriteBatchNode的前身是旧版本中的SpriteSheet,也就是俗称的精灵表单。
    使用SpriteBatchNode类时需要注意两个问题:
    SpriteBatchNode类只能接受精灵对象成为其子节点。粒子、标签以及图层都不能成为SpriteBatchNode类的子节点。
    所有的精灵对象必须使用同一张纹理图片,并且最好不要对这些精灵对象使用一些混合效果。
    3.0版本之后推荐直接使用Sprite,不需要再将Sprite作为子节点添加到SpriteBatchNode中。

    示例:测试SpriteFrameCacheTest精灵帧缓存类
    SpriteFrameCacheTest
    cocos2d-x-3.2/test/cpp-tests/Resources/animations
    本例我们选择使用grossini-aliases.plist和grossini-aliases.png
    cocos2d-x-3.2/test/cpp-tests/Resources/Images
    grossini_dance_01.png到grossini_dance_14.png
    如果开发者没有先使用addSpriteFramesWithFile函数添加精灵帧缓存,之后的操作都会提示找不到对应的.png文件。
    //3.x
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("dzpk.plist","dzpk.png");
    //2.x
    CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("dzpk.plist");
    CCString *pBatchName=CCString::createWithFormat("dzpk.png");
    cocos2d::CCSpriteBatchNode * s_dzpk= CCSpriteBatchNode::create(pBatchName->getCString());

    3.6.1 cocos2d::Vector<T>
    设计者们将cocos2d::Vector<T>设计为cocos2d::CCArray的替代品。存入容器的对象必须是cocos2d::Ref或其派生类。当容器被释放时,它所保存的所有元素都会被释放一次引用。
    使用现代的C++,本地存储对象比堆存储对象要好。所以不要使用new操作来申请cocos2d::Vector<T>的堆对象,要使用栈对象。cocos2d::Vector<T>并不是cocos2d::Ref的子类,所以不要像使用其他Cocos2d类一样使用retain/release和引用计数内存管理。Vector<Vector<>>也是非法的。

    4.8示例:测试函数回调动作——CallFuncActionTest 
    函数回调动作包括如下几种:
    CallFunc:不包含参数
    CallFuncN:参数表示要调用动作的节点对象
    __CCCallFuncND和__CCCallFuncO在3.0当中已经弃用了,使用CallFuncN代替。
    由于从Cocos2d-x 3.0开始支持最新的C++ 11语法,因而,CallFuncActionTest的init函数中的回调方法可以使用更加简单的方式实现。
    有关C++ 11新特性的知识请参考《C++ Prime中文版(第5版)》或官网文档。

    3.0后新的回调接口,由四个CC_CALLBACK取代。
    CC_CALLBACK的差别就在于后面的数字,0就代表回调是没有参数的函数,1就是有1个参数,2就是有2个参数,3就是有3个参数。
    示例:测试菜单——MenuTest
    ③精灵菜单项的使用
    //3.x
     Sprite *CloseNormal=Sprite::createWithSpriteFrame(spriteFrame("CloseNormal.png"));//正常状态
     Sprite *CloseSelected=Sprite::createWithSpriteFrame(spriteFrame("CloseSelected.png"));//选择状态
     [=]以传值的方式传入所有变量,值不可以被修改
    //重置
    MenuItemSprite *pCloseItemSprite1 = MenuItemSprite::create(pCloseNormalButton1,pCloseSelectButton1,[=](Ref* ){
     reset(NULL);
    });
    MenuItemSprite* CloseItem = MenuItemSprite::create(CloseNormal,CloseSelected,CC_CALLBACK_1(DZPKLayer::menuCloseCallback, this));
     CloseItem->setAnchorPoint(ccp(0.5,0.5));
     CloseItem->setTag(ExitTag);
     CloseItem->setPosition(ccp(m_WinSize.width*0.5,m_WinSize.height*0.35));
     Menu* menu = Menu::create(CloseItem,NULL);
     menu->setAnchorPoint(ccp(0,0));
     menu->setPosition(Point::ZERO);
     menu->setZOrder(-1801);
     //menu->setTouchPriority(-1801);
     addChild(menu,100);
    void DZPKLayer::menuCloseCallback( Object* obj )
    {
     int tag = dynamic_cast<CCNode*>(obj)->getTag();
     switch (tag)
     {
     case ExitTag:
      {
       CCLOG("exit================================");
       DZPKLayer::ExitDZPK();
      }
      break;
     default:
      break;
     }
    }
    //2.x
    CCSprite *sendItemNormalImage = CCSprite::createWithSpriteFrame(spriteFrame("Button_FriendSend1.png"));
    CCSprite *sendItemSelectedImage = CCSprite::createWithSpriteFrame(spriteFrame("Button_FriendSend2.png"));
    CCSprite *deleteItemNormalImage = CCSprite::createWithSpriteFrame(spriteFrame("Button_FriendDelete1.png"));
    CCSprite *deleteItemSelectedImage = CCSprite::createWithSpriteFrame(spriteFrame("Button_FriendDelete2.png"));
    CCSprite *chatItemNormalImage = CCSprite::createWithSpriteFrame(spriteFrame("Button_FriendChat1.png"));
    CCSprite *chatItemSelectedImage = CCSprite::createWithSpriteFrame(spriteFrame("Button_FriendChat2.png"));
    CCMenuItemSprite *sendItem = CCMenuItemSprite::create(sendItemNormalImage,sendItemSelectedImage,this,menu_selector(BankerListLayer::sendButton));
    CCMenuItemSprite *deleteItem = CCMenuItemSprite::create(deleteItemNormalImage,deleteItemSelectedImage,this,menu_selector(BankerListLayer::deleteButton));
    CCMenuItemSprite *chatItem = CCMenuItemSprite::create(chatItemNormalImage,chatItemSelectedImage,this,menu_selector(BankerListLayer::chatButton));
    myFriendOperaMenu = CCMenu::create(sendItem,deleteItem,chatItem,NULL); 
    myFriendOperaMenu->setPosition(ccp(555, 42)); 
    myFriendOperaMenu->alignItemsHorizontallyWithPadding(4);
    this->addChild(myFriendOperaMenu,2);
    void BankerListLayer::chatButton(cocos2d::CCObject *obj)

    }

    P109
    Sprite * Start1 = Sprite::createWithSpriteFrame(spriteFrame(texture_name::s_start_button1));
    Sprite * Start2 = Sprite::createWithSpriteFrame(spriteFrame(texture_name::s_start_button2));
    MenuItemSprite *StartItem = MenuItemSprite::create(Start1,Start2,CC_CALLBACK_1(DZPKLayer::menuResumeCallback, this));
    StartItem->setTag(StartTag);
    // 菜单中关闭按钮
    auto *LeaveItem = MenuItemImage::create("CloseNormal.png","CloseSelected.png",CC_CALLBACK_1(DZPKLayer::menuCloseCallback, this));
    LeaveItem->setTag(CloseTag);
    // 菜单
    auto m_StartClose= Menu::create(StartItem,LeaveItem,NULL);
    m_StartClose->setPosition(ccp(0,0));//Vec2::ZERO 
    addChild(m_StartClose,0);
      
    // 使用png图片创建一个精灵
    auto exit_1 = Sprite::create("CloseNormal.png");

    附录 Cocos2d-x中常用的宏
    Cocos2d-x中提供了很多宏来完成一些特定的功能,它们在很大程度上方便了开发者。
    1、与节点创建相关的宏
    2、与平台相关的宏
    3、与命名空间相关的宏
    4、与节点属性相关的宏
    5、与内存管理相关的宏
    使用delete操作符删除一个C++对象p,如果p为NULL,则不进行操作
    CC_SAFE_DELETE(tabLayer);
    6、与日志相关的宏
    7、与调试相关的宏
    8、与转换相关的宏

    命名空间宏
    宏|作用|同等作用的C++语句
    USING_NS_CC|定义cocos2d命名空间
    USING_NS_CC_EXT|定义cocos2d::extension命名空间

    2.3.2断言宏CCAssert的使用
    cond是断言表达式,msg是错误提示信息。
    if (NULL == s_AllBullet)
      CCAssert(0, "请先调用 CBulletManage::Init(...) 函数!");
    2.3.3数组遍历宏CCARRAY_FOREACH、CCARRAY_FOREACH_REVERSE
    声明在CCArray.h中
    __array__为需要遍历的数组,__object__为Object对象
     CCSprite *pChild = NULL;
     CCObject* pObj = NULL;
     CCARRAY_FOREACH(getChildren(), pObj)
     {
      //在程序里要对该Object进行强制类型转换
                    pChild = (CCSprite*)pObj;
      pChild->setVisible( true );
     }
    2.3.5对象创建方法宏CREATE_FUNC的使用
    CREATE_FUNC自动生成一个默认的静态create方法。
    CREATE_FUNC(CLogin);

    即生成一个方法:static CLogin* create();

    4.5使用编辑框制作用户登录界面
    Cocos2d-x使用EditBox添加编辑框,EditBox继承自ControlButton。
    (1)创建一个编辑框对象m_peditAccount,用来输入姓名
    cocos2d::extension::CCEditBox * m_peditAccount;
    cocos2d::extension::CCEditBox * m_peditPW;
    (10)给输入框添加事件代理,事件代理函数在EditBoxDelegate中定义,所以自己定义的类要继承EditBoxDelegate。其中包括4个处理函数。

    class CLogin : public cocos2d::CCLayer, cocos2d::extension::CCEditBoxDelegate

    editBoxReturn:当按键盘的完成键时触发
    virtual void editBoxReturn(cocos2d::extension::CCEditBox* editBox);

    附录A 实例代码清单说明
    代码清单|实例说明
    2-2|Cocos2d-x基本数据类型的使用方式,比如String、Array、Point、Size、Rect、Dictionary、Bool
    2-3|Cocos2d-x常用宏定义的使用方法,比如随机数宏,角度转换宏,两值交换宏,数组遍历宏,字典遍历宏,属性定义宏
    2-4|锚点的使用方式,节点坐标和世界坐标的相互转换
    3-1-2|从场景中移除Node节点
    3-2-2|使用Camera缩放、旋转节点
    3-4-2|使用Scene创建一个战斗场景

    3-4-3|多个场景相互切换的方式
    3-5-2|Layer、layerColor的使用方式
    4-1|1、使用LabelBMFont显示文本,获取单个字并设置样式;2、使用LabelTTF显示文本并对文本设置样式;3、使用LabelAtlas在场景中显示自定义字体。
    4-2|介绍6种菜单项的区别和使用方式,在游戏中添加菜单并设置单击菜单后的回调函数
    4-2-2|使用菜单制作游戏菜单功能
    4-3-1|在游戏中使用ScrollView显示多页内容,监听ScrollView的滚动和缩放事件
    4-3-3|通过制作一个简单背包讲解如何使用TableView,设置TableView大小、方向、渲染顺序、数据源。自定义单元格TableViewCell
    4-3-4|当TableView里添加菜单时,如何区分处理是点击菜单还是滑动TableView
    4-5|通过制作一个用户登录界面讲解编辑框的使用方式,包括设置编辑框的显示形式、输入内容,注册4种事件的代理函数
    5-2|瞬时动作举例
    5-3|延时动作举例,如移动、跳跃、旋转、缩放、倾斜变形、曲线运动等。把简单动作联合起来,实现按顺序播放动作、同时播放动作、逆向播放动作、重复播放动作等
    6-2|介绍Cocos2d-x对背景音乐和音效的处理方法,主要内容包括播放、停止、暂停、恢复等操作
    8-2|通过三个例子讲解Cocos2d-x中的屏幕触摸事件
    8-3|使用多点触摸实现缩放图片

    8-4-1|把键盘输入事件显示在屏幕中,讲解了如何使用Cocos2d-x的键盘事件
    9-1|使用UserDefault存储修改数据

    20170219添加:
    http://www.docin.com/p-1489599279.html
    Cocos2d-x学习之路(个人总结)
    一、精灵篇
    1、创建精灵的三种方法
    1.1使用指定图片创建一个Sprite:
    auto sprite=Sprite::create("2.png");
    1.2使用矩形创建一个Sprite:
    auto sprite=Sprite::create("2.png",Rect(0,0,40,40));
    1.3使用精灵表单(Sprite Sheet)创建一个Sprite:
    加载一个精灵表单
    auto framecache=SpriteFrameCache::getInstance();
    framecache->addSpriteFramesWithFile("sprite.plist");
    auto sprite=Sprite::createWithSpriteFrameName("2.png");
    this->addChild(sprite);
    2、锚点和位置(颜色和透明度不受锚点影响)
    2.1锚点
    sprite->setAnchorPoint(Vec2(0.5,0.5));
    2.2受锚点影响的精灵属性
    位置
    缩放
    倾斜
    角度:角度增加,顺时针旋转;角度减少,逆时针旋转
    2.3不受锚点影响的精灵属性
    颜色:Color3B对象代表RGB颜色值,即0~255之间的值,预定义颜色,如Color3B::White,Color3B:Red
    透明度:取值范围(0~255),默认值为255(不透明)
    二、动作篇
    1、动作概念
    随时间改变Node的属性
    By和To的区别:
    By相对于节点是相对的
    To相对于节点是绝对的
    2、基本动作及其如何执行动作
    移动
    旋转
    缩放
    淡入淡出
    色调
    跳跃
    减速
    贝塞尔曲线
    3、序列动作及其如何执行动作
    Sequence:序列对象可以是:动作对象、函数(CallFunc对象)、甚至另一序列
    4、三种回调函数
    1、callback()
    2、callback(Node* sender)
    3、callback(Node* sender,long date)
    注意:两个对象同时调用一个动作的时候会失败,此时另外一个对象应该调用动作的clone();使用回调函数的时候,传入回调方法的时候记得没用括号,每个回调函数必须传入一个this参数。
    三、计时器篇
    1、原理介绍:
    为游戏提供定时事件和定时调用服务
    所有Node对象都知道如何调度和取消调度事件
    2、三种计时器
    2.1默认调度器:scheduleUpdate()
    例如:下面这句的作用是通过默认调度器在主线程里执行网络回调NetEngine::NetManager::getInstance().OnUpdate(dt);
    bool uiRoot::OnCreate(long nGuiW, long nGuiH)
    {
    scheduleUpdate();
    return TRUE;
    }
    取消方法:
    unscheduleUpdate()停止调度器
    2.2单次调度器:scheduleOnce(SEL_SCHEDULE selector,float delay)
    取消方法:
    unschedule(SEL_SCHEDULE selector,float delay)
    2.3自定义调度器:schedule(SEL_SCHEDULE selector,float interval,unsigned int repeat,float delay)
    取消方法:
    unschedule(SEL_SCHEDULE selector,float delay)

     

    20161217添加:
    Xcode8.2+Cocos2d-x 3.10精灵创建不了的bug:


     

    // add "HelloWorld" splash screen"

        auto sprite =Sprite::create("HelloWorld.png");
      {

    gl.supports_vertex_array_object: true

    cocos2d.x.version: cocos2d-x-3.10

    gl.vendor: Apple Inc.

    gl.supports_PVRTC: true

    gl.renderer: Apple Software Renderer

    cocos2d.x.compiled_with_profiler: false

    gl.max_texture_size: 4096

    gl.supports_ETC1: false

    gl.supports_BGRA8888: false

    cocos2d.x.build_type: DEBUG

    gl.supports_discard_framebuffer: true

    gl.supports_NPOT: true

    gl.supports_ATITC: false

    gl.max_samples_allowed: 4

    gl.max_texture_units: 8

    cocos2d.x.compiled_with_gl_state_cache: true

    gl.supports_S3TC: false

    gl.version: OpenGL ES 2.0 APPLE-13.0.7

    }

     

     

    libpng error: CgBI: unhandled critical chunk

    libpng error: CgBI: unhandled critical chunk

    libpng error: CgBI: unhandled critical chunk

    (lldb) 

    解决方法:

    加载图片资源出现libpng error: CgBI: unhandled critical chunk 

    设置Remove Text Metadata From PNG Files = NO.就可以正常显示了 


    20170104问题:DELL 14英寸笔记本+Win8.1+OS X虚拟机+Xcode7.0.1+Cocos2d-x 3.10下iPhone 6s Plus的模拟器太大了,在显示器上看不全
    解决方法:command+1,2,3,4,5调大小,虚拟机下command+4比较合适


    20161008添加:
    2.x——>3.x
    1、 CCControlEvent改为cocos2d::extension::Control::EventType、CCControlEventTouchUpInside改为Control::EventType::TOUCH_UP_INSIDE、CCControlEventTouchDown改为Control::EventType::TOUCH_DOWN
    2、 CCPointZero改为Vec2::ZERO或Point::ZERO
    3、去掉CC:
    CCControlButton、CCScale9Sprite
    4、CCArray改为Vector<SpriteFrame*>,addObject改为pushBack
    5、ccBLACK改为Color3B::BLACK
    6、CCControlStateNormal改为Control::State::NORMAL
    7、kCCRepeatForever改为kRepeatForever
    8、menu_selector改为 CC_CALLBACK_1、callfunc_selector改为CC_CALLBACK_0
    9、kCCAttributeNamePosition改为GLProgram::ATTRIBUTE_NAME_POSITION
    10、去掉CCTargetedTouchDelegate
    11、注释掉visit、keyBackClicked、registerWithTouchDispatcher、ccTouchBegan改为onTouchBegan
    12、CCCallFunc::create(this,callfunc_selector(DZPKLayer::selfTimeOut))改为使用lambda表达式或者其他函数对象。这里,DZPKLayer::selfTimeOut的函数体没有使用DZPKLayer的成员变量和其它成员函数。注:lambda表达式内部也可以使用DZPKLayer的其他成员。
    13、#include "AssetsManager/AssetsManager.h"改为#include "assets-manager/AssetsManager.h"、AssetsManager::kNoNewVersion改为AssetsManager::ErrorCode::NO_NEW_VERSION、AssetsManager::kNetwork改为AssetsManager::ErrorCode::NETWORK。
    14、CCScrollViewDirection改为cocos2d::extension::ScrollView::Direction、kCCScrollViewDirectionVertical改为ScrollView::Direction::VERTICAL。

    火烈鸟网络科技编著的2.x开发教程、Cocos2d-x游戏开发技术精解这两本书,都是基于2.0.4版本讲解且于2013年6月份出版的。
    Cocos2d-x高级开发教程——制作自己的《捕鱼达人》(王哲作序推荐)读书摘要(20130715、20150529、20151123、20160107、20160128、20160603)
    2013年6月第1版
    锚点指定了贴图上和所在节点原点(也就是设置位置的点)重合的点的位置。因此只有在CCNode类节点使用贴图的情况下,锚点才有意义。
    2.3 C++中的Cocos2d-x内存管理
    基于Cocos2d-iPhone的Objective-C风格的内存管理是Cocos2d-x的一个特色。把Objective-C的内存管理方式引入C++,使得游戏开发的内存管理下降了一个层次。
    2.3.1复杂的内存管理
    Boost库引入的智能指针从对象所有权传递的角度来解决内存管理问题。
    2.3.2现有的智能内存管理技术
    目前,主要有两种实现智能管理内存的技术,一是引用计数,一是垃圾回收。
    引用计数是资源自我管理的一种机制,资源本身以引用计数为零来得知别人不再需要自己,从而把自己kill掉。
    引用计数:它是一种很有效的机制,通过给每个对象维护一个引用计数器,记录该对象当前被引用的次数。当对象增加一次引用时,计数器加1;当对象失去一次引用时,计数器减1;当引用计数为0时,标志着该对象的生命周期结束,自动触发对象的回收释放。引用计数解决了对象的生命周期管理问题,但堆碎片化和管理繁琐的问题依然存在。
    垃圾回收:回收机制包括分代复制垃圾回收、标记垃圾回收和增量垃圾回收。
    chap3游戏的基本元素
    3.1CCDirector:大总管
    在CCDirector中,我们定义了以下管理场景的方法:
    启动游戏,并运行指定场景。 这个方法在主程序启动时第一次启动主场景时调用。      
    三种切换场景的方法
    暂停当前运行场景中的所有计时器和动作,场景仍然会显示在屏幕上。
    恢复当前运行场景中被暂停的计时器和动作。
    结束场景,同时退出应用。
    3.2场景
    场景只是层的容器,另一个作用是流程控制。
    3.3层
    层也扮演了容器的角色。
    ZOrder值越大显示顺序越靠上。ZOrder的默认值是0。
    捕鱼游戏场景大致由背景层、动作层、触摸层和菜单层组成。
    层可以接受用户输入时间,包括触摸、加速度计和键盘输入等。

    3.5.2节点的组织
    前面我们已经接触了组织节点的基本方式:addChild方法。在介绍addChild方法时,我们是以向场景中添加层作为例子讲解的。而事实上,addChild是CCNode提供的方法,我们可以把任何的CCNode添加到另一个节点之中。
    除了添加节点之外,CCNode还提供了一系列管理节点的方法,表3-3列出了这些方法。
    表3-3 组织节点相关的方法
    addChild|把child添加到当前节点之中
    removeFromParentAndCleanup|把当前节点从其父节点中移除,如果cleanup为true,则执行clean方法
    node->removeFromParentAndCleanup(true);
    removeChild|从当前节点中移除child节点,如果cleanup为true,则调用child的clean方法
    removeChild(node,true);
    removeChildByTag|从当前节点中移除标号为tag的节点
    removeChildByTag(tag_card_index);
    removeAllChildrenWithCleanup|移除当前节点的所有子节点
    this->removeAllChildrenWithCleanup(true);
    getChildByTag|返回当前节点中标号为tag的节点
    cleanup|停止此节点的全部动作与计时器

    例子:RoomChip也是一个层,是主场景层的子节点
    #include "RoomChip.h"
    CCScene *scene=CCDirector::sharedDirector()->getRunningScene();
    if (!scene->getChildByTag(10))
    {   
    return;
    }
    RoomChip *m_roomChip = dynamic_cast<RoomChip*>(scene->getChildByTag(10)->getChildByTag(3));

    3.1.1CCNode类的成员数据
    CCNode类的主要保护成员数据(子类可见,Protected关键字)如表3-2所示。
    3.12CCNode类的函数
    CCNode类的主要函数如表3-3所示。
    函数名,返回类型,描述
    getScale,浮点型,获得缩放系数
    setScale,空,设置缩放系数
    m_pCKPX_BTN->setOpacity(0);//255表示完全不透明,0表示完全透明

    m_pCKPX_PIC = Sprite::createWithSpriteFrame(spriteFrame(texture_name::s_ol_bg_ckpx));
    m_pCKPX_PIC->getTexture()->setAliasTexParameters();//图片放大时,不模糊但有锯齿
    m_pCKPX_PIC->setScale(0);//整体缩放,s为比例,s=1表示原尺寸
    m_pCKPX_PIC->setAnchorPoint(ccp(0,0));//(0,0)点代表左下点,(1,1)代表右上点
    m_pCKPX_PIC->setPosition(ccp(myConfig.m_DZPKOperaLayerPos[1][0],myConfig.m_DZPKOperaLayerPos[1][1]));//xPos和yPos为相对于父节点锚点的位置
    addChild(m_pCKPX_PIC);

    3.1.3坐标系简介
    通过前面的学习,大家会对CCNode类的属性和方法有所了解,但是可能有些名词会令你困惑,如OpenGL坐标系,世界坐标系,节点相对坐标系,仿射变换等。本节就来解决这些问题。
    1.OpenGL坐标系
    Cocos2D-x以OpenGL和OpenGL ES为基础,所以自然支持OpenGL坐标系。该坐标系原点在屏幕左下角,x轴向右,y轴向上。
    屏幕坐标系使用的是不同的坐标系统,原点在屏幕左上角,x轴向下。iOS的屏幕触摸事件CCTouch传入的位置信息使用的是该坐标系。因此在Cocos2D-x中对触摸事件做出响应前,需要首先把触摸点转化到OpenGL坐标系。这一点在后面的触屏信息中会详细介绍,可以使用CCDirector的convertToGL方法来完成这一转化。

    3.4.5常用成员
    除了前面介绍过的部分成员外,CCSprite还拥有以下常用的成员。 
    1.初始化方法
    CCSprite拥有许多不同的初始化方法,可以方便地创建精灵。
    使用这些方法,我们不仅可以通过图片文件来创建精灵。还可以直接使用纹理或精灵框帧来创建精灵。
    a.使用图片文件:
    使用两个静态工厂方法
    使用它们的构造函数加初始化方法
    b.使用CCTexture2D
    使用CCTexture2D纹理创建精灵的相关方法也有两个静态工厂方法和构造函数加初始化方法。
    CCTexture2D类型的pTexture参数为纹理对象,可以使用CCTextureCache类的addImage方法把图片文件加载为纹理并返回,而rect与使用图片文件创建精灵的rect参数相同。
    c.使用CCSpriteFrame创建
    使用CCSpriteFrame精灵框帧创建精灵也有一个静态工厂方法和构造函数加初始化方法。
    CCSpriteFrame类型的pSpriteFrame参数为纹理框帧。CCSpriteFrame保存了一个CCTexture2D的引用与一个CCRect来表示纹理中的一部分。使用CCSpriteFrame初始化精灵时,也可使精灵显示部分纹理。
    2.纹理相关的属性
    CCSprite提供了一下与纹理有关的属性,用于获取或设置精灵的内容。

    3.5精灵类
    精灵类CCSprite是一张二维的图片对象,它可以用一张图片或者一张图片的一块矩形部分来定义。CCSprite和它的子类可以作为精灵批处理类的子类。

    //所有的音效API都被定义在SimpleAudioEngine类中,该类是一个单例类
    SoundControl::sharedSoundControl()->playSoundEffect(sound_name::s_dzpk_win);
    //使用精灵帧创建一个精灵
    Sprite* GaneSuccentSprite = Sprite::createWithSpriteFrame(spriteFrame(texture_name::s_win));
    //锚点就像用图钉把一张图纸钉在墙上一样。通过锚点可以把游戏元素定位在屏幕上。
    //锚点是一个相对比例值,而不是一个坐标值。当前精灵的锚点是(0.5,0.5)表示当前锚点位于精灵的中心位置。
    GaneSuccentSprite->setAnchorPoint(ccp(0.5,0.5));
    //设置位置
    //GaneSuccentSprite->setPosition(ccp(WINSIZE_W/3,WINSIZE_H/2));
    GaneSuccentSprite->setPosition(ccp(myConfig.m_WinLosePos[0],myConfig.m_WinLosePos[1]));
    //添加子节点,并指定z坐标和tag标签
    addChild(GaneSuccentSprite,10,ApTage_03);

    3.5.5精灵帧缓存类CCSpriteFrameCache
    CCSpriteFrameCache是一个单例模式,不属于某个精灵,是所有精灵共享使用的。
    CCSpriteFrameCache类的主要函数见表3-15。
    函数名,返回类型,描述
    spriteFrameByName,精灵帧,根据定义的名称找到精灵帧,如果没有对应的,返回空。
    4.1动作类
    CCAction类继承于对象类CCObject,有三个子类:有限时间动作,跟随,移动速度,其中有限时间动作分为瞬时动作和延时动作。
    4.16组合动作
    1.CCSequence
    定义一个动作序列,可以使用动作的CCArray数组;也可以把所有的动作作为参数传入create函数中,最后结尾参数使用NULL(空值)即可;还可以把两个有限时间动作按顺序传入create函数中。

    3.5.3定时器事件
    Cocos2d-x为我们提供了两种方式实现定时机制——使用update方法以及使用schedule方法。
    1.update定时器
    第一种定时机制是CCNode的刷新事件update方法。
    2.schedule定时器
    另一种定时机制是CCNode提供的schedule方法,可以实现以一定的时间间隔连续调用某个函数。由于引擎的调度机制。这里的时间间隔必须大于两帧的间隔,否则两帧期间的多次调用会被合并成一次调用。
    3.定时器相关方法
    定时器机制是Cocos2d-x调度机制的基础。Cocos2d-x的调度是纯粹的串行机制,因此所有函数都运行在同一个线程。
    3.7Cocos2d-x调度原理
    3.7.1游戏主循环
    在主循环中,我们主要进行了以下3个操作:
    (1)调用了定时调度器的update方法,引发定时器事件。
    (2)如果场景需要被切换,则调用setNextStage方法,在显示场景前切换场景。
    (3)调用当前场景的visit。

     

    摄像机CCCamera继承自CCObject类,
    每一个节点都需要使用CCCamera。主要函数:
    init初始化函数
    setEyeXYZ设置眼睛(视角位置)的坐标
    setCenterXYZ设置中心(目标位置)的坐标
    setUpXYZ设置上方(摄像机方向)的坐标
    getEyeXYZ获取眼睛的坐标
    getCenterXYZ获取中心的坐标
    getUpXYZ获取上方的坐标

    12.1Camera类的设计----借鉴了微分几何中的活动标架(P,R,U,L)
    我们用4个摄像机向量:右向量,上向量,观察向量以及位置向量来定义摄像机相对于世界坐标系的位置和朝向。这些向量实质上为相对世界坐标系描述的摄像机定义了一个局部坐标系。由于右向量,上向量,观察向量定义了摄像机在世界坐标系中的朝向,有时我们也将这三个向量统称为方向向量。方向向量必须是标准正交的。如果一个向量集合中的向量都彼此正交,且模均为1,则称该向量是标准正交的。引入这些约束的原因是在后面我们要将这些向量插入到一个矩阵的某些行中,以使该矩阵成为标准正交矩阵(如果一个矩阵的行向量是标准正交的)。标准正交矩阵的一个重要性质是其逆矩阵与其转置矩阵相等。
    用上述4个向量来描述摄像机,我们就可对摄像机实施如下6种变换。
    绕向量right的旋转(俯仰,pitch)
    绕向量up的旋转(偏航,yaw)
    绕向量look的旋转(滚动,roll)
    沿向量right方向的扫视(strafe)
    沿向量up方向的升降(fly)
    沿向量look的平动
    通过上述6种运算,摄像机可沿3个轴平动以及绕3个轴转动,即摄像机具有6个自由度(degrees of freedom)。
    12.2.2绕任意轴的旋转
    D3DXMatrixRotationAxis绕由向量A确定的任意轴进行旋转
    12.2.3俯仰,偏航和滚动
    由于方向向量描述了摄像机在世界坐标系中的朝向,所以当摄像机发生俯仰pitch,偏航yaw或滚动roll时,我们必须指定方向向量应如何更新。
    俯仰,即绕摄像机的right向量旋转
    偏航,即绕摄像机的up向量旋转
    滚动,即绕摄像机的look向量运动
    12.2.4行走,扫视和升降
    这里的“行走(walking)”是指沿着摄像机的观察方向(即沿着向量look的方向)的平动。
    “扫视(strafing)”是指保持观察方向不变,沿向量right方向从一边平移到另一边。“升降(flying)”是指沿着向量up方向的平动。为了能够沿这些轴中的任意一个进行平动,我们只需将摄像机当前位置向量和一个与该轴方向相同的向量相加即可。
    12.4小结
    我们通过维护4个向量——right,up,look和position来表示摄像机在世界坐标系中的位置和朝向。有了这些描述工具,我们就可轻松地实现具有6个自由度的摄像机,这样就为飞行模拟器和第一人称视角游戏提供了一个灵活的摄像机接口。

    chap5动画与场景特效
    5.1动画
    我们需要引入一种特殊的动作:动画。
    5.1.1概述
    理论上来说,帧动画可以实现任何一种效果。
    帧动画与电影胶片类似。动画的帧就是指被显示出来的每一张图片。
    考虑到制作成本以及回放成本,如果没有必要,我们一般不在游戏中大规模使用动画。
    5.1.2使用动画
    动画由帧组成。显卡在绘图时,在纹理间切换时一个开销巨大的操作,由于精灵可以显示部分纹理,因此通常更为高效的做法是把动画用到的多个纹理按照一定顺序排列起来,然后放置在同一个纹理下。在创建动画时,我们需要制定动画所使用的纹理以及每一帧使用的是纹理的哪一部分。
    一个CCSpriteFrame框帧包含两个属性,纹理与区域。 一个框帧可以完整地描述精灵显示的内容,因此在动画中,我们使用框帧来表示每一帧的内容。
    为了描述一帧,除了框帧,显然我们还需要记录帧的持续时间。动画帧类CCAnimationFrame同样包含两个属性,其一是对一个框帧的引用,其二是帧的延时。动画类CCAnimation是对一个动画的描述,它包含显示动画所需要的动画帧。对于匀速播放的帧动画,只需设置所有帧的延时相同即可。
    我们使用CCAnimation描述一个动画,而精灵显示动画的动作则是一个CCAnimate对象。动画动作CCAnimate是精灵显示动画的动作,它由一个动画对象创建,并由精灵执行。动画与动画动作的关系就如同CD光盘和CD播放机的关系一样——前者记录了动画的内容,而后者是播放动画的工具。
    5.2场景特效
    特效类CCTransitionScene派生自CCScene,换句话说,场景特效本身也是一个场景。场景特效的实现方式与复合动作类似,场景特效是一类特殊的场景,它们包含了另一个场景(称作原场景),把原场景当做参数来创建一个特效场景。

     

    chap8粒子效果
    当我们希望在游戏中模拟这些大规模运动的物体时,通常有如下两种方法。
    使用帧动画来模拟。设计帧动画并把它渲染为图片序列来模拟特效,不但生成的动画体积庞大,也无法调整其运动参数,因此有失灵活性。
    本章即将介绍的粒子效果。我们把每一个对象都看做一个粒子,赋予它们一定的属性(例如外观、位置、速度、加速度和生成时间等),使它们按照一定的规律产生、运动并最终消失。
    在粒子效果中,通常存在一个对所有粒子进行统一调度的引擎,称作粒子系统,它负责粒子的产生,随时间改变粒子的状态,以及最后回收不再需要的粒子。如果按照粒子系统的维数来区分,粒子系统可以分为二维粒子系统与三维粒子系统两种。
    8.1Cocos2d-x中的粒子系统
    与其他的粒子引擎一样,CCParticleSystem实现了对粒子的控制与调度,对粒子的操作包括如下几种。
    产生粒子:这部分也被称作粒子的发射器(emitter)。
    更新粒子状态:引擎会随时间更新粒子的位置、速度以及其他状态。
    回收无效粒子:当粒子的生命周期结束后,就会被系统回收。
    因此,为了创建一个粒子效果,我们需要定义粒子如何产生以及状态如何改变。

    在粒子系统中,至少要包括四大部分:
    大量的粒子对象、每个粒子遵守的规律、每个粒子的随机性和持续更新的粒子状态。
    两种模式:
    重力式粒子系统;放射式粒子系统。
    粒子系统说明了这些粒子要遵守某种规则,而规则是通过一系列参数定义的。
    参数名称,参数表述,适用范围
    重心,粒子系统的重心,重力式或放射式
    速度,粒子的初速度,重力式
    方向,粒子的初速方向,重力式或放射式
    尺寸,粒子的大小,重力式或放射式
    生命,粒子存在的时间,重力式或放射式
    颜色,粒子的颜色,重力式或放射式
    自转角度,粒子是否要绕着自己的轴心旋转,重力式或放射式
    公转角度,粒子是否要以重心为轴心旋转,放射式

    chap9大型地图
    超过屏幕大小的地图,玩家可以像在即时战略游戏中一样在地图中滚动游戏画面。
    9.1瓦片地图
    把这些瓦片拼接在一起,一个完整的地图就组合出来了,这就是瓦片地图的原理。
    在Cocos2d-x中,瓦片地图实现的是TileMap方案。

    chap7 Cocos2d-x中的瓦片地图集
    地图编辑器的tile(瓦片,瓷砖,瓦块)是贴图的元素和事件引发的记录点。
    地图编辑器最大的优点就在于:重复使用性高,背景地图制作速度快,简单易用等。
    所准备的tile也不能太少,有些tile可能同样都是墙壁,却有好几种不同的样式,有的tile上面有斑驳的痕迹,有的tile上面可能有涂鸦等,
    又如树之类的背景,可能也要准备个好几种不同树的tile。
    在很多游戏中,整个游戏场景中除了主角精灵之外,游戏的地图背景也是“重头戏”。在手机游戏的开发中,为了节约内存空间,使用图素拼接的方法组成整个地图。也就是说,首先确定图素块(一般为正方形)的大小,其次美术人员绘制图素块,再次由策划和美术人员根据不同项目的需求使用地图编辑器将图素拼接成大地图,最后将生成的数据文件和图素交给程序处理。
    7.1瓦片地图集及编辑器
    Cocos2d-x中支持瓦片地图集,Tiled地图编辑器可以生成Cocos2d-x支持的地图文件。
    7.1.1瓦片地图的种类及用途
    Cocos2d-x中的瓦片地图集支持普通视角地图和45度角地图。
    普通视角地图在多种类型的游戏中被使用。如横版动作闯关游戏、俯视视角游戏等。这种情况下,一般在使用地图中为不同的地图块赋予不同的“意义”,如部分地图块含有阻挡,部分地图块含有机关等,一般通过给图素“编号”的方法获得。
    45度角地图一般应用于塔防游戏,战旗游戏和建造类游戏。在这些游戏中,经常要求用户点击相应的地图图素块,并在图素块上“建设”某些“建筑”。获得图素的索引,并修改图素便可实现该功能。
    地图编辑器之地形标示:
    障碍物的设定
    行走速度:高山河流=0,沼泽地带=1,沙漠区=2,草原=3,路面=4,
    tile的数值:
    草原=2,沙漠=4
    行走能力是每回合8步,
    走到草原区的tile时,会消耗掉2步的能力,剩下6步
    一直都在草原区行走的话,算算每一回合应该可以走4格tile的长度;
    在沙漠区行走的话,每一个tile的长度会消耗掉4步,一回合只能走两个tile的长度;
    经过两个草原和一个沙漠的话,能在一回合走完(2个2步+1个4步=8步)
    地图编辑器之事件处理:每一个tile除了地形数值之外,还有一个事件数值,用来表示事件的处理。
    玩过角色扮演游戏的人应该都知道,当玩家控制主角行经某处时,会出现一些偶发事件,像是忽然有人会跑出来跟你讲话,或是遇到特定的敌人等等,像这些事件,通常都是由地图编辑器来予以设定的。
    7.1.2Tiled地图编辑器
    Tiled地图编辑器的特性如下:
    使用基于XML编码形式的地图数据文件使其可以在不同的游戏引擎中通用。
    支持普通和45度角两种视角。
    对象的放置位置可以精确到像素。
    支持撤销/重做和复制/粘贴的操作。
    支持图素,层次和对象等通用的概念。
    自动重新载入图素集。
    可以充值图素的大小和偏移。
    支持图章刷和填充等高效工具。
    支持以通用的格式输入输出打开和存储文件。
    7.1.3用Tiled地图编辑器编辑地图
    7.1.4添加精灵层
    精灵层其实就是没有图素格位置限制的地图层。精灵层一般放置到图素和机关(如传送门)。精灵层和地图层结合叠加就组成了整个地图。
    7.1.5Tiled地图编辑器数据文件
    Tiled地图编辑器生成的TMX数据文件类似于XML文件。
    7.1.6瓦片地图集类CCTMXTiledMap
    7.1.7地图层类CCTMXLayer
    地图由地图层组成。
    7.1.8地图精灵组类CCTMXObjectGroup

    Chap10 Cocos2d-x绘图原理及优化
    Cocos2d-x是对不同平台下OpenGL的包装。下面我们介绍更为底层却又十分重要的内容,那就是Cocos2d-x的绘图原理以及游戏优化方法。
    OpenGL是一个开放的、跨平台的高性能图形接口。OpenGL ES则是OpenGL在移动设备上的衍生版本,具备与OpenGL一致的结构。Cocos2d-x就是一个基于OpenGL的游戏引擎,因此它的绘图部分完全由OpenGL实现。
    OpenGL是一个基于C语言的三维图形API,基本功能包含绘制几何图形、变换、着色、光照、贴图等。除了基本功能, OpenGL还提供了诸如曲面图元、光栅操作、景深、shader编程等高级功能。在本书中,我们仅介绍一些必要的概念与技术。
    1.状态机
    状态机的设计有许多优势。OpenGL把所有的参数作为状态来保存,如果没有设置新的参数,则会一直采用当前的状态来绘图。
    另一个优势在于,我们可以把绘图设备认为地分为两个部分:CPU和GPU分别充当客户端与服务器的角色。在实际使用中,OpenGL的客户端与服务器端是可以分离的,因此可以轻而易举地实现远程绘图
    2.坐标系
    OpenGL是一个三维图形接口,在程序中使用右手三维坐标系。
    屏幕向右的方向为X方向,屏幕向上的方向为Y方向,由屏幕指向我们的方向为Z方向

    DirectX9.0 3D游戏开发编程基础(段菲译)
    第Ⅰ部分基础知识 必备的数学知识
    左手坐标系和右手坐标系的差别体现在z轴的正方向上。在左手坐标系中,z轴正方向穿进纸面。在右手坐标系中,z轴正方向传出纸面。[+x水平向右,+y垂直向上。]
    第Ⅱ部分Direct3D基础Chap2绘制流水线
    2.3.8视口变换
    视口变换的任务是将顶点坐标从投影窗口转换到屏幕的一个矩形区域中,该矩形区域称为视口。在游戏中,视口通常是整个矩形屏幕区域。但视口也可以是屏幕的一个子区域或客户区。矩形的视口是相对于窗口来描述的,因为视口总处于窗口内部并且其重要位置要用窗口坐标来指定。
    [DX9的3D世界坐标系是左手三维坐标系,+z穿进纸面,+x水平向右,+y垂直向上]

    OpenGL负责把三维空间中的对象通过投影、光栅化转换成二维图像,然后呈现到屏幕上。
    在Cocos2d-x中,我们只需要呈现二维的图像,因此Z坐标只用作控制游戏元素的前后顺序,通常不做讨论。
    当我们绘制图形的时候,OpneGL会把图形绘制在当前的绘制坐标系中。
    3.渲染流水线
    当我们把绘制的图形传递给OpenGL后,OpenGL还要进行许多操作才能完成3D空间到屏幕的投影。通常,渲染流水线过程有如下几步:显示列表、求值器、顶点装配、像素操作、纹理装配、光栅化和片段操作等。
    OpenGL ES 1.0版本中采用的是固定渲染管线。
    OpenGL从2.0版本开始引入了可编程着色器(shader)。
    http://book.51cto.com/art/201305/394859.htm
    10.1.3矩阵与变换
    在10.1.1节中,我们曾经提到过坐标系变换。作为绘图的一个强大工具,坐标系变换在OpenGL开发中被广泛采用。为了理解坐标系变换,首先需要了解一些坐标系变换所需的数学知识,这些知识也是计算机图形学的数学基础。
    OpenGL对顶点进行的处理实际上可以归纳为接受顶点数据、进行投影、得到变换后的顶点数据这3个步骤。当我们设置好OpenGL的坐标系,并传入顶点数据后,OpenGL就会通过一系列计算把顶点映射到世界坐标系之中,再把世界坐标系中的点通过投影变换为可视平面上的点。这一系列变换的本质是通过对顶点坐标进行线性运算,得到处理后的顶点坐标。在计算机中,坐标变换是通过矩阵乘法实现的,用向量表示坐标,矩阵表示变换形式,则变换后的顶点坐标可以用向量与矩阵的乘法来表示。使用矩阵乘法的优点在于,计算机(包括移动设备)的图形硬件通常对矩阵乘法进行了大量优化,从而大大提高了运算效率。
    点、向量与矩阵
    在计算机中,通常不直接使用与点维度数量一样的向量来表示一个点,因为这样就无法利用矩阵乘法来对点进行平移等操作了。因此,在计算机图形学中,通常采用齐次坐标来表示一个顶点。具体地说,齐次坐标系中每一个点的维度比顶点维度多1,多出的一个维度值为1。对于任何三维中的顶点(x, y, z),它在齐次坐标系中的向量为[x, y, z, 1],例如,空间中的(1.2, 5, 10)对应的向量为[1.2, 5, 10, 1]。
    变换利用矩阵表示。常见的变换包含平移变换、旋转变换和缩放变换等,它们分别对应了平移矩阵、旋转矩阵和缩放矩阵等。下面以平移矩阵为例,展示如何使用矩阵乘法实现坐标变换。平移矩阵为{{1,0,0,t_x},{0,1,0,t_y},{0,0,1,t_z},{0,0,0,1}}
    其中(t_x,t_y,t_z)为平移的方向向量。若我们希望把点(1.2, 5, 10)平移(6, 5, 4)距离,则计算矩阵的乘法如下:
    {{1,0,0,6},{0,1,0,5},{0,0,1,4},{0,0,0,1}}×{{1.2},{5},{10},{1}}={{7.2},{10},{14},{1}}
    可以看到,我们得到了平移后的点(7.2, 10, 14)。上面是对一个点进行一次变换的情况,如果希望对点进行多次变换,则应该依次构造每个变换对应的矩阵,并利用矩阵乘法把所有矩阵与顶点向量相乘。例如,对点P依次进行缩放、平移、缩放和旋转操作,则分别构造它们对应的变换S_1、T、S_2、R,按照如下公式计算变换后的点P':P'=R×S_2×T×S_2×P
    OpenGL维护了一个当前绘图矩阵,用于表示当前的绘图坐标系。这个矩阵被初始化为单位矩阵,此时绘图坐标系与世界坐标系相同,当我们不断地在绘图矩阵后乘上新的矩阵时,会相应地改变绘图坐标系。在上面的例子中,R×S_2×T×S_2即为绘图矩阵,它表示了一个绘图坐标系。在此点上绘制的P点坐标经过映射后,可以得到它在世界坐标系中对应的坐标P'。
    OpenGL为我们提供了一系列创建变换矩阵的函数(如表10-1所示),因此,在实际开发中,我们并不需要手动构造变换矩阵。这些函数的作用是创建一个变换矩阵,并在当前绘图矩阵的后方乘上这个矩阵。现在对刚才的例子稍作修改,我们不再希望只对点P进行一系列变换,而是希望对一个完整的图形进行变换。以下代码绘制一个任意的图形,并将此图形首先放大2.5倍,然后平移(1, 2, 3)距离,最后缩小0.8倍:
    //OpenGL ES 1.0
    glScalef(0.8f, 0.8f, 0.8f);//乘上缩放矩阵
    glTranslatef(1.0f, 2.0f, 3.0f);//乘上平移矩阵
    glScalef(2.5f, 2.5f, 2.5f);//乘上缩放矩阵
    DrawObject();//绘制任意图形
    表10-1 常见的OpenGL ES 1.0变换函数
    函数名 描述
    glTranslate  平移变换
    glRotate  旋转变换
    glScale  缩放变换
    必须指出,无论是表10-1还是上面的代码,都明确提到了这些变换函数隶属于OpenGL ES 1.0。实际上,在Cocos2d-x 2.0采用的OpenGL ES 2.0中,这些函数已经不可使用了。OpenGL ES 2.0已经放弃了固定的渲染流水线,取而代之的是自定义的各种着色器,在这种情况下变换操作通常需要由开发者来维护。所幸引擎也引入了一套第三方库Kazmath,它使得我们几乎可以按照原来OpenGL ES 1.0所采用的方式进行开发。表10-2列出了常用OpenGL矩阵操作函数的替代函数,而下面的代码则可以在Cocos2d-x 2.0中实现变换操作:
    //Cocos2d-x 2.0(OpenGL ES 2.0)
    kmGLScalef(0.8f, 0.8f, 0.8f);//乘上缩放矩阵
    kmGLTranslatef(1.0f, 2.0f, 3.0f);//乘上平移矩阵
    kmGLScalef(2.5f, 2.5f, 2.5f);//乘上缩放矩阵
    DrawObject();//绘制任意图形
    表10-2 Cocos2d-x 2.0中矩阵函数的替代函数
    OpenGL ES 1.0函数 替代函数 描述
    glPushMatrix  kmGLPushMatrix 把矩阵压栈
    glPopMatrix kmGLPopMatrix 从矩阵栈中弹出
    glMatrixMode  kmGLMatrixMode 设置当前矩阵模式
    glLoadIdentity  kmGLLoadIdentity 把当前矩阵置为单位矩阵
    glLoadMatrix  kmGLLoadMatrix 设置当前矩阵的值
    glMultMatrix  kmGLMultMatrix 右乘一个矩阵
    glTranslatef  kmGLTranslatef 右乘一个平移矩阵
    glRotatef  kmGLRotatef 右乘一个旋转矩阵
    glScalef kmGLScalef 右乘一个缩放矩阵

    10.2.2渲染树的绘制
    无论如何复杂的游戏场景也都是精灵通过不同的层次、位置组合构成的,因此只要可以把精灵按照前后层次、不同的位置绘制出来就完成了游戏场景的绘制。
    渲染树是由各种游戏元素按照层次关系构成的树结构,它展示了Cocos2d-x游戏的绘制层次,因此游戏的渲染顺序就是由渲染树决定的。
    回顾Cocos2d-x游戏的层次:导演类直接控制渲染树的根节点——场景,场景包含多个层,层中包含多个精灵。实际上,每一个上述的游戏元素都在渲染树中表示为节点,游戏元素的归属关系就转换为了节点间的归属关系,进而形成树结构。
    10.2.3坐标变换
    在绘制渲染树中,最关键的步骤之一就是进行坐标系的变换。
    没有坐标系的变换,则无法在正确的位置绘制出纹理。
    许多以“km”为前缀的函数,是Cocos2d-x使用的一个开源几何计算库Kazmath。它是OpenGL ES 1.0变换函数的代替,可以为程序编写提供便利。形象地讲,transform方法的任务就是根据当前节点的属性计算出如何把绘图坐标系变换为新坐标系的矩阵。
    坐标系变换除了在绘图时有很重要的作用,它还为我们提供了一个有利的坐标转换工具。
    “节点坐标系”指的是以一个节点作为参考而产生的坐标系,换句话说,它的任何一个子节点的坐标值都是由这个坐标系确定的。
    10.3TexturePacker与优化
    在游戏开发初期,通常同时显示在屏幕上的精灵并不会很多,此时游戏的性能问题并不明显,游戏可以顺利流畅地运行(通常以60帧每秒作为流畅运行的标准)。
    然而随着游戏规模的扩大,屏幕上的精灵数量激增,精灵执行的动作越来越复杂,游戏的帧率也会随之下降。当帧率不足30帧每秒时,延迟现象还不是很明显,但是当帧率更低时,明显的延迟现象会导致极差的游戏体验,此时对游戏进行优化就十分必要了。
    10.3.1绘图瓶颈
    在Cocos2d-x中,影响游戏性能的瓶颈通常只有以下几个方面。
    a.纹理过小:OpenGL在显存中的纹理长宽像素一定是2的幂,对于大小不足的纹理,则在其余部分填充空白。
    b.纹理切换次数过多:当我们连续使用两个不同的纹理绘图时,GPU不得不进行一次纹理切换,这是开销很大的操作,然而当我们不断地使用同一个纹理进行绘图时,GPU工作在同一个状态,额外开销就小了很多,因此,如果我们需要批量绘制一些内容相近的精灵,就可以考虑利用这个特点来减少纹理切换的次数。
    c.纹理过大:显存是有限的,如果在游戏中不加节制地使用很大的纹理,则必然会导致显存紧张,因此要尽可能减少纹理的尺寸以及色深。
    针对以上绘图瓶颈,我们接下来介绍几种优化方式以显著提高游戏性能。
    10.3.2碎图压缩与精灵框帧
    到目前为止,我们都是使用各自的纹理来创建精灵,由此导致的纹理过小和纹理切换次数过多是产生瓶颈的根源。针对这个问题,一个简单的解决方案是碎图合并与精灵框帧。碎图合并可以将许多零碎的小图片合并到一张大图里,并且这张大图的大小恰好符合OpenGL纹理规范,从空间上减少无谓的浪费。框帧是纹理中的一部分,当我们把小纹理合并好之后就可以利用精灵框帧来创建精灵了。
    这里我们介绍一款强大的碎图合并工具TexturePacker,这是一个收费工具,但是它提供的免费版已经足以应付日常开发的大部分需求了。
    Cocos2d-x和TexturePacker的对接是非常平滑的,只要一句代码即可完成:
    CCSpriteFrameCache().sharedSpriteFrameCache()->addSpriteFrameWithFile("all.plist");
    我们把碎图信息放在了精灵框帧的缓存内,而不是纹理缓存内。
    10.3.3批量渲染
    有了足够大的纹理图后,就可以考虑从渲染次数上进一步优化了。如果不需要切换绑定纹理,那么几个OpenGL的渲染请求时可以批量提交的,也就是说,在同一纹理下的绘制都可以一次提交完成。在Cocos2d-x中,我们提供了CCSpriteBatchNode来实现这一优化。
    CCSpriteBatchNode可以一次批量提交所有子节点的绘图请求,以减少提交次数,提高绘图性能。这个优化要求每个子节点都使用同一张纹理。
    10.3.4色彩深度优化
    控制游戏包的尺寸:纹理尺寸优化,降低色彩深度。
    默认情况下,我们导出的纹理图片是RGBA8888格式的,因此一个像素总共需要使用4个字节表示。若降低纹理的品质,则可以采用RGBA4444格式来保存图片,因此一个像素总共占用2字节。对于不透明的图片,我们可以选择无Alpha通道的颜色格式,例如RGB565。
    565模式 (rrrrrggggggbbbbb)G值有6位,一个像素有2字节
    555模式(0rrrrrgggggbbbbb)G值有5位,一个像素有2字节

    15.2缓存机制:预加载与重复使用
    15.3Cocos2d-x中的缓存
    幸运的是,我们不需要自己实现缓存,因为Cocos2d-x已经为我们提供了足够强大的实现。
    引擎中存在3个缓存类,都是全局单例模式。
    15.3.1CCTextureCache
    首先是最底层也最有效的纹理缓存CCTextureCache,这里缓存的是加载到内存中的纹理资源,也就是图片资源。
    15.3.2CCSpriteFrameCache
    第二个是精灵帧缓存。
    15.3.3CCAnimationCache
    最后一个是CCAnimationCache动画的缓存。


    郑阿奇版
    chap5纹理映射
    纹理映射是一种将2D图像映射到3D物体上的技术。3D物体的每个顶点都包含了一对纹理坐标,它指定了该顶点所对应到2D图像上特定点的颜色值。D3D根据这些纹理坐标进行插值运算,将纹理贴图映射到3D物体的每个三角形单元中。
    5.1纹理映射基础
    纹理实际上是一张二维图像,用来表示物体的表面细节,如物体的颜色和图案等。
    在D3D中,为了使渲染的图形更具真实感,可以使用纹理映射技术将二维图像映射到三维物体表面。
    纹理映射实际上就是对指定区域的像素颜色进行计算的一个过程。
    5.1.1纹理坐标
    纹理映射所使用的2D图像被称作纹理贴图。D3D支持多种文件格式的纹理贴图文件,如BMP,JPG,PNG,TGA等。
    5.1.2创建纹理
    5.1.3启用纹理
    5.2纹理过滤
    D3D在将一个图元绘制到二维屏幕上时,如果该图元具有对应的纹理,那么D3D就必须用纹理产生屏幕上每个像素的颜色,而这一过程称为纹理过滤。
    在执行纹理过滤操作时,由于屏幕显示的图形大小总是和实际提供的纹理贴图大小不一致,而此时纹理贴图的纹理元素会被方法或缩小,从而导致颜色丢失。当纹理元素被放大时,多个像素会映射到一个纹理元素上,得到的图像可能会劝马赛克现象。而当纹理元素被缩小时,一个像素将映射到多个纹理元素上,得到的图像可能会模糊不清或有锯齿。
    为了解决纹理元素被放大或缩小所造成的图像失真问题,D3D提供了四种纹理过滤方式:最近点采样纹理过滤,线性纹理过滤,各向异性纹理过滤盒多级渐进纹理过滤。

    chap7深度、融合及模板
    在复杂的场景中,通常有多个物体需要绘制,这些物体之间通常会存在遮挡关系,离观察点较远的物体会因为近处物体的遮挡而不可见或只有部分可见。在这种情况下,为了正确地绘制场景需要使用深度测试。
    半透明物体的绘制不同于不透明物体,Direct3D通过Alpha混合实现半透明物体的绘制。深度测试可以简化复杂场景的绘制,而Alpha混合可以使绘制的三维场景更完整,更逼真。
    7.1深度测试
    深度测试是指D3D通过比较当前绘制的像素点的深度和对应深度缓冲区的点的深度值决定是否绘制当前像素。如果深度测试结构为TRUE,则绘制当前像素,并用当前像素点的深度更新深度缓冲区;反之则不予绘制。
    7.2融合技术
    在经过深度测试而绘制的场景中,处于前面的物体部分总是遮挡后面的物体。但是,若需要绘制类似玻璃,水及具有透明效果的物体时,这种方法显然不能够满足需求。为了得到具有类似于透明的效果,可以将当前计算得到的像素(即源像素)的颜色值与先前计算得到的像素(目标像素)的颜色值进行合成,这种方式称作融合。
    D3D在将集合像素的颜色值进行融合时,它是将当前进行光栅化的像素与已经写入后台缓存的像素进行融合。因此,如果需要在D3D程序中使用融合技术,那么首先应该绘制那些不需要进行融合的物体,然后将需要进行融合的物体按照自后向前的顺序逐个进行绘制。
    7.2.1融合因子
    D3D通过Alpha通道实现多个像素颜色值的融合。像素的Alpha分量用于指定像素的透明度,其取值范围在0~255之间,0表示像素完全透明,而255表示像素完全不透明。玻璃会拥有很高的透明度,而一块木头可能就没什么透明度可言。
    D3D通过定义一个表示物体半透明度的Alpha值和一个融合计算公式,可将源像素的颜色值与目标像素的颜色值相混合,因此该过程也称作是Alpha融合。
    OP表示源和目标的融合计算方式,由D3DBLENDOP枚举指定,其默认值为+。
    ARGB=K_srcARGB_src+K_dstARGB_dst
    [四维向量]K_src和K_dst分别表示源融合因子和目标融合因子,其[分量]取值范围在区间[0,1]内。[KARGB仍然是一个四维向量。]
    7.2.2Alpha来源
    像素的Alpha值可以来自顶点颜色,材质,纹理中的Alpha值。如果没有使用材质和纹理,那么当前像素的Alpha分量来自每个顶点颜色的Alpha值。如果使用了光照和材质,那么像素的Alpha值来自物体表面的材质。如果使用了纹理贴图,那么Alpha值则来自纹理贴图的Alpha通道。
    1.顶点Alpha分量
    2.材质Alpha分量
    3.纹理Alpha分量
    7.2.3启用Alpha融合
    在默认情况下,D3D是禁用Alpha融合运算的。
    7.5本章小结
    深度测试是D3D根据存储在深度缓存中的深度值,从而判断位于同一像素的显示优先权。
    深度缓存只保存像素的深度信息,以用于计算每个像素的深度值从而进行深度测试。深度缓存的格式决定了深度测试的精度,通常使用24位表示每个像素的深度值。
    Alpha融合用于将源像素与目标像素的颜色值进行融合。其中源像素是当前需要光栅化的像素,而目标像素是已经写入后台缓存中同一位置的像素。可以通过指定不同的融合因子控制源像素与目标像素的融合方式,前提是已经指定Alpha分量来自于材质的漫反射或者所创建的纹理的Alpha通道。
    chap12文字,拾取及碰撞检测
    12.1字体及文本绘制
    ID3DXFont接口用于在D3D应用程序中创建字体及实现二维文本的绘制,该接口封装了Windows字体和D3D设备指针,并且其内部实际上使用GDI实现文本的绘制。

    第14章网络
    14.1 网络传输架构
    直接使用socket传输;使用HTTP传输
    14.2 CURL
    CURL 是Cocos2d-x推荐使用的网络传输库,随引擎代码一起分发了CURL的一份CPP实现。它是免费开源的,而且支持FTP、HTTP、LDAP等多种传输方式,同时横跨了Windows、UNIX、Linux平台,可以在各种主流的移动设备上良好工作。
    14.8小结
    在游戏开发中,通常利用socket产生的长连接来维持游戏的同步,然而现在也有许多游戏采用HTTP。
    CURL是一套URL通信库,提供了HTTP、FTP等协议的支持,因此可以利用CURL方便地建立HTTP连接。
    直接在主线程中执行网络传输任务称作阻塞式传输,而在新线程中一部地进行网络传输任务则称为非阻塞式传输。【书中此处阻塞式传输的含义不恰当】
    网络游戏需要保持用户终端数据与服务器的数据一致,这个过程称为同步。

    20160804添加:
    第10章 网络编程
    HttpRequest是一种数据类型,它提供了一些方法用来定义或获取HTTP请求的参数。
    /**
    * The HttpRequest type enum used in the HttpRequest::setRequestType.
    */
    enum class Type 
    {   
    GET,
    POST, 
    PUT,    
    DELETE,
    UNKNOWN,
    };
    HttpClient使用单例模式,用来处理异步HTTP请求。
    如果在请求里设置了回调函数,当请求完成后,就会在主线程中调用该回调函数。它使用静态方法getInstance获取:
    network::HttpClient::getInstance();

    void RoomLayer::sendHttpRequest()

     //宏定义手机平台
     string url = ConfigMgr::instance()->text("display_text.xml", "t958");

    #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS)
     url = ConfigMgr::instance()->text("display_text.xml", "t957");
    #endif
     CCLOG("%s completed", url.c_str());
     network::HttpRequest* request = new network::HttpRequest();
     request->setUrl(url.c_str());// 设置请求连接
     request->setRequestType(network::HttpRequest::Type::GET);// 设置请求类型
     request->setResponseCallback(this, callfuncND_selector(RoomLayer::onHttpRequestCompleted));// 设置请求完成后的回调函数
     request->setTag("GET test1");// 为请求设置一个标记
     network::HttpClient::getInstance()->send(request);// 发送请求
     network::HttpClient::getInstance()->setTimeoutForConnect(10);// 设置请求超时的时间
     network::HttpClient::getInstance()->setTimeoutForRead(10);// 设置下载超时的时间
     request->release();
    }

     //获得网络数据
     void onHttpRequestCompleted(Node *sender, void *data);

    HttpResponse也是一种数据类型,包含请求返回的数据等信息。
    // getHttpRequest获取引发该请求的HttpRequest对象
     if (0 != strlen(response->getHttpRequest()->getTag()))  
     { 
      CCLOG("%s completed", response->getHttpRequest()->getTag()); 
     }
    // 获取请求状态码。如果返回200,说明请求成功。
     int statusCode = response->getResponseCode();
    // 判断请求是否成功
     if (!response->isSucceed())  
     { 
      CCLOG("response failed"); 
      CCLOG("error buffer: %s", response->getErrorBuffer());
      return; 
     } 
    // 获取请求返回的数据 
     std::vector<char> *buffer = response->getResponseData();
    调试信息:
    https://www.110kino.com:1000/Android/update.htm completed
    In the constructor of HttpClient!

    GET test1 completed
    response code: 200

    {"version":"1.2.3","packageUrl":"/Client/Download.html"}
    buffer = 0x0896b298 { size=61 }
    vercode = "1.2.3"
    updateUrl = "/Client/Download.html"
    url = "https://www.110kino.com:1000/Client/Download.html"
    D:\Project3.x\9yiLottery_3.x\cocos2d\cocos\network\HttpClient.h
    #include "network/HttpClient.h"

    <t957><a href="" www.110kino.com:1000="" ios="" update.htm<="" t957"="">" target="_blank">https://www.110kino.com:1000/IOS/update.htm</t957>;
    <t958><a href="" www.110kino.com:1000="" update.htm<="" android="" t958"="">" target="_blank">https://www.110kino.com:1000/Android/update.htm</t958>;

    A.3编译create-android-project脚本
    在Cocos2d-x项目的根目录下,可以找到“create-android-project”脚本。在Windows下,对应的是“create-android-project.bat”,而在其他系统下对应的是“create-android-project.sh”,这个脚本用于生成游戏的Android工程。
    使用任何支持UNIX风格换行的编辑器(例如Ultra Editor 32和Vim)打开脚本文件,并根据不同的系统进行下面所述的修改。
    对于Windows用户:在脚本文件中找到以下语句,并设置为相应的路径,其中_CYGBIN变量设置为Cygwin安装目录下的“bin”文件夹路径,_ANDROIDTOOLS设置为Android SDK安装路径下的“tools”文件夹路径,_NDKROOT设置为NDK解压路径。
    set _CYBIN=e:\cygwin\bin
    set _ANDROIDTOOLS=e:\android\android-sdk\tools
    set _NDKROOT=e:\android\android-ndk-r8d
    对于非Windows用户:在脚本文件中找到以下语句,并设置为相应的路径,其中NDK_ROOT_LOCAL设置为NDK的解压路径,ANDROID_SDK_ROOT_LOCAL设置为Android SDK的安装路径。
    完成设置后,在Cygwin或终端中运行“create-android-project”脚本,若提示如下,则修改成功:
    Input package path.For example:org.cocos2dx.example
    A.4执行create-android-project脚本
    此时输入要创建的程序包名(程序包的命名应遵守Android程序包命名规范)。输入后,脚本程序会列出计算机中安装的所有Android SDK版本,此时选择我们所需要的版本。
    完成这一步后,脚本程序会提示“input your project name”,此时输入我们希望创建的文件夹名称。在此处填入“FishingJoy2”,然后稍等片刻,则脚本会在根目录下创建“FishingJoy2”目录,并在此目录中生成Android项目文件。
    通常,我们把源代码复制到“Classes”目录下,而把资源文件放入“Resources”目录下。  
    A.5修改“Android.mk”文件
    “Android.mk”文件位于项目的“proj.android\jni”目录中,它记录了项目所包含的源码文件信息。
    其中需要修改的部分列举如下。
    LOCAL_MODULE:项目生成的名称,可以随意指定。
    LOCAL_MODULE_FILENAME:项目生成的文件名。
    LOCAL_SRC_FILES:这里需要加入所有需要编译的源代码文件。
    LOCAL_C_INCLUDES:这里添加头文件所在的目录。
    LOCAL_WHOLE_STATIC_LIBRARIES:这里添加项目所需的库文件。
    A.6执行“build_native.sh”
    现在我们执行“build_native.sh”脚本以编译我们的游戏。
    A.7导入Eclipse
    接下来需要做的是为游戏创建一个Eclipse工程,并生成Android程序包。    

    20160619、20160628添加:
    Android C++高级编程——使用NDK
    2014年1月第1版
    chap2深入了解Android NDK
    2.3.6检测Android NDK项目的结构
    jni:该目录包含原生组件的源代码以及描述原生组件构建方法的Android.mk构建文件。Android NDK构建系统将该目录作为NDK项目目录并希望在项目根目录中找到它。
    Libs:在Android NDK构建系统的构建过程中创建该目录。它包含指定的目标机体系结构的独立子目录,例如ARM的armeabi。在打包过程中该目录被包含在APK文件中。
    Obj:这是一个中间目录,编译源代码后所产生的目标文件都保存在该目录下。开发人员最好不要访问该目录。
    Android NDK项目最重要的组件是Android.mk构建文件,该文档描述了原生组件。理解构建系统是熟练运用Android NDK及其所有组件的关键。
    2.4.1 Android.mk
    Android.mk是一个向Android NDK构建系统描述NDK项目的GUN Makefile片段。它是每一个NDK项目的必备组件,构建系统希望它出现在jni子目录中。
    #注释块后的第一条指令是用来定义LOCAL_PATH变量的。
    #Android构建系统利用LOCAL_PATH来定位源文件。因为将该变量设置为硬编码值并不合适,所以Android构建系统提供了一个名为my-dir的宏功能。
    LOCAL_PATH := $(call my-dir)
    #Android构建系统将CLEAR_VARS变量设置为clear-vars.mk片段的位置。包含Makefile片段可以清除除了LOCAL_PATH以外的LOCAL_<name>变量,例如LOCAL_MODULE与LOCAL_SRC_FILES等。
    include $(CLEAR_VARS)
    #每一个原生组件被称为一个模块。LOCAL_MODULE变量用来给这些模块设定一个唯一的名称。
    LOCAL_MODULE := helloworld_shared
    #可选变量,用来重新定义生成的输出文件名称。默认情况下,构建系统使用LOCAL_MODULE的值作为生成的输出文件名称,但变量LOCAL_MODULE_FILENAME可以覆盖LOCAL_MODULE的值。
    #helloworld_shared模块会生成一个共享库文件且构建系统会将它命名为libhelloworld.so。
    LOCAL_MODULE_FILENAME := libhelloworld
    #用LOCAL_SRC_FILES变量定义用来建立和组装这个模块的源文件列表。LOCAL_SRC_FILES变量可以包含用空格分开的多个源文件名。
    LOCAL_SRC_FILES := helloworld/main.cpp \
                       ../../Classes/AppDelegate.cpp \
                       ../../Classes/ClientSocket.cpp \
                       ../../Classes/HelloControlScene.cpp \
                       ../../Classes/HelloXmlScene.cpp \
                       ../../Classes/moveLabel.cpp \
                       ../../Classes/MTNotificationQueue.cpp \
                       ../../Classes/SingleThread.cpp \
                       ../../Classes/SkillButton.cpp \
                       ../../Classes/tinyxml/tinyxmlparser.cpp \
                       ../../Classes/tinyxml/tinystr.cpp \
                       ../../Classes/tinyxml/tinyxml.cpp \
                       ../../Classes/tinyxml/tinyxmlerror.cpp \
                       ../../Classes/HelloWorldScene.cpp
    #可选目录列表,NDK安装目录的相对路径,用来搜索头文件
    LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes
    LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../Classes/tinyxml
    LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../../CocosDenshion
    LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../../CocosDenshion/include
    LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../../extensions
    LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../../../external
    #实际的Android应用程序并不直接使用静态库,并且应用程序包中也不包含静态库。静态库可以用来构建共享库。
    #LOCAL_STATIC_LIBRARIES的变体,用来指明应该被包含在生成的共享库中的所有静态库内容。当几个静态库之间有循环依赖时,LOCAL_WHOLE_STATIC_LIBRARIES很有用。
    LOCAL_WHOLE_STATIC_LIBRARIES := cocos2dx_static
    LOCAL_WHOLE_STATIC_LIBRARIES += cocos_extension_static
    #LOCAL_WHOLE_STATIC_LIBRARIES += box2d_static
    #构建共享库。Android NDK构建系统将BUILD_SHARED_LIBRARY变量设置成build-shared-library.mk文件的保存位置。该Makefile片段包含了将源文件构建和组装成共享库的必要过程。
    include $(BUILD_SHARED_LIBRARY)$(call import-add-path,E:/2.0-x-2.03)
    $(call import-add-path,E:/2.0-x-2.03/cocos2dx/platform/third_party/android/prebuilt)
    #默认情况下,import-module函数宏只搜索<Android NDK>/sources目录。
    $(call import-module,CocosDenshion/android)
    $(call import-module,cocos2dx)
    $(call import-module,extensions)
    2.4.2 Application.mk
    Application.mk是Android NDK构建系统使用的一个可选构建文件。和Android.mk文件一样,它也被放在jni目录下。Application.mk也是一个GUN Makefile片段。它的目的是描述应用程序需要哪些模块;它也定义所以模块的通用变量。
    #默认情况下,Android NDK构建系统使用作用最小STL运行库,也被称为system库。可以用该变量选择不同的STL实现。
    #APP_STL := stlport_shared
    APP_STL := gnustl_static
    #APP_CPPFLAGS变量列出了一些编译器标志,在编译任何模块的C++源文件时这些标志都会被传给编译器。
    APP_CPPFLAGS := -frtti
    #APP_CFLAGS变量列出了一些编译器标志,在编译任何模块的C和C++源文件时这些标志都会被传给编译器。
    APP_CFLAGS += -Wno-error=format-security

    英文版http://pan.baidu.com/share/link?shareid=1994603050&uk=1614005835
    Pro Android C++ with the NDK.pdf
    全部14章中文版http://pan.baidu.com/share/link?shareid=2785412732&uk=305441427
    链接: http://pan.baidu.com/s/1o6FXq6u 密码: wszz
    前3章中文版http://vdisk.weibo.com/s/u7lkQIf33pnBa
    Android C++高级编程 使用NDK完整版nw.pdf
    Android C++高级编程——使用NDK
    2014年1月第1版
    作者:(美国)辛纳(Onur Cinar) 译者:于红 佘建伟 冯艳红
    目录
    第1章Android平台上的C++入门
    第2章深入了解Android NDK
    第3章用JNI实现与原生代码通信
    我们将会掌握以下主要概念:
    Java代码如何调用原生方法
    声明原生方法
    在共享库中载入原生模块
    在C/C++中实现原生方法

    3.2.1原生方法的声明
    Cocos2dxRenderer.java
    nativeInit方法中含有关键字native以通知Java编译器,它用另一种语言提供该方法的具体实现。因为原生方法没有方法体,方法声明以语句终结符——分号结尾。
     @Override
     public void onSurfaceCreated(final GL10 pGL10, final EGLConfig pEGLConfig) {
      Cocos2dxRenderer.nativeInit(this.mScreenWidth, this.mScreenHeight);
      this.mLastTickInNanoSeconds = System.nanoTime();
     }
     private static native void nativeTouchesBegin(final int pID, final float pX, final float pY);
     private static native void nativeTouchesEnd(final int pID, final float pX, final float pY);
     private static native void nativeTouchesMove(final int[] pIDs, final float[] pXs, final float[] pYs);
     private static native void nativeTouchesCancel(final int[] pIDs, final float[] pXs, final float[] pYs);
     private static native boolean nativeKeyDown(final int pKeyCode);
     private static native void nativeRender();
            // 原生方法由原生库实现。该原生库与应用程序一起打包
     private static native void nativeInit(final int pWidth, final int pHeight);
     private static native void nativeOnPause();
     private static native void nativeOnResume();
    尽管现在虚拟机知道该方法被原生实现,但是它仍然不知道到哪儿去找方法的实现。
    3.2.2加载共享库
    原生方法被编译成一个共享库。需要先加载该共享库以便虚拟机能够找到原生方法实现。java.lang.System类提供了两个静态方法,load和loadLibrary,用于在运行时加载共享库。
    3.2.3实现原生方法
    proj.android\jni\hellocpp\main.cpp
    原生方法nativeInit也用一个名为Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit的完全限定的函数来声明,这种显式的函数命名让虚拟机在加载的共享库中自动查找原生函数。
    原生方法多带了两个参数,第一个参数JNIEnv是指向可用JNI函数表的接口指针;第二个参数jobject是Cocos2dxRenderer类实例的Java对象引用。
    (1)JNIEnv接口指针
    原生代码通过JNIEnv接口指针提供的各种函数来使用虚拟机的功能。
    (2)实例方法与静态方法
    Java程序设计语言有两类方法:实例方法和静态方法。实例方法和静态方法均可以声明为原生的。
    原生实例方法通过第二个参数获取实例引用,该参数是jobject类型的。
    原生静态方法通过第二个参数获取类引用而不是实例引用,该参数是jclass值类型的。
    #include "AppDelegate.h"
    #include "platform/android/jni/JniHelper.h"
    #include <jni.h>//这个头文件中包含JNI数据类型和函数的定义
    #include <android/log.h>

    #include "HelloWorldScene.h"

    #define  LOG_TAG    "main"
    #define  LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)

    using namespace cocos2d;

    extern "C"
    {

    jint JNI_OnLoad(JavaVM *vm, void *reserved)
    {
        JniHelper::setJavaVM(vm);

        return JNI_VERSION_1_4;
    }
    //nativeInit方法的原生实现
    void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv*  env, jobject thiz, jint w, jint h)
    {
        if (!CCDirector::sharedDirector()->getOpenGLView())
        {
            CCEGLView *view = CCEGLView::sharedOpenGLView();
            view->setFrameSize(w, h);

            AppDelegate *pAppDelegate = new AppDelegate();
            CCApplication::sharedApplication()->run();
        }
        else
        {
            ccDrawInit();
            ccGLInvalidateStateCache();
           
            CCShaderCache::sharedShaderCache()->reloadDefaultShaders();
            CCTextureCache::reloadAllTextures();
            CCNotificationCenter::sharedNotificationCenter()->postNotification(EVNET_COME_TO_FOREGROUND, NULL);
            CCDirector::sharedDirector()->setGLDefaultValues();
        }
    }

    }
    3.7线程
    作为多线程环境的一部分,虚拟机支持运行的原生代码。在开发原生构件时要记住JNI技术的一些约束:
    1、只在原生方法执行期间及正在执行原生方法的线程环境下局部引用是有效的,局部引用不能在多线程间共享,只有全局引用可以被多个线程共享。
    2、被传递给每个原生方法的JNIEnv接口指针在与方法调用相关的线程中也是有效的,它不能被其他线程缓存或使用。
    3.7.2原生线程
    JNI通过JavaVM接口指针提供了AttachCurrentThread函数以便于让原生线程附着到虚拟机上。JavaVM接口指针应该尽早被缓存,否则的

    话它不能被获取。
    JniHelper.cpp
    #define JAVAVM    cocos2d::JniHelper::getJavaVM()
        static bool getEnv(JNIEnv **env)
        {
            bool bRet = false;

            do
            {
                if (JAVAVM->GetEnv((void**)env, JNI_VERSION_1_4) != JNI_OK)
                {
                    LOGD("Failed to get the environment using GetEnv()");
                    break;
                }

                // AttachCurrentThread将当前线程附着到虚拟机
                // 可以用JNIEnv接口实现线程与Java应用程序的通信
                // DetachCurrentThread将当前线程与虚拟机分离
                if (JAVAVM->AttachCurrentThread(env, 0) < 0)
                {
                    LOGD("Failed to get the environment using AttachCurrentThread()");
                    break;
                }

                bRet = true;
            } while (0);       

            return bRet;
        }

    第4章使用SWIG自动生成JNI代码
    第5章日志、调试及故障处理
    第6章BionicAPI入门
    第7章原生线程
    7.3 POSIX线程
    7.3.1在原生代码中使用POSIX线程
    POSIX Thread的Android实现是Bionic标准C标准库的一部分。与其他平台不同,在编译时不需要链接任何其他的库。
    第8章POSIXSocketAPI:面向连接的通信
    第9章POSIXSocketAPI:无连接的通信
    第10章POSIXSocketAPI:本地通信
    第11章支持C++
    第12章原生图形AP
    第13章原生音频API
    第14章程序概要分析和NEON优化


    Cocos2d-x游戏开发技术精解
    刘剑卓 著
    2013年6月第1版
    chap2 Cocos2d-x引擎的开发环境
    2.1跨平台的开发
    2.2建立开发环境
    2.2.1 PC开发环境
    本书将会选择使用稳定的2.0.4引擎版本。
    在本书的讲解中,将会以Windows下的Visual Studio 2010为主要的开发环境。
    注意:读者最好不要选用VS 2008版本进行开发,因为引擎开发者已经明确未来不再支持此版本的开发环境。
    2.2.1 PC开发环境
    在引擎包中,提供了三个版本的项目工程。它们分别针对VS2008、VS2010以及VS2012。【VS2005也支持,VS2003不支持】
    注意:VS2010只支持Win7以后的操作系统。【XP也支持】

    20160908:搭建Cocos2d-x 2.0.3+VS2005开发环境
    libBox2D
    libchipmunk
    libcocos2d - 3 个错误,106 个警告
    1>d:\vc8project\2.0-x-2.03\cocos2dx\platform\win32\ccapplication.cpp(189) : error C2065: 'LSTATUS' : undeclared identifier
    1>d:\vc8project\2.0-x-2.03\cocos2dx\platform\win32\ccapplication.cpp(189) : error C2146: syntax error : missing ';' before identifier 'status'
    1>d:\vc8project\2.0-x-2.03\cocos2dx\platform\win32\ccapplication.cpp(189) : error C2065: 'status' : undeclared identifier
    1>libcocos2d - 1 个错误,2 个警告
    1>LINK : fatal error LNK1181: 无法打开输入文件“libzlib.lib”
    1>libcocos2d - 1 个错误,0 个警告
    1>LINK : fatal error LNK1181: 无法打开输入文件“libiconv.lib”
    1>libcocos2d - 0 个错误,60 个警告
    ========== 生成: 1 已成功, 0 已失败, 0 最新, 0 已跳过 ==========
    libCocosDenshion
    libExtensions
    liblua

    TestCpp
    1>ExtensionsTest.obj : error LNK2019: 无法解析的外部符号 "void __cdecl runEditBoxTest(void)" (?runEditBoxTest@@YAXXZ),该符号在函数 "public: void __thiscall ExtensionsMainLayer::menuCallback(class cocos2d::CCObject *)" (?menuCallback@ExtensionsMainLayer@@QAEXPAVCCObject@cocos2d@@@Z) 中被引用
    1>D:\VC8Project\2.0-x-2.03\Debug.win32\TestCpp.exe : fatal error LNK1120: 1 个无法解析的外部命令
    1>生成日志保存在“file://d:\VC8Project\2.0-x-2.03\samples\TestCpp\proj.win32\Debug.win32\BuildLog.htm”
    1>TestCpp - 2 个错误,151 个警告
    //runEditBoxTest();

    TestJavascript

    TestLua
    1>d:\vc8project\2.0-x-2.03\scripting\lua\cocos2dx_support\luacocos2d.h(16) : fatal error C1083: Cannot open include file: 'cocos-ext.h': No such file or directory
    1>d:\vc8project\2.0-x-2.03\scripting\lua\cocos2dx_support\luacocos2d.h(16) : fatal error C1083: Cannot open include file: 'cocos-ext.h': No such file or directory
    1>TestLua - 2 个错误,8 个警告
    附加包含目录
    $(SolutionDir)\extensions

    HelloLua
    1>d:\vc8project\2.0-x-2.03\scripting\lua\cocos2dx_support\luacocos2d.h(16) : fatal error C1083: Cannot open include file: 'cocos-ext.h': No such file or directory
    1>d:\vc8project\2.0-x-2.03\scripting\lua\cocos2dx_support\luacocos2d.h(16) : fatal error C1083: Cannot open include file: 'cocos-ext.h': No such file or directory
    1>HelloLua - 2 个错误,7 个警告
    附加包含目录
    $(SolutionDir)\extensions
    工作目录由$(OutDir)改为D:\VC8Project\2.0-x-2.03\samples\HelloLua\Resources


    1>HelloCpp - 5 个错误,14 个警告
    1>d:\vc8project\2.0-x-2.03\samples\hellocpp\classes\register.h(5) : fatal error C1083: Cannot open include file: 'cocos-ext.h': No such file or directory
    1>d:\vc8project\2.0-x-2.03\samples\hellocpp\classes\login.h(5) : fatal error C1083: Cannot open include file: 'SimpleAudioEngine.h': No such file or directory
    1>d:\vc8project\2.0-x-2.03\samples\hellocpp\classes\hall.h(5) : fatal error C1083: Cannot open include file: 'cocos-ext.h': No such file or directory
    1>d:\vc8project\2.0-x-2.03\samples\hellocpp\classes\hall.h(5) : fatal error C1083: Cannot open include file: 'cocos-ext.h': No such file or directory
    1>d:\vc8project\2.0-x-2.03\samples\hellocpp\classes\login.h(5) : fatal error C1083: Cannot open include file: 'SimpleAudioEngine.h': No such file or directory
    附加包含目录
    $(SolutionDir)\extensions
    $(SolutionDir)\CocosDenshion\include
    1>D:\VC8Project\2.0-x-2.03\Debug.win32\HelloCpp.exe : fatal error LNK1120: 11 个无法解析的外部命令
    1>生成日志保存在“file://d:\VC8Project\2.0-x-2.03\samples\HelloCpp\proj.win32\Debug.win32\BuildLog.htm”
    1>HelloCpp - 23 个错误,16 个警告
    F:\hxhwin7\Cocos2d-xVC2010\cocos2d-2.0-x-2.0.3
    用VC2012编译PC版通过
    “常规|包含目录”去掉
    D:\SixDivisions\Cocos2d-x\2.0-x-2.03_branch_231_NoScript\extensions\GUI\CCControlExtension
    D:\cocos2d-2.1rc0-x-2.1.2\extensions
    D:\cocos2d-2.1rc0-x-2.1.2\extensions\GUI
    “常规|库目录”去掉
    $(MSBuildProgramFiles32)\Microsoft SDKs\Windows\v7.1A\lib
    2.1.5版本LAYER_CREATE_FUNC改为CREATE_FUNC
    2.0.3引擎包的Bug:添加CCEditBox.h和.cpp到libExtensions工程,作如下修改并重新编译
    #ifndef WIN32
        CCEditBoxImpl*      m_pEditBoxImpl;
    #endif
    “链接器|高级|导入库”
    $(OutDir)$(TargetName).lib改为$(TargetDir)$(TargetName).lib
    ADT+Builder/VC2012+Cocos2d-x 2.0.3可以正常编译捕鱼原型的Android版和PC版。
    静态库libExtensions.lib
    CCControl.win32工程|预处理定义:WIN32;_DEBUG;_WINDOWS;
    工作目录
    $(ProjectDir)..\Resources或$(SolutionDir)samples\CCControl\Resources
    Android下路径文件名大小写敏感

    20160710添加:
    VS2010+2.0.3工程移植到VS2012+2.2.5的步骤:
    调试器无法继续运行该进程。项目文件""已被重命名或已不在解决方案中。
    【确定】
    python create_project.py -project ApplicationDemo -package org.cocos2dx.application -language cpp
    VS2012打开ApplicationDemo工程
    cygwin64编译so文件libcocos2dcpp.so:
    cd /cygdrive/d/cocos2d-x-2.2.5/projects/ApplicationDemo/proj.android
    export NDK_ROOT=/cygdrive/d/Hanxiaohua/android-ndk-r9
    ./build_native.sh
    ADT打包apk文件ApplicationDemo.apk:
    导入时根目录填D:\cocos2d-x-2.2.5\projects\ApplicationDemo\proj.android
    工程属性文件target=android-8改为target=android-18
    lib目录拷贝至src\org\cocos2dx\lib目录,与application目录平行
    2.2.5打包APK时,官方推荐方式:
    http://www.cnblogs.com/chenyt/p/3786368.html
    系统环境变量Path:;H:\cygwin64\bin
    Administrator@hxh ~
    $ cd $NDK_ROOT

    Administrator@hxh /cygdrive/d/Hanxiaohua/android-ndk-r9
    $ cd $COCOS2DX_ROOT

    Administrator@hxh /cygdrive/d/cocos2d-x-2.2.5
    $

    Administrator@hxh /cygdrive/d/cocos2d-x-2.2.5/projects/HelloWorld/proj.android
    $ export NDK_MODULE_PATH=/cygdrive/d/cocos2d-x-2.2.5/projects/HelloWorld:/cygdrive/d/cocos2d-x-2.2.5/projects/HelloWorld/proj.android
    $ bash build_native.sh

    20160608:
    H:\cygwin64\bin改为D:\Hanxiaohua\cygwin64\bin
    用UE修改.bash_profile
    ANDROID_NDK_ROOT=/cygdrive/D/Hanxiaohua/android-ndk-r9

    export ANDROID_NDK_ROOT
    NDK_ROOT=/cygdrive/D/Hanxiaohua/android-ndk-r9

    export NDK_ROOT
    COCOS2DX_ROOT=/cygdrive/d/cocos2d-x-2.2.5

    export COCOS2DX_ROOT
    20160603添加:
    D:\cocos2d-x-2.2.5\tools\project-creator> python
    Python 2.7.9 (default, Dec 10 2014, 12:24:55) [MSC v.1500 32 bit (Intel)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> exit();
    D:\cocos2d-x-2.2.5\tools\project-creator>python create_project.py -project HelloWorld -package org.cocos2dx.application -language cpp
    proj.ios                : Done!
    proj.android            : Done!
    proj.win32              : Done!
    proj.winrt              : Done!
    proj.wp8                : Done!
    proj.mac                : Done!
    proj.blackberry         : Done!
    proj.linux              : Done!
    proj.marmalade          : Done!
    proj.tizen              : Done!
    proj.wp8-xaml           : Done!
    New project has been created in this path: D:\cocos2d-x-2.2.5\tools\project-crea
    tor/../../projects/HelloWorld
    Have Fun!

    2.2.2 Android开发环境
    2.2.3 iOS开发环境

    1、Win7下64位JDK 1.6的安装
    C:\Users\Administrator>java -version
    java version "1.6.0_43"
    Java(TM) SE Runtime Environment (build 1.6.0_43-b01)
    Java HotSpot(TM) 64-Bit Server VM (build 20.14-b01, mixed mode)
    2、Win7下在D盘安装VMware12虚拟机+虚拟机苹果补丁unlocker203+OS X 10.11.3
    3、将Win8.1笔记本上的虚拟机OS X 10.8从C盘移动到E盘
    4、
    http://www.2cto.com/os/201406/307272.html
    http://blog.csdn.net/ldl22847/article/details/9406855
    http://www.myhack58.com/Article/sort099/sort0100/2015/58873.htm
    http://bbs.qcloud.com/thread-2208-1-1.html
    20161215:MacBook Pro下Cocos2d-x 3.x iOS开发环境搭建
    安装Cocos2d-x 3.10 mac版
    /Applications/Cocos/Cocos2d-x

    在终端中输入以下命令:

    cd /Applications/Cocos/Cocos2d-x/cocos2d-x-3.10/tools/cocos2d-console/bin

    ls
    cocos new HelloApp -p com.hpcy.mahjong -l cpp -d ~/Desktop
    20160107:Win7下Cocos2d-x iOS开发环境搭建
    0、先前已经搭好虚拟机OS X 10.8.5+Xcode4.6.3环境,原生iOS App也已经能真机调试了。
    1、将cocos2d-x根目录2.0-x-2.0.3拷贝到文稿中;
    2、启动终端,输入命令:
    cd /Users/Hsq/Documents/2.0-x-2.0.3
    sudo ./install-templates-xcode.sh
    Password:输入密码
    然后开始安装Xcode4 cocos2d-x iOS模板
    sudo ./install-templates-xcode.sh -f是重装模板命令
    安装后,打开Xcode发现找不到cocos2d-x的模板,这是因为黑苹果的管理权限问题。
    打开Finder|前往|前往文件夹,输入~/Library/Developer/Xcode/Templates/cocos2d-x
    将共享与权限处的权限都改为“读与写”,然后应用到包含的项目,重启Xcode,发现已经能看到cocos2d-x工程模板了。
    3、
    iOS模拟器-iPhone/iOS 6.1(10B141可跑)
    iOS模拟器-iPad/iOS 6.1(10B141可跑)
    生成SimpleGame.ipa,越狱iPhone4真机可跑
    注意生成.ipa时选择TARGETS|Build Phases,Run Script填写如下内容:
    export CODESIGN_ALLOCATE=/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate
    codesign -fs "iPhone Developer" --entitlements="/Applications/Xcode.app/Contents/Developer/iphoneentitlements/Entitlement1.plist" "${BUILT_PRODUCTS_DIR}/${WRAPPER_NAME}/$TARGETNAME" 
    20150614问题:在Classes目录下新建gui目录、HelloWorldScene1.h、HelloWorldScene1.cpp
    Xcode7重新编译,会出现如下链接失败的错误提示:
    Undefined symbols for architecture armv7:
      "HelloWorld1::scene()", referenced from:
          AppDelegate::applicationDidFinishLaunching() in AppDelegate.o
    ld: symbol(s) not found for architecture armv7
    clang: error: linker command failed with exit code 1 (use -v to see invocation)
    解决办法:
    选择TARGETS|Build Phases|Compile Sources(230 items)
    点击+|Add Other...
    选择HelloWorldScene1.cpp
    在弹出的Choose options for adding these files:界面中选择Create groups,然后点击【Finish】
    可以发现变为Compile Sources(231 items)
    重新编译,就可以正常运行了。

    在Xcode界面中,左侧为项目路径。
    在iOS平台,系统库是以framework的形式引入的。开发者可以点击页面下的“+”按钮进行添加。
    而由引擎编译而来的程序库,则是以“.a”为文件后缀的,比如所有游戏项目都需要引入libcocos2dx.a库作为引擎。
    除此之外读者还要设置项目依赖关系。
    2.3.2 Objective-C与C++的混合编译
    为了介绍方便,以后的内容就简写为OC。
    编译器允许用户在同一个源文件里自由地混合使用C++和Objective-C,混编后的语言叫Objective-C++。
    说明:尽量避免在一个文件中写入两种编程语言的类声明,这很容易造成混乱,不宜阅读。
    在项目路径proj.ios下,存放可以混合编写的代码文件。这些文件的后缀为“.mm”。Xcode编译器允许开发者在同一个源文件里自由地混合使用C++和OC,混合后的语言叫Objective-C++,简称为OC++。编译器就是通过文件后缀名来标识OC++文件的。在OC++文件中,可以用C++代码调用方法,也可以从Objective-C调用方法。在两种语言里对象都是指针,可以在任何地方使用。
    AppController.mm文件是引擎中应用开启的类。它负责管理游戏应用运行周期。在这里作为例子,主要是为读者介绍OC++的编程方式。
    注意:OC++没有为OC类增加C++的功能,也没有为C++类增加OC的功能。例如,你不能用OC语法调用C++对象,也不能为OC对象增加构造函数和析构函数,也不能将this和self互相替换使用。类的体系结构是独立的。C++类不能继承OC类,OC类也不能继承C++类。另外,多语言异常处理是不支持的。也就是说,一个OC抛出的异常不能被C++代码捕获,反过来C++代码抛出的异常也不能被OC代码捕获。
    再来看看iOS平台入口文件的内容。
    main
    //初始化自动释放池
    //启动应用程序
    代码来自项目路径proj.ios\main.m
    这就是iOS应用程序的入口,同样是由引擎为开发者准备妥当了。这段代码也是只有在iOS项目中才能发挥作用。
    2.4.2引擎应用入口
    在项目路径Classes中,存在一个AppDelegate类文件。此文件的命名是OC编程语言的风格。在此代码文件中,就是与引擎应用生命周期有关的函数。所谓的生命周期,就是游戏应用开始与结束的过程。在此过程中,引擎应用会存在许多的状态。
    引擎应用的生命周期
    //应用开启的入口
    applicationDidFinishLaunching
    //当应用进入待机状态时调用的函数
    applicationDidEnterBackground
    //当应用从待机恢复
    applicationWillEnterForeground

    引擎中没有提供多款应用同时运行的情况。当引擎启动之后,就会执行唯一的游戏应用。引擎将会调用applicationDidFinishLaunching函数作为游戏应用开启的入口。在引擎应用运行的状态中,还存在一个待机状态。这就是当手机来电话或者退入后台时,游戏进入了暂停状态,同时还要暂停音乐的播放。

    2.3引擎中的混合编译
    2.3.1 Java与C++的混合编译
    2.3.2 Objective-C与C++的混合编译
    2.4引擎的起点
    2.4.1应用程序入口
    2.4.2引擎应用入口
    2.5丰富的示例程序
    2.5.1 TestCpp示例项目
    2.5.2脚本示例项目
    2.5.3 MoonWarriors示例项目
    2.6本章小结
    chap3 引擎的核心——渲染框架
    3.1基本框架
    3.1.1引擎的位置
    3.1.2根源种子
    3.1.3子类结构
    3.2渲染框架
    3.2.1框架结构
    导演、场景、层次和精灵。它们对应了引擎当中的四个类:CCDirector、CCScene、CCLayer和CCSprite。
    3.2.2摄像机类CCCamera
    在每一个CCNode对象中,都存在一个摄像机对象CCCamera。
    注意:摄像机控制与物体属性控制,读者只能二选其一。如果同时使用,将会导致引擎的坐标系出现错误。
    开发者在使用摄像机时还有一些注意事项。
    (1)一些特殊的CCNode对象,比如CCParallaxNode、CCParticle依据的是世界坐标系,它们将不会受到摄像机影响。
    (2)当精灵对象CCSprite是来自精灵集合CCSpriteBatchNode时,也将不受摄像机的影响。
    (3)如果开发者只需要二维的画面效果,则无需控制摄像机。摄像机只是为了体现三维效果而准备的。
    3.2.3导演类CCDirector
    说明:单例模式也存在一个缺点,就是随处可见的调用关系,很容易导致代码结构的混乱。
    说明:在不同的平台,因为显示设备不同,显示窗口(EAGLView)初始化的方式略有不同。
    在类CCDirector的代码中,为了便于管理场景对象,采用了队列[栈:先进后出]方式来管理场景对象。
        /* The running scene */
        CCScene *m_pRunningScene;
       
        /* will be the next 'runningScene' in the next frame
         nextScene is a weak reference. */
        CCScene *m_pNextScene;
    /* scheduled scenes */
        CCArray* m_pobScenesStack;
    在类CCDirector的源代码中,runningScene_表示当前正在显示的场景,nextScene表示下一个将要显示的场景。而用于储存场景队列的对象则是一个动态可变数组sceneStack_。
    3.2.4场景类CCScene
    场景通常不包含游戏逻辑,仅仅是做为一个容器,将不同的层组合到一起,最终呈现给玩家一个完整的画面。它代表了游戏运行中的一个状态,其包含的图层是更小一级的容器。图层中包含了游戏逻辑、用户响应以及精灵对象。
    所有与显示有关的类几乎都是继承自CCNode。
    说明:类CCNode中提供了更改父节点的函数setParent()。
    3.2.5图层类CCLayer
    图层也是渲染框架中很重要的内容。场景类用来划分游戏状态。图层就用来划分游戏画面。通常图层的尺寸会与屏幕尺寸一致。
    图层之间可以叠加,也可以彼此包含。
    为了让不同的层能够叠加来产生整体的画面效果,图层中包含了一些透明的区域。不仅如此,图层叠加还可以进行颜色混合。引擎当中就通过类CCLayer提供了上述图层的功能。引擎中图层对象包含了三个功能。
    (1)接受用户操作,比如触屏、重力加速度计的信息。
    (2)作为游戏内容元素的容器,用于显示游戏画面、承载精灵类、字体文本等对象。
    (3)填充背景游戏背景颜色。
    在CCLayer头文件的源代码中,还存在一个函数node。它的作用与函数create一样,只不过是旧版本留下来的程序接口。用不了多久,它就会被弃用。
    类CCLayer中也没有实际的绘制内容。它的主要作用在于建立用户交互的功能。
    注意:默认情况下,图层是不接受用户操作的。开发者需要调用函数来开启图层将要接受的用户操作,比如setTouchEnabled、setAccelerometerEnabled以及isKeypadEnabled。
    引擎按照游戏内容提供了一些特殊的图层类。
    1、CCLayerColor颜色层
    2、CCMenu菜单图层
    3、CCMultiplexLayer复合层
    3.2.6精灵类CCSprite
    精灵类CCSprite继承自CCNode,这是为了满足渲染框架的结构。
    精灵类还继承了两个协议类,它们分别为CCRGBAProtocol和CCTextureProtocol。这两个协议类是用于处理精灵类中的纹理图片,前者是负责颜色的管理,后者是用于纹理图片的管理。
    3.2.7精灵集合类CCSpriteBatchNode
    精灵集合类CCSpriteBatchNode,它的对象常常包含了许多的精灵对象。这些精灵对象具有一个共同的特点,那就是使用同一张纹理图片。虽然是同一张纹理图片,但每个精灵所用的矩形区域不一样。
    3.2.8精灵帧缓冲CCSpriteFrameCache
    精灵帧缓冲CCSpriteFrameCache就是一个存放精灵帧CCSpriteFrame对象的缓冲池。
    它甚至都不是CCNode子类。
    3.2.9 Zwoptex纹理编辑器
    3.3文字与字体
    3.3.1 TTF类型标签(CCLabelTTF)
    3.3.2 BMFont标签类(CCLabelBMFont)
    3.3.3 Atlas标签类(CCLabelAtlas)
    3.4菜单按钮
    3.5几何绘制DrawPrimitives
    3.6 CocosBuilder编辑器
    3.6.1 CocosBuilder使用指南
    3.6.2引擎中的应用
    3.7本章小结
    chap4 动作功能
    本章是Cocos2d-x引擎中最有魅力的部分。
    4.1概述
    4.2动作基类
    4.2.1动作类的继承关系
    4.2.2动作基类CCAction的成员函数
    4.2.3 类CCNode中与动作有关的函数
    4.3时间动作
    动作基类CCAction首要的继承子类,其实就是CCFiniteTimeAction类。
    从字面的意思来理解是与时间相关的动作类,因此在类CCFiniteTimeAction的属性中就加入了一个与时间有关的成员变量,同时提供了如下两个操作函数。
    getDuration
    setDuration
    与时间相关的动作被划分为两类:即时动作和延时动作。
    4.3.1即时动作这些继承自类CCActionInstant的子类都是即时动作。这意味着当此类动作被作用在精灵或者其他的CCNode对象时,将会立即被执行,发挥其动作的作用。
    现在先来看看每一个即时动作所发挥的作用吧!
    6、函数调用动作(CCCallFunc、CCCallFuncND、CCCallFuncN以及CCCallFuncO)
    与前几个类的作用一样,函数调用的动作也经常用在动作序列当中。
    此系列包含了四个动作类。类CCCallFunc是另外三个的父类。它们只是因为传递的参数不同,而有所区分。
    CCCallFunc类的调用函数没有任何参数。
    CCCallFuncND类的调用函数有两个参数,分别为CCNode对象和Data数据指针。
    虽然有四个函数调用的动作类,但是它们的区别也仅仅是在参数上。其他部分的作用则是一样的。

    void CBulletManage::Shoot(int iPlayerID, CCPoint ptTouch, CCPoint ptShip, float fRotation, bool bReBound)
    {
     if (NULL == s_AllBullet)
      CCAssert(0, "请先调用 CBulletManage::Init(...) 函数!");

     // 子弹间隔延迟控制
     DWORD dwNow = ::timeGetTime();
     if (dwNow < m_LastShootTime+d_Max_Shoot_Delay)
      return;
     m_LastShootTime = dwNow;

     CCPoint ptMuzzle = CountTwoPointOneCoordinate(ptTouch, ptShip, 150); // 计算枪口位置
     CBullet * pBullet = CBullet::create( CCString::createWithFormat("bullet_01_00000.png")->getCString() );
     pBullet->autorelease();
     pBullet->setRotation( fRotation );
     pBullet->setIsCatching( false );
     pBullet->setIsReBound( bReBound );
     pBullet->setAnchorPoint(CCPoint(0.5, 1.0));
     pBullet->setPosition( ptMuzzle );
     pBullet->setPlayerID( iPlayerID );
     
     float fDistance = sqrtf( pow(ptTouch.x - ptMuzzle.x,  2)  + pow(ptTouch.y - ptMuzzle.y,  2) ) ; // 两点间距离
     float fTime = fDistance / d_Bullet_Speed; // 子弹需要时间
     CCActionInterval *pMove = CCMoveTo::create( fTime, ptTouch );
     
     CCActionInstant * pCallBack = CCCallFuncND::create(pBullet, callfuncND_selector( CBullet::FinishAnimation ), (void *)&ptTouch);
     CCActionInterval * pBulletRun = CCSequence::create(pMove, pCallBack, NULL);
     pBullet->runAction( pBulletRun );

     CCArray* pArray = CCArray::create();
     for (int i=0; i<10; i++)
     {
      CCString * pStrBuffer = CCString::createWithFormat( "bullet_01_%05d.png", i);
      CCSpriteFrame* pSpriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName( pStrBuffer->getCString() );
      pArray->addObject( pSpriteFrame );
     }
     pBullet->runAction( CCRepeatForever::create( CCAnimate::create( CCAnimation::createWithSpriteFrames( pArray, 0.1f ) ) ) );
     s_AllBullet->addChild( pBullet );
    }
    void CBullet::FinishAnimation(CCNode* pSender, void* data)
    {
     stopAllActions();
     setVisible( false );

     CCPoint * pTouch = (CCPoint *)data;

     // 鱼网
     CCArray *pArray = CCArray::create();
     for (int i=0; i<10; i++)
     {
      CCString *pStrBuffer = CCString::createWithFormat("FishNet_01_%05d.png", i);
      CCSpriteFrame * pFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName( pStrBuffer->getCString() );
      pArray->addObject( pFrame );
     }
     CCActionInterval * pAnimate = CCAnimate::create( CCAnimation::createWithSpriteFrames( pArray, 0.05f ) );

     setAnchorPoint(CCPoint(0.5,0.5));

     // 结束自己
     CCActionInstant *pCallBack = CCCallFunc::create(this, callfunc_selector( CBullet::Release ));
     runAction( CCSequence::create(CCShow::create(), pAnimate, pCallBack, NULL) );
    }
    void CBullet::Release()
    {
     removeAllChildren();
     removeFromParent();
    }

    4.3.2持续动作
    按照CCNode类的对象属性来划分,我们可以将上述动作分为以下几个类型。
    1、与位置有关的持续动作
    CCMoveBy、CCMoveTo、CCJumpBy、CCJumpTo、CCBezierBy、CCBezierTo,上述三对动作类在执行时都会通过修改类CCNode对象中的位置属性来发挥执行效果。
    To代表了具体移动的结果。
    有By的动作,表示动作执行的程度或者速率。
    之后,还会接触许多成对出现、采用类似命名规则的动作类。
    先从CCMoveTo和CCMoveBy开始。
    在创建此动作类的对象时,第一个参数就是时间,第二个参数就是目标点或者移动距离。

     /// 划船动画
     void OnRowingAnimation(float fTime);
     void OnRowingCompleteAnimation();
    void Player::OnRowingAnimation(float fTime)
    {
     unschedule( schedule_selector(Player::OnRowingAnimation) );

     float fOffset = 110.0f;
     if (PLAYER_COUNT/2 <= m_PlayerPT)
      fOffset = -110.0f;

     CCSprite * pShip = (CCSprite *)getChildByTag( t_Ship );
     pShip->setVisible( true );
     pShip->setPositionY( pShip->getPositionY()+fOffset );

     CCActionInterval * pMove = CCMoveBy::create(1.5f, ccp(0, -fOffset));
     CCActionInstant * pComplete = CCCallFunc::create(this, callfunc_selector(Player::OnRowingCompleteAnimation));
     CCActionInterval * pShipAnimation = CCSequence::create(pMove, pComplete, NULL);
     pShip->runAction( pShipAnimation );
    }

    void Player::OnRowingCompleteAnimation()
    {
     // 玩家划船进入,未到达捕鱼现场,除了炮与船其他信息暂隐藏。
     CCSprite *pChild = NULL;
     CCObject* pObj = NULL;
     CCARRAY_FOREACH(getChildren(), pObj)
     {
      pChild = (CCSprite*)pObj;
      pChild->setVisible( true );
     }
    }

    在Cocos2d-x引擎中存在一个专门用来表示[三阶]贝塞尔曲线的配置类ccBezierConfig。此类的对象包含了三个CCPoint对象。它们分别用来表示贝塞尔曲线的三个属性点。
    通过四个点P0、P1、P2、P3,就能表示出一条[三阶]贝塞尔曲线。在ccBezierConfig对象中保存了P1、P2、P3这三个点属性。而P0则是CCNode对象的初始位置属性。
    3、旋转动作类
    CCRotateBy和CCRotateTo,这对动作类是通过修改CCNode对象的角度属性来达到旋转的效果。
    4.4组合动作类
    在游戏当中,一个角色通常并不是只有一个动作在执行中的。
    Cocos2d-x引擎提供了一些将其他动作进行组合的动作类。这些类就好比是一个动作对象的容器。
    4.4.1序列动作类CCSequence
    序列动作类CCSequence是从CCIntervalAction类派生而来的,因此它也是一个与时间有关的类。
    每一个组合动作类都有其特定的执行规则。序列动作类就是将放置在自身序列当中的若干个动作类对象,按照前后的线性顺序逐个执行。
    以action开头的的是旧版本中延续下来的函数,在不久的将来就会消失。而create函数则是2.0版本中简化后的创建函数。
    如果需要使用序列动作,那么至少要准备两个动作对象。
    创建函数的参数恰好就是动作类的对象。
    4.4.2同步动作类CCSpawn
    组合动作只是一群特殊的持续动作类。
    类CCSpawn同样是来自CCIntervalAction类的派生,它的创建函数也与序列动作类CCSequence非常类似。
    该类与序列动作类CCSequence的区别就是,它使得CCNode对象可以同时执行若干个动作。

    void CFish::addPath()
    {
     switch(rand() % 7)
     {
      case 0:
       this->moveWithParabola(this, ccp(1200, 200), ccp(-500, 800), 0.0f, 20.0f, rand()%10+15);
       break;
      case 1:
       this->moveWithParabola(this, ccp(-200, 300), ccp(1300, 400), 180.0, 170.0, rand()%10+18);
       break;
      case 2:
       this->moveWithParabola(this, ccp(-200, 300), ccp(1000, -200), 190.0, 200.0, rand()%10+18);
       break;
      case 3:
       this->moveWithParabola(this, ccp(1300, 400), ccp(-200, 300), 10.0, 5.0, rand()%10+18);
       break;
      case 4:
       this->moveWithParabola(this, ccp(400, -1200), ccp(600, 1000), 90.0, 93.0, rand()%10+18);
       break;
      case 5:
       this->moveWithParabola(this, ccp(600, 1000), ccp(400, -200), -70.0, -80.0, rand()%10+18);
       break;
      case 6:
       this->moveWithParabola(this, ccp(1200, 2100), ccp(-200, 300), 30.0, -30.0, rand()%10+18);
       break;
     }
    }
    void CFish::moveWithParabola(cocos2d::CCSprite* mSprite, cocos2d::CCPoint startP, cocos2d::CCPoint endP, float startAngle, float endAngle, float time)
    {
     float sx = startP.x;
     float sy = startP.y;
     float ex =endP.x+rand()%50;
     float ey =endP.y+rand()%150;

     float h = mSprite->getContentSize().height * 0.5f;
     CCPoint pos = CCPointMake(sx - 200 + rand()%400, sy -200 + rand() %400);
     mSprite->setPosition(pos);
     mSprite->setRotation(startAngle);

     // 贝塞尔曲线
     ccBezierConfig bezier;
     // 控制点1(起点)
     bezier.controlPoint_1 = ccp(sx, sy);
     // 控制点2
     bezier.controlPoint_2 = ccp(sx+(ex-sx)*0.5, sy+(ey-sy)*0.5+rand()%300);
     // 终点
     bezier.endPosition = ccp(endP.x-30, endP.y+h);

     CCBezierTo* actionMove = CCBezierTo::create(time, bezier);
     CCRotateTo* actionRotate = CCRotateTo::create(time, endAngle);
     CCFiniteTimeAction* action = CCSpawn::create(actionMove, actionRotate, 0);
     CCFiniteTimeAction* sq = CCSequence::create(action,CCCallFunc::create(this, callfunc_selector(CFish::removeSelf)), 0);
     mSprite->runAction(sq);
    }

    4.4.3重复动作类CCRepeat & CCRepeatForever
    类CCRepeat和类CCRepeatForever也是开发者经常会使用的组合动作类。它是用来重复某一个动作对象的。此处的动作对象,可以是单一的基本动作对象,也可以是组合动作对象。
    此动作类的执行效果就是能够将一个动作对象重复许多次或者永无止境地循环执行。
    它可以使用任何动作对象作为参数,其中包含了单个的基本动作对象,也包含了前面介绍过的序列动作以及同步动作。
    对于类CCRepeatForever而言,其创建函数没有第二个参数,因为它是永不停止的执行动作对象。
    类CCRepeatForever并不是类CCRepeat的子类,虽然它们有着类似的功能,但它是由类CCActionInterval直接派生的。

    void CFashManage::addOneFish(CFish::Fish_Type eType/*=Fish_1*/)
    {
     if (NULL == s_AllFish)
      CCAssert(0, "请先调用 CFashManage::Init(...) 函数初始化");

     char charBuffer[256] = {0};
     CCArray* fish01 = CCArray::create();

     // 鱼帧
     for(int i = 1; i < d_Fish_Frame_Count; i++)
     {
      memset(charBuffer, 0, sizeof(charBuffer));
      _snprintf(charBuffer, sizeof(charBuffer), "fish0%d_0%d.png", eType, i);
      CCSpriteFrame* spriteFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(charBuffer);
      fish01->addObject(spriteFrame);
     }

     // 由帧缓存生成action,帧延迟0.1f
     CCActionInterval * pFishAction = CCRepeatForever::create(CCAnimate::create(CCAnimation::createWithSpriteFrames(fish01, 0.1f)));
     // 通过起始帧生成鱼实体
     CFish* fish = CFish::createWithSpriteFrameName(CCString::createWithFormat("fish0%d_0%d.png", eType, 1)->getCString());
     fish->setIsCatching( false );
     fish->setTag( eType );
     fish->runAction( pFishAction );
     fish->addPath();
     s_AllFish->addChild( fish );
    }

    4.5可变速度类CCEaseAction
    4.5.1CCEaseIn、CCEaseOut、CCEaseInOut
    4.5.2EaseSineIn、EaseSineOut、EaseSineInOut
    4.5.3CCEaseBackIn、CCEaseBackOut、CCEaseBackInOut
    4.5.4EaseExponentialIn、EaseExponentialOut、EaseExponentialInOut
    4.5.5CCEaseBounceIn、CCEaseBounceOut、CCEaseBounceInOut
    4.5.6CCEaseElasticIn、CCEaseElasticOut、CCEaseElasticInOut
    4.6速度类CCSpeed
    4.7延迟动作类CCDelay
    4.8跟随动作类CCFollow
    4.9扩展动作类
    4.9.1概述
    4.9.2翻页动作类CCPageTurn3D
    4.9.3波纹动作CCWaves3D
    4.9.4格子动作类CCGridAction
    4.10动画动作类
    在最后的内容中,留给大家一个最重要的动作类,这个动作类就是动画动作。它同样是来继承自持续动作类CCActionInterval。
    没有哪个游戏是没有动画的。
    下面的内容,也将不仅仅局限于动画动作,而是将动画动作的实现机制以及引擎当中对其优化的技术都展现出来。
    4.10.1精灵帧
    最新版的Cocos2d-x引擎专门设计了类CCAnimationFrame为动画帧对象。在旧版本的引擎当中,开发者更习惯直接使用类CCSpriteFrame(精灵帧)。

    CFish* CFish::createWithSpriteFrameName(const char *pszSpriteFrameName)
    {
     CCSpriteFrame *pFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(pszSpriteFrameName);

     char msg[256] = {0};
     sprintf(msg, "Invalid spriteFrameName: %s", pszSpriteFrameName);
     CCAssert(pFrame != NULL, msg);
     return createWithSpriteFrame(pFrame);
    }

    4.10.2精灵帧缓冲
    所谓的精灵帧缓冲类CCSpriteFrameCache,其实就是一个存放精灵帧对象的缓冲池。在引擎运行时,它将会作为一个单例对象。
    它的目的在于提升动画的性能。

    void CFashManage::Init(CCNode * pNode)
    {
     if (NULL == s_AllFish)
     {
      for (int i=0; i<d_Fish_Plist_File_Count; i++)
      {
       CCString *pListName = CCString::createWithFormat("./play/fish%02d%s",i+1,".plist");
       CCString *pBatchName = CCString::createWithFormat("./play/fish%02d%s",i+1,".png");
       CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile(pListName->getCString());
       s_AllFish = CCSpriteBatchNode::create(pBatchName->getCString());
       pNode->addChild( s_AllFish );
      }
     }

     while(NULL != s_AllFish && sharedFish()->getChildren()->count() < d_Fish_Max_Count)
     {
      // 鱼种
      CFish::Fish_Type fishTytpe = (CFish::Fish_Type)(rand() % (CFish::Fish_Type_Count-1) + 1);
      addOneFish( fishTytpe );
     }
    }

    4.10.3动画类
    动画类CCAnimation就是用在精灵之上的动画对象。它包含了一系列的动画帧以及动画帧之间的播放间隔。所以说动画类的对象是将动画帧元素组织起来的、准备播放的集合对象。它决定了动画帧的播放顺序以及时间间隔。
    4.10.4动画动作
    动画动作虽然也是持续动作类CCActionInterval的一个子类,但是其实现机制却是最为复杂的。
    CCAnimation并不是能够执行的动作。它只是作为一个精灵帧的有序集合。同时,它也存有一些与动画相关的播放属性。
    Cocos2d-x引擎也提供了一个专门用来执行动画的持续动作类,那就是类CCAnimate。
    动画动作也是存在反序动作的。它的执行效果看上去就像是录像的倒带播放。

     /// 设置炮类型
     void SetGunType(bool bAdd);
     /// 发炮动作
     void EmitGun(CCPoint pt);
     /// 旋转炮
     /// @return 返回旋转的角度值
     float whirlGun(CCPoint &pt);
    CCAnimation * m_pGunAnimation;
    void Player::SetGunType(bool bAdd)
    {
     if ( bAdd )
     {
      m_iGunType = m_iGunType % d_Gun_Count+1;
     }
     else
     {
      m_iGunType = (m_iGunType-1) % d_Gun_Count;
      m_iGunType = (0 >= m_iGunType) ? d_Gun_Count : m_iGunType;
     }

     char cBuffer[64]="";
     _snprintf(cBuffer, sizeof(cBuffer), "gun_%d_00000.png", m_iGunType);
     CCSprite * pShip = (CCSprite *)getChildByTag( t_Ship );
     CCSprite * pGun = (CCSprite *)pShip->getChildByTag( t_Gun );
     pGun->setDisplayFrame( CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName( cBuffer ) );

     // 创建发炮动画
     CCArray *pGunArray = CCArray::create();
     for (int i=0; i<d_Gun_Frame_Count; i++)
     {
      char cBuffer[64]="";
      _snprintf(cBuffer, sizeof(cBuffer), "gun_%d_0000%d.png", m_iGunType, i);
      CCSpriteFrame* pGunFrame = CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName( cBuffer );
      pGunArray->addObject( pGunFrame );
     }

     if (NULL != m_pGunAnimation)
     {
      m_pGunAnimation->release();
      m_pGunAnimation = NULL;
     }
     m_pGunAnimation = CCAnimation::createWithSpriteFrames( pGunArray, 0.05f );
     m_pGunAnimation->retain();
    }
    void Player::EmitGun(CCPoint pt)
    {
     // 设置炮旋转
     CCSprite * pShip = (CCSprite *)getChildByTag( t_Ship );
     CCSprite * pGun = (CCSprite *)pShip->getChildByTag( t_Gun );

     float angle = whirlGun( pt );

     CCLog("xy:(%f,%f),(%f,%f), angle:%f\r", pt.x, pt.y, pGun->getPosition().x, pGun->getPosition().y, angle);
     pGun->runAction( CCAnimate::create(m_pGunAnimation) );
     CCPoint ptBullet[PLAYER_COUNT] = {ccp(0, 0), ccp(0, 0), ccp(0, 0), ccp(1, 17)};
     ptBullet[m_PlayerPT].x += pShip->getPositionX();
     ptBullet[m_PlayerPT].y += pShip->getPositionY();
     m_BulletManage->Shoot(getPlayerPT(), pt, ptBullet[m_PlayerPT], pGun->getRotation(), false);

     CCLog("ZOrder: Bullet=%d, pShip=%d, pGun=%d", m_BulletManage->sharedBullet()->getZOrder(), pShip->getZOrder(), pGun->getZOrder());
    }

    float Player::whirlGun(CCPoint & pt)
    {
     // 设置炮旋转
     CCSprite * pShip = (CCSprite *)getChildByTag( t_Ship );
     CCSprite * pGun = (CCSprite *)pShip->getChildByTag( t_Gun );

     float angle = (pt.y - pShip->getPosition().y)/(pt.x - pShip->getPosition().x);
     angle = atanf(angle)/M_PI*180;
     angle = (0 > angle) ? -(90 + angle) : 90 - angle;

     // 单击炮水平线以下,旋转90,-90
     if (pt.y < pShip->getPositionY())
     {
      angle = (pt.x >= pShip->getPositionX()) ? 90 : -90;
      pt.y = pShip->getPositionY()+15;
     }

     pGun->setRotation( angle );
     return angle;
    }

    4.11动画编辑器
    4.11.1概述
    4.11.2CocosBuildr编辑器中的精灵动画
    4.11.3SpriteX草莓编辑器
    4.11.4MotionWelder动画编辑器
    4.12样例程序
    4.13本章小结

    chap9 文件操作模块
    9.1概述
    9.2引擎文件操作模块
    9.3读取文件
    9.4写入文件
    9.5游戏中用户数据
    9.5.1游戏中的用户数据
    9.5.2用户数据的基本类型
    9.5.3读取与写入操作
    9.6示例程序
    9.7本章小结
    chap10 内存管理机制
    10.1内存管理概述
    10.2引用计数
    10.3自动释放池
    10.3.1使用方法
    10.3.2实现原理
    10.4管理模式
    10.4.1引擎当中的应用
    10.4.2缓冲区
    10.5日志调试方式
    10.6本章小结
    chap14
    引擎之外的附加功能
    14.1概述
    14.2网络通信支持

    14.2.1HTTP介绍
    14.2.2 curl库(libcurl)
    14.2.3 HTTP在引擎中的运用
    14.2.4 HTTP示例项目
    14.2.5 Socket的介绍
    14.2.6 BSD Socket在引擎中的应用
    14.3收费模式
    14.3.1下载计费
    14.3.2内置计费
    14.3.3广告版本
    14.4社交网络游戏在游戏中的应用
    14.4.1 Game Center
    14.4.2 OpenFeint
    14.5数据分析
    14.5.1 Flurry介绍
    14.5.2友盟 

     

    Cocos2d-x引擎捕鱼达人多人游戏功能的设计和实现.pdf
    http://max.book118.com/html/2016/0113/33145341.shtm
    4.6.1智能电视平台移植的介绍
    在Android系统中有onKeyDown和onKeyUp两个函数,
    这两个函数是响应按键的按下和弹起状态的两个函数,装有Android系统的智能电视中,遥控器不同的按有着不同的键值。
    含义|宏|键值
    确认键|KEYCODE_ENTER|23或者66
    返回键|KEYCODE_BACK|4
    上键|KEYCODE_DPAD_UP|19
    下键|KEYCODE_DPAD_DOWN|20
    左键|KEYCODE_DPAD_LEFT|21
    右键|KEYCODE_DPAD_RIGHT|22
    我们通过重写这两个函数,在函数中添加自己的逻辑,用来截获遥控器的按键消息,通过JNI使用Java调用C++。
    消息响应函数如下:
    keyEnterClicked
    keyBackClicked
    keyUpClicked
    keyDownClicked
    keyLeftClicked
    keyRightClicked


    Cocos2d-X面试题答案
    ——可以作为开发规范
    http://wenku.baidu.com/view/c1510150aaea998fcc220efa.html?from=search
    1、autorelease和release的区别
    release是立即释放引用计数,如果达到0,对象被销毁。
    autorelease是延迟释放,是为了更好管理内存产生的。
    CCObject *fun()
    {
    CCObject *myobj=new CCObject();
    //myobj->release();//语句1
    //myobj->autorelease();//语句2
    return myobj;
    }
    如果不调用语句1和语句2,会导致内存泄露,根据函数定义原则,谁污染谁治理,如果要求外部调用者释放,不科学。
    如果调用语句1,会立即被释放,外部调用者无法获取对象。
    调用语句2,延迟被释放,可以保证外部调用者获得对象指针,而又会被释放。
    autorelease的实现机制,是将对象加入一个pool统一管理,当pool被release时,pool里面每个对象都会被release。pool基于一个栈式结构管理,每一个mainloop会pop一次。同一个mainloop里面调用autorelease,会把引用加入栈顶pool。

    20160616添加:
    Cocos2d-x设计模式
    http://www.docin.com/p-1485227612.html
    1、观察者模式又称为发布-订阅模式
    一个目标对象或被观察者可以注册多个观察者,当目标对象的状态改变的时候,可以通知观察者对象作出相应的响应。
    这是标准的观察者模式的实现。
    优点:
    实现了目标对象和观察者、消息与观察者之间的抽象耦合。可以定义一种消息与消息处理对象的一对多关系,而不用担心彼此的实现细节。
    Java实现
    Office.java报社
    interface Office
    //添加观察者
    void addObserver(People p);
    //删除观察者
    void deleteObserver(People p);
    //通知所有的观察者
    void notifyObserver(String msg);
    People.java人
    interface People
    //收到来自观察者的消息
    void update(String msg);
    BrightOffice.java光明日报
    class BrightOffice
    //所有观察者的集合
    ArrayList<People> peopleList=new ArrayList<People>();
    OldPeople.java老人
    class OldPeople

    在Cocos2d-x中的被观察者是NotificationCenter,但它不是通过自身状态改变来通知观察者,而是通过显示地发送观察者感兴趣的消息(postNotification)来通知它们。
    每一种消息类型可以对应多个观察者,同时,每一个观察者也可以观察多个消息类型。
    其次,观察者定义相应的响应事件同消息类型关联,当某个地方触发postNotification来广播一个消息的时候,Notification会遍历所有的观察者,判断它们注册的消息类型是否匹配,如果匹配,则触发相应的注册响应事件。
    最后,该观察者模式采用的是推模式,即由目标对象去通知所有的观察者。
    其实NotificationCenter和NotificationObserver更准确的叫法是:订阅发布模式。
    CCNotificationObserver相当于一个组装器,把对象object、对象的方法func、事件name组装到一起,变成一个Observer实体。
    CCNotificationCenter中封装了一个CCArray容器,同一个对象、不同事件可以当做不同的Observer添加,总之,是以event来区别不同的观察者而不是object。
    20160728添加:
    根目录填:
    F:\Project3.x\QiXing\proj.android
    target=android-15改为target=android-18
    Description Resource Path Location Type
    The project cannot be built until build path errors are resolved QiXing  Unknown Java Problem

    安装3.10之后,配置ANT_ROOT环境变量
    ANT_ROOT,C:\Cocos\tools\ant\bin
    ANT_HOME,C:\Cocos\tools\ant
    path,%ANT_HOME%\bin
    classpath,C:\Cocos\tools\ant\lib
    验证ant
    如果出现如下内容,说明安装成功:
    C:\Users\DELL->ant
    Buildfile: build.xml does not exist!
    Build failed

    C:\Users\DELL->ant
    Buildfile: build.xml does not exist!
    Build failed

    3.10中用cocos命令打包apk,可行
    C:\Users\DELL->f:

    F:\>cd F:\Project3.x\QiXing\proj.android

    F:\Project3.x\QiXing\proj.android>cocos run -p android

    最终生成F:\Project3.x\QiXing\bin\debug\android\QiXing-debug.apk

    F:\Project3.x\QiXing\proj.android>cocos run -p android -m release
    目前需要使用Eclipse或android studio来完成keystore设置和release发布
    导入libcocos2dx工程
    最终生成QiXing.apk
     

    3.0一书中用Cygwin编译生成.so文件,在3.10中似乎不可行
    DELL-@DELL ~
    $ cd /cygdrive/f/Project3.x/QiXing/proj.android

    DELL-@DELL /cygdrive/f/Project3.x/QiXing/proj.android
    $ python build_native.py
    Please use cocos console instead.

    target=android-19改为target=android-18
    uffers/Android.mk: Cannot find module with tag 'cocos/platform/third_party/andro
    id/prebuilt/libcurl' in import path
    Android NDK: Are you sure your NDK_MODULE_PATH variable is properly defined ?

    make: *** [obj/local/armeabi/objs-debug/cocos2dcpp_shared/__/__/Classes/lottery/
    LotteryKindScrollView.o] Error 1
    make: *** Waiting for unfinished jobs....
    make: Leaving directory `F:/Project3.x/9yiLottery_3.x/proj.android'
    执行命令出错,返回值:2。
    TCHAR改为TCHART就可以了

    ${NDK_ROOT}/ndk-build.cmd NDK_DEBUG=0
    ${ProjDirPath}
    NDK_MODULE_PATH
    C:\Cocos\Cocos2d-x\cocos2d-x-3.10\;C:\Cocos\Cocos2d-x\cocos2d-x-3.10\external\;F:\cocos2d-x-2.2.5\;F:\cocos2d-x-2.2.5\cocos2dx\platform\third_party\android\prebuilt\
    注意不要漏掉\


    20160721添加:
    3.6
    ApplicationDemo
    libbox2d
    libcocos2d
    libSpine

    3.10多了两个工程:
    libbullet
    librecast
    C:\Users\DELL->cocos new -p com.QiXing.hapigame -l cpp -d F:\Project3.x QiXing
    > 拷贝模板到 F:\Project3.x\QiXing
    > 拷贝 cocos2d-x ...
    > 替换文件名中的工程名称,'HelloCpp' 替换为 'QiXing'。
    > 替换文件中的工程名称,'HelloCpp' 替换为 'QiXing'。
    > 替换工程的包名,'org.cocos2dx.hellocpp' 替换为 'com.QiXing.hapigame'。
    > 替换 Mac 工程的 Bundle ID,'org.cocos2dx.hellocpp' 替换为 'com.QiXing.hapigame
    '。
    > 替换 iOS 工程的 Bundle ID,'org.cocos2dx.hellocpp' 替换为 'com.QiXing.hapigame
    '。
    C:\Users\DELL->

    DELL-的用户变量
    安装CocosForWin-v3.10前:
    COCOS_CONSOLE_ROOT
    F:\hxhwin7\cocos2d-x-3.6\tools\cocos2d-console\bin

    COCOS_TEMPLATES_ROOT
    F:\hxhwin7\cocos2d-x-3.6\templates

    安装CocosForWin-v3.10后:
    COCOS_CONSOLE_ROOT
    C:\Cocos\Cocos2d-x\Cocos2d-x-3.10\tools\cocos2d-console\bin
    COCOS_TEMPLATES_ROOT
    C:\Cocos\Cocos2d-x\Cocos2d-x-3.10\templates
    COCOS_X_ROOT
    C:\Cocos\Cocos2d-x
    PATH
    C:\Cocos\Cocos2d-x\Cocos2d-x-3.10\templates;C:\Cocos\Cocos2d-x\Cocos2d-x-3.10\tools\cocos2d-console\bin;

    F:\hxhwin7\cocos2d-x-3.6\setup.py
    NDK_ROOT输入
    F:\android-ndk-r9
    ANDROID_SDK_ROOT输入
    F:\adt-bundle-windows-x86_64-20130729\sdk
    ANT_ROOT忽略
    打开DOS命令行
    C:\Users\DELL->echo %COCOS_CONSOLE_ROOT%
    F:\hxhwin7\cocos2d-x-3.6\tools\cocos2d-console\bin

    C:\Users\DELL->echo %ANDROID_SDK_ROOT%
    F:\adt-bundle-windows-x86_64-20130729\sdk

    C:\Users\DELL->echo %NDK_ROOT%
    F:\android-ndk-r9
    输入cocos命令,该命令不仅可以创建项目,还可以编译、运行、编译js版本和部署到不同的平台。
    创建工程:
    C:\Users\DELL->cocos new -p org.cocos2dx.simplegame -l cpp -d F:\Project3.x Simp
    leGame
    Running command: new
    > Copy template into F:\Project3.x\SimpleGame
    > Copying cocos2d-x files...
    > Rename project name from 'HelloCpp' to 'SimpleGame'
    > Replace the project name from 'HelloCpp' to 'SimpleGame'
    > Replace the project package name from 'org.cocos2dx.hellocpp' to 'org.cocos2dx
    .simplegame'
    > Replace the mac bundle id from 'org.cocos2dx.hellocpp' to 'org.cocos2dx.simple
    game'
    > Replace the ios bundle id from 'org.cocos2dx.hellocpp' to 'org.cocos2dx.simple
    game'
    C:\Users\DELL->cocos new -p org.cocos2dx.application -l cpp -d F:\Project3.x App
    licationDemo
    Running command: new
    > Copy template into F:\Project3.x\ApplicationDemo
    > Copying cocos2d-x files...
    > Rename project name from 'HelloCpp' to 'ApplicationDemo'
    > Replace the project name from 'HelloCpp' to 'ApplicationDemo'
    > Replace the project package name from 'org.cocos2dx.hellocpp' to 'org.cocos2dx
    .application'
    > Replace the mac bundle id from 'org.cocos2dx.hellocpp' to 'org.cocos2dx.applic
    ation'
    > Replace the ios bundle id from 'org.cocos2dx.hellocpp' to 'org.cocos2dx.applic
    ation'
    C:\Users\DELL->
    问题1:
    无法启动程序,因为计算机中丢失MSVCR120.dll。尝试重新安装该程序以解决此问题。
    【确定】
    解决方法:
    64位MSVCR120.DLL放在C:\Windows\SysWOW64
    问题2:
    错误 1 error C1083: 无法打开包括文件:“extensions/ExtensionExport.h”: No such file or directory f:\project3.x\applicationdemo\cocos2d\extensions\gui\cccontrolextension\cccontrolutils.h 39 1 ApplicationDemo
    解决方法:
    1、在你自己的头文件中加入#include ”cocos-ext.h"
    2、使用命名空间USING_NS_CC_EXT;
    3、选中工程右键“属性”->"配置属性“->"c/c++"->"常规”->"附加包含目录"中添加“”$(EngineRoot)
    名称,值
    $(EngineRoot),F:\Project3.x\ApplicationDemo\cocos2d\cocos\2d\..\..\
    问题3:
    Cocos2d-x 3.10+VS2012 UIScale9Sprite.cpp编译报错    
    1>..\ui\UIScale9Sprite.cpp(1172): error C2059: 语法错误:“{”
    1>..\ui\UIScale9Sprite.cpp(1172): error C2143: 语法错误 : 缺少“;”(在“{”的前面)
    1>..\ui\UIScale9Sprite.cpp(1172): error C2143: 语法错误 : 缺少“;”(在“}”的前面)
    1>..\ui\UIScale9Sprite.cpp(1176): error C2059: 语法错误:“{”
    1>..\ui\UIScale9Sprite.cpp(1176): error C2143: 语法错误 : 缺少“;”(在“{”的前面)
    1>..\ui\UIScale9Sprite.cpp(1176): error C2143: 语法错误 : 缺少“;”(在“}”的前面)
    1>..\ui\UIScale9Sprite.cpp(1210): error C2059: 语法错误:“{”
    解决方法:
    VS2012对C++11语法支持不全,那个语法刚好就是它不支持的,改掉就好。
    如下:
                uvCoordinates = {Vec2(u0,v3), Vec2(u3,v0)};
    改成
                uvCoordinates.push_back(Vec2(u0,v3));
                uvCoordinates.push_back(Vec2(u3,v0));


    20160712添加: 
    2.3文字类
    引擎提供了3种方式来实现文字的处理,
    第一种方式有3个类,分别是LabelTTF、LabelAtlas和LabelBMFont;
    第二种方式全部由Label类完成;
    第三种方式则是使用Widget的子类Text、TextAtlas和TextBMFont完成。
    这3种方式实现的结果一样,只是处理方式不同。
    每种方式又包括3种文字的创建,分别是使用系统字体或TTF字体来创建;使用一张图片来创建,图片中的每个字符都有固定的宽和高;
    使用图片和对应的配置文件来创建,图片中每个字符的宽和高可以变化,配置文件中存储着各个字符的信息。
    2.5列表类
    对于列表,引擎提供了几种实现方式,既有2.x版本中extension命名空间的ScrollView类和TableView类,又有3.x版本中ui命名空间的ScrollView类、PageView类和ListView类。
    对于ui命名空间下对应的类,效果是一样的,只是实现方式不一样而已。
    2.5.2 TableView类
    当使用滚动视图时,对于显示的每一项没有什么规律时,一般使用ScrollView类实现,如游戏中使用文字的方式介绍游戏或聊天系统等。
    但当其中每一项的格式都类似时,则使用TableView的方式来实现,如游戏中的充值列表、好友榜等,该类继承自ScrollView。
    CCTableViewDelegate类的函数
    // 当TableViewCell被单击时,响应函数,它是一个纯虚函数,当继承该类时,必须重写该方法
    tableCellTouched
    CCTableViewDataSource类的函数
    // 设置TableView中每一项的大小
    cellSizeForTable
    // 列表中每一项的数据,都在这里创建和设置,是一个纯虚函数,子类必须重写
    tableCellAtIndex
    // 设置列表一共有多少项
    numberOfCellsInTableView
    第10章 图形用户界面
    F:\hxhwin7\cocos2d-x-3.6\cocos\ui
    在Cocos2d-x根目录下的ui目录下,是本章要讲到的图形用户界面类,这些类包括:布局文本Text、按钮Button、复选框CheckBox、视图列表ListView、图片视图ImageView、分页视图PageView和布局Layout等。
    这些UI组件可以通过两种方式使用,
    一、通过CocosBuilder构建好,并产生json配置文件,程序加载json文件,实现UI组件。
    二、直接使用UI代码来实现。
    F:\hxhwin7\cocos2d-x-3.6\extensions\GUI
    另外的一部分UI组件位于Cocos2d-x根目录下的extensions/GUI目录下,这里面包括:编辑框EditBox、滚动视图ScrollView、表格视图TableView和控制视图Control及其子类。
    10.1文本
    文本(Text)就像我们前面学习的Label标签,可以显示一些文字,也可以设置文字的字体、字号、颜色和对齐方式等属性。
    10.2按钮
    按钮(Button)类似于我们前面讲到的精灵菜单项,或者图片菜单项。按钮可以响应回调事件,实现一些事件处理逻辑。
    10.3复选框
    复选框(CheckBox)有选中和未选中两个状态,可以为其添加事件监听器来响应事件处理。
    10.4滑块
    滑块(Slider)一般用来控制一些音量、屏幕明暗等。滑块组件由滑块和进度组成,可以添加监听器事件,事件处理函数中可以获得当前进度大小。
    10.5加载条
    加载条(LoadingBar)也叫进度条,一般应用在游戏启动中,在加载资源时显示加载进度,或者从网络获取资源时显示进度等。
    10.6布局
    所谓布局就是界面上的组件如何摆放,是水平、垂直,还是相对布局等。布局的实现由布局类Layout、布局参数类LayoutParameter和边距类Margin来实现。
    10.7滚动视图
    滚动视图(ScrollView)继承自Layout,也是一个布局类,可以使放置在其内部的视图组件滚动显示。
    ScrollView的创建比较简单,使用create方法来创建。
    可以使用setDirection方法来设置滚动方向。
    Direction是一个枚举常量类。
    10.8列表视图
    列表视图(ListView)继承自ScrollView,具有滚动视图的特性。ListView里面维护着一个数组列表项,我们可以通过ListView中的添加、删除列表项方法来维护这些列表项。可以为ListView添加事件监听器来检测哪个列表项被选中。
    10.9表格视图
    Cocos2d-x中的表格视图(TableView),完全借鉴了iOS中TableView的设计。
    首先,表格视图TableView继承自ScrollView,是一个滚动视图,这里的ScrollView是cocos2d::extension命名空间下面的ScrollView。另外,创建一个TableView,有两个辅助类,一个是数据源类TableViewDataSource,封装了表格的数据源,另外一个是代理类TableViewDelegate,提供了一些额外操作方法。
    表格里面的每一行数据都由TableViewCell对象封装。
    10.10编辑框
    编辑框(EditBox)常用来收集一些用户信息。
    create静态函数:
    编辑框的创建。第一个参数是编辑框的大小;第二个参数是默认情况下的图片(这里是一个可以缩放的图片,被封装成精灵);第三个参数是按下的图片,默认为空;第四个参数是禁用的图片,默认为空。

    Cocos2d-x 3.x手游开发实例详解
    于浩洋 著
    2014年9月第1版
    前言
    以《疯狂的小鸟》为代表的第一批手机游戏
    本书以Cocos2d-x 3.0版本为基础。
    博客是http://blog.watchtech.net
    chap1 准备
    1.3.1 Windows开发环境搭建
    系统为Windows7 32位,编译工具为Visual Studio 2012.
    笔者下载的是3.0版本。
    1.3.2 Mac开发环境搭建
    操作系统需为Mac OS X 10.8+,编译工具为Xcode 4.6.2+。

    4.4扩展控件
    4.4.1滑动条控件ControlSlider
    滑动条控件常用来设置一个连续值的调整,比如音量调整等。ControlSlider继承自Control类。
    1、ControlSlider::create(滑动条背景,滑动条进度,滑动条拖曳图标)
    2、setMaximumValue、setMinimumValue设置滑动条的最大值和最小值
    3、setMaximumAllowedValue、setMinimumAllowedValue设置滑动条可使用的最大值和最小值
    4、setAnchorPoint、setPosition设置slider的位置
    5、addTargetWithActionForControlEvents为滑动条添加值变化事件监听器,当值发生变化时就会触发该函数


    chap10 网络编程
    Cocos2d-x提供了3种网络通信方式,分别是使用HTTP协议、Socket协议和WebSocket协议。
    10.1 HTTP实现网络通信
    HTTP有3种数据提交方式,分别是GET、POST、PUT。
    Cocos2d-x封装了3个类来处理HTTP请求,HttpRequest、HttpClient和HttpResponse。它们在命名空间cocos2d-x::network中定义,所以使用时要先声明该命名空间。
    using namespace cocos2d::network;
    1.HttpRequest
    常用方法包括下面几种:
    设置请求连接
    void setUrl(const char *url);

    设置请求类型
    void setRequestType(Type type);
    Type是Cocos2d-x定义的一个枚举类型,包括5种类型。
    设置请求完成后的回调函数
    void setResponseCallback(Ref *pTarget,SEL_HttpResponse pSelector);
    设置请求的数据,参数buffer是提交的数据,len是请求数据的长度
    void setRequestData(const char *buffer,unsigned int len);
    为请求设置一个标记
    void setTag(const char *tag);
    格式为字符串,可以通过HttpResponse->getHttpRequest->getTag()获取该标记。标记是HTTP请求的可选设置,不是必须设置的。
    设置请求头信息,比如Content-Type等:
    void setHeaders(std::vector<std::string> pHeaders);
    上面的设置函数都有一个对应的get函数用来获取设置的信息,比如getUrl用来获取setUrl设置的请求连接。

    python create_project.py -project HelloHttp -package org.cocos2dx.hellohttp -language cpp
    cocos2d-x使用CCHttpClient类进行网络请求:
    按钮请求处理:
    void TestLayer::btncallback( CCObject* pSender )
    {
     bool requestType_is_get=true;//采用get方式或者post方式
     if (requestType_is_get)
     {
      CCHttpRequest* request = new CCHttpRequest();//创建请求对象
      string str1 = "127.0.0.1:81/index.php?";
      string str2 = p_User_EditBox->getText();
      string str3 = p_Psw_EditBox->getText();
      string struser="username=";
      string strpsw="&password=";
      str1=str1+struser+str2+strpsw+str3;
      request->setUrl(str1.c_str());//设置请求的url,username和password已经包含在url中
      request->setRequestType(CCHttpRequest::kHttpGet);//设置为Get模式
      request->setResponseCallback(this, httpresponse_selector(TestLayer::onHttpRequestCompleted));//设置响应的回调
      request->setTag("GET test");
      CCHttpClient::getInstance()->send(request);//发送请求
      request->release();//释放请求
     }
     else
     {
      CCHttpRequest* request = new CCHttpRequest();//创建请求对象
      string str1 = "127.0.0.1:81/index.php";
      string str2 = p_User_EditBox->getText();
      string str3 = p_Psw_EditBox->getText();
      string struser="username=";
      string strpsw="&password=";
      str2=struser+str2+strpsw+str3;

      request->setUrl(str1.c_str());//设置请求的url,只是请求页面的url,并不包含username和password
      request->setRequestType(CCHttpRequest::kHttpPost);//设置为Post模式
      request->setResponseCallback(this, httpresponse_selector(TestLayer::onHttpRequestCompleted));//设置响应的回调

      const char* postData = str2.c_str();
      request->setRequestData(postData, strlen(postData));//设置请求数据,也就是username和password

      request->setTag("POST test");
      CCHttpClient::getInstance()->send(request);//发送请求
      request->release();//释放请求
     }
    }
    响应回调处理:
    void TestLayer::onHttpRequestCompleted( CCHttpClient* client, CCHttpResponse* response )
    {
     if (!response->isSucceed())//如果响应失败,输出错误信息
     { 
      CCString strError;
      strError.initWithFormat("Receive Error! \n%s\n",response->getErrorBuffer());
      m_labelStatusCode->setString(strError.getCString());
      return ;  
     } 

     std::vector<char> *buffer = response->getResponseData();//接收响应信息
     string recieveData;
     for (unsigned int i = 0; i < buffer->size(); i++)
     { 
      recieveData += (*buffer)[i]; 
     }
     size_t begin= recieveData.find("<body>")+6;//这里简单处理,获取<body>标签内数据,即是响应内容
     size_t end= recieveData.find("</body>");
     string result(recieveData,begin,end-begin);
     m_labelStatusCode->setString(result.c_str());
    }

     

    IE8里面输入:
    http://localhost:81/index.php?username=jackystudio&password=123


    GET方式会显示
    Login Success
    POST方式会显示
    No Username or Password

    采用get方式代码index.php
    <html>

    <body>

    <?php
    $open=fopen("log.txt","a" );
    if(isset($_GET["username"]) && isset($_GET["password"]))  
    {
    if($_GET["username"]=="jackystudio" && $_GET["password"]=="123")  
    {  
    fwrite($open,"Username:".$_GET["username"]);  
    fwrite($open,"\r\n");  
    fwrite($open,"Password:".$_GET["password"]);  
    echo "Login Success";
    }  
    else  
    {  
    fwrite($open,"Wrong Username or password!");  
    echo "Login Failed";
    }  
    }  
    else  
    {  
    fwrite($open,"No password");  
    echo "No Username or Password";
    }  
    fclose($open);  
    ?>

    </body>

    </html>

    采用post方式代码index.php

    <html>  
    <body>  
    <?php  
    $open=fopen("log.txt","a" ); //Save password  
    if(isset($_POST["username"]) && isset($_POST["password"]))  
    {  
    if($_POST["username"]=="jackystudio" && $_POST["password"]=="123")  
    {  
    fwrite($open,"Username:".$_POST["username"]);  
    fwrite($open,"\r\n");  
    fwrite($open,"Password:".$_POST["password"]);  
    echo "Login Success"; //return to client  
    }  
    else  
    {  
    fwrite($open,"Wrong Username or password!");  
    echo "Login Failed"; //return to client  
    }  
    }  
    else  
    {  
    fwrite($open,"No password");  
    echo "No Username or Password"; //return to client  
    }  
    fclose($open);  
    ?>  
    </body>  
    </html>  


    遇到的问题1:
    HTTP 错误 500.0 - Internal Server Error
    发生未知 FastCGI 错误
    解决办法:
    第一步:打开Internet信息服务(IIS)管理器,在左侧的树形列表找到“应用程序池”
    第二步:在右侧的列表中右击“DefaultAppPool”弹出菜单,选择“高级设置”,弹出“高级设置”对话框。
    第三步:找到“标识”选项卡,点击右侧的方形按钮,弹出“应用程序标识”对话框中的“内置账户”的下拉菜单中选择“LocalSystem”,然后重启IIS服务器,至此,这个问题就彻底解决了。

    附录B 3.X主要版本间的区别
    使用V3.2进行开发时用到的编译工具要满足以下要求:
    Xcode 5.1+
    gcc 4.9+
    NDK R9D+
    VS2012+
    使用V3.1开发的游戏的运行平台要求如下:
    Android 2.3+
    iOS 5.0+
    OS X 10.7+
    Windows7+
    Windows Phone 8+
    Linux Ubuntu 12.04+
    编译环境要求如下:
    Xcode 4.6+
    gcc 4.7+
    NDK R9+
    VS2012+
    使用V3.0开发的游戏的运行平台要求如下:
    Android 2.3+
    iOS 5.0+
    OS X 10.7+
    Windows7+
    暂不支持Windows Phone 8
    Linux Ubuntu 12.04+
    编译环境要求如下:
    Xcode 4.6+
    gcc 4.7+
    NDK R9+
    VS2012+

    Cocos2d-x 3.x游戏开发详解
    郭志宏 编著
    2015年1月第1版
    第1章 开发环境搭建
    1.1下载Cocos2d-x
    本书使用的是3.0、3.1版本。
    1.2下载安装Python
    Python安装配置成功
    C:\Users\DELL->python
    Python 2.7.9 (default, Dec 10 2014, 12:24:55) [MSC v.1500 32 bit (Intel)] on win
    32
    Type "help", "copyright", "credits" or "license" for more information.
    >>>
    本书使用的是Python 2.7.6(2013.11.10)。
    1.4下载安装JDK
    本书使用的是jdk_1.7.0_05。
    1.8在Windows平台使用Visual Studio创建项目
    本书使用的是VS2012。
    第2章 编程起步
    本书使用的是开发环境是Mac OS+Xcode,所以这里打开proj.ios_mac。
    2.1.4场景类的实现
    场景类是用户逻辑主要实现的地方,场景类通常由一个或者多个图层组成,图层里面可以有菜单、标签和精灵等游戏对象。

    // 条件编译语句,防止头文件的重复包含
    #ifndef __LOGIN_SCENE_H__
    #define __LOGIN_SCENE_H__

    #include "cocos2d.h"//3.x中也是这个头文件
    #include "SimpleAudioEngine.h"
    #include "cocos-ext.h"

    class CLogin : public cocos2d::CCLayer, cocos2d::extension::CCEditBoxDelegate
    {
    public:
     /*
     init方法是一个初始化方法,该方法沿用了OC中的实现方式,OC中没有构造方法,类的初始化是通过init方法来实现的。
     init在这里充当构造方法的作用,CREATE_FUNC宏定义会调用init。
     */
        virtual bool init(); 
     // 创建场景类,是一个静态方法,用来创建场景,添加图层到其内部,并返回该场景
        static cocos2d::CCScene* scene();
     virtual void editBoxReturn(cocos2d::extension::CCEditBox* editBox);
       
        void RegisterCallback(CCObject* pSender, cocos2d::extension::CCControlEvent controlEvent);
     void LogoinCallback(CCObject* pSender, cocos2d::extension::CCControlEvent controlEvent);

     // a selector callback
     // 2.x中菜单回调方法
     virtual void menuCloseCallback(cocos2d::CCObject* pSender);
     // 3.x中菜单回调方法
     //是一个菜单事件回调方法,在OC中被称为Selector选择器,其实就是一个函数指针。
     virtual void menuCloseCallback(cocos2d::Ref* pSender);

     // 一个宏,实现静态的create方法。3.x中也有这个宏。
     CREATE_FUNC(CLogin);

     CCNode * m_pDefaultEdit;
     CCNode * m_pActionEdit;

     cocos2d::extension::CCEditBox         * m_peditAccount;
     cocos2d::extension::CCEditBox         * m_peditPW;
    };

    #endif  // __LOGIN_SCENE_H__

    // 创建场景
    CCScene* CLogin::scene()
    {
    #if 1
    //3.x
      // 创建场景
         auto scene = CCScene::create();
      // 创建图层
      auto layer = CLogin::create();
      // 将图层添加到场景
         scene->addChild(layer);
    #else
    //2.x
        CCScene * scene = NULL;
        do
        {
            // 'scene' is an autorelease object
            scene = CCScene::create();
            CC_BREAK_IF(! scene);

            // 'layer' is an autorelease object
            CLogin *layer = CLogin::create();
            CC_BREAK_IF(! layer);

            // add layer as a child to scene
            scene->addChild(layer);
        } while (0);
    #endif
        // return the scene
        return scene;
    }
    // on "init" you need to initialize your instance
    // 初始化方法
    bool CLogin::init()
    {
        bool bRet = false;
        do
        {
            //
            // super init first
            //
      //2.x
            CC_BREAK_IF(! CCLayer::init());
      //3.x中初始化父类
      if(!Layer::init())
      {
       return false;
      }

      //2.x
            CCSize size = CCDirector::sharedDirector()->getVisibleSize();
      //3.x中获得屏幕大小
      Size size = Director::getInstance()->getVisibleSize();

      //2.x
            CCSprite* pSprite = CCSprite::create("login/bg.jpg");
            CC_BREAK_IF(! pSprite);
            pSprite->setPosition(ccp(size.width/2, size.height/2));
            this->addChild(pSprite, 0);

      //3.x中添加一个精灵
      Sprite* pSprite = Sprite::create("login/bg.jpg");
      //3.x中设置精灵位置
      pSprite->setPosition(Vec2(size.width/2, size.height/2));
      //3.x中将精灵添加到图层
      this->addChild(pSprite, 0);

      int nX = size.width*3/4;
      int nY = size.height*6/10;
      //TextFieldTTFDefaultTest * pDefaultInput = EditBox::create(CCRect(nX, size.height*5/10, 200, 40));
      //TextFieldTTFActionTest * pActionInput = EditBox::createAnimation(CCRect(nX, nY, 200, 40));
      //this->addChild( pDefaultInput, 1, k_Default_Tag );
      //this->addChild( pActionInput, 1, k_Action_Tag );

      m_peditAccount = EditBox::create(this, CCRect(nX, nY, 200, 40), "login/white_edit.png", "Name:", 8);
      addChild( m_peditAccount );
      m_peditPW = EditBox::create(this, CCRect(nX, size.height*5/10, 200, 40), "login/white_edit.png", "PW:", 8, "微软雅黑", 20, kEditBoxInputFlagPassword, kEditBoxInputModeSingleLine);
      addChild( m_peditPW );
      
      //2.x
      CCLabelTTF *titleButton = CCLabelTTF::create("Login", "微软雅黑", 20);
      titleButton->setColor(ccc3(159, 168, 176));
      addChild( Button::create(this, CCPoint(nX-65, nY - 120), cccontrol_selector(CLogin::LogoinCallback), titleButton, "login/button1.png", "login/button2.png", NULL, NULL) );
      CCLabelTTF *titleRegister = CCLabelTTF::create("Register", "微软雅黑", 20);
      titleRegister->setColor(ccc3(159, 168, 176));
      addChild( Button::create(this, CCPoint(nX+65, nY - 120), cccontrol_selector(CLogin::RegisterCallback), titleRegister, "login/button1.png", "login/button2.png", NULL, NULL) );

      //3.x中创建一个标签
      auto titleButton = LabelTTF::create("Login", "微软雅黑", 20);


            bRet = true;
        } while (0);

        return bRet;
    }

    // 菜单回调函数
    void HelloWorld::menuCloseCallback(CCObject* pSender)
    {
     // "close" menu item clicked
            //2.x
     CCDirector::sharedDirector()->end();
            //3.x中结束游戏
            Director::getInstance()->end();
    }
     
    2.2.1导演类Director
    2.窗口尺寸相关方法
    返回以点为单位的OpenGL窗口大小
    //2.x
    CCSize s = CCDirector::sharedDirector()->getWinSize();
    //3.x
    Size s = Director::getInstance()->getWinSize();
    Director::getInstance()->getWinSize();//获得设计分辨率大小

    返回可视窗口大小
    //2.x
    CCSize sVisible = CCDirector::sharedDirector()->getVisibleSize();
    //3.x
    Size sVisible = Director::getInstance()->getVisibleSize();
    返回窗口的起始点
    //2.x
    float fMinX = CCDirector::sharedDirector()->getVisibleOrigin().x+pShip->getContentSize().width/2;
    //3.x
    float fMinX = Director::getInstance()->getVisibleOrigin().x+pShip->getContentSize().width/2;
    UI坐标转换为OpenGL坐标
    //2.x
    whirlGun(CCDirector::sharedDirector()->convertToGL(pTouch->getLocationInView()));
    //3.x
    whirlGun(Director::getInstance()->convertToGL(pTouch->getLocationInView()));
    3.场景管理相关方法
    运行某个场景

    // 2.x和3.x
    pDirector->runWithScene(pScene);
    停止动画

    //2.x
    CCDirector::sharedDirector()->stopAnimation();
    //3.x
    Director::getInstance()->stopAnimation();
    开始动画
    //2.x
    CCDirector::sharedDirector()->startAnimation();
    //3.x
    Director::getInstance()->startAnimation();
    2.2.2节点类Node
    设置位置
    //2.x
    bg->setPosition(ccp(s.width/2, s.height/2));
    //3.x
    bg->setPosition(Vec2(s.width/2, s.height/2));
    添加子节点
    //2.x
    pNode->addChild(pPlayer);
    添加子节点,并指定z坐标和tag标签
    //2.x
    addChild( pShip, Z_SHIP,  t_Ship);
    根据标签获得节点
    //2.x
    CCSprite * pShip = (CCSprite *)getChildByTag( t_Ship );

    开启定时器,指定间隔interval
    //2.x
    schedule(schedule_selector(Player::OnRowingAnimation), 1.0f);


    20160708添加:
    第5章 菜单
    在Cocos2d-x中通过Menu和MenuItem及其子类MenuItemLabel(标签菜单项)、MenuItemFont(字体菜单项)、MenuItemAtlasFont(图集字体菜单项)、MenuItemSprite(精灵菜单项)、MenuItemImage(图片菜单项)和MenuItemToggle(开关菜单项)来实现菜单。
    菜单类Menu继承自Layer图层类,可以响应事件处理。MenuItem菜单项类继承自Node节点类,具有节点类的所有特性。Menu菜单类是MenuItem菜单项类的容器类,多个菜单项被放置在菜单类Menu中显示。
    5.2 Menu菜单类
    create静态函数:
    指定若干个菜单项来创建菜单,注意最后以NULL结尾。
    5.3标签菜单项(MenuItemLabel)

    另外,我们还可以使用MenuItemLabel的子类MenuItemFont和MenuItemAtlasFont来创建字体菜单和图集菜单。
    // 菜单
    CCMenu* menu = CCMenu::create();
    menu->setPosition(Vec2::ZERO);
    bk1->addChild(menu);
      
    CCMenuItemFont* itemReset = CCMenuItemFont::create("reset",this,menu_selector(UpdateLayer::reset));
    itemReset->setPosition(ccp(visibleSize.width/2, 50));
    menu->addChild(itemReset);
    // 创建一个字体标签菜单项,第一个参数是显示的内容,第二个参数是函数回调。
    CCMenuItemFont* itemGetClientVersion = CCMenuItemFont::create("getClientVersion",this,menu_selector(UpdateLayer::getClientVersion));
    itemGetClientVersion->setPosition(ccp(visibleSize.width/2, 100));
    menu->addChild(itemGetClientVersion);
    //
    CCMenuItemFont* itemGetServerVersion = CCMenuItemFont::create("checkUpdate",this,menu_selector(UpdateLayer::checkUpdate));
    itemGetServerVersion->setPosition(ccp(visibleSize.width/2, 150));
    menu->addChild(ite·etServerVersion);
    //
    CCMenuItemFont* itemUpdateVersion = CCMenuItemFont::create("updateVersion",this,menu_selector(UpdateLayer::update));
    itemUpdateVersion->setPosition(ccp(visibleSize.width/2, 200));
    menu->addChild(itemUpdateVersion);
    //
    CCMenuItemFont* itemEnterScene = CCMenuItemFont::create("enterScene",this,menu_selector(UpdateLayer::enterScene));
    itemEnterScene->setPosition(ccp(visibleSize.width/2, 250));
    menu->addChild(itemEnterScene);

    5.4精灵菜单项(MenuItemSprite)
    可以通过精灵来创建一个菜单项,使用精灵创建菜单可以指定三个状态:默认状态、选中状态和实效状态,另外,还可以让精灵执行一些动作,使得游戏画面更加生动。
    create静态函数:
    第一个参数是默认精灵,第二个是选中精灵,第四个参数是回调函数。
    // 3.x使用精灵菜单项创建【开始】、【离开】菜单
    Sprite * Start1 = Sprite::createWithSpriteFrame(spriteFrame(texture_name::s_start_button1));
    Sprite * Start2 = Sprite::createWithSpriteFrame(spriteFrame(texture_name::s_start_button2));
    Sprite * exit_1 = Sprite::createWithSpriteFrame(spriteFrame(texture_name::s_exit_button1));
    Sprite * exit_2 = Sprite::createWithSpriteFrame(spriteFrame(texture_name::s_exit_button2));
    MenuItemSprite *StartItem = MenuItemSprite::create(Start1,Start2,CC_CALLBACK_1(DZPKLayer::LeaveGameResume,this));
    StartItem->setTag(StartTag);
    StartItem->setPosition(ccp(WINSIZE_W*3/4,WINSIZE_H/3));
    MenuItemSprite *LeaveItem = MenuItemSprite::create(exit_1,exit_2,CC_CALLBACK_1(DZPKLayer::LeaveGameResume,this));
    LeaveItem->setTag(LeaveTag);
    LeaveItem->setPosition(ccp(WINSIZE_W*3/4,WINSIZE_H/5));  
    StartLeave= Menu::create(StartItem,LeaveItem,NULL);
    StartLeave->setPosition(ccp(0,0)); 
    addChild(StartLeave,0);
    5.5 图片菜单项(MenuItemImage)
    继承自MenuItemSprite。可以使用图片直接创建菜单项。
    create静态函数:
    第一个参数是默认图片,第二个参数是选中图片,第四个参数是菜单回调函数。


    第6章 精灵及其相关类
    本章内容如下:
    精灵类Sprite[2.x中是CCSprite]
    精灵帧类SpriteFrame[2.x中是CCSpriteFrame]和精灵帧缓存类SpriteFrameCache[2.x中是CCSpriteFrameCache]
    精灵批量节点类SpriteBatchNode[2.x中是CCSpriteBatchNode]
    精灵动画类Animation[2.x中是CCAnimation]和精灵动画缓存类AnimationCache[2.x中是CCAnimationCache]
    6.1精灵类Sprite
    指定图片路径创建一个精灵
    //2.x
    CCSprite * pShip = CCSprite::create("play/ship.png");
    使用精灵帧创建一个精灵
    //2.x
    CCSprite * pGun = CCSprite::createWithSpriteFrame( pGunFrameCache->spriteFrameByName("gun_1_00000.png") );
    6.2精灵帧类SpriteFrame和精灵帧缓存类SpriteFrameCache
    通过plist配置文件添加所有精灵帧到缓存
    //2.x
    CCSpriteFrameCache::sharedSpriteFrameCache()->addSpriteFramesWithFile("play/bulletNet.plist");
    //3.x
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("play/bulletNet.plist");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("pic_dating.plist");
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("cardtable1.plist","cardtable1.png");
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("cardtable2.plist","cardtable2.png");
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("cardtable3.plist","cardtable3.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("normal_button.plist","normal_button.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("plaza.plist","plaza.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("sprite1.plist","sprite1.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("sprite2.plist","sprite2.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("tab_button.plist","tab_button.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("shop.plist","shop.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("exchange.plist","exchange.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("luckyDraw1.plist","luckyDraw1.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("luckyDraw2.plist","luckyDraw2.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("player_info.plist","player_info.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("medal.plist","medal.png");
    //SpriteFrameCache::getInstance()->addSpriteFramesWithFile("DZPKImageAdd.plist","DZPKImageAdd.png");        //dzpk
    根据精灵帧名称获得精灵帧
    //2.x
    CCSpriteFrame *pFrame=CCSpriteFrameCache::sharedSpriteFrameCache()->spriteFrameByName(pszName);
    //3.x
    SpriteFrame *pFrame=SpriteFrameCache::getInstance()->getSpriteFrameByName(pszName);

    第8章 动作
    大部分动作都继承自FiniteTimeAction,另外的两个动作是Follow和Speed。FiniteTimeAction的两个子类是ActionInstant和

    ActionInterval,瞬时动作是持续时间为0的FiniteTimeAction动作,即FiniteTimeAction中duration属性值为0的动作。
    Action是动作类的基类,动作作用于Node。
    8.3瞬时动作
    8.3.4 CallFunc
    //2.x
    CCFiniteTimeAction *pReleaseFishAnimation = CCCallFunc::create(pFish, callfunc_selector(CFish::removeSelf)); // 释放鱼
    CCFiniteTimeAction *pBulletNetAnimation = CCCallFuncND::create(pBullet, callfuncND_selector(CBullet::FinishAnimation),

    (void *)&ptHit ); // 子弹隐藏,鱼网打开
    //3.x
    FiniteTimeAction *pReleaseFishAnimation = CallFunc::create(CC_CALLBACK_0(CFish::removeSelf,pFish)); // 回调函数没有任何参数
    FiniteTimeAction *pBulletNetAnimation = CallFunc::create(CC_CALLBACK_0(CBullet::FinishAnimation,pBullet,(void *)&ptHit));

    // 给回调函数传递一个数据值
    8.5复合动作
    8.5.1 Spawn
    8.5.2 Sequence
    //2.x
    CCFiniteTimeAction* catchSequence=CCSequence::create(pBulletNetAnimation,pStruggleAnimation,pReleaseFishAnimation,NULL);
    //3.x
    FiniteTimeAction* catchSequence=Sequence::create(pBulletNetAnimation,pStruggleAnimation,pReleaseFishAnimation,NULL);
    第9章 调度器
    绘制由Director来管理,而更新及更新的频率由调度器(Scheduler)来管理。
    在游戏引擎内部,动作的执行是由调度器来控制的,另外,使用调度器可以实现游戏的逻辑函数回调,在指定的时间频率中调用这些函数。
    下面我们再来看一下Scheduler的API。
    update
    更新调度器,每隔dt时间间隔
    schedule
    执行调度器,callback是回调函数,target是目标节点,interval是时间间隔,repeat是是否重复执行,delay是延迟执行时间,paused是暂停,key是标示该调度器的一个键。
    unschedule
    根据key,取消调度器的执行
    unschedule
    根据选择器,取消调度器的执行
    unscheduleUpdate
    取消update的更新
    unscheduleAllForTarget
    取消所有目标调度器的执行
    unscheduleAll
    取消所有调度器
    pauseTarget
    暂停目标节点调度器
    resumeTarget
    恢复目标节点调度器
    9.2调度器的三种实现方式
    Cocos2d-x提供了多种调度机制,在开发中我们通常会用到三种调度器。
    1、默认调度器:schedulerUpdate
    2、自定义调度器:schedule
    3、单次调度器:scheduleOnce
    9.2.1默认调度器
    该调度器是使用Node的刷新事件update方法,该方法在每帧绘制之前都会被调用一次。
    Cocos2d-x中Node默认是没有启用update事件的,因此需要重载update方法来执行自己的逻辑代码。
    通过执行schedulerUpdate调度器,即可每帧执行update方法,如果需要停止这个调度器,则可以使用unschedulerUpdate方法。
    9.2.2自定义调度器
    由于引擎的调度机制,自定义时间间隔必须大于两帧的间隔,否则两帧内的多次调用会被合并成一次调用。所以自定义时间间隔应在0.1s以上。
    同样,取消该调度器可以用unschedule。
    我们可以定义多个调度器来完成游戏中不同的逻辑需求。
    9.2.3单次调度器
    单次调度器只会触发一次,用scheduleOnce来触发,用unschedule来取消该触发器。


    第11章 数据结构和常用类
    11.2字符串类CCString
    CCString就是OC风格的一个字符串处理类,不过在3.0之后被废弃,推荐使用std::string。
    CCString是一个可变字符串类。
    //2.x字符串格式化
    CCString *pBatchName = CCString::createWithFormat("play/bulletNet.png");
    11.3数组CCArray
    CCArray在3.0之后被废弃,建议使用cocos2d::Vector。
    //2.x数组的创建、添加元素
    CCArray* pArray = CCArray::create();
    CCArray * pArray1=CCArray::createWithCapacity(iSize);
    pArray1->addObject(pSpriteFrame);


    20160702添加:
    2.2.5中HelloWorld.png的尺寸是480*320。
    第12章 屏幕适配
    Android设备的屏幕分辨率可以任意大小,iOS设备的分辨率也至少有5种以上,因此,屏幕适配是Cocos2d-x中的一个难题。
    本章内容如下:
    FileUtils工具类
    屏幕适配的相关接口
    屏幕适配解决方案
    12.3.2适配策略
    在没有提出设计分辨率的概念之前,我们的程序有两种分辨率,分别是:资源分辨率和设备分辨率,如果增加了设计分辨率后,就有三种分辨率。

    资源分辨率:RW=720,RH=1280
    设计分辨率:DW=720,DH=1280
    大厅场景竖屏:720*1280
    游戏场景横屏:1280*720
    Android测试机设备分辨率:SW=?,SH=?:
    小米2S,Android 4.1.1,设备分辨率1280*720
    红米2,Android 5.1.1,设备分辨率1280*720
    华为G9 Younth,Android 6.0,设备分辨率1920*1080

    14.2 XML数据解析
    TinyXML是一个开源的解析XML的解析库,Cocos2d-x集成了该库,在Cocos2d-x项目的根目录的external/tinyxml2目录下面有该类的实现。
    在TinyXML中,根据XML的各种元素定义了一些类。
    XMLDocument:文档
    XMLElement:元素
    XMLAttribute:属性
    XMLComment:注释
    XMLNode:节点
    XMLText:文本
    XMLDeclaration:声明
    XMLUnknown:未知部分

    Cocos2d-x游戏编程——C++篇
    徐飞 著
    2015年9月第1版
    20160709添加:
    1.2.4回调函数的变化
    在2.x中,回调函数使用函数指针的方式来实现
    //类函数指针定义
    //update函数,带一个float参数
    //回调不带参数
    //回调带一个节点指针参数
    //回调带一个节点指针和一个void指针参数
    //回调带一个对象指针参数
    例子:O代表对象指针参数
    //游戏记录新增
    void AddGameRecord(Object *obj);

    void CardTableLayer::AddGameRecord(Object *obj)

    {
     
    int ZXP = ((Integer *)obj)->getValue();
     
    AddWinLostResult(ZXP);

    }
    NotificationCenter::getInstance()->addObserver(this, callfuncO_selector(CardTableLayer::AddGameRecord), "AddGameRecord", NULL);
    NotificationCenter::getInstance()->removeObserver(this, "AddGameRecord");
    NotificationCenter::getInstance()->postNotification("AddGameRecord", Integer::create(ZXP));

    //菜单对应函数,带一个对象指针参数
    //回调带一个事件指针参数
    //回调带一个对象指针参数和一个int返回值
    //宏定义
    1、用在update等函数的定义中
    schedule_selector宏、参数为float的例子:
    //点击菜单中的【开始】按钮,开始下一局 
    schedule(schedule_selector(DZPKLayer::restartGame),0.1f);
    //玩家超时没有准备,30秒没执行则返回大厅
    schedule(schedule_selector(DZPKLayer::CloseRoundOpera),30.0f);
    2、用在动画的执行中,用来定义瞬时动画
    3、用在菜单响应事件上
    而在3.x中,引入C++ 11标准中的bind函数,只有4个宏定义来实现。
    //不带参数宏定义
    //带一个参数宏定义
    //带二个参数宏定义
    //带三个参数宏定义
    在3.x中,使用bind函数的功能,通过回调函数的参数个数来决定使用哪个宏,而不用区分哪个是对应菜单回调,哪个是update回调等。

    1.3 Cocos2d-x中的C++ 11知识
    自从C++标准委员会发布C++ 11标准后,引进了一些新的知识点,如auto指针、Lambda表达式、bind和function函数等,Cocos2d-x引擎把这些知识点也加入了引擎的编写过程中。
    1.3.1 Lambda表达式
    在C++中,有许多算法用来比对输入序列,如find_if、sort、for_each等,这些算法第三个断言(predicate)既可以是<或==操作符,也可以是一个能调用的对象(函数、函数指针等)。这些断言普遍的一个特点是,要么带一个参数,要么带两个参数。
    find_if算法中的断言是带一个参数的,而sort算法中的断言是带两个参数的。
    有时,我们想传递多于断言参数个数。
    为了解决这个问题,C++中引入了Lambda表达式,它是一个可调用对象,可以认为它是一个未命名的内联函数,有返回值、参数列表和函数体等。
    Lambda表达式原型:捕获列表、参数列表、返回类型、函数体
    Lambda表达式中除了捕获列表外,其他的三个部分和普通函数类似。其中,参数列表和返回类型可以忽略,其他两个必须存在。
    如果函数体中只有一个带return的语句,那么表达式可以推断出返回类型,否则将返回void类型。
     1、捕获列表
    捕获列表捕获的是调用该表达式函数内的局部非静态变量,对于那些函数内的静态变量和定义在函数外的变量,Lamda表达式也可以直接使用。
    捕获列表的类型如表所示。
    类型|说明
    []|空的捕获列表
    [a1,a2...]|捕获的参数通过逗号来分割,参数既可以是值捕获,也可以是引用捕获
    [&]|隐式的引用捕获
    [=]|隐式的值捕获
    [&,a1,a2...]|a1,a2是值捕获,&表示其他值是隐式引用捕获,a1、a2等捕获必须在&参数捕获之后
    [=,&a1,&a2...]|a1,a2是引用捕获,其他值是隐式值捕获
    编译器在解释Lambda表达式时,会解释成一个包含未命名类的未命名对象,该类包含了一个重载函数调用操作符的函数,如果在捕获列表中包含有值捕获参数时,那么在该类中会有一个相对应的成员参数。
    2、返回值
    如果Lambda表达式的函数体不只是一个return语句,那么这时就应该指定表达式的返回值了,它由一个箭头后面带一个返回类型来制定。
    对于Lambda表达式,通常只会使用在一个地方,且执行语句较少,如果有多个地方用到该功能,使用函数来实现会更方便些。另一种情况是,如果捕获列表为空,通常使用函数来实现比较直接。
    1.3.2 bind函数
    C++提供的库函数bind,用于函数绑定的模版,它可以指定被绑定函数中的部分参数,也可以是全部参数。
    auto f=bind(func,arg_list);
    其中:func是原函数,arg_list是func函数中的参数,是一个逗号分割的参数列表,其中参数可以用占位符来表示,_1表示func函数的第一个参数,_2表示该函数的第二个参数,以此类推,这些占位符可以交换顺序,它们位于std的placeholders命名空间下,f是一个新的可调用对象。
    默认情况下,bind函数在不使用占位符时,参数是直接进行复制的。有时,某些绑定函数想通过引用传递或者有的参数是不能复制时,在这种情况下,C++引进了ref和cref

    函数。其中,cref是const引用,使用ref函数解决了ostream &os不能复制的问题。
    1.3.3 function函数
    C++中的可调用对象,包括函数、函数指针、Lambda表达式、bind函数创建的对象和重载了函数调用操作符的类,这些对象可以存储在function函数模版中。
    1、函数
    function绑定函数
    2、Lambda表达式
    function绑定Lambda表达式
    3、bind函数
    function绑定bind函数
    4、重载了()操作符的类
    function绑定重载了()的类
    1.3.4 auto和nullptr
    C++ 11引入了auto关键字。由auto定义的变量,通过对该变量的初始化来推断它的类型。
    nullptr是新标准中的一个关键字,表示一个空指针。
    1.3.5 override和final
    override关键字表明,子类的函数一定重写了父类函数。而final关键字表明,该函数不能被继承,已经是最终版本了,如果子类有重写该函数,则编译器就会报错。

    20170418添加:
    chap9 Cocos2d-x中的3D开发
    9.1 3D精灵类Sprite3D
    静态obj格式的3D模型
    9.2 3D摄像机Camera
    不同的模型及场景可能调用不同的摄像机观察
    9.3 3D骨骼动画
    Cocos2d-x中加载的骨骼动画是c3b格式的文件,可以将FBX格式的骨骼动画文件通过fbx-conv转换成Cocos2d-x自身支持的c3t和c3b格式文件。
    骨骼动画相关类
    骨骼动画Animation3D
    骨骼动画动作Animate3D
    精灵执行骨骼动画动作runAction
    chap15渲染机制
    15.1渲染树VS渲染命令
    从Cocos2d-x 2.x到3.0的一个重要的改进就是使用渲染命令来替换渲染树,新的渲染框架将渲染功能从节点身上剥离开,交由专门的Renderer来处理,大大降低了耦合度。自动合并渲染批次更是极大提升了渲染效率。
    DrawCall就是一次客户端到服务器的通讯,BatchNode起到的作用就是,将N次通信转换为1次,从而节省通信带来的额外开销。但使用批次渲染的对象有若干限制,以便保证渲染的连续。
    限制包括一次渲染只能渲染一张纹理,只能使用统一的混合方式及同一个Shader,并且该Shader不能使用Uniform。
    15.1.1渲染树
    15.1.2渲染命令
    15.2 Cocos2d-x 3.0的渲染框架
    主要由Renderer、RenderQueue、RenderCommand组成。
    RenderCommand负责具体的渲染细节,有多个子类,提供了各种渲染功能。
    Renderer的职责是管理RenderQueue及执行RenderCommand进行渲染。这里包含了VBO(顶点缓冲区对象)和VAO(顶点数组对象)的初始化以及管理。VBO是位于OpenGL内部的一大块缓冲区,用于缓存大量顶点信息,VAO则包含了一个或多个VBO。
    RenderQueue有两种,一种是普通的RenderQueue,另一种是专门处理3D透明渲染的TransparentRenderQueue,二者的职责都是管理RenderCommand,并对其进行排序,唯一的区别是RenderQueue的内部根据RenderCommand的Z值分成了大于0、小于、及等于0的3个列表,而TransparentRenderQueue只有一个列表。
    Cocos2d-x 3.0之后引入了摄像机的概念,一般只会有一个默认摄像机,但同时可以存在多个摄像机,如果同时激活多个摄像机的话,在渲染场景时会Visit多次场景,并执行多次渲染。
    第13章 自动更新
    13.2 so文件更新
    Cocos2d-x引擎中,如果编程语言是C++,那么跨平台编译到Android平台后,所有的代码都编译到一个so文件中。所以,如果代码中有一个小的改动,则需要更新这个so文件。
    根据MD5算法的特性,如果某文件有一个小的改动,那么它的MD5值就会变化,所以可以根据计算MD5值判断so文件是否更新。

    第15章 iOS
    以引擎的2.2.3版本来介绍,对于3.5版本,是类似的。
    Copy items if needed指是否把加入的文件复制到当前目录下,如果选择它,资源会复制到proj.ios目录下。
    我们的Classes与Resources目录和proj.ios是同级的,所以这一项最好不要选择;下面两个单选是group和folder的区别,前者以组的方式组织文件,它是逻辑上的,在硬盘上资源可以是凌乱的。如果是代码文件,以group的方式加入项目中。Xcode编译器会对代码进行编译;后者以文件夹的方式组织文件。以这种方式加入到项目中的文件,如果是代码文件,编译器不会对其进行编译,文件夹里面的资源只会直接copy到bundle包中。
    所以,Classes文件夹以group的方式加入到项目中,而Resources中的资源文件以folder的方式加入。
    15.2.1 info.plist文件
    项目中有一个info.plist文件,它用于存储游戏的元数据,是一个xml格式的文件,以key和value的方式组织数据,用于存储和读取我们游戏中用到的数据。
    表15-1 info.plist部分属性
    属性|描述
    Bundle display name|游戏名字,用于在iOS系统界面显示
    Bundle name|包的简称,应该小于16个字符,可以和Bundle display name不同
    Bundle Identifier|包的ID标识
    Supported interface orientations|设备支持的方向,与General设置Device Orientation的方向对应
    Bundle version|和General中的Build号对应
    Bundle version string|和General中的Version号对应
    15.2.2 Icon和Launchimage设置
    iOS应用,必须为其指定App Icons和Launch Images。在Xcode5后,它们通过Asset Catalog来管理。
    Lauch Screen File是Xcode6.0和iOS 8.0之后引入的,它用一个xib文件作为启动画面。
    iPhone的启动页面只支持竖屏的,对于横屏游戏,也应该做成竖屏图片。
    15.3真机调试
    开发者账号有4种类型:个人\公司\企业\教育
    15.3.1证书
    证书有两种:开发证书(Development)和发布证书(Production)。
    本书讲解开发证书申请的流程,发布证书类似。
    在苹果系统的应用程序中,找到keychain access,在这里申请CSR。
    CSR文件在申请证书时要用到。在申请CSR文件过程时,生成一对公钥和私钥,保存在计算机的keychain中,代码签名基于这种非对称秘钥的加密方式,用私钥进行签名,用公钥进行验证。私钥保存在计算机中,可以把私钥导出,提供给别的开发者使用,它是一个后缀为p12的文件。
    而公钥保存在下面生成的Certificate中,当你用自己的私钥对代码签名后,苹果就可以用Certificate中的公钥进行验证了,保证了代码的安全性。

    15.4 In-App Purchase
    In-App Purchase通过使用Store Kit框架把商店嵌入你的App中。
    Store Kit提供了一组API来确保APP和App Store之间的通信。
    程序从App Store中请求产品信息,显示给用户。
    当用户购买某个产品时,Store Kit收集用户的购买信息,并向App Store请求,然后把购买的结果通知用户,在这个结果中,包含有一个收据,用于验证此次购买的合法性,用该收据向App Store验证。
    如果是单机游戏,可以直接向App Store验证;如果是网络游戏,则可以把收据发给服务器,由服务器去验证,然后再把结果告诉客户端。
    15.4.1 Store Kit框架类
    Store Kit中包含一些功能类和协议,如SKProduct、SKMutablePayment、SKProductRequestDelegate等,通过它们实现和App Store进行通信。下面将介绍一些主要的类和结构。
    1、SKProduct结构
    该结构中数据都是只读的,它存储的是在iTunes Connect中注册的产品信息,这些产品信息是我们在请求产品时通过SKProductResponse返回的。
    字段|描述
    localizedDescription|产品的描述
    localizedTile|产品的名字
    price|本地货币产品的价格
    priceLocale|本地化信息
    productIdentifier|在iTunes Connect中注册的Product ID
    2、SKProductRequestDelegate
    函数|功能描述
    productsRequest:didReceiveResponse|获得产品列表回调函数
    requestDidFinish|请求完成回调函数
    request:didFailWithError|请求失败回调函数

    20160807添加:
    chap13 使用Cocos2d-x制作2048休闲游戏
    13.2使用CocoStudio制作UI界面
    Helper::seekWidgetByName(m_playWayIntroducePanel, "GameDesc_Panel")
    13.3编写逻辑代码
    13.3.1把UI界面添加到游戏界面中
    游戏开始后加载该场景
    runWithScene
    在init方法中加入UI界面
    widgetFromJsonFile
    13.3.2添加获取分数控件并设置分数
    Helper::seekWidgetByName
    13.3.3添加数字方块类
    13.3.4初始化游戏数据
    13.3.5添加按钮功能
    要从UI界面中获取两个按钮
    Helper::seekWidgetByName
    为两个按钮注册回调函数
    addTouchEventListener
    toucheventselector
    13.3.6添加事件监听

    19.4 CocoStudio
    CocoStudio是Cocos2d-x官方推出的一款游戏编辑器,它集成了UI编辑器、动画编辑器、场景编辑器和游戏数据编辑器。
    启动界面下有4个编辑器选项,分别是动画编辑器、UI编辑器、场景编辑器和数据编辑器。可以选择某一个编辑器来运行它们。

    CocoStudio中的UI Editor是非常实用并且操作简单的UI编辑工具。
    步骤1:打开UI Editor
    打开CocoStudio,选择下方第二个选项UI Editor。
    步骤2:新建项目
    步骤3:导入资源
    步骤4:添加按钮
    步骤5:添加背景、滑动条、数字标签、文本标签控件
    步骤6:修改对象结构关系
    步骤11:初始化UI界面
    #include "cocostudio/CocoStudio.h"

    #include "cocos/editor-support/cocostudio/CocoStudio.h"
    #include "cocos/ui/CocosGUI.h"

    using namespace cocostudio;
    using namespace ui;
    cocos2d::ui::Widget*       m_uiWidget;
     m_uiWidget = GUIReader::getInstance()->widgetFromJsonFile("ExchangeLayer/ExchangeLayer.ExportJson");
     this->addChild(m_uiWidget);


    setScaleX

    const int kResolutionWidth   = 1360;
    const int kResolutionHeight   = 768;
     this->setContentSize(Size(NS_lrbyFish::kResolutionWidth,NS_lrbyFish::kResolutionHeight));

     // 按照屏幕尺寸缩放
     this->setScaleX(UI_SCREEN_W/(float)kResolutionWidth);
     this->setScaleY(UI_SCREEN_H/(float)kResolutionHeight); 


     Widget* UI = GUIReader::getInstance()->widgetFromJsonFile("ComLayer/BankerList_1/BankerList_1.json");
     this->addChild(UI,10);

     //按比例缩放 长宽
     UI->setScaleX((float)1136/1920);
     UI->setScaleY((float)640/1080);

    getVisibleSize
    getContentSize
    步骤12:UI对象的使用
    1、获取UI中的对象。
    2、添加事件。
    3、设置属性。
    getChildByName
    cocos2d::ui::Button*       closeBtn;
    closeBtn = dynamic_cast<Button*>(panel->getChildByName("close"));
    void RechargeLayer::SetBtnCallBack(cocos2d::Ref* target,cocos2d::ui::SEL_TouchEvent selector)
    {
     if (closeBtn != nullptr)
     {
      closeBtn->addTouchEventListener(target,selector);
     }
    }
    ((RechargeLayer*)m_gameWidget)->SetBtnCallBack(this,SEL_TouchEvent(&LobbyScene::OnBtnCallBack));

    Text* content= (Text*) m_list_Daili->getItems().at(0)->getChildByName("content");
    content->setString(CGameLobby::GetInstance()->readLanguageString("Daili002")->getCString());
    Label* text = dynamic_cast<Label*>(subBg->getChildByName("score_text"));
    if (text != nullptr)
    {
       text->setString(StringUtils::format("%lli", score));
    }

    

    20161111添加:
    Cocos2d-x 3.x游戏开发实战
    肖文吉 编著
    2015年1月第1版
    chap1 Cocos2d-x游戏引擎介绍
    1.3.4 Cocos2d-x 3.0的新特性
    1、使用C++(C++ 11)的特性取代了Objective-C的特性
    2、新的渲染器
    3、优化了Labels
    4、新的事件分发机制
    所有事件都由EventDispatcher分发。
    5、物理引擎集成
    chap3 Cocos2d-x的核心类
    3.1节点类(Node)
    继承自Ref类,同时也是所有节点类的父类。
    3.1.1 Node类的成员变量
    Node类常用的受保护的成员变量如下。
    _scaleX、_scaleY、_scaleZ:表示节点X、Y、Z轴的缩放比例,节点以锚点为中心缩放该比例。变量是float,默认为1.0。
    例如:
    playerInfo[i].head->setScale(0.8f);
    playerInfo[i].level->setScale(0.75f);
    playerInfo[i].chipValue->setScale(0.8);
    _position:表示节点在屏幕中的位置。注意:Position设置的是节点在父节点中的坐标值。
    例如:
    int positionX = playerInfo[i].head->getPositionX();
    int positionY = playerInfo[i].head->getPositionY()+2;
    m_pCountDown[i]->setPosition(ccp(bkWidth/2,bkHeight - yDelta-yDelta-10));
    StartSpriteTag[i]->setPosition(ccp(bkWidth/2,bkHeight - yDelta-yDelta-70));
    playerCard[7].cards[0]->setPosition(ccp(bkWidth/2+15*cardDelta,bkHeight-yDelta*3/2));
    playerCard[7].cards[1]->setPosition(ccp(bkWidth/2+17*cardDelta,bkHeight-yDelta*3/2));
    playerInfo[i].level->setPosition(ccp(positionX-40,positionY+30));
    playerInfo[i].infoBG->setPosition(ccp(positionX+100,positionY));
    playerInfo[i].chipValue->setPosition(ccp(positionX+100,positionY - 15));
    _anchorPoint:表示节点的锚点。锚点的变量类型是Vec2。锚点的x和y取值通常是0~1之间的实数,表示锚点相对于节点长宽的位置。另外,还有_anchorPointInPoints成员变量,区别在于其采用绝对像素值为单位。
    例如:
    playerInfo[i].playerName->setAnchorPoint(Point::ZERO);
    _visible:用于判断节点是否可见。变量类型是bool,默认值为true。
    例如:
    playerInfo[i].nullHead->setVisible(false);
    playerInfo[chairID].nullHead->setVisible(true);


    chap14 Cocos2d-x的内存管理
    Cocos2d-x的内存管理采用了Objective-C当中的“引用计数”和“自动释放池”的方式。
    14.1内存管理概述
    在内存管理中经常会遇到两个问题。
    1、内存溢出
    2、内存泄露
    有效的内存管理通常包括两个方面的内容。
    1、内存分配
    2、内存回收
    典型的内存回收策略有两种。
    自动回收
    混合回收
    Cocos2d-x的内存管理结合了手动管理内存和自动管理内存两种方式。
    手动的内存回收:
    retain
    release
    autorelease
    自动释放池是Cocos2d-x推荐的内存管理方式。
    14.2手动内存管理
    14.2.1对象的引用计数
    Cocos2d-x采用了一种称之为引用计数的机制来跟踪对象的状态。
    Ref中提供了有关引用计数的如下函数。
    retain:将该对象的引用计数加1。
    release:将该对象的引用计数减1。
    autorelease:不改变对象的引用计数器的值,只是将对象添加到自动释放池中。该函数将会返回该方法的对象本身。
    getReferenceCount:返回该对象的引用计数器的值。
    14.2.2对象所属权
    14.2.3函数中的保留和释放
    14.2.4使用自动释放池
    14.3自动释放池
    chap7 Cocos2d-x的常用控件
    4.3.3使用TableView展示多页内容
    TableView继承自ScrollView。
    TableViewDelegate继承自ScrollViewDelegate。
    TableView的使用方式类似cocoa框架的UITableView,用到TableView的类需要继承TableViewDataSource,然后声明几个函数。

    class LotteryKindScrollView : public Layer,TableViewDataSource,TableViewDelegate
    {
    public:
        LotteryKindScrollView();
        ~LotteryKindScrollView();
        virtual bool init();
        static Scene* scene();
        void pressKindButton(int tag);
        void connectGameRoomServer(int tag);
        CREATE_FUNC(LotteryKindScrollView);

            //视图滚动时调用
     virtual void scrollViewDidScroll(ScrollView* view);
            //视图缩放时调用
     virtual void scrollViewDidZoom(ScrollView* view);

     //tableCellTouched:触摸单元格时,会调用该函数
     //触摸事件 计算的是点击的是那个子页
     virtual void tableCellTouched(TableView* table, TableViewCell* cell);
     //3.0 tableCellSizeForIndex:根据index设定每一个单元格的大小
     //3.5 cellSizeForTable:设置TableView中每一项的大小
     //每一项的高度和宽度
     virtual cocos2d::Size cellSizeForTable(TableView *table);
     //tableCellAtIndex:根据index为单元格设置内容
     //生成列表的每一项内容
     virtual TableViewCell* tableCellAtIndex(TableView *table, ssize_t idx);
     //numberOfCellsInTableView:返回单元格的个数
     //一共多少项
     virtual ssize_t numberOfCellsInTableView(TableView *table);

     //刷新
     void refreshLayer();
    public:
        void onEnter();
        void onExit();
        bool onTouchBegan(Touch *pTouch, Event *pEvent);
        void onTouchMoved(Touch *pTouch, Event *pEvent);
        void onTouchEnded(Touch *pTouch, Event *pEvent);
        void onTouchCancelled(Touch *pTouch, Event *pEvent);
     //itype 0为游戏 1为彩票 2为体育
     void resetTable(const char* preName,const int count,const int iType);

    private:
     int selectedSpriteTag;
     std::vector<int> m_Data;
     int m_Count;
     string m_name;
     TableView* m_table;
     TableViewCell* pCell;
     Vec2 start_pos;
    };
    20151123添加:
    https://github.com/walzer
    Cocos2d-x版本变迁:
    2010年8月,HelloWorld发布
    用OpenGL ES 1.1绘制静态图片
    Boolean THelloWorldApp::initCocos2d()
    {
        // init director
        CCDirector::getSharedDirector()->setOpenGLView(m_pMainWnd);
        CCDirector::getSharedDirector()->setDeviceOrientation(kCCDeviceOrientationLandscapeLeft);

        // load background image texture and get window size
        CCTexture2D * pTexture = CCTextureCache::sharedTextureCache()->addImage("splash-angry-01.png");
        CGSize size = CCDirector::getSharedDirector()->getWinSize();

        // create sprite instance
        CCSprite * pSprite = (new CCSprite())->initWithTexture(pTexture);
        pSprite->setPosition(CGPoint(size.width / 2, size.height / 2));
        pSprite->autorelease()->retain();

        // create layer instance
        CCLayer * pLayer = new CCLayer();
        pLayer->addChild(pSprite)->autorelease();

        // add layer to scene
        CCScene * pScene = CCScene::node();
        pScene->addChild(pLayer);

        // add scene to director
        CCDirector::getSharedDirector()->runWithScene(pScene);
        return TRUE;
    }
    2010年11月30日,Cocos2d-x第一个版本发布
    第一年有125个基于Cocos2d-x的移动游戏或App,总下载量超过2000万。
    2010年11月,0.99.4-x-0.7.0发布
    2010年12月,0.99.4-x-0.7.1发布
    2011年3月,0.8.0发布
    2011年4月,0.8.2发布
    更新日志:
    支持Zwoptex输出的.plist格式
    2011年4月,集成了网络库libcurl
    2011年4月,在不同平台保存或读取文件文件,困难是保存或读取文件的路径
    2011年4月,Cocos2dxSimpleGame和新手教程发布
    2011年4月,设置默认的ndk版本为ndk-r5
    ndk-r5支持STL
    2011年5月,0.99.5-x-0.8.3发布
    2011年5月,实现Android 3.0支持
    2011年5月,0.99.5-x-0.8.4发布
    2011年6月,0.99.5-x-0.8.5发布
    支持Lua,xcode4模板
    HelloLua/Resource/hello.lua
    install-templates-xcode.sh
    CCApplication::getCurrentLanguage()
    Android上CCLabelTTF支持换行符
    2011年7月,Cocos2dxSimpleGame更新至0.8.5并重新发布
    2011年7月,1.0.0-x-0.9.0发布
    与cocos2d-iphone v1.0.0同步发布的第一个版本
    2011年8月,1.0.1-x-0.9.1发布
    2011年10月,Cocos2d-x移植到BlackBerry
    2011年10月,1.0.1-x-0.9.2发布
    文档:
    iOS有4种设备的物理方向:
    Portrait
    Upside Down
    Landscape Left
    Landscape Right
    改变Android的设备的物理方向:
    landscape
    portrait
    在Win32下模拟Android行为:
    action on win32|equal to behavior on android
    Shift + F1|Back key pressed
    Shift + F2|Menu key pressed
    minimize the window|switch to background
    maximize the window|resume to foreground
    0.9.2的更新日志:
    2011年12月,1.0.1-x-0.10.0发布
    主要特性:
    集成pthread,实现CCTextrueCache::addImageAsync(),支持bada平台、android x86平台,升级Box2d到2.2.1,添加一些音效函数。
    新文档:
    如何使用pthread:
    1、不要调用任何调用了Ref::retain()、Ref::release()、Ref::autorelease()的函数,因为AutoreleasePool不是线程安全的,除了数据结构之外,不要在一个新线程里调用任何cocos2d-x API
    2、如果你想在新线程里加载资源,可以使用TextureCache::addImageAsync()
    3、pthread_cond_wait好像有bug,第一次不会等待,随后就正常了
    你应该牢记,OpenGL context不是线程安全的。
    cocos2d-x/extensions/network/HttpClient.cpp使用pthread_t和pthread_mutex_t创建了一个网络线程。
    传递一个结构给一个分离线程并设置互斥体的例子:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

    struct SimpleStructure
    {
        int data;
        float otherData;
    };

    void* ThreadFunction(void* arg)
    {
        pthread_mutex_lock(&mutex);
        SimpleStructure* args = (SimpleStructure*)arg;
        // do something with args->data and args->otherData
        delete args;
        pthread_mutex_unlock(&mutex);
        return NULL;
    }

    void CreateThread()
    {
        pthread_t thread;
        SimpleStructure* args = new SimpleStructure();
        args->data = 1;
        args->otherData = 2.0f;
        pthread_create(&thread, NULL, &ThreadFunction, args);
    }
    使用ndk-r7生成工程
    在Android x86平台生成HelloWorld
    ndk-r6开始支持x86平台
    2012年3月,1.0.1-x-0.13.0-beta发布
    请用ndk-r7b生成本机代码
    2012年6月,2.0-rc2-x-2.0.1与cocos2d-iphone v2.0 rc2同步发布
    实现CCBIReader,支持CocosBuilder
    大的改进:
    引擎的所有java文件移到cocos2dx/platform/android/java目录
    Win32下,用OpenGL代替OpenGL ES
    2012年8月,CCEditBox在iOS上实现了
    2012年8月,2.0.2发布
    2012年9月,2.0.3发布
    更新extensions/CCBReader到v2.1 beta2支持骨骼动画
    增加CCTableView代替CCListView
    更新CCControlExtension
    大的改进:
    Javascript绑定更稳定
    重构lua绑定
    重构java库代码
    2012年10月,Windows Phone 8版出来了
    2012年11月,2.0.4发布
    2013年2月,2.1发布
    2013年6月,2.1.4发布
    2013年9月,3.0-alpha版发布
    2013年11月,编译Android例程的python脚本
    build/android-build.python
    不用安装cygwin了,删掉build_native.sh
    # build hellocpp
    $ cd cocos2d-x
    $ python build/android-build.python hellocpp

    # build all cpp samples
    python build/android-build.python cpp

    # build all lua samples
    python build/android-build.python lua

    # build all jsb samples
    python build/android-build.python jsb

    # build all samples
    python build/android-build.python all

    # pass parameter to ndk-build
    python build/android-build.python -n clean hellocpp
    2013年11月,在Android ARM上,char默认是无符号的,在Application.mk的APP_CPPFLAGS加上-fsigned-char变为有符号的,因为其它平台默认是有符号的
    2013年11月,增加ThreadHelper::runOnGLThread()
    cocos2d-x和OpenGL ES不是线程安全的。不必使用Schedule做类似的事情了。
    2014年1月,3.0-beta版发布
    2014年1月,2.2.2发布
    2014年3月,3.0rc0版发布
    2014年3月,2.2.3发布
    2014年4月,3.0正式版发布
    支持WinPhone8,C++基础底层优化:新渲染器、新API、性能飞跃
    2014年5月,3.1发布
    支持3D模型
    2014年6月,2.2.4发布
    2014年7月,2.2.5发布
    2014年7月,3.2.0正式版发布
    支持3D骨架动画、支持游戏手柄
    2014年8月,3.3 alpha0发布
    2014年9月,3.3 beta0发布
    2014年12月,3.3正式版发布
    2014年12月,2.2.6正式版发布
    支持iOS 64位
    2015年2月,3.4正式版发布
    2015年3月,3.5发布
    2015年4月,3.6发布
    2015年8月,3.7发布
    新增3D物理引擎和3D地图导航
    2015年8月,3.7.1发布
    2015年8月,3.8 beta0、3.8正式版发布
    2015年11月,3.9发布

    2016年1月,3.10发布
    2016年5月,3.11发布
    2016年7月,3.12发布
    2016年8月,3.13发布
    2017年1月,3.14发布
    2017年4月,3.15发布
    2017年10月,3.16发布


    Cocos2d-x v3.1开始支持3D模型。
    Cocos2d-x v3.2支持3D骨骼动画。加入对游戏手柄的支持以及一些性能上的优化。
    http://www.docin.com/p-888314762.html
    比较著名的帧动画格式是Quake2所采用的MD2。
    帧动画原理
    将每帧所需要的所有顶点的:顶点位置、法线或顶点颜色、纹理坐标等信息全部存储起来,在渲染的时候逐帧播放或插值播放,这就是帧动画的原理。
    骨骼动画技术后于帧动画技术的出现。
    最开始,骨骼动画仅用于非实时渲染的建模领域,如3DMax这类建模软件之中,以方便美工的建模。
    后来,CPU从渲染中解放后,骨骼动画才用于实时渲染的游戏中。
    骨骼动画的想法来源于人体骨骼。
    两个概念:
    骨骼SKELETON:用以控制蒙皮的一种抽象的概念。在最终的渲染结果中,它不可见。
    蒙皮SKINMESH:被骨骼控制、并显示在外的因素。实际上就是顶点、法线、纹理坐标等将被渲染的元素。
    MDL是比较经典的骨骼动画格式,由于CS的成功而被追捧。
    为了播放骨骼动画,需要有骨骼的数据,模型的数据,关联骨骼和模型上每个顶点的关联数据,以及关键帧的坐标变换数据。
    20160914:

    3.x手柄,PC下链接不过
    http://www.cocos.com/doc/tutorial/show?id=1166
    2>HelloWorldScene.obj : error LNK2019: 无法解析的外部符号 "public: static class cocos2d::EventListenerController * __cdecl cocos2d::EventListenerController::create(void)" (?create@EventListenerController@cocos2d@@SAPAV12@XZ),该符号在函数 "public: virtual bool __thiscall HelloWorld::init(void)" (?init@HelloWorld@@UAE_NXZ) 中被引用
    2>HelloWorldScene.obj : error LNK2019: 无法解析的外部符号 "public: static void __cdecl cocos2d::Controller::startDiscoveryController(void)" (?startDiscoveryController@Controller@cocos2d@@SAXXZ),该符号在函数 "public: virtual bool __thiscall HelloWorld::init(void)" (?init@HelloWorld@@UAE_NXZ) 中被引用

    3.x虚拟手柄
    https://github.com/cheyiliu/joystick

    


    20161121-20161207资源热更新
    http://blog.csdn.net/linchaolong/article/details/42321767
    https://coding.net/u/linchaolong/p/Cocos2d-x_HotUpdate/git
    http://www.cocos.com/docs/creator/advanced-topics/hot-update.html
    http://blog.csdn.net/renzhe20092584/article/details/9184163
    http://www.cocos2d-x.org/wiki/Assets_manager
    cocos2d-x 3.10 热更新 使用AssetsManagerEx
    http://blog.csdn.net/strivero/article/details/51130784
    AssetsManagerEx组件使用说明
    http://www.cnblogs.com/anxin1225/p/5318793.html
    cocos2dx-3.x 用脚本生成 AssetsManagerEx自动更新所需的manifest文件
    http://blog.csdn.net/qlt445_ndsc/article/details/51325296

    http://zengrong.net/post/2131.htm
    这里说的热更新(Hot update),指的是客户端的更新。
    大致的流程是,客户端在启动后访问更新api,根据更新api的反馈,下载更新资源,然后使用新的资源启动客户端,或者直接使用新资源不重启客户端。
    这种方式可以跳过AppStore的审核,避免了用户频繁下载、安装、覆盖产品包。
    我们一般使用这种方式快速修复产品BUG和增加新功能。
    我以前的习惯,是在主版本变化的时候需要整包更新,而次版本变化代表逻辑更新,编译版本代表资源更新等等。这些需要自己来定义升级规则。
    我们只需要记住这个Updater是使用AssetsManager修改的即可。
    在上面SunLightJuly和Henry同学的方法中,使用的是CCHTTPRequest来获取网络资源的。CCHTTPRequest封装了cURL操作。而在Updater中,是直接封装的cURL操作。
    在我的设计中,逻辑应该尽量放在lua中,C++部分只提供功能供lua调用。因为lua可以进行热更新,而C++部分则只能整包更新。
    从2.1.2版本开始,2dx在libExtensions下添加了一个AssetsManager类用于资源的在线更新和简单的版本管理,同时添加了AssetsManagerTest项目示范了AssetsManager类的用法。

    

    20170319添加:
    9.2文件
    9.2.1文件处理类FileUtils
    9.2.5设置文件搜索路径
    获取路径使用getSearchPaths函数,该函数返回所有搜索路径的数组。
    设置搜索路径使用setSearchPaths函数

    chap16 Android
    在本章,首先系统地讲解环境的搭建,然后介绍C++与java的互调,最后实现一些与平台相关的功能。
    16.1 Win32平台下开发环境搭建
    先把引擎源码从Cocos2d-x官网上下载下来,不需要安装,解压到一个目录即可。然后找到VS的项目文件,打开并编译,成功后,把它的TestCpp项目设置为启动项,运行后就可以看到官方实现的很多例子,对于初学者,这是很好的学习材料。有了引擎源码之后,就可以创建属于我们自己的项目了。
    16.1.1安装配置python
    对于项目的创建,引擎提供了python这个脚本工具。

    用命令行工具查看python版本的方法
    F:\hxhwin7\Django-1.7.8>python
    Python 2.7.9 (default, Dec 10 2014, 12:24:55) [MSC v.1500 32 bit (Intel)] on win
    32
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import django
    >>> django.get_version()
    '1.7.8'
    >>>


    设置环境变量
    C:\Program Files (x86)\Common Files\NetSarang;C:\Python27\;C:\Python27\Scripts;c:\program files (x86)\intel\icls client\;c:\program files\intel\icls client\;%systemroot%\system32;%systemroot%;%systemroot%\system32\wbem;%systemroot%\system32\windowspowershell\v1.0\;c:\program files\intel\intel(r) management engine components\dal;c:\program files\intel\intel(r) management engine components\ipt;c:\program files (x86)\intel\intel(r) management engine components\dal;c:\program files (x86)\intel\intel(r) management engine components\ipt;c:\program files (x86)\windows live\shared;c:\program files\tortoisesvn\bin;c:\program files (x86)\microsoft sql server\90\dts\binn\;c:\program files (x86)\microsoft sql server\90\tools\binn\;c:\program files (x86)\microsoft sql server\90\tools\binn\vsshell\common7\ide\;c:\program files (x86)\microsoft visual studio 8\common7\ide\privateassemblies\;c:\program files\mysql\mysql server 5.5\bin;C:\Program Files (x86)\MATLAB71\bin\win32;C:\Program Files (x86)\Rational\Common;%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;C:\Python27\Lib\site-packages\Django-1.7.8-py2.7.egg\django\bin;C:\Program Files\Microsoft\Web Platform Installer\;C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages\v1.0\;C:\Program Files (x86)\Windows Kits\8.0\Windows Performance Toolkit\;C:\Program Files\Microsoft SQL Server

    16.1.2 Cocos2d-x 2.x项目创建

    使用Windows下的命令行工具,进入引擎的project-creator目录,输入如下命令:
    python create_project.py -project HelloSocket -package org.cocos2dx.application -language cpp
    python create_project.py -project ZJHClient -package com.hapi.wj -language cpp
    python create_project.py -project SimpleGame -package org.cocos2dx.simplegame -language cpp
    农场养成类游戏HelloLua
    python create_project.py -project HelloLua -package org.cocos2dx.hellolua -language lua

    其中:
    -project后面的参数表示项目名
    -package后面的参数表示包名,包名由点分割,最好由3部分组成。
    -language后面的参数表示项目的开发语言,可以有3个值:cpp、lua和javascript
    16.1.3 Cocos2d-x 3.x项目创建
    其中:
    new后面的参数为项目名称
    -p后面的参数为包名
    -l后面的参数表示开发语言,也有3个值:cpp、lua和js
    -d后面的参数表示项目存放的目录
    2.1.1 Cocos2d-x的安装与配置
    Cocos2d-x从2.1.4版本之后已经不再支持使用模版生成项目,而是使用官方提供的Python直接创建项目。
    创建项目的步骤如下:
    (1)打开终端,进入/Applications/Cocos/Cocos2d-x/cocos2d-x-3.10目录,执行setup.py文件,运行该文件用来配置系统的一些环境变量(NDK_ROOT、ANDROID_SDK_ROOT、ANT_ROOT)。
    (2)打开终端,执行cocos new SimpleGame -p org.cocos2dx.simplegame -l cpp -d ~/Desktop
    编译前:400,647,661 字节(磁盘上的 413.7 MB),共 6,408 项
    各个子目录说明如下:
    Classes:Cocos2d-x的游戏类文件,也就是游戏开发者编写的类文件目录。
    Frameworks:Cocos2d-x所使用的框架部分。
    ios:iOS平台相关专用类。
    mac:mac平台相关专用类。
    Products:编译生成的包文件。
    Resources:资源文件目录。

    

    

    

    

    

    

    

    

    

    

    

    

    

    

    

    

    

    

    

    展开全文
  • ExMobi文档

    千次阅读 2015-07-04 11:28:21
    EXmobi官方文档 ExMobi®从入门到精通         本书电子版和示例代码请访问GIT仓库: https://github.com/nandy007/ExMobiBeginnerBook       ExMobi门户:...支撑电话:400-110-

    EXmobi官方文档

    ExMobi®从入门到精通

     

     

     

     

    本书电子版和示例代码请访问GIT仓库:

     

     

     

    ExMobi门户:http://www.exmobi.cn

    ExMobi论坛:http://bbs.exmobi.cn

    支撑电话:400-110-1111  025-6677-7333

    官方微博(新浪):@ExMobi

     

     

     

    南京烽火星空通信发展有限公司

    2014年


     

    第1篇ExMobi基础篇

    1 ExMobi概述

    1.1 ExMobi概述

    ExMobi是烽火星空公司推出的跨平台移动应用开发中间件产品。ExMobi通过全面的数据集成技术和丰富的跨平台客户端展现能力,将业务系统快速、安全、高效的移植于移动终端,并从开发(IDE环境)、集成(IT系统对接、云服务)、打包(各个操作系统的应用打包)、发布(应用的运行)、管理(日志管理,更新管理)上提供了一整套的解决方案。

    1.2 ExMobi组成元素

    ExMobi包含了一系列的技术和产品,主要包括:ExMobi客户端、ExMobi服务端、MBuilder集成开发工具以及ExMobi产品门户。

    1.2.1 ExMobi产品门户

    ExMobi产品门户网址为www.exmobi.cn ,它包括ExMobi产品中心、EDN开发者门户、BBS论坛(bbs.exmobi.cn)以及ExMobi开放平台。

    ExMobi产品中心可以了解到ExMobi产品的功能和最新动态;EDN开发者门户为开发者提供应用的开发、发布和管理等一站式服务;BBS论坛为开发者提供可持续的学习和交流空间;ExMobi开放平台提供广阔的基于ExMobi的原生插件开发资源,开放平台开发者开发的插件可以作为ExMobi客户端引擎的一部分。

    EDN开发者门户、ExMobi开放平台、BBS论坛和MBuilder均需要在ExMobi产品门户上进行注册成功后得到账号和密码方能登陆使用。

     

    1.2.2 ExMobi客户端

    ExMobi客户端负责应用在移动终端的展示和交互,以及与ExMobi服务端的通信。它主要包含:PC模拟器客户端、Android客户端、IOS客户端、Windows8客户端等。

    ExMobi客户端实现跨平台的原理,是在不同移动终端上将同样的功能和交互封装成统一的接口,如:XHTML、JavaScript、CSS、主题、Native插件接口等。对于移动应用开发者来说实际上就像WEB开发一样开发一套XHTML的应用即可进行跨平台的数据展现和交互。而能够执行这种特殊应用的引擎我们称为“基座”。所以,对于一个完整的ExMobi客户端应该包含基座和应用。

    而为了方便开发调试,ExMobi客户端存在两种状态,一种是基座状态,一种是打包状态。

    基座状态主要在开发调试时使用,安装基座客户端的时候,里面是没有应用的。客户端安装好之后,打开基座客户端首先看到的就是基座,在基座的“设置”功能中配置好开发调试环境的IP和端口即可方便的安装和卸载应用,并对应用进行开发调试,而不是像Native原生开发一样每次都要编译,方便了调试也节省了编译的时间。

    打包状态为应用开发完毕后将基座和应用一起打包生成最后发布安装包的状态。打包客户端实际上就是在打开客户端的时候把基座隐藏起来直接看到应用。打包客户端可以使用ExMobi开发者门户的云打包服务进行在线打包。

    下图为ExMobi基座客户端效果图。

    1.2.3 ExMobi服务端

    ExMobi服务端负责对ExMobi客户端请求过来的数据进行处理,并把处理结果响应给客户端进行操作。所以,它主要的功能就是对数据的集成能力。

    ExMobi服务端主要包含4大组件:ExMobi管理平台(EMP)、基本核心引擎(BCS)、统一推送引擎(PNS)、统一文档转换引擎(DCS)。

    EMP为ExMobi的管理平台,对ExMobi应用和客户端的管理、终端用户使用授权、统计报表展现、其他引擎和服务的管理等。

    BCS为数据集成的服务引擎,主要包括:HTTP请求的模拟、Web Service集成、数据库集成、标准接口集成、接口发布等。

    PNS为统一推送引擎,实现与BCS的对接,通过UDP/TCP Push、二进制短信push、APNS/C2DM等通道实现应用的统一推送。

    DCS为统一文档转换引擎,可以对标准OFFICE文档、压缩包、图片等格式进行支持。也支持对书生SEP、方正CEB、点聚AIP等特殊格式文档进行转换在客户端展示。

    下图为部署了ExMobi服务端后在浏览器上看到的EMP管理平台的效果图:

    1.2.4 MBuilder集成开发工具

    MBuilde是为了方便开发者开发移动应用而研发的一款基于Eclipse的ExMobi移动应用集成开发工具。

    MBuilder包括ExMobi的Eclipse插件、ExMobi的PC模拟器基座客户端以及ExMobi服务端的DEV开发版本(BCS-mini版本+PNS-mini版本+DCS-mini版本,不包含EMP)。

    使用MBuilder就像使用Eclipse或者其他开发工具一样,能够方便的创建、编辑、调试和发布应用。

    1.3 ExMobi的应用工作原理

    ExMobi的应用包含客户端资源和服务端资源。客户端资源运行在ExMobi客户端;服务端资源运行在服务端。

    应用的运行原理如下图所示:

    ExMobi应用的运行原理实际上是ExMobi服务端将第三方系统数据源转换为ExMobi客户端识别的语言在不同的终端进行展示和交互的一个过程。而服务端转换的依据是通过客户端发起的请求指令经过MAPP路由传递到服务端的。

    2 MBuilder的使用

    MBuilder集成开发工具为开发者提供了简单易操作的开发环境。正确使用MBuilder可以极大的提高开发的效率。

    2.1 安装、升级和卸载

    2.1.1 安装前准备工作

    安装操作系统要求:支持XP、WIN7(32bit、64bit)。

    JDK版本要求:JDK1.6.0_20。

    MBuilder版本要求:2.X.X版本。

    JDK和MBuilder安装包可以通过下载和安装指引页面下载:

    http://www.exmobi.cn/sdkdownload.jsp

    2.1.2 安装MBuilder

    运行安装文件,如下图所示:

    注:文件名根据实际版本情况而定,其中2.3.6是版本号。

    点击“下一步”后选择“安装目录”

    安装目录后点击“安装”即可执行安装操作。

    安装过程要求安装的组件也需要安装。

    模拟器依赖组件:

    WebKit组件库:

    全部安装完毕,点击“完成”按钮即可完成MBuilder的安装:

    2.1.3 运行MBuilder

    安装完成后,会在桌面有MBuilder的快捷图标:

    点击即可运行MBuilder,运行前需要选择工作空间,如下图所示:

    由于卸载MBuilder的时候整个MBuilder安装目录的内容都会被删掉,为了保险起见,请在MBuilder外部创建工作空间,然后选择该目录。

    选择完毕后,点击“OK”按钮进到MBuilder工作界面,由于首次使用需要进行进行登陆,在登陆前需要在ExMobi产品门户进行注册(www.exmobi.cn),使用注册的账号和密码进行登陆,如下图所示:

    若账号、密码正确,即可进到MBuilder的工作界面:

    2.1.4 升级MBuilder

    MBuilder提供了在线升级的功能,点击主菜单-“help”-“about MBuilder”即可看到界面,如下图所示:

    点击“check for updates”即可进行在线升级。

    2.1.5 卸载MBuilder

    在MBuilder安装根目录下,找到“uninst.exe”文件,点击执行即可进入卸载向导。

    2.2 安装目录介绍

    在MBuilder的安装目录下,存在如下目录:

    MBuilder

    |---eclipse                 存放eclipse开发平台相关文件

    |     |--- dropins           存放MBuilder 插件目录

               |---env                     依赖组件的安装包(ExMobi/Webkit组件库)

               |---ExMobi                   ExMobiPC模拟器基座客户端

               |---ExMobi-server            ExMobi服务端DEV版

               |---apache-tomcat-7.0.22     自带服务依赖的tomcat

               |---uninst.exe               卸载程序

    MBuilder的ExMobi客户端和服务端是可以单独升级的。可以到ExMobi产品门户下载PC模拟器安装包和ExMobi服务端的压缩包,安装或者解压相应的目录到MBuilder的客户端和服务端的安装目录下即可。

    2.3 首选项配置

    首选项里面主要对配置文件编码、Tomcat容器(自动配置)、服务端及客户端配置(自动配置)、第三方服务引擎配置、文件模板配置。

     首选项位于主菜单-“window”-“preferences”选项,如下图所示:

    选择后会进入到首选项的配置页面,如下图所示:

    本节的配置均在首选项中进行。

    2.3.1 文件编码配置

    ExMobi中所有文件的编码要求为“UTF-8”编码,如果编码不对可能会造成乱码和提交错误等问题。

    在首选项中一般需要配置的编码的文件为:HTML、CSS、JS、JAVA、JSP等,如下图所示:

    2.3.2 Tomcat配置

    Tomcat默认已经是自动配置好的,此处只需要对其进行验证一下即可,如下图所示:

    2.3.3 服务端及客户端配置

    服务端及客户端的配置也是默认配好的,此处也只需要进行确认即可,如下图所示:

    2.3.4 第三方服务引擎配置

    服务引擎指的是ExMobi服务端的PNS、DCS、BCS引擎,由于ExMobi服务端支持分布式部署,所以这些引擎是相对独立部署在不同的服务器上的。

    如果使用不同服务器上服务,则需要在此处进行配置。

    而一般开发者只需要使用本机的服务即可,配置本机服务请看下图:

    2.3.5 文件模板配置

    MBuilder中提供了对所有可编辑文件格式的模板配置,在创建相应格式文件的时候就可以通过模板快速创建出带一定结构的文件。

    本小节以XSL文件为例,说明文件模板的配置。

    在首选项中找到XSL文件的templates选项,如下图所示:

    点击“edit”按钮后,即可对相应的模板进行修改,也可以点击“new”按钮新建一个模板,如下图所示:

    编辑好的模板可以在MBuilder中使用,如下图所示:

    2.4 MBuilder主要功能介绍

    2.4.1 MBuilder工作界面

    MBuilder工作界面如下:

    主菜单:MBuilder的所有功能操作菜单。

    快捷菜单:常用的MBuilder功能。

    应用程序目录结构:应用的完整结构,对应用资源的管理。

    代码编辑区域:应用中可编辑资源的编辑区域,可对资源内容进行修改。

    控制台和工具区域:主要是开发辅助调试的工具展示区域。

    2.4.2 启动Tomcat

    Tomcat是ExMobi服务端的运行容器,Tomcat的启动、停止和重启就是ExMobi服务端的启动、停止和重启。

    Tomcat的启动、停止、重启操作依次在快捷菜单中,如下图所示:

    启动    停止   重启

    点击第一个按钮即可启动Tomcat,启动成功会在控制台中看到成功日志,如下图所示:

    如果有客户端要接入该服务端,需要配置对应的IP和端口,比如:

    这里的IP和端口就是TOMCAT所在服务器的IP和TOMCAT启动的端口。如果是同一台机器,比如PC模拟器可以使用127.0.0.1的IP,当然,使用实际的IP也可以,从MBuilder的控制台可以看出普通端口是8001,加密端口是8443,如果开启“使用数据加密”,需要填写加密端口;如果是用真机设备上的客户端,需要确保设备和MBuilder的网络是通的,并且填写的IP是同一网段的实际IP,端口仍然使用TOMCAT启动的端口。

    2.4.3 新建和导入向导

    MBuilder的新建和导入向导位于快捷菜单中,如下图所示:

    点击该按钮即可新建一个应用或者导入一个已经存在的应用,如下图所示:

    继续每一个“next”或有相应的配置,最后一个配置是在应用中使用模板和皮肤主题,如下图所示:

    配置完成后点击“finish”即可完成应用的新建或者导入。

    2.4.4 自动代码同步

    ExMobi应用包含了客户端和服务端资源,而客户端资源是运行在客户端的,服务端资源是运行在服务端的。

    在创建应用的时候应用的资源仅仅存在MBuilder的工作空间中,尚未部署到客户端和服务端,所以需要进行代码同步。

    MBuilder中提供了自动代码同步的功能,可以把应用的客户端和服务端资源分别同步到PC模拟器基座客户端和ExMobi的DEV开发版中,这样才能在基座中进行开发调试。

    自动同步按钮的图标位于快捷菜单中,图标有两种状态:启动和关闭。

    如下图所示的状态为自动同步关闭状态,在该状态下应用不会自动同步:

    如下图所示的状态为自动同步启动状态,在该状态下应用才会自动同步:

    要同步哪个应用需要先选中该应用,然后再点击自动同步按钮,如下图所示:

    2.4.5 应用导出

    应用开发完成只是运行在MBuilder的开发环境中,要投入生产还需要将完整应用包部署到ExMobi的工程环境中,并且需要在EDN开发者门户中将应用的客户端资源包和基座打成打包客户端。

    这里涉及两个包的导出:一个是完整应用包的导出,一个是客户端资源包的导出。

    一、完整应用包的导出功能在MBuilder的快捷菜单中,如下图所示:

    点击按钮前需要先选中要导出的应用,选中后点击该按钮,会弹出如下导出向导界面:

    点击“finish”即可导出指定应用的完整应用包到指定的目录,格式为zip。

    二、客户端资源包的导出也是位于快捷菜单,如下图所示:

    点击该按钮,即可进入导出客户端资源包的导航页面,如下图所示:

    第一步:选择应用的版本:

    第二步:选择要生成客户端资源的应用,以及要生成的分辨率、存放目录等,如下图所示:

    点击“finish”即可完成应用客户端资源的打包,包的格式为zip。

    2.5 PC模拟器基座客户端功能

    PC模拟器基座客户端是为了进行开发调试方便而研发的一款产品。它可以最大限度的模拟移动设备终端的效果,使开发达到所见即所得的目的。

    2.5.1 打开PC模拟器

    打开PC模拟器基座客户端的按钮也位于快捷菜单中,如下图所示:

    他主要分为“菜单栏”、“模拟手机客户端界面”、“模拟手机按键”三部分。

    2.5.2 菜单栏

    菜单栏包含:设置、编辑、工具、性能测试和帮助功能

    一、 “设置”部分主要进行三类设置

    1)        模拟器模拟的终端平台和型号等,比如:android的三星手机、iOS的iPad2平板等。

    2)        界面缩放。有时候模拟pad的设备,在PC机中会显示比较大,可以通过缩放功能对显示的界面进行等比缩放,方便开发查看和切换。

    3)        横竖屏切换。横竖屏切换是开发中常遇到的问题,对模拟器进行相应的切换设置可以及时看到效果。

    二、“编辑”部分可以对开发展示的界面进行截屏处理。

    三、“工具”部分可以启动简易抓包工具、JSON格式数据、打开当前显示页面的源码、复制当前页面地址以及打开PC模拟器的程序目录。

    四、“性能测试”部分可以对整个应用的运行性能进行测试,让开发人员了解运行的情况以进行应用的优化。

    五、“帮助”部分可以查看模拟器的版本号等信息。

    2.5.3 模拟器手机客户端界面

    手机客户端界面主要分四部分:演示中心、参数设置、进入基座和关于我们。

    一、演示中心:包含烽火星空公司的一系列开发模板、行业应用和成功案例,可以方便开发者或者项目经理对开发进行参考以及项目前期的演示等使用。

    二、参数设置:可以设置应用访问相关的一些信息,如:ExMobi服务端的IP、端口、页面动画效果的开关、缓存的设置等。MBuilder中的PC模拟器默认设置的IP和端口使用的是本机的环境。

    三、进入基座:该界面与开发者息息相关,在MBuilder中创建的应用经过同步或者应用的安装等操作后即可在该界面实时看到应用运行的效果,方便开发者开发调试。

    四、关于我们:在该界面中,主要可以查看模拟器的版本号、IMSI、ESN、模拟的分辨率等信息。

    2.5.4 模拟手机按键

    手机按键主要有:菜单键、home键、返回键。下图是模拟键和手机按键的对应关系:

    2.6 使用MBuilder创建第一个应用

    第一个应用很容易就让人想起“Hello World”,没错我们现在就通过创建一个“Hello World”的应用来展示一个“Hello World”的页面。

    2.6.1 点击新建应用按钮

    在MBuilder中点击新建应用按钮,如下图所示:

    点击后就会进到创建应用的导航页面。

    2.6.2 配置应用信息

    在创建应用的导航页面,填写应用信息,我们的应用名称为“helloworld”如下图所示:

    其中:

    ·  Project name 项目名;(必填项)

    ·  Application ID 应用ID,必须和项目名一致;(必填项)

    ·  Application Name 应用名。(必填项)

    ·  Application Version 版本号。(必填项)

    ·  Scope 应用支持的表现形式,是客户端方式的还是wap方式的,还是都支持;

    ·  Access 该应用是否需要网络(network)、gps定位(gps)、拍照(camera);

    ·  HomePage 该应用访问的第一个页面,可以是本地页面,也可以是网络地址。Res开头的为本地页面,http的地址是网络页面。这里使用的是本地页面。

    点击“next”按钮进到“设置应用图片”面板

    2.6.3 设置开发者信息

    在开发者信息设置面板可以填写一些开发者的基本信息,如下图所示:

    此步骤一般可以直接跳过。

    继续点击“next”按钮,可以进入皮肤模板选择面板。

    2.6.4 设置选择皮肤模板

    进入“皮肤模板选择面板”后,可以选择使用某个模板的某个皮肤,如下图所示:

    本次创建不使用模板,后面的章节中我们会单独对模板的开发和使用进行介绍。

    所以该面板我们选择结果如下:

    不选择模板则所有的模板选项均不可选择。

    点击“finish”即可完成应用的创建。

    2.6.5 应用同步和启动Tomcat

    应用创建完成后,打开PC模拟器基座客户端,点击“进入基座”,这时候会发现创建的应用并没有在客户端中显示,如下图所示:

    可以看到界面中左上角和右上角都有一个操作按钮。

    其中左上角的按钮为“应用管理”,点击进去可以看到存在的应用列表;右上角的按钮为“更多选项”,点击进入可以对基座的一些信息和参数进行设置和读取。

    点击左上角的按钮,可以看到提示无应用,如下图所示:

    为什么我们创建好了应用,但是在应用列表中却看不到应用?

    这是因为应用管理是客户端向服务端拉取应用列表。而应用创建于MBuilder的工作空间中,并没有同步部署到ExMobi服务端,所以这时候在客户端是请求不到应用的。

    这时候我们就需要做一件事情,那就是“应用自动同步”。

    在MBuilder中首先选中要同步的应用,这里选择“helloworld”应用跟目录,然后点击快捷菜单中的“自动同步”按钮,如下图所示:

    将“不同步”状态改成“自动同步”状态,如下图所示:

    退出基座界面到客户端首页,重新点击“进入基座”,这时候就可以看到新创建的应用已经同步到客户端,如下图所示:

    如果继续点击左上角的“应用管理”按钮,可以对已经同步的应用进行卸载和安装,如下图所示:

    然后点击“启动Tomcat”的图标把Tomcat启动好。

    2.6.6 应用效果查看

    当点击应用的“进入”按钮的时候,会提示“链接文件不存在”,如下图所示:

    图2-6-6-1

    为什么会报这个错误,这里先卖一个关子。我们将通过下一节2.7的内容进行讲解。

    2.7 认识应用结构

    在上一节中,我们看到图2-6-6-1中的报错,那是因为点击“进入”按钮,实际上是请求应用的首个页面,这个提示的意思就是说首个页面的地址文件不存在。

    这就需要先来了解应用的结构。如下图所示是一个应用的基本结构:

    应用根目录下有一个文件(config.xml)和两个文件夹(server、client)。

    2.7.1 config.xml基本信息配置

    config.xml文件为应用的基本信息配置文件,使用MBuilder默认生成的配置内容和说明如下:

    这里需要注意的是,homepage项即为应用的入口节点,它支持本地文件(res:开头)也支持网络地址(http://开头)。当点击应用图标,也就是图2-6-6-1中点击”进入”按钮的时候会触发该首页地址。页面上提示“链接文件不存在”意思就是说我们设置的“res:page/index.xhtml”本地页面不存在,因为我们还没有创建该文件。

    除此之外还有几个特殊配置项:

    配置项

    功能

    说明

    access.land

    横竖屏切换

    (重力感应)

    取值为boolean值,false(默认值)代表强制竖屏,true代表横竖屏自动切换。

    access.orientation

    默认显示方向

    为应用默认显示的方向,有两个取值——port:设置应用为竖屏模式;land:设置应用为横屏模式;padland_phoneport:设置PAD为横屏模式,phone为竖屏模式;padport_phoneland:设置PAD为竖屏模式;phone为横屏模式。

    config.theme

    皮肤设置

    取值为对应皮肤包的id。

    homepage.defaultsrc

    网络连接错误的处理页面

    只支持本地静态页面,如果不设置,则跳转到基座的设置页面。

     

    2.7.2 client客户端资源文件夹

    client文件夹下包含page、css、script、theme等子目录,存放的都是客户端资源。所谓客户端资源就是运行在客户端的文件。

    由于不同终端存在分辨率不同的问题,所以在client的子目录下都区分了phone和pad目录,再往下又分为default(默认资源)、ldpi(低分辨率资源)、mdpi(中分辨率资源)、hdpi(高分辨率资源)、xhdpi(超高分辨率资源),以image目录为例,如下图所示:

    对于DPI的划分,可以参考附录14.1。

    这时候客户端读取的资源就是client/image/phone/hdpi目录下的资源。到ExMobi的PC模拟器的apps目录,找到应用image的目录地址,比如:C:\developer\MBuilder\ExMobi\apps\helloworld\image,可以看到如下图片:

     

    可以看到,同步到客户端的资源都没有了分辨率的信息。

    其实,当打开PC模拟器的时候,在模拟器标题栏可以看到如下信息:

    从右往左看,它表明的是当前模拟器模拟的是终端类型为phone,DPI为hdpi。

    客户端使用什么资源就是根据客户端的信息来读取相应的资源的。

    如果客户端要使用logo.png这个图片,中间的分辨率信息是不需要写的,也就是说正确的引用方法为res:image/logo.png即可引用logo.png图片。

    需要特别说明default目录。该目录是公共文件目录,也就是说不管是哪个DPI的客户端都可以使用default目录的资源。Default目录的资源跟DPI下的资源一样也会同步到image目录下,不含有default目录本身。所以如果default目录和DPI目录如有有相同名字的资源,那么DPI下的资源会覆盖default下的资源。

    所以可以总结客户端资源使用的规则为:客户端使用的资源根据客户端的类型(phone、pad)和DPI信息(ldpi、mdpi、hdpi、xhdpi)找到对应的dpi资源,同时也会去找default下的资源,如果DPI下有和default下同名的资源,那么使用的将会是DPI目录下的资源。

    根据本节前面的提示信息“链接文件不存在”确定是因为尚未创建首页地址“res:page/index.xhtml”,所以接下来我们可以在client/page/phone目录下的default目录或者hdpi目录下创建index.xhtml文件。在default目录点击右键,如下图所示:

    即可打开创建新XHTML页面的面板,如下图所示:

    修改文件名为“index.xhtml”,然后点击“finish”即可创建完毕页面,并在MBuilder中打开默认页面,如下图所示:

    再次点击“进入”应用就不会报错,并且显示该页面信息,如下图所示:

    在开发的过程中,可以使用这些可以允许的客户端资源来进行展示和脚本操作。

    2.7.3 server服务端资源文件夹

    server文件夹用于存放在服务端执行的文件。其目录结构如下:

    其中,JSP文件夹用于存放JSP文件;xsl文件夹用于存放xsl文件;mapp.xml文件为服务端的规则配置文件,是一个统一的全局配置。

    mapp.xml配置文件其功能主要有:

    1)        配置处理第三方系统的JSP文件。

    2)        设置第三方系统的伪域名。

    3)        Push推送频道的配置。可执行Push消息的推送。

    4)        数据库资源配置。可以指定多个数据源。可在JSP中使用配置好的数据源。

    5)        定时器配置(定时执行某个请求)。

    6)        多媒体类型映射(为特殊contenttype指定文档格式,如doc、ppt等)。

    7)        Session类型设置(默认是有session限制,如果为新浪网等新闻类系统可以设置无session限制)。

    8)        文档预览和下载缓存配置。可以设置缓存的天数。

    其常用配置如下表:

    英文名称

    中文名称

    描述

    <maxml/>

    根元素

    用于标识符合MAXML规范的xml文件

    <route/>

    路由元素

    用来表明路由节点信息元素, 其baseaddr属性为第三方系统的域名/ip地址,该域名可以为伪域名

    <forward/>

    _PAGE元素转发元素

    用来对每个请求处理进行转发元素,通过pattern属性正则匹配第三方系统的地址,然后转向path属性设置的JSP进行处理。

    <config/>

    配置元素

    应用相关配置元素

    <domain/>

    应用伪域名元素

    用于应用伪域名配置,address属性指定第三方系统的真实域名/ip,name属性指定伪域名。通过配置,整个应用不管是xhtml、jsp还是mapp.xml的route配置中都可以使用伪域名。

    <database/>

    数据源元素

    数据库的数据源配置,用于在JSP中作为数据源直接调用。

    <pushchannels/>

    推送元素

    用来表明多组推送频道元素,其authpage属性

    <pushchannel/>

    推送频道元素

    用来设置一个推送频道的处理策略,必须包含一个id作为频道的唯一标识,path为轮循推送必须的处理JSP,并且needsubscribed指明是否为订阅频道,同时需要设置corn子标签指明推送的周期间隔。

    <services/>

    接口元素

    用来表明多组接口服务元素。

    <http-service/>

    http接口服务元素

    提供第三方系统调用的http接口服务,必须配置pattern属性指明访问路径,配置path路径指明中间件对接收到的数据的处理JSP页面。

    一个mapp.xml最基本的内容一般为:

    <maxml version="2.0" xmlns="http://www.nj.fiberhome.com.cn/map"

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

        xsi:schemaLocation="http://www.nj.fiberhome.com.cn/map maxml-2.0.xsd">

        <route baseaddr="http://domain">

            <!-- 登陆页面get -->

            <forward pattern="/app/template/login.jsp" path="login.jsp"/>

            <!-- 登陆校验post带键值对 -->

            <forward pattern="/app/template/checkLogin.jsp" path="checkLogin.jsp"/>

            <!-- 新建任务get -->

            <forward pattern="/app/template/jsp/addTask.jsp" path="addTask.jsp"/>

            <!-- 保存任务post带附件 -->

            <forward pattern="/template/action/taskManagerAction.jsp\?handler=save" path="taskManager.jsp"/>

            <!-- 列表展示postXML请求体 -->

            <forward pattern="/app/template/action/taskManagerAction.jsp\?handler=list&amp;dataType=xml.*" path="taskManagerListXML.jsp"/>

            <!-- 列表展示postJSON请求体 -->

            <forward pattern="/app/template/action/taskManagerAction.jsp\?handler=list&amp;dataType=json.*" path="taskManagerListJSON.jsp"/>

        </route>

        <config>

            <!-- 为第三方系统的实际访问地址配置一个简写域名domain,以后所有请求前面部分都可以使用domain代替,routebaseaddr也可以写为domain -->

            <domain address="miap.cc:1001" name="domain"/>

        </config>

    </maxml>

    里面最常用的就是route请求路由配置。实际上,客户端的所有http请求并不是立即触发的,而仅仅是告诉服务端要触发http请求,真实的请求是在服务端的JSP里面触发的。那么客户端的http请求如何知道是哪个服务端的JSP来处理自己的请求?route配置其实就起到了桥梁的作用,通过route配置,可以为客户端的请求配置处理的JSP文件进行逻辑处理。

    在helloworld应用的index.xhtml中增加一个“ExMobi门户网站”的超链接:

     

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.nj.fiberhome.com.cn/exmobi.dtd">

    <html>

    <head>

    <meta charset="UTF-8"/>

    <title>Hello World</title>

    <script>

    <![CDATA[

     

    ]]>

    </script>

    </head>

    <body>

    <a href="http://www.exmobi.cn/">ExMobi门户网站</a>

    </body>

    </html>

    其效果如下:

    当点击该超链接的时候,并没有实际的去请求“ExMobi门户网站”,而是先在mapp的route里面去找是否有该请求对应的处理JSP。因为还没有配置,页面会报错,如下图所示:

    该错误信息可以在MBuilder的响应码查询中进行查看报错原因,如下图所示:

    途中提到的“需要配置添加相应的应用处理页面”指的就是服务端的处理JSP。也就是说还没在route中配置处理的JSP文件。

    现在可以在mapp.xml中配置route如下:

    <?xml version="1.0" encoding="UTF-8" ?>

    <maxml version="2.0" xmlns="http://www.nj.fiberhome.com.cn/map"

          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

          xsi:schemaLocation="http://www.nj.fiberhome.com.cn/map maxml-2.0.xsd">

      <config>

        <htmlformat wellformat="true" />

      </config>

     

      <route baseaddr="http://www.exmobi.cn">

           <forward pattern="/" path="index.jsp"/>

      </route>

    </maxml>

    这就指明了“http://www.exmobi.cn/”这个请求处理的JSP名为“index.jsp”。所以还需要在sever目录的JSP子目录中新建index.jsp,如下图所示:

    在弹出的新建面板中输入文件名,如下图所示:

    即可创建默认的JSP文件,如下图所示:

    <%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*"

     contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%>

    <%@ include file="/client/adapt.jsp"%>

    <!DOCTYPE html SYSTEM "http://www.nj.fiberhome.com.cn/exmobi.dtd">

    <html>

    <head>

    <meta charset="UTF-8"/>

    <title>Hello World</title>

    <script>

    <![CDATA[

     

    ]]>

    </script>

    </head>

    <body>

    This is the page content.

    </body>

    </html>

    重新点击客户端的“ExMobi门户网站”链接就可以看到一个新的页面,如下图所示:

     

    特别的,如果为第三方地址配置的JSP的文件名和第三方地址的文件名刚好一样,这时候是可以不用配置MAPP路由的,它会自动智能匹配处理的JSP文件。

    比如客户端发起如下几种格式的URL请求,他们的特点是名称都为action:

    http://domain/action.jsp

    http://domain/action.jsp?type=1

    http://domain/action.jsp?type=2

    http://domain/action.do?type=1

    http://domain/action.php?type=2

    http://domain/action?type=3

    如果在jsp目录新建一个文件action.jsp,由于其名称也是action,在不给上述URL配置MAPP路由的情况下,服务端会自动匹配该action.jsp进行处理。

    所以对于basic@fiberhome应用里的几个配置项:

    <!-- 登陆页面get -->

    <forward pattern="/app/template/login.jsp" path="login.jsp"/>

    <!-- 登陆校验post带键值对 -->

    <forward pattern="/app/template/checkLogin.jsp" path="checkLogin.jsp"/>

    <!-- 新建任务get -->

    <forward pattern="/app/template/jsp/addTask.jsp" path="addTask.jsp"/>

    其实是可以不用配置的,因为第三方系统的URL和处理的JSP名称一致。

    所以对于一些名称一样,参数不一样的第三方系统URL,最好配置的PATH不要以该URL的名称命名MAPP路由的JSP文件,这样可能导致逻辑混乱。

    3 准备工作

    3.1 集成开发环境检查

    以下各项请逐个检查:

    n  检查ExMobi集成开发环境MBuilder是否已经安装好。

    n  运行Tomcat看是否能正常启动。

    n  启动客户端PC模拟器,在“系统设置”中配置为本机IP和Tomcat运行的端口(8001),并点击“应用管理”菜单看是否能正常进入。

    注:没有安装、配置或者出现运行异常的请参照《MBuilder安装手册》。

    3.2 辅助开发工具介绍

    辅助开发工具主要是帮助开发人员在不写代码的情况来分析第三方系统的交互情况,并进行模拟和取值,方便在写代码前理顺思路。

    名称

    作用

    下载方式

    HTTPAnalyzerStdV2

    抓包和网络请求分析工具

     

    Regex Util

    正则校验工具

    已经集成在MBuilder中

    XMLSpy

    XPATH校验工具,SOAP模拟请求工具

     

    JMeter

    模拟http请求

     

     

    3.3 使用二次开发手册

    二次开发手册在MBuilder菜单中的“Help-》Help Contents”中,如下图所示:

    点击后的帮助界面有一些二次开发的相关文档,其中就包含二次开发手册:

    其中的“ExMobi二次开发手册”为客户端和服务端的API手册,开发者可以方便的查找API来实现不同的业务场景功能。

    3.4 基础知识准备

    使用ExMobi进行开发需要大致了解:HTML、JavaScript、CSS的基本语法;正则表达式的用法;XML的文档结构以及XPATH的基本语法;TCP/IP网络协议的原理和报文分析。


     

    第2篇ExMobi编程基础


     

     

    4 客户端XHTML编程

    4.1 XHTML概述

    XHTML是ExMobi客户端的标记语言。与W3C规范的XHTML不同的是,ExMobi的XHTML是对标准HTML的集成和扩展——继承适合在移动终端使用的控件,并扩展更多新的控件更方便移动应用的开发和移动终端的展示。

    后续提到的XHTML均指代ExMobi的XHTML。

    4.1.1 使用XHTML的好处

    移动应用开发涉及多平台开发,不同平台开发语言不同,所以能够统一、简单、快捷的进行开发是选择使用XHTML的最大原因。

    因为使用XHTML有诸多好处:

    1)        开发语言统一。XHTML只是一种描述性标记语言,不同平台的ExMobi的客户端都可以解析正确语法的XHTML。所以开发者只需要编写一套XHTML代码即可在不同平台上运行。

    2)        代码结构简单。XHTML代码结构采用的是XML结构,层次分明。

    3)        代码简洁,表达丰富。XHTML只需要给指定的标签设置不同的属性和样式即可展现丰富的界面效果。

    4)        可重用性好。XHTML由于其XML特性,故可以很方便的进行模板化以达到最大化的可重用性。

    5)        可扩展性好。XHTML继承自ExMobi强大的组件内核,可以很方便的进行扩展出更多原本没有的控件,根据不同的使用场景进行插件的定制开发。

    6)        纠错方便。XHTML代码必须符合ExMobi的规范,此规范是验证XHTML代码正确性的标准,在XHTML代码中只要声明使用此规范即可进行快速纠错。

     

    4.1.2 XHTML的基本结构

    XHTML遵循ExMobi的语法定义,并严格符合XML的语法规范。

    其基本结构如下:

    其中:

    1)        第1-2行是文档规范声明,意思是文档内容必须符合ExMobi语法规范。

    2)        第3行表示XHTML内容的开始,第18行表示XHTML内容的结束。

    3)        第4-12行是head头信息声明区域,可以设置字符编码、title标题信息、JS脚本和CSS样式表等。

    4)        第5行是声明页面编码。

    5)        第6行是设置title标题头的信息。

    6)        第7-11行是JS代码块,其中第9行为JS中的代码注释写法。

    7)        第13-17行为body区域,用于显示页面的主体内容,其中第14行是XHTML中的代码注释写法。

    除此之外,在XHTML中可以添加不同功能的控件、JS和CSS,后续章节中会有详细介绍。

    4.1.3 符合XML规范的XHTML

    XHTML是一种特殊的XML。所以,XML中的关键字需要被转义。XML中已经定义的转义字符包括:

    实体

    字符

    含义

    &lt;

    <

    小于号

    &gt;

    >

    大于号

    &amp;

    &

    &apos;

    单引号

    &quot;

    "

    双引号

     

    修改helloworld应用的index.xhtml页面如下所示:

    由于该页面中含有&没有转义为&amp;所以导致页面不符合XML,所以打开此页面会报错,如下图所示:

    他会明确告知是第14行错误,将&字符转义为&amp;后再看页面效果:

    CDATA块是XML中的特殊区块,CDATA中的内容不会被解析器解析,而是原样输出。所以一般JS的内容都是很不确定的,并且JS语法经常使用引号、大于小于号等XML的关键字,所以JS内容通常包在CDATA中,这些内容就不需要进行转义。

    但是JS中有一个很常用的功能——innerHTML,改属性是给控件设置内部HTML内容。在ExMobi中,作为显示的HTML片段如果包含XML的关键字也是需要转义的。

    而JS中如果是给某个控件赋值,比如obj.value = str;这样的赋值,如果value中包含XML的关键字是不需要转义的。

    所以,符合XML规范的XHTML应该具备如下特征:

    1)        xml中使用

    a)         text内

       ' 与 &apos;  均代表 '

       " 与 &quot; 均代表 "

       > 与 &gt;   均代表 >

       &amp;      代表 &,不支持直接放置 &

       &lt;        代表 <,不支持直接放置 <

      

    b)        属性内

       ' 与 &apos;  均代表 '

       > 与 &gt;   均代表 >

       &quot;      代表 "

       " 当属性通过 ‘   ‘ 包裹时,可直接放置 “,当属性通过 ” “包裹时,必须用&quot;

       &amp;        代表 &,不支持直接放置 &

       &lt;        代表 <,不支持直接放置 <

      

      

    2)        js中使用  注:为防止xml转义引起歧义,js语句必须被<![CDATA[   ]]>包裹

    a)         通过innerHTML构建控件,控件属性或者text中

                  &lt;       代表 <

                  &gt;       代表 >

                  &amp;      代表 &

                  &apos;     代表 '

                  &quot;     代表 "

                 

    如:

     

    function change(){

                                var ctrl = document.getElementById("mydiv1");

                                ctrl.innerHTML = "<textarea id=\"mytextarea1\">&lt;&gt;&apos;&quot;&amp;</textarea><div id=\"mydiv2\" href=\"&lt;&gt;&apos;&quot;&amp;\">&lt;&gt;&apos;&quot;&amp;</div>";

                                var ctrl2 = document.getElementById("mydiv2");

                                alert(ctrl2.href);

                    } 

     

       结果为:<>/"&

      

    b)        通过js设置控件属性

             &lt;      代表 &lt;

             &gt;      代表 &gt;

             &amp;     代表 &amp;

             &apos;    代表 &apos;

             &quot;    代表 &quot;

                 

    如:

    function change(){

                                var ctrl = document.getElementById("mytextarea1");

                                ctrl.value = "&lt;&gt;&apos;&quot;&amp;";

                    }

     

             设置后结果为:&lt;&gt;&apos;&quot;&amp;

            

                  \"        代表 "

                  \'        代表 ‘

                  &         代表 &

               <         代表 <

               >         代表 >

    如:

    function change(){

                                var ctrl = document.getElementById("mytextarea1");

                                ctrl.value = "\"\'&<>";

                    }

     

            设置后结果为:"'&<>

    4.1.4 XHTML文档类型的声明

    XHTML是一种语法规范,符合XHTML规范的文档称为XHTML文档。

    有两种方式声明XHTML文档类型:

    1)        静态文件后缀名为xhtml。该种方式仅针对静态文件,就是创建一个本地的页面,其后缀为xhtml格式。Helloworld应用中的首页地址index.xhtml就是一个xhtml文档。

    2)        动态文件声明响应content type为“application/uixml+xml”。动态文件是指JSP、PHP、.NET等语言开发的页面,而content-type是http协议中规定文档格式的头信息。如果这些页面是要在ExMobi客户端中进行显示,必须声明响应的content- type为“application/uixml+xml”。比如helloworld应用的index.jsp就声明了content-type,如下所示:

    <%@ page language="java" import="java.util.*,com.fiberhome.bcs.appprocess.common.util.*"

     contentType="application/uixml+xml; charset=UTF-8" pageEncoding="UTF-8"%>

    但是无论是哪种方式,一旦声明了文档类型为XHTML,内容则必须符合XHTML的规范,否则在ExMobi客户端中将无法进行正确解析和展示。

    4.2 XHTML与JS、CSS的关系

    JS是JavaScript的简称,是一种客户端脚本语言,ExMobi客户端可以执行XHTML页面中的JS;CSS是层级样式表,可以渲染XHTML页面的控件显示效果。

    4.2.1 JS的引用和使用

    JS有两种使用方式,一种是直接在XHTML中的script代码块中编写JS代码;另一种是把JS代码写在本地的JS文件中,通过script标签引用本地的JS文件即可使用。如下所示:

    注意:XHTML里的JS无法引用网络侧的JS文件。

    比如下面的引用是错误的:

     

    <script src="http://domain/script/index.js"></script>

     

     

    4.2.2 CSS的引用和使用

    CSS有三种使用方式,第一种是直接在XHTML中的style代码块中编写CSS代码;第二种是把CSS代码直接写在某个控件的style属性中;第三种把CSS代码写在本地的CSS文件中,然后通过link控件引用本地的CSS文件。如下所示:

    通过方法一和方法三使用CSS,只能通过类选择器和派生选择器进行引用。而且派生选择器只支持一级。

    上图方法一的就是类选择器的用法,即给控件设置class样式名,然后在创建一个以该名为引用的样式。

    派生选择器的用法为控件的名称即为样式的引用,比如要给一个a控件写样式可以这么写CSS:

    a{

         font-weight:bold;

    }

     

    4.3 XHTML与JSP的关系

    JSP是一种动态语言,属于服务端语言中的一种,并不直接运行在客户端。所以JSP的运行是在依赖于运行容器的,比如Tomcat。容器运行后会得到一个执行结果,执行结果可以返回给客户端进行操作。

    所以ExMobi中的JSP是运行在ExMobi的服务端的,其响应的内容文档可以是XHTML,也可以是XML、JSON等一些比较常用的数据格式,还可以是doc、ppt、jpg等office文档格式或者图片格式。

    通常,如果JSP响应结果直接作为页面展现,则需要输出为XHTML文档,这时候需要声明JSP的content-type为“application/uixml+xml”;如果只是作为数据需要进一步的处理,比如页面中发起的AJAX请求的JSP响应可以输出为XML或者JSON等方便JS操作的格式,同时也需要把content-type设置为相应数据格式的类型,比如JSON数据的头信息为“application/json”。

    4.4 XHTML控件分类

    XHTML是由不同控件组合而成,这些控件主要分为6大类:语义控件、顶级容器控件、布局控件、导航控件、表单控件和多媒体控件。

    4.4.1 语义控件

    所谓语义控件,就是指只有一定的含义,但是不会在客户端中进行展现的控件。比如:html、script、link、meta、style、head等控件都是语义控件。

    其中html控件为XHTML文档的根容器控件,一个XHTML文档以html控件为开始。

    4.4.2 顶级容器控件

    顶级容器控件是XHTML中的主容器控件,这些控件都只能位于html根容器之下,并且互相不能嵌套。

    顶级容器控件包括:body(内容主体容器)、dialog(对话框容器)、fix(绝对定位容器)、footer(固定底部容器)、header(固定顶部容器)、leftcontainer(左侧区域容器)、rightcontainer(右侧区域容器)、title(标题区域容器)。

    其中,dialog和fix容器是浮动在其他容器之上的,不会占用其他容器的区域;而剩下的其他6个容器的区域共同铺满整个XHTML的屏幕页面,并且如果其中一个不使用或者不显示,其所占区域会让渡给其他的容器使用,其他容器仍然铺满整个XHTML的屏幕页面。

    可以通过如下代码:

    <html>

    <head>

    <meta charset="UTF-8"/>

    <title>title区域</title>

    </head>

    <header id="header" style="background-color:#aaaaaa">

       header是固定在头部的容器,不出现滚动条

    </header>

     

    <leftcontainer id="lefter" style="background-color:#bbbbbb;width:70;">

       <div style="height:1001;">

          leftcontainer是固定在左边的容器,内容超出会显示滚动条

       </div>

       下面没有内容了

    </leftcontainer>

     

    <body style="background-color:#cccccc">

       <div style="height:1001;">

          body中的内容,超过会有滚动条。必须显示,不能隐藏。

          <a href="document.getElementById('header').style.display='none'">隐藏header</a>

          <br/>

          <a href="document.getElementById('footer').style.display='none'">隐藏footer</a>

          <br/>

          <a href="document.getElementById('lefter').style.display='none'">隐藏leftcontainer</a>

          <br/>

          <a href="document.getElementById('righter').style.display='none'">隐藏rightcontainer</a>

          <br/>

          <a href="document.getElementById('fixer').style.display='none'">隐藏fix</a>

          <br/>

          可以看到,除了header、footer、leftcontainerrightcontainer以外剩下的区域都属于body,并且不受fix的影响,所以,如果这些容器不写,则body的空间就会更大。

       </div>

       下面没有内容了

    </body>

     

    <fixset>

       <fix id="fixer" style="top:70%;left:0;background-color:#ffffff;">

          fixset可以包含多组fix容器,fix是绝对定位的容器,悬浮于窗口之上,所以可以看到其内容可以横跨<br/>

          header、footer、body、leftcontainer、rightcontainer<br/>

          可以设置其left或者right之一以及top或者bottom之一来实现绝对定位

       </fix>

    </fixset>

     

    <rightcontainer id="righter" style="background-color:#dddddd;width:70;">

       <div style="height:1001;">

          rightcontainer是固定在右边的容器,内容超出会显示滚动条

       </div>

       下面没有内容了

    </rightcontainer>

     

    <footer id="footer" style="background-color:#eeeeee;">

       footer是固定在底部的容器,不出现滚动条

    </footer>

    </html>

    其效果如下:

    将header、footer、leftcontainer隐藏后的效果:

    继续隐藏rightcontainer和fixset后的效果:

    可以看到原来header、footer、leftcontainer、rightcontainer的区域都给了body,而fixset只是隐藏对body没有影响。

    title区域在页面中不能改变显隐状态,默认是显示的,如果需要隐藏可以给title控件加属性show="false"来隐藏。上面代码设置后的显示效果如下:

    4.4.3 布局控件

    布局控件提供不同粒度的控件来对页面布局提供强有力的保证。

    独立布局控件为最小粒度的布局控件,它们只能对自身的布局进行控制。如:a、artfont(艺术字)、br、font、hr等。

    整体布局控件本身是具有固定结构的一个整体,它可以对内部的特定元素进行布局。如:table、tree(树控件)、htmlgroup(页面组)等。

    局部布局控件可以针对部分的其他控件进行布局调整,它不想整体布局控件那样有固定结构。比如:div、page(页容器)、scroll、slidingcontainer、h(横向布局)、v(纵向布局)等。

    实际上,除了布局控件外,其他控件也都可以对自身进行布局。只是布局控件的作用是以布局为主。

     

    4.4.4 导航控件

    导航控件顾名思义就是做引导用的,方便用户能够知道如何进行操作、引导用户操作等。

    导航类控件主要通过简单的标题和图标指示用户的操作。比如:list(动态列表)、listitem(单行、双行列表)、titlebar(标题栏,可代替title控件)等。

    菜单类控件通常是提供一组相似功能的菜单给用户选择使用。比如:grid(九宫格)、animationmenu(动画菜单)、contextmenu(弹出菜单)、menubar(底部导航菜单)、tabbar(顶部导航菜单)等。

    引导类控件通过一些简单的动作引导用户操作。比如:dragrefresh(拉拽刷新)、marquee(跑马灯)、toggle(显隐组)等。

    4.4.5 表单控件

    表单控件主要用于表单页面的展现以及数据的提交,可以提交值的控件都有value属性。

    要作为表单提交的两个必要条件为:

    1)        要作为表单提交的表单控件必须放置于form控件中;

    2)        form控件的enctype属性必须为: application/x-www-form-urlencode(普通提交方式)或者multipart/form-data(带有附件的提交方式),不写则默认为application/x-www-form-urlencode。

    部分控件在form表单中的提交值如下:

    控件标签

    控件提交值

    <eselect>可编辑选择框

    编辑框value值

    <handsign>手写签名

    手写生成文件路径(sys 开头)

    <input:autocompletetext>记忆框

    编辑框value值

    <input:camera>拍照摄像

    拍照生成文件路径(sys: 开头)

    <input:checkbox>复选框

    相同name值value组成字符串,以&连接 如 奥迪&奔驰

    <input:decode>动态解码

    编辑框内解码结果

    <input:file>文件选择框

    已选中文件路径(sys: 开头)

    <input:password>密码框

    编辑框内密码值

    <input:radio>单选框

    单选框控件value值

    <input:text>输入框

    编辑框内输入值

    <object:date>日期选择框

    已选中日期值

    <object:time>时间选择框

    已选中时间值

    <select>选择框

    已选中option项value值

    <textarea>多行文本域

    textarea输入值

    <handwriting>增强手写

    手写完成后生成图片文件的完整路径(sys:开头)

    <photoupload>增强型拍照控件

    手写完成后生成图片文件的完整路径(sys:开头),多个文件以&连接

    <input:record>录音

    录音完成后生成的录音文件路径(sys:开头)

    <switch>开关

    value值

    可以看出来,部分表单控件具备了调用本地能力的功能,比如:input:camera可以调用摄像头、input:record可以调用录音功能等。

    4.4.6 多媒体控件

    所谓多媒体控件是指使用了多种元素进行展现的控件。比较常用的多媒体控件有:

    控件标签

    说明

    <baidumap>

    百度地图

    <browser>

    浏览器控件

    <fileset>

    附件集,进行附件的预览、下载、打开、签批等

    <gaodemap>

    高德地图

    <img>

    图片控件

    <photoupload>

    增强型拍照控件

     

    4.5 XHTML常用界面展现

    4.5.1 基本文字展现

    中间件客户端支持对文字多样性的展现,比如:文字的大小、颜色、样式等,也能给文字添加超链接,使文字能够与其他页面进行交互。如下面的页面效果:

    其代码实现如下:

    <html>

    <head>

    <meta charset="UTF-8"/>

    <title>基本文字展现</title>

    </head>

    <body>

    <br/>

    <font>一般用font修饰字体,这是没有经过修饰的字体</font>

    <br/>

    <font style="font-size:large;">将文字变大</font>

    <br/>

    <font style="font-size:small;">将文字变小</font>

    <br/>

    <font style="color:#ff0000;">给文字添加颜色</font>

    <br/>

    <font style="font-weight:bold;">将文字加粗</font>

    <br/>

    <font style="font-style:italic;">将文字显示为斜体</font>

    <br/>

    <font style="text-decoration:underline;">给文字添加下划线</font>

    <br/>

    <font style="font-size:small;color:#00ff00;font-weight:bold;font-style:italic;text-decoration:underline;">综合使用样式的文字,可以任意组合</font>

    <br/>

    <div style="font-size:large;color:#0000ff;font-style:italic;">很多控件可以修饰文字,这是在div中修饰文字</div>

    <br/>

    <br/>

    <a href="res:page/interactive.xhtml">给文字添加超链接,不加样式</a>

    <a href="res:page/layout.xhtml" style="color:#ff0000;text-decoration:none;">这是一个超链接,去掉了下划线并设置了颜色</a>

    </body>

    </html>

     

    4.5.2 页面跳转和超链接

    页面跳转可以通过超链接来实现,能够通过超链接打开的页面类型有两种:

    页面类型

    地址特征

    示例

    应用中的本地文件

    res:开头

    <a href="res:page/index.xhtml">打开一个本地页面</a>

    网络地址

    http://开头

    <a href="http://www.exmobi.cn">合作伙伴门户</a>

    超链接通常搭配target属性使用,指定为新打开一个页面(_blank)还是在本页面中打开(_self)。

    任何页面类型都可以通过以下方式触发:

    触发方式

    示例

    href

    <div href="http://www.nj.fiberhome.com.cn">烽火星空门户网站</div>

    onclick

    <input type="button" value="请点击" οnclick="res:page/index.xhtml"/>

    JS

    window.open("res:page/index.xhtml");

    任何具有href、onclick的控件都能触发超链接,而且超链接除了可以打开页面,也可以进行其他的交互,如下表:

    交互类型

    特征

    示例

    应用资源

    res:开头

    <img src="res:image/logo.png"/>

    <script src="res:script/exmobijs/base.js" />

    <a href="res:page/index.xhtml">打开一个本地页面</a>

    客户端资源

    sys:开头

    <a href="sys:data/sys/help.xhtml">客户端帮助页面</a>

    终端资源

    file:开头

    <a href="file:SD/text.txt ">安卓系统文件</a>

    网络资源

    http://开头

    <a href="http://www.exmobi.cn">合作伙伴门户</a>

    FTP资源

    ftp://开头

    <a href="ftp://admin:111@192.168.100.231/home/gaea/ ">可以指定FTP账号密码ip目录等信息</a>

    JS函数

    JS语法

    <a href="window.open('res:index.xhtml')">用JS打开一个页面</a>

    拨打电话

    tel:开头

    <a href="tel:17584068201">拨打电话</a>

    发送短信

    sendsms:开头

    <a href="sendsms:17584068201,13813900000:true:节日快乐">短信群发</a>

    调用手机程序打开文件

    open:开头

    <a href="open:file:SD/ppt培训.pdf ">打开安卓文件 </a>

    <a href="open:http://192.168.100.131:8001/Test/file/mp4/cat.mp4 " target="_blank " >cat.mp4播放</a>

    调用浏览器

    browser:开头

    <a href="browser:http://wap.sohu.com">进入WAP主页</a>

    下载文件

    download@http://开头

    <a href="download@http://192.168.1.doc/test/tywt.doc">下载天下无天.doc</a>

    <a href="download@http://192.168.1.doc/test/tywt.doc">下载天下无天.doc</a>

    内置脚本

    script:开头

    <a href="script:close">返回上一页</a>

    <a href="script:exit">退出客户端</a>

     

    4.5.3 基本布局

    ExMobi采用的是流式布局的原则对界面控件进行布局。所谓流式布局是指在容器(区域控件和布局控件)中,从左到右、至上而下对该容器里面的控件进行布局,当一行不能容纳的时候自动换行。

    横向布局可以设置容器控件(如:div、font等)的text-align属性来指定子控件的布局,也可以使用控件本身的align属性指定本控件在父容器中的布局,他们的属性值是一样。比如:center为居中、left停靠左侧、right停靠右侧显示。

    纵向布局只能通过div的text-valign属性来指定子控件的布局,比如:top为停靠容器顶部、middle垂直居中、bottom停靠容器底部显示。也可以通过br、hr对内容进行换行和分隔。

    内外边距可以使容器和控件间产生一些间距。其中外边距margin可以使容器或者控件与其他容器控件产生间距;内边距padding可以使容器和它内部的容器或者控件以及文本产生间距。也可以给控件添加border边框,并设置边框的弧度。

    需要注意的是,控件的宽度width和高度height已经包含了margin、padding和border的大小。而一般控件的margin、padding和border都会有默认值。所以实际显示的控件大小应该是宽度或者高度减去margin、padding和border后剩下的大小。

    比如下面代码:

    <html>

    <head>

    <meta charset="UTF-8"/>

    <title>基本布局</title>

    </head>

    <body>

    <font style="color:#ff0000">横向布局</font><font>:给div设置text-align(对子容器有效)和给button设置align(对自身有效)</font>

    <div style="border-size:1dp;padding:4 4 4 4;text-align:center">

       <input type="button" value="div设置text-align:center"/>

    </div>

    <div style="border-size:1dp;padding:4 4 4 4; ">

       <input type="button" value="button设置align:center" style=" align:center;"/>

    </div>

    <font>下面的两个div在一行显示,各占50%,各div内部左边的内容占40%,右边的输入框占60%,并且div设置了外边距(margin),让div之间有间隔:</font>

    <div style="width:50%;border-size:1dp;margin:0 0 0 2%">

       <font style="width:30%;text-align:right;">姓名</font>

       <input type="text" style="width:70%"/>

       <font style="width:30%;text-align:right;">性别</font>

       <input type="text" style="width:70%"/>

    </div>

    <div style="width:50%;border-size:1dp;margin:0 0 0 2%">

       <font style="width:30%;text-align:right;">班级</font>

       <input type="text" name="class" style="width:70%"/>

       <font style="width:30%;text-align:right;">年级</font>

       <input type="text" name="grade" style="width:70%"/>

    </div>

    <font style="color:#ff0000">纵向布局</font><font>div设置text-valign(对子容器有效):</font>

    <div style="border-size:1dp;height:100;text-valign:middle;">

       <font>文字</font><input type="text" style="width:40%/>

    </div>

    <font>下面的的div设置了上下内边距(padding)都为30,也可以达到垂直居中的效果:</font>

    <div style="border-size:1dp;padding:30 0 30 0;">

       这里的文字跟div有上下间距

    </div>

    </body>

    </html>

    实现效果如下:

    除了使用div布局,还有其他框架布局控件:page(页容器)、scroll(滚动容器)、 slidingcontainer(左右滑动容器)、table(表格容器)、tree(树容器)。

    布局的基本原则是:

    1)        框架布局控件多用于构建一个页面的框架结构;

    2)        其他布局控件(如:div、font等)则是在进行微调的时候使用。

    3)        应尽量避免使用div来实现大量布局和嵌套使用以提升界面显示的效率。

    4)        表单控件具有布局能力。表单控件本身能布局的不要使用布局控件代替。

    4.5.4 使用导航

    常见的导航一般有位于底部的菜单导航(menubar)、九宫格导航(grid)、列表导航(listitem)、分页、位于顶部的tab导航(tabbar)。

    通过下面代码认识它们:

    <html>

    <head>

    <meta charset="UTF-8"/>

    <title>导航页面</title>

    <style>

    .tabbar{

       height:40;

       border-size:1;

       padding:8 4 0 4;

    }

    .tab{

       height:40;

       width:33%;

       border-size:1;

       border-radius:8;

       padding:4 4 4 4;

       margin:0 4 0 4;

       text-align:center;

       text-valign:middle;

       background-click-color:#dddddd;

    }

    .current{

       background-color:#cccccc;

    }

    </style>

    </head>

    <header>

     

       <tabbar showtype="text">

           <tab text="tab1" selected="true" bindpage="page1"/>

            <tab text="tab2" bindpage="page2"/>

            <tab text="tab3" bindpage="page3" />

     

        </tabbar>

     

    </header>

    <body>

    <page id="page1">

       <grid style="color:black;click-color:#ff8800;color-current:#0000ff;cell-background-click-color:#cccccc;cell-background-radius:4;">

          <cell text="列表控件" href="res:page/listitem.xhtml" icon="res:image/icon_man.png" />

          <cell text="拖动列表" href="res:page/list.xhtml" icon="res:image/icon_man.png" />

          <cell text="功能模块" icon="res:image/icon_man.png" />

          <cell text="功能模块" icon="res:image/icon_man.png" />

          <cell text="功能模块" icon="res:image/icon_man.png" />

          <cell text="功能模块" icon="res:image/icon_man.png" />

          <cell text="功能模块" icon="res:image/icon_man.png" />

          <cell text="功能模块" icon="res:image/icon_man.png" />

          <cell text="功能模块" icon="res:image/icon_man.png" />

          <cell text="功能模块" icon="res:image/icon_man.png" />

          <cell text="功能模块" icon="res:image/icon_man.png" />

          <cell text="功能模块" icon="res:image/icon_man.png" />

       </grid>

    </page>

    <page id="page2">

       这是第二页的内容

    </page>

    <page id="page3">

       这是第三页的内容

    </page>

    </body>

    <footer>

    <menubar showtype="mix" style="menu-background-current-color:#000000;">

       <menu text="待办" bindpage="page1" icon="res:image/menubar-icon-daiban.png" currenticon="res:image/menubar-icon-daiban.png"/>

       <menu text="新建" bindpage="page2" icon="res:image/menubar-icon-xinjian.png" currenticon="res:image/menubar-icon-xinjian.png"/>

       <menu text="通讯录bindpage="page3" icon="res:image/menubar-icon-tongxunlu.png" currenticon="res:image/menubar-icon-tongxunlu.png"/>

       <menu text="公告icon="res:image/menubar-icon-tongzhigonggao.png" currenticon="res:image/menubar-icon-tongzhigonggao.png"/>

       <menu text="更多" icon="res:image/menubar-icon-gengduo.png"&