cocos2d-x: 死磕"HelloWorld"(2)——应用实例的创建

该篇分析应用实例的创建(构造),并解决第一篇中的前两个疑问,即 Q1, app对象建立之后去哪了?Q2, 最后是什么对象在run?我们先来看看AppDelegate类的头文件:

AppDelegate.h

#ifndef  _APP_DELEGATE_H_
#define  _APP_DELEGATE_H_

#include "cocos2d.h"

/**
@brief    The cocos2d Application.

The reason for implement as private inheritance is to hide some interface call by CCDirector.
*/
class  AppDelegate : private cocos2d::CCApplication
{
public:
    AppDelegate();
    virtual ~AppDelegate();

    /**
    @brief    Implement CCDirector and CCScene init code here.
    @return true    Initialize success, app continue.
    @return false   Initialize failed, app terminate.
    */
    virtual bool applicationDidFinishLaunching();

    /**
    @brief  The function be called when the application enter background
    @param  the pointer of the application
    */
    virtual void applicationDidEnterBackground();

    /**
    @brief  The function be called when the application enter foreground
    @param  the pointer of the application
    */
    virtual void applicationWillEnterForeground();
};

#endif // _APP_DELEGATE_H_

可见AppDelegate类私有继承了CCApplication类。除了构造和析构函数外,只有三个成员函数,它们分别与应用的初始化、进入后台以及恢复到前台有关。我们再去基类CCApplication的头文件里看看

CCApplication.h

#ifndef __CC_APPLICATION_WIN32_H__
#define __CC_APPLICATION_WIN32_H__

#include "CCStdC.h"
#include "platform/CCCommon.h"
#include "platform/CCApplicationProtocol.h"
#include <string>

NS_CC_BEGIN

class CCRect;

class CC_DLL CCApplication : public CCApplicationProtocol
{
public:
    CCApplication();
    virtual ~CCApplication();

    /**
    @brief    Run the message loop.
    */
    virtual int run();

    /**
    @brief    Get current applicaiton instance.
    @return Current application instance pointer.
    */
    static CCApplication* sharedApplication();

    /* override functions */
    virtual void setAnimationInterval(double interval);
    virtual ccLanguageType getCurrentLanguage();
    
    /**
     @brief Get target platform
     */
    virtual TargetPlatform getTargetPlatform();

    /**
     *  Sets the Resource root path.
     *  @deprecated Please use CCFileUtils::sharedFileUtils()->setSearchPaths() instead.
     */
    CC_DEPRECATED_ATTRIBUTE void setResourceRootPath(const std::string& rootResDir);

    /** 
     *  Gets the Resource root path.
     *  @deprecated Please use CCFileUtils::sharedFileUtils()->getSearchPaths() instead. 
     */
    CC_DEPRECATED_ATTRIBUTE const std::string& getResourceRootPath(void);

    void setStartupScriptFilename(const std::string& startupScriptFile);

    const std::string& getStartupScriptFilename(void)
    {
        return m_startupScriptFilename;
    }

protected:
    HINSTANCE           m_hInstance;
    HACCEL              m_hAccelTable;
    LARGE_INTEGER       m_nAnimationInterval;
    std::string         m_resourceRootPath;
    std::string         m_startupScriptFilename;

    static CCApplication * sm_pSharedApplication;
};

NS_CC_END

#endif    // __CC_APPLICATION_WIN32_H__

在该头文件里我们找到了两个熟悉的身影,run()和sharedApplication(),正如我们所尿,后者是静态函数,并返回一个CCApplication类指针。前者run()是个虚函数,这是为了方便派生类重载该函数(但是AppDelegate并未重载该函数)。既然CCApplication被AppDelegate继承了,那么CCApplication里的所有函数都能被AppDelegate调用,换句话说AppDelegate类就是CCApplication类,只是多了几个成员函数。当创建AppDelegate对象时,会自动调用基类CCApplication的构造函数。我们现在就来看一下CCApplication类的构造函数(AppDelegate类自身只有一个空的无参构造函数,等价于默认构造函数)

CCApplication.cpp

// sharedApplication pointer
CCApplication * CCApplication::sm_pSharedApplication = 0;

CCApplication::CCApplication()
: m_hInstance(NULL)
, m_hAccelTable(NULL)
{
    m_hInstance    = GetModuleHandle(NULL);
    m_nAnimationInterval.QuadPart = 0;
    CC_ASSERT(! sm_pSharedApplication);
    sm_pSharedApplication = this;
}

我们特意把一个静态成员变量sm_pSharedApplication的定义也包含了进来,因为构造函数里将对该变量进行赋值。该静态成员变量是一CCApplication类指针(时刻记住AppDelegate类也是CCApplication类),初始值为零(静态成员数据的初始化在程序一开始就完成,也就是说在构造任何对象之前便完成了)。该构造函数还以冒号(初始化列表)形式调用了成员数据m_hInstance和m_hAccelTable的构造函数。望文生义,这两个成员数据分别是实例句柄和加速键表句柄(加速键即热键,请百度或谷歌)。构造函数的第一步就是获取一个模块句柄传递给m_hInstance,然后初始化另一成员数据m_nAnimationInterval(望文生义,动画帧间隔。它是大整数类型,用于设定每一帧之间时间间隔。将在第三节run()中用到)。接下来是关键,用了一个断言(CC_ASSERT是assert的宏),假如sm_pSharedApplication为零,则将当前(构造中的)对象的指针传递给sm_pShareApplication,否则报错。这是一种非常重要的单例模式(是时候学一点设计模式了),也就是说最多只能创建一个实例,创建第二个的时候,sm_pSharedApplication已经不为零,因此会报错。这保证了一个应用有且仅有一个代理。至此,我们知道了入口函数中创建app对象时,至少干了一件事,就是把app对象的指针传递给了基类的一个静态成员数据。这个传递过程其实是基类本身利用了构造函数和继承关系主动实现的,而非派生类AppDelegate硬塞给基类的。

(经验小结3,以m开头的是成员数据,以s开头的是静态变量,以sm开头的是静态成员数据)

下面我们再来看一下入口函数最后一行里的sharedApplication()。我们已经知道它返回的肯定是个指针,但是指向谁呢?转到定义看看

CCApplication.cpp

CCApplication* CCApplication::sharedApplication()
{
    CC_ASSERT(sm_pSharedApplication);
    return sm_pSharedApplication;
}

相当简单明了,只有一个断言,假如sm_pSharedApplication为真(不为零即为真),则返回指针sm_pSharedApplication,否则报错。这下是否有种恍然大悟的赶脚?前面创建app对象时传递给基类的指针在这里派上了用场。假如前面没有创建app对象,那么sm_pSharedApplication为零,程序运行到sharedApplication()时会断言报错(sharedApplication不仅在最后一行出现,而是在sharedOpenGLView()里就有出现,可以通过设置断点来验证)。

至此,我们的头两个疑问已经基本解决。它们的答案互为补充。一开始建立的app对象正是后面run的对象。什么是对象?对象在内存里承载的是其成员数据,创建一个对象就是分配内存给对象的成员数据(静态数据除外)。而对象的成员函数(在程序的一开始就存在于内存的代码区)正是对这些成员数据进行操作。一般情况下,为了维持封装性,一个对象的成员函数不会直接操作另一个对象的成员数据,但是可以调用另一对象的公有成员函数。我们现在就再来回顾一下,app这个对象里有些什么成员数据。我们发现AppDelegate类里只有成员函数,而没有成员数据,所以app对象只继承了基类CCApplication里的成员数据,其中有一个静态成员数据,还有五个普通成员数据。静态成员数据已经分析过,就是用来传递对象指针的,也就是说用来指向其他五个成员数据的(非严谨说法,因为对象还包括成员数据与成员函数之间的耦合)。这五个成员数据分别是m_hInstance实例句柄,m_hAccelTable加速键表句柄,m_nAnimationInterval动画帧间隔,m_resourceRootPath资源根目录,m_startupScriptFilename启动脚本文件名。这几个成员数据主要用于应用和外界之间的信息交流,因此放在应用类里是比较恰当的。但是app对象不是游戏中的唯一对象(但是是“根”对象),我们在接下来的篇章中会看到,在run()中会通过调用函数来创建更多的对象,例如导演,场景,图层,精灵等对象。

最后有一个问题是,为什么要把类对象的指针传递给基类的一个静态成员数据,而不直接利用对象的名字app,然后用点操作符来调用成员函数呢?其实如果只运行一个run函数是可以用点操作符的,但实际上,程序并不止在最后一行的run()处调用了该对象,而是在几个地方都调用了这个对象,而且都被封装了起来,外面看不到,因此如果不传递指针,就要进入封装好的程序内部一个个去修改对象名(对象名是程序员自己任意取的),所以就无法实现封装。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值