之前的博客已经给出了如何自己定义一个string类,以及其内部应该有的操作,今天就让我们根据STL库中给出的string来看看,它重要的写实拷贝实现和一点点读时拷贝(也是写时拷贝)
创新互联坚持“要么做到,要么别承诺”的工作理念,服务领域包括:做网站、成都网站制作、企业官网、英文网站、手机端网站、网站推广等服务,满足客户于互联网时代的社旗网站设计、移动媒体设计的需求,帮助企业找到有效的互联网解决方案。努力成为您成熟可靠的网络建设合作伙伴!
1、写时拷贝(copy-on-write)
class String
{
 public:
 String(const String &str)
 :_pData(NULL)
 {
     String temp(str._pData);
     swap(_pData,temp._pData);
 }
 private:
 char *_pData;
}
void test()
{
    String s1("hello world");//构造
    String s2(s1);//拷贝构造
}这里面实现的是用空间换时间的一种方法,定义极其简单,然而大神们写出来的STL库中的string是更为精巧的。就是应用了写实拷贝的技术,防止浅拷贝发生,并且还省了空间,
那么问题来了????
Q:什么是写时拷贝呢?
A:写时拷贝就是一种拖延战术,当你真正用到的时候才去给它开辟空间,不然它只是看起来存在,实际上只是逻辑上的存在,这种方法在STL的string中体现的很明显。
由于string类是用char* 实现的,其内存都是在堆上开辟和释放的。堆上的空间利用要很小心,所以当你定义一个sring类的对象,并且想对这个对象求地址,其返回值是const char*类型,onlyread属性哦,如果还想对该地址的内容做什么改变,只能通过string给的方法去修改。
举个栗子:
#include#include using namespace std; int main() { string s1("再来一遍:hello world"); string s2(s1); //c++方式打印一个字符串的地址!!!!! //static_cast---c++中的强制类型转换,不检查 //string的c_str()方法返回值是const char * cout< (s1.c_str())< (s2.c_str())< _<)~~~~我也这么觉得 printf("%x\n",s1.c_str()); printf("%x\n",s2.c_str()); } 
(vs2010版本)
结果是不是和你想的不一样。。。。(明明应该不变的说~)
vc 6.0版本下:s1,s2的地址是一样的。这里就不进行截屏了,如果有兴趣的同学,下去可以试试哈~
那么当对s1,s2进行修改时是怎么样的呢
s1[0]='h'; s2[0]='w';
(vs2010版本)
VC6.0版本下:s1,s2的地址不一样(同vs2010版本)
所以我们得出的结论是:
当对string对象只进行拷贝构造时,发生的是写时拷贝(假拷贝),只有对其对象进行修改时(有写的操作),才对其对象另外开辟空间,进行修改。
要想达到这样的效果,在一定程度上节省了空间。
必须做到两点:内存的共享,写时拷贝。
(1)copy-on-write的原理?
         “引用计数”,程序猿就是这般机智~~~~
当对象s1实例化,调用构造,引用计数初始化=1;
当有对象对s1进行拷贝时,s1的引用计数+1;
当有对象是由s1拷贝来的或者是s1自身进行析构是,s1的引用计数进行-1;
当有对象是由s1拷贝来的或者是s1自身需要修改时,进行真拷贝,并且引用计数-1;
当引用计数==0的时候,进行真正的析构。
(2)引用计数应该如何设计在?
            关于引用计数的实现,你是不是也有这样的疑惑呢?
当类的对象之间进行共享时,引用计数也是共享的
当类中的对象从公共中脱离出来,引用计数就是它自己的了。
那么如何做到从独立--->共享--->独立的呢???
如果你想将引用计数当做String类的成员变量,那么什么样的类型适合它呢?
int _count; 那么每个对象的实例化都拥有一个自己的引用计数,无法实现共享
class String
{
    public:
        String(pData=NULL)
        :_pData(new char[strlen(pData)+1])
        ,_count(1)
        {
            strcpy(_pData,pData);
        }
        ~String()
        {
            if(--_count==0)
            {
                delete []_pData;
            }
        }
        String(String &str)
        :_pData(str._pData)
        {
            str._count++;
            _count=str._count;
        }
    private:
        char *_pData;
        int _count;
};
string s1="hello world";
string s2(s1);
//s1构造,s2拷贝构造:s1和s2指向同一空间,s1和s2的_count都变成2
//当s2先析构,s2的_count--变成1,不释放
//当s1析构时,s1的_count--变成1,不释放
//造成内存泄露static int _pCount;那么每个对象的实例化都拥有这唯一的一个引用计数,共享范围过大
class String
{
    public:
        String(pData=NULL)
        :_pData(new char[strlen(pData)+1])
        {
            _count=1;
            strcpy(_pData,pData);
        }
        ~String()
        {
            if(--_count==0)
            {
                delete []_pData;
            }
        }
        String(String &str) //不加const,不然底下的浅拷贝会出错
        :_pData(str._pData)
        {
            str._count++;
        }
    private:
        char *_pData;
        static int _count;  //静态的成员变量要在类外进行初始化
};
int String::_count=0;
string s1="hello world";
string s2(s1);
string s3("error");
//s1构造,s2拷贝构造:s1和s2指向同一空间,_count都变成2
//s3构造,_count变成1
//当s3先析构,_count--变成0,释放
//s1,s2造成内存泄露int *_pCount;可以实现引用计数。
class String
{
    public:
        String(pData=NULL)
        :_pData(new char[strlen(pData)+1])
        ,_pCount(new int(1))
        {
            strcpy(_pData,pData);
        }
        ~String()
        {
            if(--(*_pCount)==0)
            {
                delete []_pData;
                delete _pCount;
            }
        }
        String& operator=(const String *str)
        {
            if(_pData!=str._pData)
            {
                if(--(*_pCount)==0)
                {
                    delete _pCount;
                    delete []_pData;
                }
                (*str._pCount)++;
                _pCount=str._pCount;
                _pData=str._pData;
            }
            return *this;
        }
        String(String &str) //不加const,不然底下的浅拷贝会出错
        :_pData(str._pData)
        ,_pCount(str._pCount)
        {
            (*str._pCount)++;
        }
    private:
        char *_pData;
        int *_pCount;  
};这些字符串都是在堆上开辟的,那么引用计数也可以在堆上开辟,要从逻辑上,看引用计数是个指针,存次数,从物理上看,引用计数应该和字符指针放在一起,便于管理。让数据相同的对象都可以共享同一片内存。
综上,引用计数的设计如图:
(3)引用计数什么时候需要共享呢?
情况1:string s2(s1); //s2拷贝自s1,即s2中的数据和s1的一样
情况2:string s2; s2=s1;//s2的数据由s1赋值而来,即s2中的数据和s1的一样
综上所述:
string类中的拷贝构造和赋值运算符重载需要引用计数
(4)什么情况下需要进行写时拷贝
对内容有修改时
(5)c++版实现代码
class String
{    
    private:
        char *_pData;  //引用计数存在于_pData[-1]
    public:
        //构造函数
        String(pData=NULL)
        :_pData(new char[strlen(pData)+1+sizeof(int)])
        {
              //强转在头上4个字节存放引用计数的值
               (*(int *)_pData)=1;
              //恢复其字符串的长度
              _pData+=4; 
              strcpy(_pData,pData);
        }
        //拷贝构造
        String(const String&str)
        :_pData(str. _pData)
        {
           
           //(*(--(int *)str._pData)) ++;  
           //这个版本是错的,大家看看错在哪里?可以留言告诉我哦
            (*(--(int*)_pData-1)++;
        }
        //赋值运算符重载
        String& operator=(const String &str)
        {
            if(_pData!=str._pData)
            {
                if(--(*(--(int*)_pData-1)==0)
                {
                    _pData-=4;
                    delete []_pData;
                }
                
                else
                {
                    _pData=str._pData;
                    (*(--(int*)_pData-1)++;
                }
            }
            return *this;
        }
        //析构函数
        ~String()
        {
            if(--(*(--(int*)_pData-1)==0)
                {
                    _pData-=4;
                    delete []_pData;
                }  
        }  
};2、读时拷贝(copy-on-read)
当C++的STL库中的string被这么利用时:
string s1="hello world"; long begin = getcurrenttick(); for(size_t i=0;i
分享文章:通过STL中的string看写时拷贝和读时拷贝
网页路径:http://www.scyingshan.cn/article/jpgois.html

 建站
建站
 咨询
咨询 售后
售后
 建站咨询
建站咨询 
 