[C++]String类的理解

来源:互联网 发布:ip和端口查询 编辑:程序博客网 时间:2024/06/04 14:02
如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”

的方式自动生成缺省的函数(浅拷贝)。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。
以类 String 的两个对象 a,b 为例,假设 a.m_data 的内容为“hello”, b.m_data 的内容为“world”。现将a赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data= a.m_data。 这将造成三个错误:
1. b.m_data 原有的内存没被释放,造成内存泄露;
2. b.m_data 和 a.m_data 指向同一块内存,a 或 b 任何一方变动都会影响另一方;
3. 在对象被析构时,m_data 被释放了两次。
故需要深拷贝

下面是最容易理解的一种写法:

#define _CRT_SECURE_NO_WARNINGS#include<iostream>#include<string.h>using namespace std;class String{    friend ostream& operator<<(ostream &out,String &rhs);    friend istream& operator>>(istream &in,String &rhs);public:    String(char *str = "")        :_pstr(new char[strlen(str)+1])    {        strcpy(_pstr,str);    }    String(const String& s)        :_pstr(new char [strlen(s._pstr)+1])    {        strcpy(_pstr,s._pstr);    }    String& operator=(const String& other)    //1.检查自赋值    //2.用 delete 释放原有的内存资源    //3.分配新的内存资源,并复制字符串    //4.返回本对象的引用(连等)    {        if(this != &other)        {            delete []_pstr;            _pstr = new char [strlen(other._pstr)+1];            strcpy(_pstr,other._pstr);        }        return *this;    }    ~String()    {        if(_pstr != NULL)        delete []_pstr;    }    void Debug()    {        cout<<"string:> "<<_pstr<<endl;        cout<<"count:> "<<*_ref<<endl;    }private:    char *_pstr;};ostream& operator<<(ostream &out,String &rhs){    out<<rhs._pstr;    return out;}istream& operator>>(istream &in,String &rhs){    in>>rhs._pstr;    return in;}void Fun1(){    String A("aaaaa");    String B(A);    String C = B;    C = A;    cin>>C;    cout<<C<<endl;}

下面还有一种简洁的拷贝构造和赋值运算符重载函数:
理解图:
这里写图片描述

    String(const String& s)        :_pstr(NULL)//没有指向的指针不能交换里面的内容    {        String temp(s._pstr);        std::swap(temp._pstr,_pstr);    }    String& operator=(String rhs)//运算符重载隐含了左值,故不需要赋空    //此时没有将参数赋为const String& other的原因:    //将下面实现换成注释也可完成同样功能,但重复了上面的拷贝构造内容,如换成没有注释的形式,则增强代码重用率,在下面进行交换的时候可以重复调用拷贝构造函数,也可达到赋值的目的。    {        //String temp(s._pstr);        //std::swap(temp._pstr,_pstr);        std::swap(_pstr,rhs._pstr);        return *this;    }//返回值联等  参数会产生临时变量

然而深拷贝并不见的完全好,如果多个对象都存储了相同的内容,那岂不是很浪费空间,于是我们又发明了一个叫做引用计数的东西,就是把相同内容的对象只存储一遍,用一个计数器记下他的数目,每构造一次将其置一,每拷贝构造一次将其加一,每赋值一次将其加一(原来的引用计数减一),但如果将引用计数设为 int _ref 我们每次进行操作都会对其原来的引用计数加一(其实他原来的_ref并不需要改变,甚至在调用赋值重载时还会减一),而将其换为 static int _ref 时,如果我们申请了新的对象

比如:    String A;    String B(A);    String C;

C的出现会将前面A B的引用计数一同改变,所以这也不是什么好办发,然而最好的办法是将引用计数设为指针
int * _ref 这样实现起来解容易多了,也可像库函数STL实现一样将该引用计数放于私有成员字符串的前4个字节,节省了一个私有成员的开销,这种方法与库函数中的new类似,new = 开辟空间(malloc)+调用构造函数(定位new) new operator = 开辟空间。
下面是写时拷贝的实现:

class String{    friend ostream& operator<<(ostream &out,String &rhs);    friend istream& operator>>(istream &in,String &rhs);public:    String(char *str = "")        :_pstr(new char[strlen(str)+1])        ,_ref(new int[1])//申请一个4字节的空间  new int(1)    {        strcpy(_pstr,str);        _ref[0]=1;//里面存放计数    }    String(const String &s)        :_pstr(NULL)        ,_ref(NULL)    {        _pstr = s._pstr;        _ref = s._ref;        (*_ref)++;    }    String& operator=(const String &rhs)    {        if(rhs._pstr != this->_pstr)        {            if( --(*_ref) == 0)            {                delete []_pstr;                if(_ref != NULL)//_ref可能为空                    delete []_ref;            }            _pstr = rhs._pstr;            _ref = rhs._ref;            (*_ref)++;//_red[0]++        }        return *this;    }    ~String()    {        if(--(*_ref) == 0)        {            delete []_pstr;            if(_ref != NULL)                delete []_ref;        }    }    void Debug()    {        cout<<"string:> "<<_pstr<<endl;        cout<<"count:> "<<*_ref<<endl;    }private:    char *_pstr;    int *_ref;};ostream& operator<<(ostream &out,String &rhs){    out<<rhs._pstr;    return out;}istream& operator>>(istream &in,String &rhs){    in>>rhs._pstr;    return in;}void Fun2(){    String a("qqq");    a.Debug();    String b("dfhdfhd");    //a.Debug();    String c(b);    b.Debug();    c = a;    a.Debug();    b.Debug();    c.Debug();}
2 0