IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> c++实现string的写时复制技术(COW)——注释超细版 -> 正文阅读

[C++知识库]c++实现string的写时复制技术(COW)——注释超细版

c++实现string的写时复制技术(COW)——注释超细版

COW(copy-on-write

前言

当字符串进行复制的时候,如很长的字符串(2k大小),如果全部采用堆空间存储的话那是非常浪费空间的,复制一次变成4k大小,两次6k…

所以为了节约空间,在两个字符串都是相同内容的时候,将复制后的指针指向原始字符串的地址空间,然后采用引用计数的方式对该空间的引用数+1;当修改某一指针的内容时,其实就是改变了字符串数据,那么显然变成了一个新的字符串,这时候就需要给这个新的字符串重新申请一块堆空间了,然后对原始字符串的引用计数-1。

代码实现
#include <iostream>
#include <string.h>

using namespace std;

class String
{
private:
    char *_pstr;

public:
    String()
        // 申请5个字节的堆空间(4个字节的int类型的引用计数,一个字节字符串'\0'
        // 向后偏移四个字节的空间(越过引用计数直接到字符串)
        : _pstr(new char[5]() + 4)
    {
        // static_cast不能用于两个明确类型的指针进行转换,可以使用reinterpret_cast转换
        // *static_cast<int *>(_pstr - 4) = 1;      // error
        // *reinterpret_cast<int *>(_pstr - 4) = 1; // ok, 但是不安全
        *(int *)(_pstr - 4) = 1; // 指针偏移到引用计数位,初始值为1
        cout << "String()" << endl;
    }

    // 有参构造函数
    String(const char *pstr)
        : _pstr(new char[strlen(pstr) + 5]() + 4)
    {
        strcpy(_pstr, pstr);
        *(int *)(_pstr - 4) = 1;
        cout << "String(const char *pstr)" << endl;
    }

    // 拷贝构造函数,由于内容一样,所以采用浅拷贝,两个指针指向同一个空间,
    // 增加引用计数值,不重新申请内存空间
    String(const String &rhs)
        // 指针指向同一块堆空间
        : _pstr(rhs._pstr)
    {
        ++*(int *)(_pstr - 4); // 拷贝构造函数,引用计数+1
        cout << "String(const String &rhs)" << endl;
    }

    // 赋值运算符函数,还是相同的字符串,所以采用浅拷贝
    String &operator=(const String &rhs)
    {
        cout << "String operator=(const String &rhs)" << endl;
        // 判断是否是自己赋值给自己
        if (this != &rhs)
        {
            // 如果被赋值的字符串引用计数为1,那么就释放(被赋值的)原始内存空间,
            // 防止脏数据(如被赋值的数据长度大于赋值过来的数据)
            if (1 == *(int *)(_pstr - 4))
            {
                delete[](_pstr - 4);
            }

            // 两个指针指向同一个字符串,实现浅拷贝
            _pstr = rhs._pstr;
            // 对字符串引用计数+1
            ++*(int *)(_pstr - 4);
        }
        // 返回字符串
        return *this;
    }

    // 析构函数
    ~String()
    {

        // 当引用计数为1时,表明没有两个指针同时指向一块内存空间,可以直接删除
        if (1 == *(int *)(_pstr - 4))
        {
            delete[](_pstr - 4);
            _pstr = nullptr;
        }
        cout << "~String()" << endl;
    }

    class charProxy
    {
    private:
        String &_self;
        size_t _idx;

    public:
        // 需要修改数据,所以参数应该有需要修改的字符串、下标
        charProxy(String &self, size_t idx)
            : _self(self), _idx(idx) {}

        // 实现下标写操作
        char &operator=(const char &rhs)
        {
            cout << "char &operator=(const char &rhs)" << endl;
            // 判断下标合法性
            if (_idx < _self.size() && _idx >= 0)
            {
                // 如果是共享的,此时需要修改数据了所以需要申请一块新的堆空间
                if (_self.refCount() > 1)
                {
                    char *ptmp = new char[_self.size() + 5]() + 4;
                    strcpy(ptmp, _self._pstr);

                    // 由于分配了新的空间所以引用计数-1
                    --*(int *)(_self._pstr - 4);

                    // 将新的字符串的指针赋值给_pstr,这样才可以传出去
                    _self._pstr = ptmp;

                    // 改写后新的字符串的引用计数变为1
                    *(int *)(_self._pstr - 4) = 1;
                }
                // 真正赋值操作
                _self._pstr[_idx] = rhs;
                // 返回修改后的值
                return _self._pstr[_idx];
            }
            else
            {
                cout << "error idx" << endl;
                static char nullchar = '\0';
                return nullchar;
            }
        }

        // 隐式转换:将charProxy转换为char类型,免去重载新的输出流运算符
        operator char()
        {
            return _self._pstr[_idx];
        }
    };

    // 重载下标访问运算符,返回类型为charProxy类,为了区分对数据的读操作与写操作,
    // 如果写成char &operator[](size_t idx)这种,没有办法区分读下标操作还是写下标操作,
    // 如果返回类型为charProxy,那么在进行赋值操作的时候会触发charProxy的赋值运算符函数
    // 这样就去分了string的读写操作,单独读下标是不会触发charProxy的赋值运算符函数的
    charProxy operator[](size_t idx)
    {
        return charProxy(*this, idx);
    }

    // 字符串大小获取函数
    size_t size() const
    {
        return strlen(_pstr);
    }

    // 引用计数值获取函数
    int refCount() const
    {
        return *(int *)(_pstr - 4);
    }

    const char *strAddr() const
    {
        return _pstr;
    }

    // 重载输出流运算符,输出自定义类型
    friend ostream &operator<<(ostream &os, const String &rhs);
};

ostream &operator<<(ostream &os, const String &rhs)
{
    if (rhs._pstr)
    {
        os << rhs._pstr;
    }
    return os;
}

int main(int argc, char **argv)
{
    String s1("hello");
    cout << "s1 = " << s1 << endl;
    cout << "s1.refcount = " << s1.refCount() << endl;
    printf("s1.addr = %p\n", s1.strAddr());

    cout << endl;
    cout << "拷贝操作String s2 = s1" << endl;
    String s2 = s1;
    cout << "s1 = " << s1 << endl;
    cout << "s2 = " << s2 << endl;
    cout << "s1.refcount = " << s1.refCount() << endl;
    printf("s1.addr = %p\n", s1.strAddr());
    cout << "s2.refcount = " << s2.refCount() << endl;
    printf("s2.addr = %p\n", s2.strAddr());
    s2.strAddr();

    cout << endl;
    cout << "赋值操作s3 = s2" << endl;
    String s3;
    s3 = s2;
    cout << "s2.refcount = " << s2.refCount() << endl;
    printf("s2.addr = %p\n", s2.strAddr());
    cout << "s3.refcount = " << s3.refCount() << endl;
    printf("s3.addr = %p\n", s3.strAddr());

    cout << endl;
    cout << "读下标操作" << endl;
    cout << "s2[0] = " << s2[0] << ", s1 = " << s1 << endl;
    cout << "s1.refcount = " << s1.refCount() << endl;
    printf("s1.addr = %p\n", s1.strAddr());
    cout << "s2.refcount = " << s2.refCount() << endl;
    printf("s2.addr = %p\n", s2.strAddr());

    cout << endl;
    cout << "对数据写操作,将会触发写时复制技术!" << endl;
    s2[0] = 'H';
    cout << "s2[0] = " << s2[0] << ", s2 = " << s2 << endl;
    cout << "s1.refcount = " << s1.refCount() << endl;
    printf("s1.addr = %p\n", s1.strAddr());
    cout << "s2.refcount = " << s2.refCount() << endl;
    printf("s2.addr = %p\n", s2.strAddr());
    cout << "s3.refcount = " << s3.refCount() << endl;
    printf("s3.addr = %p\n", s3.strAddr());

    return 0;
}
运行结果

注意看内存地址变化,及引用计数的变化

String(const char *pstr)
s1 = hello
s1.refcount = 1
s1.addr = 0x55d2299c0e74

拷贝操作String s2 = s1
String(const String &rhs)
s1 = hello
s2 = hello
s1.refcount = 2
s1.addr = 0x55d2299c0e74
s2.refcount = 2
s2.addr = 0x55d2299c0e74

赋值操作s3 = s2
String()
String operator=(const String &rhs)
s2.refcount = 3
s2.addr = 0x55d2299c0e74
s3.refcount = 3
s3.addr = 0x55d2299c0e74

读下标操作
s2[0] = h, s1 = hello
s1.refcount = 3
s1.addr = 0x55d2299c0e74
s2.refcount = 3
s2.addr = 0x55d2299c0e74

对数据写操作,将会触发写时复制技术!
char &operator=(const char &rhs)
s2[0] = H, s2 = Hello
s1.refcount = 2
s1.addr = 0x55d2299c0e74
s2.refcount = 1
s2.addr = 0x55d2299c12a4
s3.refcount = 2
s3.addr = 0x55d2299c0e74
~String()
~String()
~String()

如果本文对你有帮助,记得一键三连哦,一键三连笑哈哈,代码能力顶呱呱!

本人能力有限,如有错误,望不吝指正;原创不易,欢迎转载,转载请注明出处!

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-28 22:43:27  更:2021-12-28 22:43:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:40:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码