C++String类与深浅拷贝

C++String类与深浅拷贝

OJ常用的String类接口

getline(cin, string) 从标准输入拿走一行给string
find / rfind() 返回字串第一次出现的下标
substr(下标) 取子串
c.str() 拿到string类封装的char* ,搭配strcpy使用

浅拷贝

编译器默认合成的拷贝构造和赋值运算符是浅拷贝,意思是从编译器的角度看到什么就原封不动拷贝到当前对象。
导致的问题:多个对象使用同一块空间,释放的空间被其他对象访问会导致程序崩溃。

以下实现深拷贝版本,将对象动态申请的资源也拷贝到当前对象。

class String {
public:
    String(const char* str = "")
    {
        if (NULL == str) {
            str = "";
        }
        _pStr = new char[strlen(str) + 1];
        strcpy(_pStr, str);
    }

    String(int len, char c = 0)
        :_pStr(new char[len + 1]) {
        fill(_pStr, _pStr + len, c);
        _pStr[len] = '\0';
    }
    //深拷贝构造
    String(const String& s)
        :_pStr(new char[strlen(s._pStr) + 1])
    {
        strcpy(_pStr, s._pStr);
    }

    String& operator=(String s) {
        swap(_pStr, s._pStr);
        return *this;
    }
    //四步赋值版本
    //String& operator=(const String& s)
    //{
    //  if (this != &s) {
    //      char* pStr = new char[strlen(s._pStr) + 1];
    //      strcpy(pStr, s._pStr);
    //      delete[] _pStr;
    //      _pStr = pStr;
    //  }
    //  return *this;
    //}
    ~String()
    {
        if (_pStr) {
            delete[] _pStr;
        }
    }
    String operator+(const String& s) {
        int len = strlen(s._pStr) + strlen(_pStr) + 1;
        String tmp(len);//借助构造函数
        strcpy(tmp._pStr, _pStr);
        strcat(tmp._pStr, s._pStr);
        return tmp;
    }

    //String operator+(String s) {
    //  int len = size() + s.size() + 1;
    //  char* tmp = new char[len];
    //  strcpy(tmp, _pStr);
    //  strcat(tmp, s._pStr);
    //  swap(tmp, s._pStr);
    //  delete[] tmp;
    //  return s;
    //}
    bool operator==(const String &str)const
    {
        if (strcmp(_pStr, str._pStr) == 0) {
            return true;
        }
        return false;
    }

    size_t size() const
    {
        return strlen(_pStr);
    }

    const char* c_str() const
    {
        return _pStr;
    }
    //取从position所指位置连续取len个字符组成子串返回  
    String& sub_str(int position, int len) {
        if (position<0 || position >= size() || len<0 || len >size()) //参数不合理,不取子串  
        {
        }
        else
        {
            if (position + len - 1 >= size())            //字符串不够长  
                len = size() - position;
            for (int i = 0, j = position; i'\0';
        }
        return *this;
    }
private:
    char* _pStr;
};
/*错误版本*/
    //交换了指针之后会调用析构函数,会释放原先_str指向的空间发生错误
    String(const String& s)
    {
        String strTmp(s._pStr);
        swap(_pStr, strTmp._pStr);
    }

浅拷贝的引用计数版

为了解决浅拷贝的资源管理问题,提出了引用计数,在每个对象里保存关于资源被引用的次数,如果在对象析构时,发现引用计数>0,则不需要释放资源。

具体实现:

思路1:用一个静态成员变量保存计数,让所有该类对象可以访问。错误!当对象没有调用拷贝构造,而是普通构造函数,它的计数和其他引用了资源的对象造成二义性。

思路2:每个对象保存一个计数指针,给每一块空间分配一个计数,拷贝构造时复制指针,析构时–自己指针的引用计数。可行!

思路3:在字符串空间前预先分配四字节用来计数,以下基于此实现!

class String {
public:

    String(const char* str = "")
    {
        if (NULL == str) {
            str = "";
        }
        _pStr = new char[strlen(str) + 1 + 4];
        _pStr += 4;
        get_ref_count() = 1;
        strcpy(_pStr, str);
    }

    String(const String& s)
        :_pStr(s._pStr)
    {
        ++get_ref_count();
    }
    String& operator=(const String& s) {
        if (this != &s) {
            _Release();
            _pStr = s._pStr;
            ++get_ref_count();
        }
        return *this;
    }
    void _Release()
    {
        --get_ref_count();
        if (get_ref_count() == 0 && _pStr) {
            delete[] (_pStr-4);
        }
    }
    ~String()
    {
        if (_pStr) {
            _Release();
        }
    }
    int& get_ref_count()
    {
        return *(int*)(_pStr - 4);
    }
    //返回值修改内容,写时拷贝
    char& operator[](size_t index)
    {
        //有两个以上对象引用了这块空间,不能修改其他对象的值
        if (get_ref_count() > 1) {
            --get_ref_count();
            String strTmp(_pStr);
            _pStr = NULL;
            swap(_pStr, strTmp._pStr);
        }
        return _pStr[index];
    }
    //[]做右值
    const char& operator[](size_t index)const
    {
        return _pStr[index];
    }
private:
    char* _pStr;
};

由于引用计数出现,必须要保证对计数和资源的操作必须是原子的,存在竟态条件。所以在多线程下这个类还不完善。

测试用例:

String s("abc");
String s2(s);
String s3("lll");
s3 = s;
s3[0] = 'w';//写时拷贝
String s4("444");
String s5(s4);
s5 = s;

你可能感兴趣的:(c/c++编程艺术)