Cocos2d-x内存管理研究<二>
http://hi.baidu.com/tzkt623/item/46a26805adf7e938a3332a04
上一篇我们讲了内核是如何将指针加入管理类进行管理.这次我将分析一下内核是如何自动释放指针的.
不过在这之前,我们要先引入另一个类.Cocos2d-x的核心类之一CCDirector.这个类可以说是引擎最主要的类了,没有他,引擎内的所有东西都无法运转起来.由于这个类有点大,做的事情很多,我就不一一粘出来看了,我们只关心跟内存管理有关的函数.
因为一部片子只需要一个导演,所以CCDirector也就是一个单例类,是多重继承自CCObject和TypeInfo,有什么用,暂时不管.不过是采用的二段构建的单例.什么是二段构建,说简单点,就是不在构造函数中做初始化工作,而在另一个函数里做初始化工作.这样做有什么好处?可以说没有好处,也可以说有好处.的看用的人是出于一种什么样的目的了.比如说我们这里,如果不用二段构建是不可能实现的.因为CCDirector是个抽象类,我们知道,抽象类是不能被实例化的,也就是说,你new XXX是不可能的,编译器直接弹错.所以要想子类拥有父类的初始化功能,那只能另外写一个了.
那我们是如何构建的CCDirector这个单例的呢?
static CCDisplayLinkDirector *s_SharedDirector = NULL;
CCDirector* CCDirector::sharedDirector(void)
{
if (!s_SharedDirector)
{
s_SharedDirector = new CCDisplayLinkDirector();
s_SharedDirector->init();
}
return s_SharedDirector;
}
看见了吧,他其实是new了一个自己的子类,来完成自己的功能的.而这个子类里,就有我们非常重要的一个函数.
在CCDirector的初始化函数里,我们看到了一个熟悉的面孔
bool CCDirector::init(void)
{
CCLOG("cocos2d: %s", cocos2dVersion());
.........................................
// create autorelease pool
CCPoolManager::sharedPoolManager()->push();
return true;
}
他在整个内核开始运行之前,就初始化了一个内存管理类.之后,在他的析构函数里
CCDirector::~CCDirector(void)
{
CCLOG("cocos2d: deallocing CCDirector %p", this);
.....................
// pop the autorelease pool
CCPoolManager::sharedPoolManager()->pop();
CCPoolManager::purgePoolManager();
...........................
}
用管理类执行了一个弹栈操作pop().不过看到这里,我有点不解,pop是弹出当前管理池并clear掉,那如果当前有几个管理池同时存在呢?只弹一次,后面几个怎么办?我们还是慢慢来吧.
purgePoolManager()其实是这样
void CCPoolManager::purgePoolManager()
{
CC_SAFE_DELETE(s_pPoolManager);
}
他删掉当前单例的指针.这样整个单例所保存的数据都会被删除掉,所以也就不用pop所有的元素了.
然后这个CCDirector 中剩下的唯一跟内存有关的,也是最最重要的函数mainLoop(),从他的名字我们就能看出来他的重要性了--主循环.他是一个纯虚函数virtual void mainLoop(void) = 0,在上面提到的子类CCDisplayLinkDirector中被覆写了.
现在我们来看看这个类
class CCDisplayLinkDirector : public CCDirector
{
public:
CCDisplayLinkDirector(void)
: m_bInvalid(false)
{}
virtual void mainLoop(void);
virtual void setAnimationInterval(double dValue);
virtual void startAnimation(void);
virtual void stopAnimation();
protected:
bool m_bInvalid;
};
他其实就是覆写了抽象类的几个纯虚函数而已.并且通过注释,我们知道他还有些其他的功能和限制.
1.他负责显示并以一定的频率刷新计时器.
2.计时器和界面绘制都是通过一定的频率是同步进行的.
3.只支持每秒60,30,15帧设置.
这些不属于我们讨论范围,简答提一下,我们只关心重要的mainLoop()
void CCDisplayLinkDirector::mainLoop(void)
{
if (m_bPurgeDirecotorInNextLoop)
{
m_bPurgeDirecotorInNextLoop = false;
purgeDirector();
}
else if (! m_bInvalid)
{
drawScene();
// release the objects
CCPoolManager::sharedPoolManager()->pop();
}
}
这里在没有失效的状况下(即m_bInvalid不为真),他会执行管理池中的pop函数.至于何时m_bInvalid为真,其实是在这里
void CCDisplayLinkDirector::stopAnimation(void)
{
m_bInvalid = true;
}
而上面的条件语句中的这个变量m_bPurgeDirecotorInNextLoop,我们从名字里就能看出来,他是否是结束CCDirector的一个标志.既然是mainLoop,那就一定要Loop起来,而这里并没有看到任何Loop的迹象.于是我在内核中查找一下mainLoop在哪里被用过.
int CCApplication::run()
{
PVRFrameEnableControlWindow(false);
// Main message loop:
MSG msg;
LARGE_INTEGER nFreq;
LARGE_INTEGER nLast;
LARGE_INTEGER nNow;
QueryPerformanceFrequency(&nFreq);
QueryPerformanceCounter(&nLast);
// Initialize instance and cocos2d.
if (!applicationDidFinishLaunching())
{
return 0;
}
CCEGLView* pMainWnd = CCEGLView::sharedOpenGLView();
pMainWnd->centerWindow();
ShowWindow(pMainWnd->getHWnd(), SW_SHOW);
while (1)
{
if (! PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
// Get current time tick.
QueryPerformanceCounter(&nNow);
// If it's the time to draw next frame, draw it, else sleep awhile.
if (nNow.QuadPart - nLast.QuadPart >m_nAnimationInterval.QuadPart)
{
nLast.QuadPart = nNow.QuadPart;
CCDirector::sharedDirector()->mainLoop();
}
else
{
Sleep(0);
}
continue;
}
if (WM_QUIT == msg.message)
{
// Quit message loop.
break;
}
// Deal with windows message.
if (! m_hAccelTable || ! TranslateAccelerator(msg.hwnd,m_hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
如此大的一个while(1),就是在这里循环的.这个run又是在哪里运行的呢?大家看工程里的main.cpp
int APIENTRY _tWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// create the application instance
AppDelegate app;
CCEGLView* eglView = CCEGLView::sharedOpenGLView();
eglView->setFrameSize(960, 640);
returnCCApplication::sharedApplication()->run();
}
在这里,WIN32平台下的入口函数中,我们的引擎就已经启动了.其他的功能,是启动的一些初始化工作,以及跨平台的东西,这里不在讨论范围之内,我们只管内存管理的东西.
好,基本的过程我都找到了,现在来理一下自动释放的思路.
假设我们程序已经运行,并且已经存进指针了.那么mainLoop这个函数,在不受到阻碍的情况下,会一直执行,并且一直执行CCPoolManager::sharedPoolManager()->pop().这里我们再把这个pop搬出来看看.还有一个附带的clear().
void CCPoolManager::pop()
{
if (! m_pCurReleasePool)
{
return;
}
int nCount = m_pReleasePoolStack->count();
m_pCurReleasePool->clear();
if(nCount > 1)
{
m_pReleasePoolStack->removeObjectAtIndex(nCount-1);
m_pCurReleasePool = (CCAutoreleasePool*)m_pReleasePoolStack->objectAtIndex(nCount- 2);
}
}
void CCAutoreleasePool::clear()
{
if(m_pManagedObjectArray->count() > 0)
{
//CCAutoreleasePool* pReleasePool;
CCObject* pObj = NULL;
CCARRAY_FOREACH_REVERSE(m_pManagedObjectArray, pObj)
{
if(!pObj)
break;
--(pObj->m_uAutoReleaseCount);
}
m_pManagedObjectArray->removeAllObjects();
}
}
1.如果没有有释放池,就不做任何事,我们有池,一开始就push了一个进去.
2.记录当前池中的指针个数,假设我们有3个.在栈中的顺序以及引用计数分别为4(最后一针),2(第二针),1(第一针);
3.清除掉当前池中的指针.释放的过程是,遍历池中每一个指针,将他们的自动释放计数减一,然后removeAllObject.看看removeAllObjects()干了些什么事吧.
void CCArray::removeAllObjects()
{
ccArrayRemoveAllObjects(data);
}
额,他其实只是调用了一下ccArray的函数而已,而里面的参数data,就是ccArray结构体的指针,也就是我们当前的自动释放池.
/** Removes all objects from arr */
void ccArrayRemoveAllObjects(ccArray *arr)
{
while( arr->num > 0 )
{
(arr->arr[--arr->num])->release();
}
}
这个函数写着,从数组里移除所有的指针.其实不算是移除,不算是真正的移除,他只是遍历了一次储存指针的内存地址(即储存内存地址的内存块),并分别执行了一次release操作,即将我们指针的引用计数减1,如果减一之后为0了,那么就删除(delete)这个指针.
我来模拟一下他的运行过程:
1.找到第一个引用计数为4的指针(最后一针),将他的释放计数减1,变为0;然后找到第二针,释放计数减1,变为0;最后找到第一针,释放计数减1,变为0.
2.执行removeAll操作,将栈中每一个指针的引用计数减1.找到最后一针,4->3,第二针2->1,第一针1->0,然后第一针执行delete this操作.即立刻执行析构函数.
CCObject::~CCObject(void)
{
// if the object is managed, we should remove it
// from pool manager
if (m_uAutoReleaseCount > 0)
{
CCPoolManager::sharedPoolManager()->removeObject(this);
}
// if the object is referenced by Lua engine, remove it
.....................
}
不过因为m_uAutoReleaseCount已经变成0了,就等于什么都不做,只是例行C++内核过程.那前面一篇提到的m_uAutoReleaseCount什么时候会大一1,这个经过我的研究发现,他不能大于1,他只有0和1两个值,你把它定成bool类型的也可以,只是不方便运算.
呵呵,重点来了.别以为delete干了件跟牛X的事,其实很多初学者都被他的名字骗了.delete其实只干了一件事,那就是把指针指向的内存中的数据给删了,但是指针并没有消失.他任然存在.这就是传说中的野指针,瞎指!,如果使用它,可能就会出现莫名其妙的BUG.所以,一般delete后,都要将指针的赋值为NULL,让系统知道,这指针暂时没用了.不过这里是delete this,this这个指针是极平常又不平常的一个东西,每个类生成的时候,都会自动带一个this指针.this指向的其实就是这个类自己,那delete this就等于"自杀".C++中,允许一个类通过成员函数申请"自杀操作",不过你必须保证,这个类是new出来的,不是对象,而且"自杀"是并且必须是这个类执行的最后一个操作,之后不在对这个类做任何有关数据,以及检查指针等操作.(想对C++有更深的了解,大家可以去看侯捷先生的<<深度探索C++对象模型>>)那你说,我把delete this放在析构里就行了.NO,这是绝对不行的,delete this之后的第一个操作就是执行析构函数,如果再放进去,会形成代码无限循环,导致堆栈溢出.
这样看来,我们保存指针数组中,任然有3个指针存在,虽然有个已经废了........
3.mainLoop再运行一次,然后pop在次执行.不过看到这里我出现了疑问,因为前一次的pop操作,仅仅只是释放了指针所指向的内存,但并没有把指针赋值为NULL,所以,如果再次操作指针,岂不是变成操作野指针了!来吧,再看看代码.
通过把输出数据打印到控制台,我发现了一个现象.
CCPoolManager::pop 1
CCAutoreleasePool::clear 4
0
1
2
3
..................
CCPoolManager::pop 1
CCAutoreleasePool::clear 0
也就是说,在pop一次之后,当前释放池中所储存的指针全部被移除了,不是删除指针,只是从数组中把它们去除.
注意,以下为我个人分析过程中非常重要的一段!!!
这里我要重要提示一下,这里卡了我很久很久,也废了我很多的时间!大家还记得那个自动释放的计数么?就是这玩意m_uAutoReleaseCount,他在add中被加了1,在clear中被减一.开始我的想法是,这个值只要是1,就说明他被加入了自动管理池中管理,为0表示没有加入自动管理池.但是我发现我错了,我找遍了整个内核,都没有找到有哪个地方在判断这个值是否为1(除了CCObject的析构,不过那里没有实际意义).
也就是说,按照我推理的思路,在pop中清理一次指针后,你得把指针移除吧,所以我想到了removeXX函数,可是他们一次也没有被执行.但是,上面的信息中却显示了释放池中没有元素了.那是怎么释放的呢?我这时想到了那个释放计数,他们不是在clear中被归零了么,应该有个判断,找到归零的指针就删除,可惜我错了,我愣是没找到这样的或类似的方法.那这数组究竟是如何变成0的呢.
其实秘密在这里.还记得上面自动释放池执行的那个函数么?在clear中的removeAllObjects,他最终执行的函数是ccArrayRemoveAllObjects,而这个函数干的事我们都知道,那就是
while( arr->num > 0 )
{
(arr->arr[--arr->num])->release();
}
等一下,这里有个非常阴险的地方,不注意看完全看不出来,我就是没注意,就是这个东西!!!!--arr->num!!!!.最开始我仅仅认为这是循环遍历数组执行release操作.天哪,当我分析了一遍又一遍时,才发现,这就是让自动释放池数组变成0的原因所在!
内核作者并没有真正的把数组释放掉,而是强行把数组的元素个数归零了!!!!!
这句判断if(m_pManagedObjectArray->count()> 0),其中的count()也就是获得数组元素个数的函数,他的原型是这样的.
unsigned int CCArray::count()
{
return data->num;
}
其中的data就是一个指向ccArray的指针,而ccArray结构体中的第一个参数就是num.
各位肯定还记得我说过的,只delete指针不赋值NULL,是没办法真正删除一个指针的,而他会成为野指针.作者仅仅执行了delete this,没有赋值NULL,clear中却还继续对野指针进行操作,但是整个引擎却没有出现丝毫的BUG.也就是这个原因,让我纠结了很久,以至于发现了如此坑爹的删除数组方式.
这里给大家介绍一下m_uAutoReleaseCount这个自动释放计数的前身.Cocos2d-x这个引擎其实是来源于Cocos2d,而这个引擎是苹果机专用,也就是用的Object-C,而带引用计数的指针是Object-C语言层面就支持的东西,在苹果里面,这个释放计数其实是布尔值. .而C++语言层面并不支持释放计数,也就是带引用计数的指针,所以只能模拟.这下好了,一模拟就模拟了个我目前为止觉得是不仅废材还费脑子的变量m_uAutoReleaseCount.我曾经试图控制他的值,不过引擎会报错.但是我实在是没找到有哪里在判断他的值了.除了那个无关紧要的~CCObject.求高手解答吧!
也就是说,按照内核的自动管理措施,确实可以释放掉不用的内存,但是,会生成一大堆的野指针.真是不负责任的自动管理.不过通过我简单的研究,这个自动管理类,确实没办法将野指针消除,只能让系统回收,算是他引擎的缺陷吧.要不然,Cocos2d的开发者也不会叫我们尽量不要用自动释放.
好啦,重要的分析结束啦!
下面呢,我就把整个自动管理的过程串起来,给大家一个清晰的思路了.
我们首先new一个类出来 CCSprite *pSprite = CCSprite::create(),这个静态工厂方法里,直接就执行了autorelease操作了.
,这里面的过程就是这样的:new操作将引用计数初始化为1,释放计数初始化为0, autorelease通过几次操作,将我们的pSprite,也就是他的内存地址存放进了自动释放池里.然后retain将引用计数加1.这时引用计数为2.这时,我们就可以理解,为什么后面有一个pObject->release()操作了.我们只是想把他加入自动管理,但并不想retain他.于是乎,引用计数还是1.
这时,我们执行addChild(pSprite),将我们的精灵加入节点中,这个操作也会将引用计数加1.来给大家看看代码.
……………………..
,我只挑重要的函数讲,这里执行了一个insertChild操作.
void CCNode::insertChild(CCNode* child, int z)
{
m_bReorderChildDirty = true;
ccArrayAppendObjectWithResize(m_pChildren->data, child);
child->_setZOrder(z);
}
,我们上一篇才提到的函数ccArrayAppendObjectWithResize又出现了,他的出现,就意味着retain的出现.我就不继续粘代码了,那这里的m_pChildren就一定是一个CCArray的指针了.这时pSprite的引用计数为2.
假设这时我们的游戏中有且仅有一个精灵,也就是我们的pSprite,我们暂时把他看成是玩家,游戏中,玩家是我们主要操作的对象,加入自动释放后,就有被自动释放的可能,但是这是我们所不允许的.引擎自然也知道则一点.所以,这时候mainLoop执行一次,pop执行一次,我们当前有自动释放池,所以clear执行一次.clear中,释放计数被减1,归零,然后由于removeAllObjects的执行,我们的玩家pSprite执行一次release,引用计数减由2变成1.然后数组被强行归零.这时mainLoop再次执行的话,释放池中的元素个数就为0了(没有再添加其他的东西).
,这不是坑爹吗?释放池就是用来释放指针的,但是pSprite的引用计数还有1,是不会执行delete this操作的.你说对了,这就是为什么,我们还能操作玩家的原因,如果这个指针被释放了,我们岂不是在操作野指针了?那如何控制玩家?
其实说到这里,大家应该明白了.这个自动释放池,做的事情,仅仅是释放掉引用计数为1的指针.只要引用计数超过1,那就不会释放.做的只是将引用计数减一的操作.那我们结束游戏,或者切换场景时,那些引用计数大于1的指针,改如何释放呢? 分析到这里的,我个人也认为,这个内存管理算是比较坑的了.
仔细想一下,内存泄露的原因是什么,说简单点,就是只new而不delete.什么情况下会发生这样的情况呢?比如说,我们的一个射击游戏,要发射很多子弹,于是我们在一个界面里new很多子弹出来,但是子弹碰撞后不delete掉,以至于子弹越来越多,内存越占越大.然后我们突然切换场景Scene,因为场景的对象消亡了,所以场景的析构函数被执行,自动释放了他占的内存,但是我们的子弹并没有执行析构函数,所以一直把空间占着,那段内存就废掉了,于是乎内存泄露.
但是我们切换界面的时候,玩家的引用计数为1,并且不再释放池内了,那该如何释放?这里,我们就要看下CCNode的析构函数了.
CCNode::~CCNode(void)
{
CCLOGINFO("cocos2d: deallocing" );
……………………………
if(m_pChildren&& m_pChildren->count() > 0)
{
CCObject*child;
CCARRAY_FOREACH(m_pChildren, child)
{
CCNode*pChild = (CCNode*) child;
if(pChild)
{
pChild->m_pParent = NULL;
}
}
}
// children
CC_SAFE_RELEASE(m_pChildren);
}
他帮我们完成了这一步.我们的玩家不是在Layer上的么?Layer又在Scene上,当Scene被替换掉时,会自动执行他的析构函数,再执行父类的析构函数,也就是上面这段,这其中的m_pChildren中就保存着指向Layer的指针,他将Layer的父类赋值为空,然后release掉m_pChildren.而这个m_pChildre是指向CCArray的指针, CCArray是CCObject的子类,初始化时引用计数被加了1,然后autorelease加入自动释放池. m_pChildren被初始化为NULL,他是这样创建的
void CCNode::addChild(CCNode *child,int zOrder, int tag)
{
if( ! m_pChildren )
{
this->childrenAlloc();
}
}
void CCNode::childrenAlloc(void)
{
m_pChildren = CCArray::createWithCapacity(4);
m_pChildren->retain();
}
一来就被retain一次,经过一次pop引用计数为1,所以不会被释放.而最后的CC_SAFE_RELEASE(m_pChildren),将他的引用计数变为0.执行delete this操作,进而执行析构函数.
CCArray::~CCArray()
{
ccArrayFree(data);
}
析构函数里执行了一次释放操作.
void ccArrayFree(ccArray*& arr)
{
if( arr == NULL )
{
return;
}
ccArrayRemoveAllObjects(arr);
free(arr->arr);
free(arr);
arr = NULL;
}
这里又执行了一个remove操作,这个蒙了我很久的函数ccArrayRemoveAllObjects(arr),他用m_pChildren数组里的成员执行一次release操作,也就是layer->release(),因为我们的layer并没有手动retain过.所以他的引用计数减1变为0,然后执行delete this.回收内存.接着,保存layer的这个数组被free掉,然后m_pChildren被free掉,接着赋值为NULL,彻底删除指针.
这样一来,layer就彻底没有了.我们以此类推,存在layer上的东西,也就是储存在layer里m_pChildren中的什么CCSprite ,CCMenu,CCLabelTTF等等,都会在界面切换时被彻底删除掉.所以,内存管理不仅仅只是autorelease做的事情,节点CCNode其实承担了相当大一部分内存管理工作.相比起来,释放池做的工作,仅仅是担心我们使用局部指针变量时,忘记release的一种防范策略.
不过这也提醒了我们,如果我们new了一个局部的指针,并且手动retain了一下,那就必须在必要的地方手动release他一次,并且两个操作的次数必须一样.为什么呢?回顾一下上面分析的就知道了,最后仅仅只是release了一下而已,也就是说在不手动retain的情况下,我们的内存管理,最多能回收掉引用计数为2的指针,如果你手动retain了,那最后的那个release不足以把引用计数减到0,那么就内存泄露了………..
不过如果你执行的是退出游戏,那就无所谓了,现在的操作系统,都能在程序退出时,将他所占用的内存全部回收掉,就算是你new了一堆东西出来还不delete.
Cocos2d-x的内存管理到这里就分析完了,虽然没有我想想的那样智能,但是也让我学到很多内存管理的思想.我们下一篇内核分析再见~
后记:
写完这篇文章后,我在做开发时又想了一下,其实这个自动释放池不能算是坑,他的目的是把我们new出来的指针的引用计数释放到1,从而让Scene切 换,Layer切换,退出程序时,真正的内存管理,也就是CCNode和CCObject的内存管理能顺利吧所有的指针都释放掉.
所以总结一下,此内存管理的思路是,用autorelease将指针的释放计数控制在一定范围内(最大值是2,autorelease之后必须最大是1),以至于当界面切换,各个类执行remove操作,程序退出等情况下能将占用的内存全部释放掉.
Cocos2d-x内存管理研究<二>的更多相关文章
- Linux内存管理(二)
Linux内存管理之二:Linux在X86上的虚拟内存管理 本文档来自网络,并稍有改动. 前言 Linux支持很多硬件运行平台,常用的有:Intel X86,Alpha,Sparc等.对于不能够通用的 ...
- 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法
垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己主动内存管理 上(一)内存分配 垃圾回收GC:.Net自己主动内存管理 上(二)内存算法 垃圾回收GC:.Net自己 ...
- DPDK内存管理-----(二)rte_mempool内存管理
DPDK以两种方式对外提供内存管理方法,一个是rte_mempool,主要用于网卡数据包的收发:一个是rte_malloc,主要为应用程序提供内存使用接口.本文讨论rte_mempool.rte_me ...
- Cocos2d-X内存管理研究<一>
http://hi.baidu.com/tzkt623/item/651ca7d7a0aff6e055347f67 半夜没事干,研究内核,作为我cocos2d-x的第一篇教程.cocos ...
- linux内核--内存管理(二)
一.进程与内存 所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等.不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内 ...
- qemu对虚拟机的内存管理(二)
上篇文章主要分析了qemu中对虚拟机内存管理的关键数据结构及他们之间的联系,这篇文章则主要分析在地址空间发生变化时,如何将其更新至KVM中,保持用户空间与内核空间的同步. 这一系列操作与之前说的Add ...
- Redis内存管理(二)
上一遍详细的写明了Redis为内存管理所做的初始化工作,这篇文章写具体的函数实现. 1.zmalloc_size,返回内存池大小函数,因为库不同,所以这个函数在内部有很多的宏定义,通过具体使用的库来确 ...
- Android 内存管理(二)
很多开发者都是从j2me或j2ee上过来的,对于内存的使用和理解并不是很到位,Android开发网本次给大家一些架构上的指导,防止出现豆腐渣工 程的出现.Android作为以Java语言为主的智能平台 ...
- 内存管理之二——Cocos2d-x学习历程(六)
1.工厂方法 工厂方法是程序设计中一个经典的设计模式,指的是基类中只定义创建对象的接口,将实际的实现推迟到子类中. CCObject* factoryMethod() { CCObject* ret ...
随机推荐
- bzoj 1806 [Ioi2007]Miners 矿工配餐(DP)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=1806 [题意] 给定一个权在1..3内的序列,在保持相对位置不变的情况下拆分成两个序列 ...
- vim开发环境配置
一.大饱眼福 看了效果图,肯定有人说, 这都有啥功能?就花哨? 告诉你,你说花哨就错了,开玩笑?我们程序猿可都是实打实的人,说谎都不会,咋会忽悠人呢. 下面我来告诉你,这都有些什么功能: 文件索引功能 ...
- 链表回文串判断&&链式A+B
有段时间没有练习了,链表回文串判断用到了栈.链式A+B将没有的项用0补充.链表有没有头节点,及结点和链表的区别,即pNode和pHead. //#include<iostream> //u ...
- 初识-----基于Socket的UDP和TCP编程及测试代码
一.概述 TCP(传输控制协议)和UDP(用户数据报协议是网络体系结构TCP/IP模型中传输层一层中的两个不同的通信协议. TCP:传输控制协议,一种面向连接的协议,给用户进程提供可靠的全双工的字节流 ...
- CSS抗锯齿 font-smoothing
CSS3里面加入了一个“-webkit-font-smoothing”属性. 这个属性可以使页面上的字体抗锯齿,使用后字体看起来会更清晰舒服. 加上之后就顿时感觉页面小清晰了. 淘宝也在用哦! 它有三 ...
- 安装CiscoWorks LMS
安装CiscoWorks LMS,需要安装5张光盘,CiscoWorks LMS的安装步骤比较简单,在这里只介绍需要的重要步骤. 1.安装CD One 将CiscoWorks 2000 LMS CD ...
- VPN 隧道协议PPTP、L2TP、IPSec和SSLVPN的区别
最近软矿频繁地介绍了各种VPN,有免费的PacketiX.NET和Hotspot Shield,有付费的Astrill VPN,iVPN和PureVPN.在介绍这些VPN的时候,常常会说到PPTP.L ...
- iOS多线程总结
1.不要同时开太多的线程(1~3条线程即可,不要超过5条) 2.线程概念 1> 主线程 : UI线程,显示.刷新UI界面,处理UI控件的事件 2> 子线程 : 后台线程,异步线程 3.不要 ...
- J2EE项目相对路径、绝对路径获取
String path = getServletContext().getRealPath("/"); 这将获取web项目的全路径. this.getClass().getClas ...
- linux 下查看文件个数及大小
ls -l |grep "^-"|wc -l 或 find ./company -type f | wc -l 查看某文件夹下文件的个数,包括子文件夹里的. ls - ...