cocos2d-x: 死磕"HelloWorld"(2)——应用实例的创建
2014-07-05 07:34
399 查看
该篇分析应用实例的创建(构造),并解决第一篇中的前两个疑问,即 Q1, app对象建立之后去哪了?Q2, 最后是什么对象在run?我们先来看看AppDelegate类的头文件:
AppDelegate.h
可见AppDelegate类私有继承了CCApplication类。除了构造和析构函数外,只有三个成员函数,它们分别与应用的初始化、进入后台以及恢复到前台有关。我们再去基类CCApplication的头文件里看看
CCApplication.h
在该头文件里我们找到了两个熟悉的身影,run()和sharedApplication(),正如我们所尿,后者是静态函数,并返回一个CCApplication类指针。前者run()是个虚函数,这是为了方便派生类重载该函数(但是AppDelegate并未重载该函数)。既然CCApplication被AppDelegate继承了,那么CCApplication里的所有函数都能被AppDelegate调用,换句话说AppDelegate类就是CCApplication类,只是多了几个成员函数。当创建AppDelegate对象时,会自动调用基类CCApplication的构造函数。我们现在就来看一下CCApplication类的构造函数(AppDelegate类自身只有一个空的无参构造函数,等价于默认构造函数)
CCApplication.cpp
我们特意把一个静态成员变量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()处调用了该对象,而是在几个地方都调用了这个对象,而且都被封装了起来,外面看不到,因此如果不传递指针,就要进入封装好的程序内部一个个去修改对象名(对象名是程序员自己任意取的),所以就无法实现封装。
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()处调用了该对象,而是在几个地方都调用了这个对象,而且都被封装了起来,外面看不到,因此如果不传递指针,就要进入封装好的程序内部一个个去修改对象名(对象名是程序员自己任意取的),所以就无法实现封装。
相关文章推荐
- cocos2d-x: 死磕"HelloWorld"(4)——HelloWorld场景的创建
- cocos2d-x: 死磕"HelloWorld"(1)——入口函数及其带来的疑问
- cocos2d-x: 死磕"HelloWorld"(3)——游戏运行主函数run()
- cocos2d-x: 死磕"HelloWorld"(6)——场景渲染的实施
- cocos2d-x: 死磕"HelloWorld"(5)——场景渲染准备工作
- 实例介绍Cocos2d-x物理引擎:HelloPhysicsWorld
- 创建“Hello, world”应用(使用 C#/VB 和 XAML 的 Metro 风格应用)摘自官网(存档)入门必须
- Windows下qt的环境配置及运行一个c++"hello world"实例详细步骤
- 实例介绍Cocos2d-x物理引擎:HelloPhysicsWorld
- Cocos2d-x--"Hello World"深入分析
- Windows下qt的环境配置及运行一个c++"hello world"实例详细步骤
- python爬虫"Hello World"级入门实例(二),使用json从中国天气网抓取数据
- 6.1 创建"Hello World"图形用户界面
- 1.3创建一个“HelloWorld"程序
- 翻译:创建 Windows8 应用 Part I: Hello, world!
- Windows下qt的环境配置及运行一个c++"hello world"实例详细步骤
- 创建"Hello World"图形用户界面
- cocos2d 游戏开发:Cocos2d v3 "hello world"+显示飞船
- 『原创』用C++开发WM应用系列(1)——"Hello World" Pro!(下)
- Windows下qt的环境配置及运行一个c++"hello world"实例详细步骤