C++深拷贝与浅拷贝(实现String类)

浅拷贝:

1.什么是浅拷贝? 浅拷贝会出现什么问题?

所谓浅拷贝,指的是在对象复制时,只是对对象中的数据成员进行简单的复制,默认拷贝构造函数执行的也是浅拷贝。简单的说,浅拷贝就是值传递,将源空间里面的内容复制到目标空间中。

存在缺陷:多个指针可能共用管理一块内存空间,在释放时,导致对一块空间的多次释放,造成内存泄露。

深拷贝:

2. 什么是深拷贝?

在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间。

深拷贝与浅拷贝:

3.浅拷贝与深拷贝的不同之处:

用深动形象的语言来说,如果把拷贝看作下面这幅图,那么,浅拷贝只能拷走美人鱼的头,水下部分它将无法操作,而深拷贝 不仅可以拷走头,还可以操作水下隐含的部分。
图一

好了,说到这里,咱们可能对深浅拷贝或多或少的有了一些认识,那么,下面,我们将实现一个String 类,来更加具体权威的解释深浅构造函数。

4.完成String类-普通版(浅拷贝)

#include<iostream>
using namespace std;
class String
{
    public :
      String(const char*pStr="")//构造函数
        {
          if(NULL==pStr)
            {
                _pStr=new char[1];
                *_pStr='\0';
                }
        else 
        {
             _pStr=new char[strlen(pStr)+1];
             strcpy(_pStr,pStr);
        }
            }
    String(const String& s)//拷贝构造函数
            :_pStr(s._pStr)
        {    
            //s2已经释放,但s1不知道,会对空间再次释放
            } 
    String& operator=(const String& s)//赋值运算符重载
    {
        if(this!=&s)
        {
            _pStr=s._pStr;//内存泄露
        }
    return *this;
    }
~String ()//析构函数
{
  if(_pStr)
   {
      delete[] _pStr;
        _pStr=NULL;         
     }
}
private: 
    char* _pStr;

};
void FunTest()
{
    String s1("hello");
    //String s2(s1);
    String s3;
    s3=s1;
}

int main ()
{
    FunTest();
    /*String s1("hello");
    String s2(s1);
    String s3(NULL);
    String s4(s1);
    s3=s4;
    s3=s1;*/
    return 0;
}
该例证明了浅拷贝会存在多个对象共用同一块空间,在调用析构函数销毁空间时,会出现对一块空间多次释放的情况,导致内存崩溃

5. 完成String类深拷贝—简洁版

#include<iostream>
using namespace std;
class String
{
public :
//构造函数************************************************
    String(const char*pStr="")  
    {
        if(NULL==pStr)
        {
            _pStr=new char[1];
            *_pStr='\0';
        }
        else 
        {
            _pStr=new char[strlen(pStr)+1];
             strcpy(_pStr,pStr);
        }
            }
//拷贝构造函数********************************************
    String(const String& s)
        :_pStr(NULL)//选择最佳
        {
            //_pStr=new char[1];//第二选择
            String strTemp(s._pStr );
            swap(_pStr,strTemp._pStr);
            } 
//赋值 运算符重载******************************************
//方式一:
    String& operator=(const String& s)
    {
        if(this!=&s)
        {
            String strTemp(s._pStr);
            //String strTemp(s) ;
            swap(_pStr,strTemp._pStr) ;
        }
    return *this;
    }
//方式二:
//String& operator=(const String& s)
//  {
//      String strTemp(s) ;
//      swap(_pStr,strTemp._pStr) ;
//return *this;
//}
    //方式三:
    //String& operator=( String s)
    //{
    //  swap(_pStr,s._pStr );
    //  return *this;
    //}
//三种赋值运算符重载解决方案, 第一种方式为最优方案
//析构函数*****************************************
~String ()
{
  if(_pStr)
   {
      delete[] _pStr;
        _pStr=NULL;         
  }
}
private: 
    char* _pStr;
    int _count;
};

void  FunTest()
{
    String s1("Hello");
    String s2(s1);
    s2=s1;
}
int main ()
{
    FunTest();
    /*String s1("hello");
    String s2(s1);
    String s3(NULL);
    String s4(s1);
    s3=s4;
    s3=s1;*/
    return 0;
}

6.引用计数:

A.什么是引用计数?
在开辟空间时,为了记录该空间有多少对象在共用它,也就是说有多少指针指向它,采用再开辟一个空间的方式,记录该空间被指向的次数,这种方式被称为引用计数。
图二
B.用引用计数实现String时,引用计数可以普通的成员变量?为什么?
解析:引用计数不可以为普通的成员变量,因为一旦出了作用域,该空间被销毁,达不到想要的效果

C.用引用计数实现String时,引用计数可以类的静态成员变量吗? 为什么?
解析:类的静态成员变量,但在需要另外开辟空间时,采用这种方式就

#include<iostream>
using namespace std;
class String
{
public :
    //构造函数
    String(const char* pStr="")
    {
        if(NULL==pStr)
        {
            _pStr=new char[1];
            *_pStr='\0';
        }
        else 
        {
            _pStr=new char[strlen(pStr)+1];
             strcpy(_pStr,pStr);
             }
    _count=1;
    }
String (const String& s)
    :_pStr(s._pStr)

   {
       _count++;
      }

~String ()
{
    if(_pStr&&(0==--*_count))
    {
        delete[] _pStr;
        _pStr=NULL;
    }
}
private: 
    char* _pStr;
    static int _count;
    };

int String::  _count=0;
void FunTest()
{
    String s1("hello");
    String s2(s1);

    String s3;
}

int main()
{
    FunTest();
    return 0;
}

为了在释放的时候,防止忘记释放引用计数所开辟的空间,所以尽量采用new [ ]
的方式来开辟空间,与delete[ ]搭配使用。

7.完成引用计数版本的String类—该引用计数也属于浅拷贝

    //************引用计数*******************************
#include<iostream>
using namespace std;
class String
{
public :
    //构造函数
    String(const char* pStr="")
        :_pCount(new int (1))
    {
        if(NULL==pStr)
        {
            _pStr=new char[1];
            *_pStr='\0';
        }
        else 
        {
            _pStr=new char[strlen(pStr)+1];
             strcpy(_pStr,pStr);
        }
    }
String (const String& s)
    :_pStr(s._pStr)
    ,_pCount(s._pCount)

   {
       ++(*_pCount);
      }

String& operator=(const String& s)
{
    if(_pStr!=s._pStr)//被赋值的对象与当前对象不是同一块空间
    {
        if(_pStr&&0==--*_pCount)
        {
            delete [] _pStr;
            delete _pCount;
        }
        _pStr =s._pStr;
        _pCount=s._pCount;
        ++_pCount;
    }
    return *this;
}
~String ()
{
    if(_pStr&&(0==--*_pCount))
    {
        delete[] _pStr;
        _pStr=NULL;
        delete _pCount;
        _pCount=NULL;
    }
}
private: 
    char* _pStr;
     int* _pCount;
};
void FunTest()
{
    String s1("hello");
    String s2(s1);
    String s3;
}
int main()
{
    FunTest();
    return 0;
}

7. 完成COW(写时拷贝版的String)

(COW不是奶牛)

//****写时拷贝:如果要朝当前对象写东西,最好使用这种方式*********
//单线程不会有问题
#include<iostream>
using namespace std;
class String
{
public :
    //构造函数
    String(const char* pStr="")
    {
        if(NULL==pStr)
        {
            _pStr=new char[1+4];
            _pStr+=4;
            *_pStr='\0';
        }
        else 
        {
            _pStr=new char[strlen(pStr)+1+4];
            _pStr+=4;
             strcpy(_pStr,pStr);
        }
        GetRaf()=1;
    }
String (const String& s)
    :_pStr(s._pStr)


   {
       GetRaf()++;
      }

String& operator=(const String& s)
{
    if(_pStr!=s._pStr)//被赋值的对象与当前对象不是同一块空间
    {
        Release();
        _pStr =s._pStr;
        ++GetRaf();
    }

    return *this;
}

~String ()
{
    Release();
}

char& operator [] (size_t index)
{
    if(GetRaf()>1)//如果当前空间不止存放的一个对象
    {
        char* pTemp=new char [strlen(_pStr)+1+4];
        *(int*)pTemp=1;
        pTemp+=4;
        strcpy(pTemp,_pStr);
        --GetRaf();//一定是在改变指针指向之前
        _pStr=pTemp;
    }
        return _pStr[index];
}
const char& operator [](size_t index)const 
{
    return _pStr[index];
}
private:
    int& GetRaf()
    {
            return *((int*)_pStr-1);
    }
    void Release()
    {
        if(_pStr&&(0==--GetRaf()))
            {
            _pStr-=4;
            delete[] _pStr;
            _pStr=NULL;
            }

    }
private: 
    char* _pStr;
};

void FunTest()
{
    String s1("hello");
    String s2(s1);
    s2[0]='w';
}

int main()
{
    FunTest();
    return 0;
}

下面,在这里提出两点关于string类来说非常重要点:
一.熟悉库中string类的每个接口,查文档,熟悉库中的string类。
二.调研vs和linux系统下string类的结构,他们是否采用用深拷贝原理实现。

设计并实现一个动态整型数组Vect,要求: (1)实现构造函数重载,可以根据指定的元素个数动态创建初始值为0的整型数组,或根据指定的内置整型数组动态创建整型数组。 (2)设计拷贝构造函数和析构函数,注意使用深拷贝。 (3)设计存取指定位置的数组元素的公有成员函数,并进行下标越界,若越界则输出“out of boundary”。 (4)设计获取数组元素个数的公有成员函数。 (5)设计用于输出数组元素的公有成员函数,元素之间以空格分隔,最后以换行符结束。 在main函数中按以下顺序操作: (1)根据内置的静态整型数组{1,2,3,4,5}构造数组对象v1,根据输入的整型数构造数组对象v2。 (2)调用Vect的成员函数依次输出v1和v2的所有元素。 (3)输入指定的下标及对应的整型数,设置数组对象v1的指定元素。 (4)根据数组对象v1拷贝构造数组对象v3。 (5)调用Vect的成员函数依次输出v1和v3的所有元素。 设计并实现一个动态整型数组Vect,要求: (1)实现构造函数重载,可以根据指定的元素个数动态创建初始值为0的整型数组,或根据指定的内置整型数组动态创建整型数组。 (2)设计拷贝构造函数和析构函数,注意使用深拷贝。 (3)设计存取指定位置的数组元素的公有成员函数,并进行下标越界,若越界则输出“out of boundary”。 (4)设计获取数组元素个数的公有成员函数。 (5)设计用于输出数组元素的公有成员函数,元素之间以空格分隔,最后以换行符结束。 在main函数中按以下顺序操作: (1)根据内置的静态整型数组{1,2,3,4,5}构造数组对象v1,根据输入的整型数构造数组对象v2。 (2)调用Vect的成员函数依次输出v1和v2的所有元素。 (3)输入指定的下标及对应的整型数,设置数组对象v1的指定元素。 (4)根据数组对象v1拷贝构造数组对象v3。 (5)调用Vect的成员函数依次输出v1和v3的所有元素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值