中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

通過STL中的string看寫時拷貝和讀時拷貝

發布時間:2020-07-04 13:06:01 來源:網絡 閱讀:2115 作者:momo462 欄目:編程語言

   之前的博客已經給出了如何自己定義一個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是更為精巧的。就是應用了寫實拷貝的技術,防止淺拷貝發生,并且還省了空間,通過STL中的string看寫時拷貝和讀時拷貝

那么問題來了????

Q:什么是寫時拷貝呢?

A:寫時拷貝就是一種拖延戰術,當你真正用到的時候才去給它開辟空間,不然它只是看起來存在,實際上只是邏輯上的存在,這種方法在STL的string中體現的很明顯。

由于string類是用char* 實現的,其內存都是在堆上開辟和釋放的。堆上的空間利用要很小心,所以當你定義一個sring類的對象,并且想對這個對象求地址,其返回值是const char*類型,onlyread屬性哦,如果還想對該地址的內容做什么改變,只能通過string給的方法去修改。

舉個栗子:

#include <iostream>
#include <string>
using namespace std;
int main()
{
    string s1("再來一遍:hello world");
    string s2(s1);
    //c++方式打印一個字符串的地址!!!!!
    //static_cast---c++中的強制類型轉換,不檢查
    //string的c_str()方法返回值是const char *
    cout<<static_cast<const void *>(s1.c_str())<<endl;
    cout<<static_cast<const void *>(s2.c_str())<<endl;
    //就讓我們再來復習一下c語言是如何打印一個字符串的地址
    //是不是看起來超簡單,~~~~(>_<)~~~~我也這么覺得
    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;  
};

這些字符串都是在堆上開辟的,那么引用計數也可以在堆上開辟,要從邏輯上,看引用計數是個指針,存次數,從物理上看,引用計數應該和字符指針放在一起,便于管理。讓數據相同的對象都可以共享同一片內存。

綜上,引用計數的設計如圖:通過STL中的string看寫時拷貝和讀時拷貝

(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<s1.size();i++)
{
    cout<<s1[i]<<endl;
}
cout<<getcurrenttick()-begin<<endl;
//你會發現這樣的時間和修改內容進行的寫時拷貝的時間一樣長
//這是因為string中對于operator[]無法自主進行判斷
//client是進行讀還是寫,所以一律按寫考慮


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

镇康县| 随州市| 保定市| 万源市| 溆浦县| 开封县| 雅江县| 浏阳市| 湖南省| 九寨沟县| 陵川县| 晋城| 扬中市| 巫山县| 视频| 柘城县| 庄浪县| 二连浩特市| 凤庆县| 白朗县| 石柱| 弋阳县| 哈尔滨市| 瑞安市| 霞浦县| 商河县| 图木舒克市| 迁安市| 景宁| 米泉市| 阳新县| 鹿邑县| 扎鲁特旗| 莎车县| 邓州市| 涿州市| 湖口县| 沧源| 白玉县| 阿荣旗| 南溪县|