0拷贝实现C字符串原地分割

     

如果你曾经写过几天python代码,你将不会再轻易喜欢上另一种新语言。你感觉它们简直是丑爆了于python而言,从名字上它们就已经败了,虽说什么语言,都无所谓,关键是合不合适,但谁TM还没个最爱?对没错,我太简直是太爱C语言了,,

python 中要分割一个字符串是这样来操作的

0拷贝实现C字符串原地分割_第1张图片

  

用过python str类再用c++string类,你会发现c++string类竟然没有split函数,你造? 

C++中你可以利用string类提供的findsubstr函数随手就写一个如同python中那样既可以让你优雅调用而又不失身份的split函数,但稍不注意,就会涉及到分割出来的string对象的多次拷贝构造,而你用其它面向对象的语言如javapython,你就不用考虑这些,它们总是在拷贝对象的引用,最多不过8个字节而已,而在C++中尽管大多版本string有引用计数,但你还是无法避免对象被拷贝构造

 

要实现0拷贝,首先要知道c语言的字符串是什么?我们来看下《C语言参考手册》上对字符串的描述,谁都可以不信,但你该信它

 0拷贝实现C字符串原地分割_第2张图片

字符串即以’\0’结尾的字符数组,就是说c语言中所有对字符串处理的且未带n(如strcpysprintf,这些函数导致的代码问题及系统bug你往往不是一眼就能发现)的函数,都是以读取到了‘\0’才认为已访问到了字符串的结尾,否则就继续读取下一个字节直到遇到一个’\0’,或一个内存越界访问,然后崩掉,,,

 

如此我们便可肆意的操作字符串了,如图拆分C字符串str,遍历字符串修改str字符串的内容,把分隔符的第一个字符替换成’\0’,一次遍历后就破坏性的把字符串原地切分成多个子字符串a,b,c,d,e,f,g,h了,然后返回各个子字符串的首地址即完成了字符串的分割

0拷贝实现C字符串原地分割_第3张图片

 

现在我们继承C++ string类实现String类,增加一个split函数,对于此次的操作我们无需对C++string类有太多了解,反正我现在所使用版本是有引用计数的并使用了copy-on-write,来减少字符串的拷贝,与内存的分配。我们只需要知道,C++ string类提供了一个c_str()成员函数可以返回一个C字符串的首地址,通过这个地址我们可以修改字符串的内容就可以了

 

代码如下

class String:public string
{
        private:;
        public:
                vector fieldVec;
                template
                string typeToStr(Type typeObj)
                {
                        ostringstream ostr;
                        ostr<                        return ostr.str();
                }
                String():string(){}
                template
                String(Type typeObj):string(typeToStr(typeObj)){}
                vector& split(const char *splitStr)
                {
                        int strLen=size();
                        int lastFindIndex=0;
                        int nowFindIndex=0;
                        char *cStr=(char*)c_str();
                        int splitStrLen=strlen(splitStr);
                        for(nowFindIndex=0;nowFindIndex!=strLen;)
                        {
                                int i=0;
                                for(i=0;i                                {
                                        if(cStr[nowFindIndex+i]!=splitStr[i])
                                        {
                                                nowFindIndex+=1;
                                                break;
                                        }
                                }
                                if(i==splitStrLen)
                                {
                                        char *endAddress=cStr+nowFindIndex;
                                        char *startAddress=cStr+lastFindIndex;
                                        char tmpChr=*(endAddress);
                                        *(endAddress)='\0';
                                        fieldVec.push_back(startAddress);
                                        lastFindIndex=nowFindIndex+splitStrLen;
                                        nowFindIndex=lastFindIndex;
                                }
                        }
                        fieldVec.push_back(cStr+lastFindIndex);
                        return fieldVec;
                }
                 void recover(const char *splitStr)
                 {
                      int size=fieldVec.size();
                      for(int i=0;i                      {
                           *(fieldVec[i]+strlen(fieldVec[i]))=*splitStr;
                      }
                 }
};

实例化一个String 对象String str (“1sp2sp3sp4sp5sp6sp7sp8”),字符串以sp为分隔符,调用split()对字符串进行分割,但是返回后string 对象str中存储的字符串内容从输出内容的第三行可以看出已经被破坏了,通过第二行与第三行输出,我们可以看出C++string的操作是以字符串长度来标识字符串已经结束了而不是’\0’

 

如果你在调用了split函数进行了分割后还要使用原str的内容,那么我会提供一个recover函数来恢愎String对象str内部存储的字符串,其实就是将每个字符串结尾的字符’\0’替换成分隔符的第一个字符’s’,看情况去调用

 

int main()

{

       String str="123sp4s5psp6sp7sp8ssp9spsp10sp11sp";

       cout<

       vector&b=str.split("sp");

cout<

       cout<

       for(int i=0;i

       {

              cout<

       }

       str.recover("sp");

       cout<

}

输出如下

0拷贝实现C字符串原地分割_第4张图片

 

确实是0拷贝?其实你要是较真的话也并不是啦,还有一个char指针,和一个vector引用的拷贝哦,32位系统下就拷贝了4*fieldVec.seze()+4字节,64位系统就拷了8*fieldVec.size()+8字节,但这并未拷贝字符串本身,这也是无论如何也必免不了的。除非你把代码直接强入到你的代码中分割出来一个字符串直接原地处理它,一共进行了str.size()+fieldVec.size()次循环

 

仔细一看其实这个String类是有bug的,split()函数中返回的是对象fieldVec的引用,用的时候可要小心啦,你的函数返回后你很有可能会访问到一个已经不存在的临时变量。你可以直接返回fieldVec对象,又要拷贝构造一次fieldVec,或把fieldVec声明为static,又要考虑多线程环境下的互斥访问

正确的做法不应该是这样?int split(const char *splitStr,vector &fieldVec)

 


我只是更喜欢python的那种自然又优雅的接口而已,但人家可以保证对象都有引用计数并且都是可以当垃圾回收,在C++中你new了一个字节,你就要记住这一个字节,什么时候,什么地点,什么条件,去delete它,,,
那么以长度标识的字符串,正确的折分方式该是怎样?

你可能感兴趣的:(C,C++)