CCString重点方法解析

| 分类 cocos2d-x  | 标签 cocos2d-x-2.2.3  C++ 
  1. CCString::operator= (const CCString& other)方法,关于=的重载是怎么实现的

    CCString& CCString::operator= (const CCString& other)
    {
    	m_sString = other.m_sString;
    	return *this;//返回CCString的引用
    }
    
  2. bool initWithFormatAndValist(const char* format, va_list ap)是一个private方法,说它主要是为了想学习下va_list

    bool CCString::initWithFormatAndValist(const char* format, va_list ap)
    {
    	bool bRet = false;
    	char* pBuf = (char*)malloc(kMaxStringLen);
    	if (pBuf != NULL)
    	{
    		vsnprintf(pBuf, kMaxStringLen, format, ap);//将可变参数格式化输出到一个字符数组
    		m_sString = pBuf;
    		free(pBuf);
    		bRet = true;
    	}
    	return bRet;
    }
    	
    //备注:int_vsnprintf(char*str,size_tsize,constchar*format,va_listap);
    	 
     参数说明:
     		 1.char *str [out],把生成的格式化的字符串存放在这里.
             2.size_t size [in], str可接受的最大字节数,防止产生数组越界.
           	 3.const char *format [in], 指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序
             4.va_list ap [in], va_list变量. va:variable-argument:可变参数
    
  3. bool CCString::initWithFormat(const char* format, …)这个方法应该很熟悉,将数据格式化成CCString类型,在这方法中回调了initWithFormatAndValist这个方法。

    bool CCString::initWithFormat(const char* format, ...)
    {
    	bool bRet = false;
    	m_sString.clear();//Erases the string, making it empty.
    
    	va_list ap;//在调用参数表之前,定义一个 va_list 类型的变量
    	va_start(ap, format);//对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数
    
    	bRet = initWithFormatAndValist(format, ap);
    
    	va_end(ap);// 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。
    
    	return bRet;
    }
    	
    备注:
        1.在C中,当我们无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
          void foo(...);
          void foo(parm_list,...);
          这种方式和我们以前认识的不大一样,但我们要记住这是C中一种传参的形式,在后面我们就会用到它。
    	      
        2.函数参数的传递原理
    	    
        函数参数是以数据结构:栈的形式存取,从右至左入栈。
        首先是参数的内存存放格式:参数存放在内存的堆栈段中,在执行函数的时候,从最后一个开始入栈。因此栈底高地址,栈顶低地址
        举个例子如下:void func(int x, float y, char z);
        那么,调用函数的时候,实参 char z 先进栈,然后是 float y,最后是 int x,因此在内存中变量的存放次序是 x->y->z,因此,从理论上说,我们只要探测到任意一个变量的地址,并且知道其他变量的类型,通过指针移位运算,则总可以顺藤摸瓜找到其他的输入变量。
    		
        下面是 <stdarg.h> 里面重要的几个宏定义如下:
    		
        1.	typedef char* va_list;
        2.	void va_start ( va_list ap, prev_param ); /* ANSI version */
        3.	type va_arg ( va_list ap, type ); 
        4.	void va_end ( va_list ap ); 
    		
        va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
    		
        <Step 1> 在调用参数表之前,定义一个 va_list 类型的变量,(假设va_list 类型变量被定义为ap);
    		
        <Step 2> 然后应该对ap 进行初始化,让它指向可变参数表里面的第一个参数,这是通过 va_start 来实现的,第一个参数是 ap 本身,第二个参数是在变参表前面紧挨着的一个变量,即“...”之前的那个参数;
    		
        <Step 3> 然后是获取参数,调用va_arg,它的第一个参数是ap,第二个参数是要获取的参数的指定类型,然后返回这个指定类型的值,并且把 ap 的位置指向变参表的下一个变量位置;
    		
        <Step 4> 获取所有的参数之后,我们有必要将这个 ap 指针关掉,以免发生危险,方法是调用 va_end,他是输入的参数 ap 置为 NULL,应该养成获取完参数表之后关闭指针的习惯。说白了,就是让我们的程序具有健壮性。通常va_start和va_end是成对出现。
    		
       1.	_INTSIZEOF 宏,获取类型占用的空间长度,最小占用长度为int的整数倍:
       #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
    	   
       2.	VA_START宏,获取可变参数列表的第一个参数的地址(ap是类型为va_list的指针,v是可变参数最左边的参数)
       #define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
    	   
       3.	VA_ARG宏,获取可变参数的当前参数,返回指定类型并将指针指向下一参数(t参数描述了当前参数的类型)
       #define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
    	   
       4.	VA_END宏,清空va_list可变参数列表
       #define va_end(ap)      ( ap = (va_list)0 )
    	   
       例如 int max(int n, ...); 其函数内部应该如此实现:
       #include <iostream.h> 
       void fun(int a, ...) 
       { 
       		int *temp = &a;
       		temp++;
       		for (int i = 0; i < a; ++i) 
       		{
       			cout << *temp << endl; 
       			temp++; 
       		} 
       	}
    	   	
       	int main() 
       	{
    	   	
       		int a = 1; 
       		int b = 2; 
       		int c = 3; 
       		int d = 4;
       		fun(4, a, b, c, d); 
       		system("pause"); 
       		return 0;  
    	   	
       	}
       	Output:: 
       	1
       	2
       	3
       	4
    
  4. int CCString::compare(const char * pStr)-pStr与CCString比较大小,真正比较的是字符串本身

    int CCString::compare(const char * pStr) const
    {
    	return strcmp(getCString(), pStr);//通过strcmp来比较
    }
    
  5. CCObject* CCString::copyWithZone(CCZone* pZone)复制一份CCString

    CCObject* CCString::copyWithZone(CCZone* pZone)
    {
    	CCAssert(pZone == NULL, "CCString should not be inherited.");
    	CCString* pStr = new CCString(m_sString.c_str());//重新创建了一份CCString
    	return pStr;
    }
    	
    //备注:CCZone类基于Cocosd-x,在copyWithZone中传人,主要是CCZone拥有一个CCObject,下面是CCZone的主要代码:
    	
    class CC_DLL CCZone
    {	
    public:
    	CCZone(CCObject *pObject = NULL);
    
    public:
    	CCObject *m_pCopyObject;//当初始化CCZone的时候会将CCObject传入。
    };
    	
    //备注:CCCopying实现该协议,就能复制对象
    class CC_DLL CCCopying
    {
    public:
    	virtual CCObject* copyWithZone(CCZone* pZone);
    };
    	
    //同时在CCObject中有这么个方法,用于复制对象
    CCObject* CCObject::copy()
    {
    	return copyWithZone(0);//会回调各个实现了CCCopying的copyWithZone方法
    }
    
  6. bool CCString::isEqual(const CCObject* pObject)比较两个CCObject是否相等

    bool CCString::isEqual(const CCObject* pObject)
    {
    	bool bRet = false;
    	const CCString* pStr = dynamic_cast<const CCString*>(pObject);
    	if (pStr != NULL)
    	{
    		if (0 == m_sString.compare(pStr->m_sString))
    		{
        		bRet = true;
    		}
    	}
    	return bRet;
    }
    	
    备注:
        dynamic_cast < type-id > ( expression )
        该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void*;
    	    
        如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个引用。
    	    
        dynamic_cast运算符可以在执行期决定真正的类型。如果downcast是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。如果downcast不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
    	    
        dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
    	    
        在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
    	    
        在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
    	    
        class B
        {
        public:
                int m_iNum;
                virtual void foo();
        };
        class D:public B
        {
        public:
                char* m_szName[100];
        };
        void func(B* pb)
        {
            D* pd1=static_cast<D*>(pb);
            D* pd2=dynamic_cast<D*>(pb);
        }
    		
        1.在上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;
    		
        2.但是,如果pb指向的是一个B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针.
    		
        3.另外要注意:B要有虚函数,否则会编译出错;static_cast则没有这个限制。
    		
    dynamic_cast还支持交叉转换(cross cast)
    	
    class A
    {
    public:
            int m_iNum;
            virtual void f(){}
    };
    class B:public A
    {
    };
    class D:public A
    {
    };
    void foo()
    {
        B* pb=new B;
        pb->m_iNum=100;
        D*pd1=static_cast<D*>(pb);//compile error
        D*pd2=dynamic_cast<D*>(pb);//pd2 is NULL
        delete pb;
    }
    在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错,而使用 dynamic_cast的转换则是允许的,结果是空指针。
    	
    static_cast < type-id > ( expression )
    	
    该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
    ①.	用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
    	
    ②.	用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
    	
    ③.	把空指针转换成目标类型的空指针。
    	
    ④.	把任何类型的表达式转换成void类型。
    	
    注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。
    	
    C++中的static_cast执行非多态的转换,用于代替C中通常的转换操作。因此,被做为显式类型转换使用。比如:
    	
    int i;
    float f=166.71;
    i=static_cast<int>(f);
    	
    此时结果,i的值为166。
    	
    const_cast<type_id> (expression)
    	
    该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
    	
    1.	常量指针被转化成非常量的指针,并且仍然指向原来的对象;
    2.	常量引用被转换成非常量的引用,并且仍然指向原来的对象;
    	
    volatile和const类似。举如下一例:
    	
    class B
    {
    public:
    		B() { }
    public:
    		int m_iNum;
    };
    void foo()
    {
    	const B b1;
    	//b1.m_iNum = 100; //compile error
    	// 可以做如下转换,体现出转换为指针类型
    	B *b2 = const_cast<B*>(&b1);
    	// 或者左侧也可以用引用类型,如果对b2或b3的数据成员做改变,就是对b1的值在做改变
    	B &b3 = const_cast<B&>(b1);
    	b2->m_iNum = 200;    //fine
    	b3.m_iNum = 300;    //fine
    }
    int main( int argc, char * argv[] )
    {
    	foo();
    	return 0;
    }
    使用const_cast可以返回一个指向非常量的指针(或引用)指向b1,就可以通过该指针(或引用)对它的数据成员任意改变。
    
  7. static CCString* createWithData(const unsigned char* pData, unsigned long nLen)将char*转换成CCString*类型

    CCString* CCString::createWithData(const unsigned char* pData, unsigned long nLen)
    {
    	CCString* pRet = NULL;
    	if (pData != NULL)
    	{
    		char* pStr = (char*)malloc(nLen+1);
    		if (pStr != NULL)
    		{
        		pStr[nLen] = '\0';
        		if (nLen > 0)
        		{
            		memcpy(pStr, pData, nLen);//c和c++使用的内存拷贝函数,memcpy函数的功能是从源pData所指的内存地址的起始位置开始拷贝nLen个字节到目标pStr所指的内存地址的起始位置中
        		}
            
        		pRet = CCString::create(pStr);
        		free(pStr);//释放ptr指向的存储空间
    		}
    	}
    	return pRet;
    }
    	
    备注:
        strcpy和memcpy主要有以下3方面的区别。
    	
        1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
    		
        2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
    		
        3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
    		
    关于extern char *strcpy(char* dest, const char *src)方法,其功能是把从src地址开始且含有NULL结束符的字符串复制到以dest开始的地址空间,src和dest所指内存区域不可以重叠且dest必须有足够的空间来容纳src的字符串,返回指向dest的指针
    
  8. CCString* CCString::createWithContentsOfFile(const char* pszFileName)该方法主要是传入文件的名字,将该文件转成CCString*

    CCString* CCString::createWithContentsOfFile(const char* pszFileName)
    {
    	unsigned long size = 0;
    	unsigned char* pData = 0;
    	CCString* pRet = NULL;
    	pData = CCFileUtils::sharedFileUtils()->getFileData(pszFileName, "rb", &size);//通过该方法将pszFileName文件转换成char*
    	pRet = CCString::createWithData(pData, size);//通过上文讲到的createWithData方法将pData转换成CCString*
    	CC_SAFE_DELETE_ARRAY(pData);//#define CC_SAFE_DELETE_ARRAY(p)     do { if(p) { delete[] (p); (p) = 0; } } while(0)  使用delete[]操作符删除一个C++数组p,如果p为NULL,则不进行操作
    	return pRet;
    }
    
  9. unsigned char* CCFileUtils::getFileData(const char* pszFileName, const char* pszMode, unsigned long* pSize)该方法传入文件名,以及读写模式,读出该文件的大小,是long*类型

    unsigned char* CCFileUtils::getFileData(const char* pszFileName, const char* pszMode, unsigned long * pSize)
    {
    	unsigned char * pBuffer = NULL;
    	CCAssert(pszFileName != NULL && pSize != NULL && pszMode != NULL, "Invalid parameters.");
    	*pSize = 0;
    	do
    	{
    		// read the file from hardware
    		std::string fullPath = fullPathForFilename(pszFileName);//获取该文件的完成的路径
    		FILE *fp = fopen(fullPath.c_str(), pszMode);//pszMode是指示文件读取方式的字符串:"r" = read "w" = write "rw" = read&write
        		
    		CC_BREAK_IF(!fp);
        
    		fseek(fp,0,SEEK_END);//设置文件指针fp的位置,如果执行成功,stream将指向以fromwhere为基准,偏移offset(指针偏移量)个字节的位置,函数返回0。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置,函数返回一个非0值。
    		*pSize = ftell(fp);//使用fseek函数后再调用函数ftell()就能非常容易地确定文件的当前位置,当然将文件的当前位置移到文件的末尾,然后调用函数ftell()获得当前位置相对于文件首的位移,该位移值等于文件所含字节数
    		fseek(fp,0,SEEK_SET);
    		pBuffer = new unsigned char[*pSize];
    		*pSize = fread(pBuffer,sizeof(unsigned char), *pSize,fp);//fread是一个函数。从一个文件流中读数据,最多读取count个元素,每个元素size字节,如果调用成功返回实际读取到的元素个数,如果不成功返回 0
    		fclose(fp);//关闭一个流。注意:使用fclose()函数就可以把缓冲区内最后剩余的数据输出到磁盘文件中,并释放文件指针和有关的缓冲区。
    	} while (0);
    
    	if (! pBuffer)
    	{
    		std::string msg = "Get data from file(";
    		msg.append(pszFileName).append(") failed!");
        
    		CCLOG("%s", msg.c_str());
    	}
    	return pBuffer;
    }
    	
    备注:
        1.	FILE * fopen(const char * path,const char * mode);
        文件顺利打开后,指向该流的文件指针就会被返回。如果文件打开失败则返回NULL
    	    
        2.	int fseek( FILE *stream, long offset, int origin );
        第一个参数stream为文件指针
        第二个参数offset为偏移量,正数表示正向偏移,负数表示负向偏移
        第三个参数origin设定从文件的哪里开始偏移,可能取值为:SEEK_CUR、 SEEK_END 或 SEEK_SET
        SEEK_SET: 文件开头
        SEEK_CUR: 当前位置
        SEEK_END: 文件结尾
        其中SEEK_SET,SEEK_CUR和SEEK_END依次为0,1和2.
    		
        简言之:
        fseek(fp,100L,0);把文件内部指针移动到离文件开头100字节处;
        fseek(fp,100L,1);把文件内部指针移动到离文件当前位置100字节处;
        fseek(fp,-100L,2);把文件内部指针退回到离文件结尾100字节处。
    
        3.	 long ftell(FILE *stream);
        使用fseek函数后再调用函数ftell()就能非常容易地确定文件的当前位置。
    		
        4.	size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
        buffer 用于接收数据的内存地址
        size 要读写的字节数,单位是字节
        count 要进行读写多少个size字节的数据项,每个元素是size字节.
        stream 输入流
    		
        返回值:实际读取的元素个数.如果返回值与count不相同,则可能文件结尾或发生错误.从ferror和feof获取错误信息或检测是否到达文件结尾.
    

关于CCString相关的方法,就讲到这。。。

青春是一场大雨,即使感冒了,还盼回头再淋一次...image...微笑永远是一个人身上最好看的东西...


PREVIOUS     NEXT