c++ 基础类 string - 深浅拷贝 , 引用计数 , 写时拷贝

本文介绍了在C++中实现深拷贝和写时拷贝的方法,以避免浅拷贝带来的问题,如双释放错误。通过实例展示了深拷贝、引用计数和写时拷贝的具体实现过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

string 的深浅拷贝

class String
{
public:
    // 1. 构造
    String(const char *str = " ")
        :_str(new char[strlen(str)+1])
        {
            strcpy(_str, str); 
        }
    // 2. 拷贝构造
    // s2(s1)
    String(const String &str)
        :_str(str._str)
    {}
    // 3. 赋值操作符重载
    // s2 = s1
    String& operator=(const String &str)
    {
        if(this != &str)
        {
            _str = str._str;
        }
        return *this;
    }
    // 4. 析构
    ~String()
    {
        if(_str)
            delete[] _str;
    }
    // 5. C 风格字符串
    const char *c_str()
    {
        return _str;
    }
    // 6. 写时拷贝
    void CopyOnWrite();
    // 7. 随机访问
    char &operator[](size_t pos)
    {
        return _str[pos];
    }

private:
    char *_str;

};

当类里面有指针对象时,如果进行简单赋值的浅拷贝,使两个对象指向同一块内存,程序就会存在崩溃的问题

void TestString()
{
    String s1("hello, world");
    String s2 = s1;
    cout << s1.c_str() << endl;
    cout << s2.c_str() << endl;
}

Error in ./main: double free or corruption (fasttop): 0x0000000000c27010

这里因为是浅拷贝 , 所以 s1 和 s2 是指向同一块空间的 , 所以当 s1 的生命周期结束时调用析构函数已经把这块空间释放了 , 当 s2 的生命周期结束时 , 再调用析构函数 , 就成了 double free , 程序就崩溃了
为了解决这个问题 , 我们就需要用 深拷贝

深拷贝就是 拷贝 s2 时重新开一块和 s1 一样大的空间 , 并将 s1 的值拷贝下来 , 这样 s1 和 s2 指向各自的空间 , 析构时释放各自的空间

// 二. 深拷贝
class String
{
public:
    // 1. 构造
    String(const char *str = " ")
        :_str(new char[strlen(str)+1])
        {
            strcpy(_str, str); 
        }
    // 2. 拷贝构造
    // s2(s1)
    String(const String &str)
        :_str(new char[strlen(str._str) + 1])
    {
        strcpy(_str, str._str);
    }
    // 3. 赋值操作符重载
    // s2 = s1
    String& operator=(const String &str)
    {
        if(this != &str)
        {
            // 深拷贝
            char* tmp = new char[strlen(str._str) + 1];
            strcpy(tmp, str._str);
            delete[] _str;
            _str = tmp;
        }
        return *this;
    }
    // 4. 析构
    ~String()
    {
        if(_str)
        {
            delete[] _str;
            _str = NULL;
        }
    }
    // 5. C 风格字符串
    const char *c_str()
    {
        return _str;
    }
    // 6. 随机访问
    char &operator[](size_t pos)
    {
        return _str[pos];
   }
private:
    char *_str;
};

但是每次都深拷贝 , 消耗太大了 , 效率不高 , 于是我们又想到了一种方法 - 引用计数

设置一个引用计数器 , 每次拷贝的时候 , 引用计数器加一 , 以表示当前有多少个对象指向这块空间 , 析构的时候先判断引用计数 , 如果引用计数大于 1 , 那么表示还有别人正在用这块空间 , 此时只用把引用计数减一 , 等到引用计数为 1 的时候 , 才需要真正释放这块空间

那么如何设置引用计数器呢 ?

private:
    char *_str;
    int _count;

这种方法是不行的 , 因为这样每个对象都有一个独立的计数器 , 而不是共享的 , 就起不到引用计数的作用了

private:
    char *_str;
    int* _pcount;

这样就可以了 , 设置一个指针 , 让每个对象的这个指针都指向同一块空间 , 对这块空间上的值进行修改 , 从而达到引用计数的效果

~String()
{
    if(--(*m_pcount) == 0)
    {
        delete[] m_str;
        delete m_pcount;
    }
}

每次调用析构函数时 , 先检查引用计数 , 如果引用计数为 1 了 , 才真正释放空间 , 否则只用把引用计数减一就好了

引用计数解决了析构时空间被多次释放的问题 , 但是如果要对拷贝对象的值进行修改的话 ,

还需要用到 写时拷贝

    String s1("hello");
    String s2 = s1;
    cout << s1.C_str() << endl;
    cout << s2.C_str() << endl;
    s2[0] = 'x';
    cout << s1.C_str() << endl;
    cout << s2.C_str() << endl;

这里写图片描述

修改 s2 的值也会影响 s1 , 但其实他们俩指向同一块空间 , 就是同一个东西 , 修改一个 , 另一个必定受影响 . 前面说了深拷贝可以解决这个问题 , 但是还有一种更好的方法 - 写时拷贝

void CopyOnWrite()
{
    if(*m_pcount > 1)
    {
        char *tmp = new char[strlen(m_str) + 1];
        strcpy(tmp, m_str);
        --(*m_pcount);
        m_str = tmp;
        m_pcount = new int(1);
    }
}

char &operator[] (size_t pos)
{
    CopyOnWrite();
    return m_str[pos];
}

在需要对对象的值进行修改的时候 , 再开空间拷贝 , 可以节省空间 , 提高效率

void TestCopyOnWrite()
{
    TEST_HEAD;
    cow::String s1("hello");
    cow::String s2 = s1;
    cout << s1.C_str() << endl;
    cout << s2.C_str() << endl;
    s2[0] = 'x';
    cout << s1.C_str() << endl;
    cout << s2.C_str() << endl;
}

这里写图片描述

这样修改 s2 的值就不会影响 s1 了

其实 , 不光是在 “写” 的时候会拷贝 , “读” 的时候也会拷贝 , 因为 [] 是一个可读可写的接口

完整代码
#include <iostream>
#include <string.h>
#include <assert.h>
#include <stdio.h>

using namespace std;

#define TEST_HEAD printf("=====%s=====\n", __FUNCTION__)

// 实现两种方式的引用计数, 写时拷贝。

// 一. 浅拷贝
/*
   class String
   {
   public:
// 1. 构造
String(const char *str = " ")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
// 2. 拷贝构造
// s2(s1)
String(const String &str)
:_str(str._str)
{}
// 3. 赋值操作符重载
// s2 = s1
String& operator=(const String &str)
{
if(this != &str)
{
_str = str._str;
}
return *this;
}
// 4. 析构
~String()
{
if(_str)
delete[] _str;
}
// 5. C 风格字符串
const char *c_str()
{
return _str;
}
// 6. 写时拷贝
void CopyOnWrite();
// 7. 随机访问
char &operator[](size_t pos)
{
return _str[pos];
}
private:
char *_str;
};
*/

// 二. 深拷贝1.0
/*
   class String
   {
   public:
// 1. 构造
String(const char *str = " ")
:_str(new char[strlen(str)+1])
{
strcpy(_str, str);
}
// 2. 拷贝构造
// s2(s1)
String(const String &str)
:_str(new char[strlen(str._str) + 1])
{
strcpy(_str, str._str);
}
// 3. 赋值操作符重载
// s2 = s1
String& operator=(const String &str)
{
if(this != &str)
{
// 深拷贝
char* tmp = new char[strlen(str._str) + 1];
strcpy(tmp, str._str);
delete[] _str;
_str = tmp;
}
return *this;
}
// 4. 析构
~String()
{
if(_str)
{
delete[] _str;
_str = NULL;
}
}
// 5. C 风格字符串
const char *c_str()
{
return _str;
}
// 6. 写时拷贝
void CopyOnWrite();
// 7. 随机访问
char &operator[](size_t pos)
{
return _str[pos];
}
private:
char *_str;
};
*/

// 三. 深拷贝2.0
class String
{
public:
    // 1. 构造
    String(const char *str = " ")
        : _size(strlen(str))
        , _capacity(_size)
    {
        _str = new char[_size + 1];
        strcpy(_str, str);
    }
    void Swap(String &s)
    {
        swap(_str, s._str);
        swap(_size, s._size);
        swap(_capacity, s._capacity);
    }
    // 2. 拷贝构造
    // s2(s1)
    String(const String &s)
        : _str(NULL)
    {
        String tmp(s._str);
        this->Swap(tmp);
    }
    // 3. 赋值操作符重载
    // s2 = s1
    String &operator= (String &s)
    {
        this->Swap(s);
        return *this;
    }
    // 4. 析构
    ~String()
    {
        if(_str)
        {
            delete[] _str;
            _str = NULL;
        }
    }
    // 5. 返回 C 字符串
    const char *c_str()
    {
        return _str;
    }
    // 6. 写时拷贝
    void CopyOnWrite();
    // 7. 随机访问, 重载 []
    char &operator[] (size_t pos)
    {
        return _str[pos];
    }
    // 8. 扩容
    void Expand(size_t n)
    {
        if(n > _capacity)
        {
            char *tmp = new char[n + 1];
            strcpy(tmp, _str);
            delete[] _str;
            _str = tmp;
            _capacity = n;
        }
    }
    // 9. 预先开空间
    void Reserve(size_t n)
    {
        Expand(n);
    }
    // 10. 调整大小
    void Resize(size_t n, char ch = '\0')
    {
        if(n < _size)
        {
            _size = n;
            _str[_size] = '\0';
        }
        else
        {
            if(n > _capacity)
            {
                Expand(n);
            }
            for(size_t i = _size; i < n; i++)
            {
                _str[i] = ch;
            }
            _str[n] = '\0';
            _size = n;
        }
    }
    // 11. 任意位置插入字符
    void Insert(size_t pos, char ch)
    {
        assert(pos <= _size);
        if(_capacity == _size)
        {
            Expand(_capacity * 2);
        }
        int end = _size;
        while(end >= (int)pos)
        {
            _str[end + 1] = _str[end];
            --end;
        }
        _str[pos] = ch;
        ++_size;
    }
    // 12. 在任意位置插入字符串
    void Insert(size_t pos, const char *str)
    {
        assert(pos <= _size);
        int len = strlen(str);
        if(_size + len >= _capacity)
        {
            Expand(_size + len);
        }
        int end = _size;
        while(end >= (int)pos)
        {
            _str[end + len] = _str[end];
            --end;
        }
        strncpy(_str + pos, str, len);
        _size += len;
    }
    // 13. 尾插
    void PushBack(char ch)
    {
        Insert(_size, ch); // 调在任意位置插入字符的 Insert
    }
    // 14. 添加字符串
    void Append(const char *str)
    {
        Insert(_size, str); // 调在任意位置插入字符串的 Insert
    }
    // 15. 重载 +=
    // s1 += "hello"
    String &operator+= (const char *str)
    {
        this->Append(str);
        return *this;
    }
    // s1 += s2
    String &operator+= (const String &s)
    {
        *this += s._str;
        return *this;
    }
    // 16. 重载 +
    // s1 + "hello"
    String operator+ (const char *str)
    {
        String ret(*this);
        ret.Append(str);
        return ret;
    }
    // s1 + s2
    String operator+ (const String &s)
    {
        return *this + s._str;
    }
    // 17. 尾删
    void PopBack()
    {
        _str[--_size] = '\0';
    }
    // 18. 从 pos 位置开始删除 len 个字符
    void Erase(size_t pos, size_t len)
    {
        assert(pos <= _size);
        if(pos + len > _size)
        {
            _str[_size] = '\0';
            _size = pos;
        }
        else
        {
            strcpy(_str + pos, _str + pos + len);
            _size -= len;
        }
    }

    // 19. 求 size
    size_t Size()
    {
        return _size;
    }
    // 20. 求 capacity
    size_t Capacity()
    {
        return _capacity;
    }
    // 21. 判空
    bool Empty()
    {
        return _size == 0;
    }
    // 22. 查找字符
    size_t Find(char ch) const
    {
        for(size_t i = 0; i < _size; i++)
        {
            if(ch == _str[i])
                return i;
        }
        return npos;
    }
    // 23. 查找字符串
    size_t Find(const char *sub) const
    {
        char *src = _str;
        while(*src)
        {
            const char *src_tmp = src;
            const char *sub_tmp = sub;
            while( *sub_tmp && *src_tmp == *sub_tmp )
            {
                ++src_tmp;
                ++sub_tmp;
            }
            if(*sub_tmp == '\0')
            {
                return src - _str;
            }
            else
            {
                ++src;
            }
        }
        return npos;
    }
    // 24. 字符串比大小 重载 <
    bool operator< (const String &s) const
    {
        const char *str_left = _str;
        const char *str_right = s._str;
        while(*str_left && *str_right)
        {
            if(*str_left < *str_right)
            {
                return true;
            }
            else if(*str_left > *str_right)
            {
                return false;
            }
            else
            {
                ++str_left;
                ++str_right;
            }
        }
        if(*str_left == '\0' && *str_right != '\0')
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    // 25. 判断字符串是否相等 重载 ==
    bool operator== (const String &s) const
    {
        const char *str_left = _str;
        const char *str_right = s._str;
        while(*str_left && *str_right)
        {
            if(*str_left != *str_right)
            {
                return false;
            }
            else
            {
                ++str_left;
                ++str_right;
            }
        }
        if(*str_left == '\0' && *str_right == '\0')
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    // 重载 <=
    bool operator<= (const String &s) const
    {
        return (*this < s) || (*this == s);
    }
    // 重载 >
    bool operator> (const String &s) const
    {
        return !(*this <= s);
    }
    // 重载 >=
    bool operator>= (const String &s) const
    {
        return !(*this < s);
    }
    // 重载 !=
    bool operator!= (const String &s) const
    {
        return !(*this == s);
    }

private:
    char *_str;
    size_t _size;
    size_t _capacity;

public:
    static size_t npos;
};
size_t String::npos = -1;

// 四. 引用计数, 写时拷贝
namespace cow
{
    class String
    {
    public:
        // 1. 构造
        String(const char *str = " ")
        {
            m_str = new char[strlen(str) + 1];
            strcpy(m_str, str);
            m_pcount = new int(1);
        }
        // 2. 拷贝构造
        // s2(s1)
        String(const String &s)
        {
            m_str = s.m_str;
            m_pcount = s.m_pcount;
            ++(*m_pcount); // 引用计数加一
        }
        // 3. 赋值操作符的重载
        // s2 = s1
        String &operator= (const String &s)
        {
            if(m_str != s.m_str)
            {
                if(--(*m_pcount) == 0)
                {
                    delete[] m_str;
                    delete m_pcount;
                }
                m_str = s.m_str;
                m_pcount = s.m_pcount;
                ++(*m_pcount);
            }
            return *this;
        }
        // 4. 析构
        ~String()
        {
            if(--(*m_pcount) == 0)
            {
                delete[] m_str;
                delete m_pcount;
            }
        }
        // 5. 写时拷贝
        void CopyOnWrite()
        {
            if(*m_pcount > 1)
            {
                char *tmp = new char[strlen(m_str) + 1];
                strcpy(tmp, m_str);
                --(*m_pcount);
                m_str = tmp;
                m_pcount = new int(1);
            }
        }
        // 6. 随机访问 重载 []
        char &operator[] (size_t pos)
        {
            CopyOnWrite();
            return m_str[pos];
        }
        // 读时不拷贝
        const char &operator[] (size_t pos) const
        {
            cout << "const []" << endl;
            return m_str[pos];
        }

        char *C_str()
        {
            return m_str;
        }

    private:
        char *m_str;
        int *m_pcount; // 引用计数器
        size_t m_size;
        size_t m_capacity;
    };
}

void TestCopyConstructor()
{
    String s1("hello");
    String s3("world");
    cout << s1.c_str() << endl;
    cout << s3.c_str() << endl;
    s1 = s3;
    cout << s1.c_str() << endl;
    cout << s3.c_str() << endl;
    String s2(s1);
    cout << s1.c_str() << endl;
    cout << s2.c_str() << endl;
}

void TestCopyOnWrite()
{
    TEST_HEAD;
    cow::String s1("hello");
    cow::String s2 = s1;
    const cow::String s3(s1);
    cout << s1.C_str() << endl;
    cout << s2.C_str() << endl;
    // cout << s3.C_str() << endl;
    // s2[0] = 'x';
    cout << s3[1] << endl;
    cout << s1.C_str() << endl;
    cout << s2.C_str() << endl;
}

void TestString()
{
    TEST_HEAD;
    String s1("hello, world");
    String s2 = s1;
    cout << s1.c_str() << endl;
    cout << s2.c_str() << endl;
    s1.PushBack('a');
    s1.PushBack('b');
    s1.PushBack('c');
    cout << s1.c_str() << endl;
    s1 += "hahahaha";
    cout << s1.c_str() << endl;
    s1.PopBack();
    s1.PopBack();
    s1.PopBack();
    cout << s1.c_str() << endl;
    s1.Append("zxc");
    cout << s1.c_str() << endl;
    s1.Resize(30, 'a');
    cout << s1.c_str() << endl;
    cout << s1.Size() << endl;
    cout << s1.Capacity() << endl;
    cout << s1.Find('a') << endl;
    cout << (int)s1.Find("ccc") << endl;
    s1.Erase(5, 10);
    cout << s1.c_str() << endl;
    bool ret = s1 < s2;
    cout << ret << endl;
}

void TestNoCount()
{
    TEST_HEAD;
    String s1("111111111111111");
    int begin = clock();
    for(int i = 0; i < 1000000; i++)
    {
        String s2(s1);
    }
    int end = clock();
    cout << end - begin << endl;
}
void TestCount()
{
    TEST_HEAD;
    cow::String s1("111111111111111");
    int begin = clock();
    for(int i = 0; i < 1000000; i++)
    {
        cow::String s2(s1);
    }
    int end = clock();
    cout << end - begin << endl;
}

int main()
{
    TestString();
    TestCopyOnWrite();
    TestNoCount();
    TestCount();
    TestCopyConstructor();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值