String类浅拷贝,深拷贝,引用计数,写时拷贝

String类浅拷贝,深拷贝,引用计数,写时拷贝
String类:标准库类型string类表示可变长的字符序列,定义在std中,专门用来管理字符串

浅拷贝

浅拷贝,是指原对象与拷贝对象公用一份实体,仅仅是对象名字不同而已,其中任何一个对象改变都会导致其他的对象也跟着它变。如下面这段代码:

#include<iostream>
#include<cstring>
using namespace std;

class String
{
public:
    String(const char* pstr = "")
        :_pstr(new char[strlen(pstr)+1])//为字符串开辟空间
    {
        if (0 == *pstr)  //如果字符串为空
        {
            *_pstr = '\0'; 
        }
        else   //字符串不为空
        {
            strcpy(_pstr, pstr);
        }
    }
    //s2(s1)
    String(const String& s) //拷贝构造函数
    {
        _pstr = s._pstr;
    }
    // s3=s1
    String& operator=(String& s)  //运算符重载
    {
        if (_pstr != s._pstr)
        {
            _pstr = s._pstr;
        }
        return *this;
    }
    ~String()
    {
        if (NULL == _pstr)
        {
            return;
        }
        else
        {
            delete[] _pstr;
            _pstr = NULL;
        }
    }
private:

    char* _pstr;
};
void Fun1()
{
    String s1("abcd");
    String s2(s1); 
    String s3 = s2;  
    String s4;
    s4 = s3;

}

int main()
{
    Fun1();
    system("pause");
    return 0;
}

其中s1,s2,s3,s4包含的指针对象同时指向一块内存,析构时delete了这个空间四次
可是代码并没有判断内存是否有效,会导致内存泄漏。

改进1:用一个计数器来控制析构函数

   结果: 还是无法避免浅拷贝造成的内存泄漏问题
   因为:1 四个对象本来指向同一块空间,计数器应该为4,现在结果却是计数器
   只能控制与它相邻对象的计数器,对象创建完成后计数器并不统一
        2 调用4次析构函数后,本来应该四个对象同时被释放,结果却没有一个
    对象的计数器为0,也就是这块空间没有被释放

改进2 :采用 static计数器

#include<iostream>
#include<cstring>

using namespace std;

class String
{
public:
    String(const char* pstr = "")
        :_pstr(new char[strlen(pstr)+1])//为字符串开辟空间
    {
        if (0 == *pstr)  //如果字符串为空
        {
            *_pstr = '\0'; 
        }
        else   //字符串不为空
        {
            strcpy(_pstr, pstr);
        }
        _count++;
    }
    //s2(s1)
    String(const String& s) //拷贝构造函数
    {
        _pstr = s._pstr;
        s._count++;
        _count = s._count;
    }

    ~String()
    {
        if (NULL == _pstr)
        {
            return;
        }
        else
        {
            if (--_count == 0)
            {
                delete[] _pstr;
                _pstr = NULL;
            }
        }
    }
    String& operator=(String& s)  //运算符重载
    {
        if (_pstr != s._pstr)
        {
            _pstr = s._pstr;
            s._count = _count;
            _count++;
        }
        return *this;
    }

private:

    char* _pstr;
    static int _count;
};

int String::_count = 0;

void Fun1()
{
    String s1("abcd");
    String s2(s1); 
    String s3 = s2;  
    String s4;
    s4 = s3;

}

int main()
{
    Fun1();
    system("pause");
    return 0;
}
    结果:还是出现bug
    分析:1 我们创建了4个对象可是计数器却是5,因为静态成员变量为对象共享
    任何对象都可用对它进行修改,每创建一个对象,我们对计数器加1,却忽略
    创建的新对象是否与已经存在的对象占同一块空间
         2 调用4次析构函数,计数器值为1,导致空间又没有被释放

改进3:采用 指针 计数

#include<iostream>
#include<cstring>

using namespace std;

class String
{
public:
    String(const char* pstr = "")
        :_pstr(new char[strlen(pstr)+1])//为字符串开辟空间
    {
        if (0 == *pstr)  //如果字符串为空
        {
            *_pstr = '\0'; 
        }
        else   //字符串不为空
        {
            strcpy(_pstr, pstr);
            cout << "String" << endl;
        }
        _count++;
    }
    //s2(s1)
    String(const String& s) //拷贝构造函数
    {
        _pstr = s._pstr;
        s._count++;
        _count = s._count;
        cout << "Stirng kaobei" << endl;
    }

    ~String()
    {
        if (NULL == _pstr)
        {
            return;
        }
        else
        {
            cout << "~String" << endl;
            if (--_count == 0)
            {
                delete[] _pstr;
                _pstr = NULL;
            }
        }
    }
    String& operator=(String& s)  //运算符重载
    {
        if (_pstr != s._pstr)
        {
            cout << "operator=" << endl;
            _pstr = s._pstr;
            s._count = _count;
            _count++;
        }
        return *this;
    }

private:

    char* _pstr;
    static int _count;
};

int String::_count = 0;

void Fun1()
{
    String s1("abcd");
    String s2(s1); 
    String s3 = s2;  
    String s4;
    s4 = s3;

}

int main()
{
    Fun1();
    system("pause");
    return 0;
}

这里写图片描述

空间被准确释放,指针计数看起来是很完美的操作 ,然而还是有瑕疵
比如:每个对象为指针计数多创建一个指针,浪费空间,还有释放麻烦,有可能我们只记得释放_pstr,却忘记释放计数器指针的空间,造成内存泄漏。

写时拷贝

class String
{
public:
    String(const char* pstr = "")
        :_pstr(new char[strlen(pstr) + 4+1])//每次多开辟4个空间存放当前地址有几个对象
    {
        if (NULL == pstr)    //如果当前字符为空
        {
            (*(int*)_pstr) = 1; //将前4个字节用来计数
            _pstr += 4;
            *_pstr = '\0';
        }
        else   //字符串不为空
        {
            (*(int*)_pstr) = 1;//将前4个字节用来计数
            _pstr += 4;//指针向后偏移4个字节
            strcpy(_pstr, pstr); //将pstr内容拷贝到当前对象_pstr中
            cout << "String" << endl;
        }
    }
    //s2(s1)
    String(const String& s) //拷贝构造函数
        :_pstr(s._pstr)
    {
        ++(*(int*)(_pstr - 4)); //向前偏移4个字节将计数加1
        cout << "Stirng kaobei" << endl;
    }

    ~String()
    {
        if (NULL == _pstr)
        {
            return;
        }
        else
        {
            cout << "~String" << endl;
            if (--(*(int*)(_pstr-4)) == 0) //向前偏移4个字节判断计数是否为0,是0则是否
            {
                delete (_pstr-4);
                _pstr = NULL;
            }
        }
    }
    String& operator=(String& s)  //运算符重载
    {
        if (_pstr != s._pstr)
        {
            if (--(*(int*)(_pstr - 4)) == 0) //释放旧空间
            {
                delete (_pstr - 4);
                _pstr = NULL;
            }
            _pstr = s._pstr;  //指向新空间
            ++(*(int*)(_pstr - 4)); //计数加1
        }
        return *this;
    }

private:

    char* _pstr;
};


void Fun1()
{
    String s1("abcd");
    String s2(s1);
    String s3 = s2;
    String s4;
    s4 = s3;

}

int main()
{
    Fun1();
    system("pause");
    return 0;
}

这里写图片描述
这里写图片描述

深拷贝

所谓深拷贝,就是为新对象开辟一块新的空间,并将原对象的内容拷贝给新开的空间,释放时就不会牵扯到多次析构的问题

class String
{
public:
    String(const char* pstr = "")
        :_pstr(new char[strlen(pstr)+1]) //开辟空间
    {
        if (0 == *_pstr)      //如果是空内容
        {
            *_pstr = '\0';
        }
        else
        {
            strcpy(_pstr, pstr); //拷贝字符串
        }
    }
    String(String& s)  //拷贝构造函数
        :_pstr(NULL) //防止交换后temp指向随机空间,本函数调用结束导致内存泄漏致崩溃
    {
        String temp(s._pstr);//如果不给出临时变量,交换后s的值将为NULL
        std::swap(_pstr, s._pstr);
    }

    String& operator=(const String &s)//赋值运算符重载
    {
        if (_pstr != s._pstr) //防止自己给自己赋值
        {
            String temp(s._pstr); //如果不给出临时变量交换后的值为NULL
            std::swap(_pstr, temp._pstr);
        }
        return *this;
    }
    ~String()
    {
        if (NULL == _pstr)
        {
            return;
        }
        else
        {
            delete[]_pstr;
            _pstr = NULL;
        }
    }
private:
    char* _pstr;
};

void Funtest()
{
    String s1("abcd");
    String s2(s1);
    String s3 = s2;//调用拷贝构造函数(编译器会s2直接初始化s3)
    String s4;//s4对象已经存在了
    s4 = s3;//编译器会调用赋值运算符重载将s3的值赋给s4
}
int main()
{
    Funtest();
    system("pause");
    return 0;
}

猜你喜欢

转载自blog.csdn.net/yu876876/article/details/81482718