Cocos2dx学习笔记(18)——Cocos数据结构

Cocos2dx自定义了三种数据结构,用于存储数据。

本文仅供个人记录和复习,不用于其他用途

2.x与3.x

2.x版本:

  • CCArray
  • CCDictionary
  • CCString

3.x版本:

  • Vector
  • Map
  • Value

CCArray

看名字就能知道大概的意思,这个是Cocos自定义的数组,方便存储Cocos中的数组型数据。

创建

1
2
3
4
5
6
7
8
9
10
// 创建一个数组
static CCArray* create();
// 使用一些对象创建数组
static CCArray* create(CCObject* pObject, …);
// 使用一个对象创建数组
static CCArray* createWithObject(CCObject* pObject);
// 创建一个指定大小的数组
static CCArray* createWithCapacity(unsigned int capacity);
// 使用一个现有的CCArray数组来新建一个数组
static CCArray* createWithArray(CCArray* otherArray);

插入

1
2
3
4
5
6
// 插入一个对象
void addObject(CCObject* object);
// 插入别外一个数组里面的全部对象
void addObjectsFromArray(CCArray* otherArray);
// 在一个确定的索引位置插入一个对象
void insertObject(CCObject* object, unsigned int index);

删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 移除最后的一个对象
void removeLastObject(bool bReleaseObj = true);
// 移除一个确定的对象
void removeObject(CCObject* object, bool bReleaseObj = true);
// 移除一个确定索引位置的元素
void removeObjectAtIndex(unsigned int index, bool bReleaseObj = true);
// 移除全部元素
void removeObjectsInArray(CCArray* otherArray);
// 移除所有对象
void removeAllObjects();
// 快速移除一个对象
void fastRemoveObject(CCObject* object);
// 快速移除一个确定索引位置的对象
void fastRemoveObjectAtIndex(unsigned int index);

removefastRemove的区别在于,remove将元素删除,后面的元素向前移动。而fastRemove只是释放了对应元素,整体的结构没变。

遍历

遍历用到的是CCARRAY_FOREACH(arr, obj)这个宏,有正向和逆向之分:

1
2
3
4
5
6
7
8
9
10
11
#define CCARRAY_FOREACH(__array__, __object__) \
if ((__array__) && (__array__)->data->num > 0) \
for(CCObject** __arr__ = (__array__)->data->arr, **__end__ = (__array__)->data->arr + (__array__)->data->num-1; \
__arr__ <= __end__ && (((__object__) = *__arr__) != NULL/* || true*/); \
__arr__++)
#define CCARRAY_FOREACH_REVERSE(__array__, __object__) \
if ((__array__) && (__array__)->data->num > 0) \
for(CCObject** __arr__ = (__array__)->data->arr + (__array__)->data->num-1, **__end__ = (__array__)->data->arr; \
__arr__ >= __end__ && (((__object__) = *__arr__) != NULL/* || true*/); \
__arr__--)

上面这个是3.x版本的代码,但是基本思路是一样的。

操作元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 返回元素个数
unsigned int count() const;
// 返回array容量
unsigned int capacity() const;
// 返回指定CCObject的位置,如果不存在返回UINT_MAX
unsigned int indexOfObject(CCObject* object) const;
// 返回指定位置的CCObject
CCObject* objectAtIndex(unsigned int index);
// 返回最后一个元素
CCObject* lastObject();
// 返回随机元素
CCObject* randomObject();
// 返回某个元素是否存在于array中
bool containsObject(CCObject* object) const;
// 判断array是否相等
bool isEqualToArray(CCArray* pOtherArray);

注意,容量和个数是不一样的,个数<=容量,添加元素前会先判断是否要增加容量。

操作CCArray内容

1
2
3
4
5
6
7
8
9
10
// 交换2个元素
void exchangeObject(CCObject* object1, CCObject* object2);
// 交换2个指定位置元素
void exchangeObjectAtIndex(unsigned int index1, unsigned int index2);
// 用一个对象替代指定位置元素
void replaceObjectAtIndex(unsigned int uIndex, CCObject* pObject, bool bReleaseObject = true);
// 反转array
void reverseObjects();
// 收缩array内存以匹配元素个数
void reduceMemoryFootprint();

判等

判断2个CCArray是否相等使用isEqualToArray(),判断相等的条件是CCArray中的每个元素相等即可,与CCArray的容量无关。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 初始化
CCArray* pArray;
pArray = createWithCapacity(100);
pArray = CCArray::create();
// 添加元素
CCSprite* pRet = CCSprite::create("test.png");
pArray->addObject(pRet);
// 删除元素
pArray->removeObject(pRet); // 第二参数为是否调用release,默认为true
pArray->removeObjectAtIndex(0); // 删除o位置上的元素
void removeObjectAtIndex(unsigned int index, bool bReleaseObj = true);
void removeAllObjects();
// 遍历
CCMenuItem *item = CCMenuItemFont::create(menu_array[i], this, menu_selector(TMenu::menuCallBack));
CCMenu *menu = CCMenu::create();
menu->addChild(item);
CCArray *array = menu->getChildren();
CCObject *obj;
CCARRAY_FOREACH(array, obj)
{
CCMenuItem *item = (CCMenuItem *)obj;
item->setTag(i + 1 + 10000);
i++;
}

遍历的操作已经用宏定义好了,我们只需要编写相应的代码即可。

注意事项

CCArray不会使用addChild()加到其他类,所以它的引用计数是1,而且被设置为自动释放。所以创建CCArray对象时要记得调用retain,而且在析构的时候也要调用release来释放内存。

CCDictionary

CCDictionary支持两种类型的关键字,一个是std::string,一个是int。一个CCDictionary实例对象只支持唯一的关键字。所以在你调用setObject方法的时候,你需要确认一下。

key

CCDictionary只支持整型和字符串型这两种类型,key的作用主要是方便取出对应元素。

1
2
3
4
5
6
enum CCDictType
{
kCCDictUnknown = 0,
kCCDictStr,
kCCDictInt
};

CCDictElement

CCDictElementCCDictionary的一个元素,包含了一个key-valuekey支持整型和字符串,使用的时候要注意在同一个CCDictionarykey类型必须要一致,value可以不一致。总的来讲,要么key都是字符串,要么都是整型,不能都有:

创建

1
2
3
4
5
6
7
8
9
// 创建一个CCDictionary
static CCDictionary* create();
// 用一个已存在的CCDictionary来创建一个新的CCDictionary
static CCDictionary* createWithDictionary(CCDictionary* srcDict);
// 用一个plist来创建CCDictionary
static CCDictionary* createWithContentsOfFile(const char *pFileName);
// 返回一个非autorelease对象的CCDictionary,它讷讷感够确保在新线程中使用
// 但是你必须手动管理它的生命周期,当你不再需要它的时候,必须调用CC_SAFE_RELEASE
static CCDictionary* createWithContentsOfFileThreadSafe(const char *pFileName);

查找

1
2
3
4
5
6
7
8
// 返回指定字符串类型key的value,如果CCDictionary的key是整型,会出现断言
CCObject* objectForKey(const std::string& key);
// 返回指定整型key的value,如果CCDictionary的key是字符串型,会出现断言
CCObject* objectForKey(intptr_t key);
// 返回指定字符串类型key的CCString,这里假定value是CCString型,如果不是或者未找到,则返回空串
const CCString* valueForKey(const std::string& key);
// 返回指定整型类型key的CCString,这里假定value是CCString型,如果不是或者未找到,则返回空串
const CCString* valueForKey(intptr_t key);

增加

1
2
3
4
5
6
// 插入一个key-value,如果是第一次调用,那么CCDictionary的key类型会被确定为字符串型,之后就不能插入整型key
// 如果已存在该key,则旧key-value会被释放和移除,被新的替代
void setObject(CCObject* pObject, const std::string& key);
// 插入一个key-value,如果是第一次调用,那么CCDictionary的key类型会被确定为整型,之后就不能插入字符串型key
// 如果已存在该key,则旧key-value会被释放和移除,被新的替代
void setObject(CCObject* pObject, intptr_t key);

移除

1
2
3
4
5
6
7
8
9
// 移除指定key
void removeObjectForKey(const std::string& key);
void removeObjectForKey(intptr_t key);
// 移除一个CCArray中keys
void removeObjectsForKeys(CCArray* pKeyArray);
// 通过元素来移除value
void removeObjectForElememt(CCDictElement* pElement);
// 移除所有的key-value
void removeAllObjects();

其他操作

1
2
3
4
5
6
7
8
9
10
// 返回一个随机元素,这个使用得注意,因为value可以不一样,所以返回类型每次都可能不同,在类型转换的时候要非常小心
CCObject* randomObject();
// 返回一个包含所有key的CCArray
CCArray* allKeys();
// 返回指定value的所有key,因为value是可以相同的,内部使用==比较两个value是否相同
CCArray* allKeysForObject(CCObject* object);
// 返回元素个数
unsigned int count();
// 把CCDictionary写到一个plist中,写入的value要求是字符串型
bool writeToFile(const char *fullPath);

遍历

1
2
3
4
#define CCDICT_FOREACH(__dict__, __el__) \
CCDictElement* pTmp##__dict__##__el__ = NULL; \
if (__dict__) \
HASH_ITER(hh, (__dict__)->m_pElements, __el__, pTmp##__dict__##__el__)

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Create a dictionary, return an autorelease object.
CCDictionary* pDict = CCDictionary::create();
// Insert objects to dictionary
CCString* pValue1 = CCString::create("100");
CCString* pValue2 = CCString::create("120");
CCInteger* pValue3 = CCInteger::create(200);
pDict->setObject(pValue1, "key1");
pDict->setObject(pValue2, "key2");
pDict->setObject(pValue3, "key3");
// Get the object for key
CCString* pStr1 = (CCString*)pDict->objectForKey("key1");
CCLog("{ key1: %s }", pStr1->getCString());
CCInteger* pInteger = (CCInteger*)pDict->objectForKey("key3");
CCLog("{ key3: %d }", pInteger->getValue());
CCString* pStr3=static_cast<CCString*>(pDict->randomObject()); // 这里有问题了,因为value有不同类型,所以随机返回时类型处理要小心
CCLog("{ random key: %s }",pStr3->getCString()); // 如果返回的是整型pValue3,那么会出现断言
if(pDict->writeToFile("pdic.plist")) // 整型的value无法写入,会提示This type cannot appear in property list
CCLog("Write to file success!");
// 遍历
CCDictElement* pElement;
CCDICT_FOREACH(dict, pElement)
{
const char*key = pElement->getStrKey();
// You certainly know the type of value, so we assume that it's a CCSprite.
CCSprite* pSprite = (CCSprite*)pElement->getObject();
// ......
}

CCString

CCString是自动释放的,除非你retian了。

创建

1
2
3
4
5
6
7
8
// 可以传递c字符串指针
static CCString* create(const std::string& str);
// 该方法与sprintf类似
static CCString* createWithFormat(const char* format, …);
// 使用二进制数据创建
static CCString* createWithData(const unsigned char* pData, unsigned long nLen);
// 使用文件来创建
static CCString* createWithContentsOfFile(const char* pszFileName);

转换

CCString可以转换成其他类型的变量:

1
2
3
4
5
int intValue() const;
unsigned int uintValue() const;
float floatValue() const;
double doubleValue() const;
bool boolValue() const;

常用宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define CCStringMake(str) CCString::create(str)
#define ccs CCStringMake
CCArray *stringArray = CCArray::create(
ccs("Hello"),
ccs("Variable"),
ccs("Size"),
ccs("!"),
NULL);

这样我们可以方便地创建许多CCString对象。

Vector

Vector类似于C++标准库中的vector,用于替代CCArrayVector是一个可以动态增长的容器,按照一定的顺序存储数据。T必须是继承自Ref的指针,其他类型(包括基本类型)都不能作为模板参数。实例化Vector对象时,一般在栈上实例化,所以不用担心内存释放的问题。Vector不是继承自Ref,不能使用retainrelease进行内存管理。

注意,Vector没有重载[],不能使用下标来获取对象

创建

为了演示,这里先创建两个精灵:

1
2
3
4
5
6
7
auto sp1 = Sprite::create("CloseNormal.png");
sp1->setPosition(Point(100,100));
this->addChild(sp1,1);
auto sp2 = Sprite::create("CloseSelected.png");
sp2->setPosition(Point(100,200));
this->addChild(sp2,1);

创建容器,将精灵加入:

1
2
3
4
Vector<Sprite*> sp_vec;
sp_vec.pushBack(sp1);
sp_vec.pushBack(sp2);

获取容器大小

1
int count = sp_vec.size();

遍历

1
2
3
4
for( auto& e : sp_vec)
{
e->runAction(MoveTo::create(0.2f, Point(100,100)));
}

删除容器中的精灵

1
2
3
4
5
6
7
8
// 删除最后一个精灵
sp_vec.popBack();
// 删除指定的精灵
sp_vec.eraseObject(sp1);
//删除所有对象
sp_vec.clear();

其他用法

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在容器中的任何一个位置插入对象:
sp_vec.pushBack(sp1);
sp_vec.pushBack(sp2);
// 目前的情况是,sp1在容器的第一个位置,sp2在容器的第二个位置,这时候我们要将sp3放入第一个位置:
sp_vec.insert(0,sp3);//ok
// 查找容器中的对象:
// 假设不知道容器中是否有sp3这个精灵,这时候可以这样:
sp_vec.contains(sp3);//如果有,返回true,无返回false;
// 如果已知容器中有sp3这个精灵,想获得它在容器中的位置:
int pos_int = sp_vec.find(sp3);
// 上面的方法可以获得sp3的位置,但返回的其实是迭代器的地址,你得到的结果可能是45214等等,如果想获得正常需要的位置,可以这样:
int pos_int = sp_vec.find(sp3) - sp_vec.begin();

Map

Map是用来替代CCDictionary的。内存管理我们无须担心,不过如果使用了new来分配内存,我们还是得使用delete释放内存。一般情况下,不要使用new。同样,Map也不能使用引用计数的方法管理内存。

注意,Map没有重载[],不能使用下标来获取对象

创建

1
2
3
4
5
6
7
8
9
10
auto sp1 = Sprite::create("CloseNormal.png");
sp1->setPosition(Point(100,100));
this->addChild(sp1,1);
auto sp2 = Sprite::create("CloseSelected.png");
sp2->setPosition(Point(100,200));
this->addChild(sp2,1);
//建立一个关联容器map,第一个参数是string型的key,第二个参数是Sprite类的key值
Map<std::string, Sprite *>sp_map;

将对象加入容器

1
2
3
// 将精灵放入容器中,第一个参数是key
sp_map.insert("sp1",sp1);
sp_map.insert("sp2",sp2);

取出容器中的元素

1
auto sp = sp_vec.at("sp1"); // 通过键值获得sp1

其他功能

1
2
3
4
5
6
7
8
9
10
auto sp5 = sp_map.at("sp1"); // 通过key取出sp1
sp_map.insert("10", sp5); // 再将sp1 以三个key值的方式存入map
sp_map.insert("20", sp5);
sp_map.insert("30", sp5);
auto _key = sp_map.keys(sp1); // 获得sp1对应的key值
for(const auto &e : _key)
{
CCLOG("_key is %s", e.c_str()); // 输出sp1 对应的key值(有四个,分别是:sp1,10,20,30)
}

Map对象的元素是键-值对,也就是每个元素包含两个部分:键以及由键关联的值。这种键和键值组成一个pair类型,它的first元素指向键,second元素则为元素。如下:

1
2
3
4
5
auto find_sp = sp_map.find("10"); // 通过find()查找key为“10”的pair类型。
auto sp6 = find_sp->second; // 键对应的对象
std::string find_str = find_sp->first;
CCLOG("sp6 key value is %s", find_str.c_str()); // 打印出键
sp6->runAction(MoveBy::create(0.3f, Point(200,0))); // 让sp6做运动

Value

Value可以保存任意基本类型,而Cocos中的std::vectorstd::unordered_map</std::string,>、std::unordered_map</int,>等等也可以存放到Value中。之前的CCBoolCCInt等将被废除,改为统一的变量。

内存管理

Value由它的析构函数释放内存,Value包含以下成员:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
union
{
unsigned char byteVal;
int intVal;
float floatVal;
double doubleVal;
bool boolVal;
}_baseData;
std::string _strData;
ValueVector* _vectorData;
ValueMap* _mapData;
ValueMapIntKey* _intKeyMapData;
Type _type;

_baseData_strData_type是由编译器和它们的析构函数负责释放内存的,而Value的析构函数释放_vectorData_mapDataintKeyMapDataValue同样不能用引用计数管理内存。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// 实例化Value
Value val;
if (val.isNull()) {
log("val is null");
}else{
std::string str = val.getDescription();
log("The description of val0:%s", str.c_str());
}
// 初始化Value
Value val1(65); // 整型初始化
//Value val1(3.4f); // float变量初始化
//Value val1(3.5); // double变量初始化
log("The description of the integer value:%s", val1.getDescription().c_str());
log("val1.asByte() = %c", val1.asByte());
// 使用string初始化
std::string strV = "string";
Value val2(strV);
log("The description of the string value:%s", val2.getDescription().c_str());
// 使用Vector初始化
auto sp0 = Sprite::create();
Vector<Object *> *vecV = new Vector<Object *>();
vecV->pushBack(sp0);
Value val3(vecV);
log("The description of the Vector value:%s", val3.getDescription().c_str());
delete vecV;
// 使用Map初始化
Map<std::string, Object *> *mapV = new Map<std::string, Object *>();
mapV->insert(strV,sp0);
Value val4(mapV);
log("The description of the Map value:%s", val4.getDescription().c_str());
delete mapV;
// 使用另一个Value初始化
Value val6(&val4);
log("The description of the Value-type value:%s", val6.getDescription().c_str());
// 使用=运算符赋值
val2 = val1;
log("operator-> The description of val2:%s", val2.getDescription().c_str());
val2 = 4;
log("operator-> The description of val4:%s",val2.getDescription().c_str());

输出如下:

cocos2d: val is null
cocos2d: The description of the integer value:
65

cocos2d: val1.asByte() = A
cocos2d: The description of the string value:
string

cocos2d: The description of the Vector value:
true

cocos2d: The description of the Map value:
true

cocos2d: The description of the Value-type value:
true

cocos2d: operator-> The description of val2:
65

cocos2d: operator-> The description of val4:
4

总结

优先使用新的数据结构,当要使用基本类型的聚合时,将基本类型包装成cocos2d::Value,然后将它们和模版容器cocos2d::Vectorcocos2d::Map联合使用。