浅拷贝+引用计数--写时拷贝---模拟实现string容器

引用计数

深拷贝
多个对象共享同一份资源时,最后能够保证该资源只被释放一次
应该由哪个对象释放资源?
由最后一个使用该资源的对象去释放
怎么知道一个对象是最后一个使用该资源的对象?
给一个计数,记录使用该资源对象的个数

实现

计数用普通整型

先来看一个例子

class string
{
public:string(char *str = ""){//如果指针为空,则初始化位空字符串if (nullptr == str)str = "";//申请空间_str = new char[strlen(str) + 1];//初始化一个对象占一个资源,引用计数+1_count = 1;//拷贝数据strcpy(_str, str);}string( string& s):_str(s._str), _count(++s._count){}string& operator=(const string& s){//自己给自己赋值,不用做任何操作if (this != &s){}return *this;}~string(){//每次释放对象计数都要减一,减完之后要看_count是不是0if (_str && 0 == --_count)delete[]_str;_str = nullptr;}//编译器生成的默认赋值运算符重载存在浅拷贝,而且会有资源泄露,没有释放资源
private:char * _str;int _count;
};

在类中增加一个变量记录使用资源的对象数
在类中增加int类型的成员变量----不行,因为这种变量每个对象都存在一份
普通的成员变量,每个对象都有一份,一个对象在修改计数时,不会影响其他对象
导致:资源没有释放而引起内存泄露

将计数变为静态成员变量

class string
{
public:string(char *str = ""){//如果指针为空,则初始化位空字符串if (nullptr == str)str = "";//申请空间_str = new char[strlen(str) + 1];//初始化一个对象占一个资源,引用计数+1_count = 1;//拷贝数据strcpy(_str, str);}string(string& s)    //静态成员变量不能再在初始化列表中使用:_str(s._str){++_count;}string& operator=(const string& s){//自己给自己赋值,不用做任何操作if (this != &s){}return *this;}~string(){//每次释放对象计数都要减一,减完之后要看_count是不是0if (_str && 0 == --_count)delete[]_str;_str = nullptr;}//编译器生成的默认赋值运算符重载存在浅拷贝,而且会有资源泄露,没有释放资源
private:char * _str;static int _count;
};int string::_count = 0;

将计数给成静态类型成员变量----不行
静态类型成员是所有对象共享,计数应该与资源个数保持一致,有多少资源就要要多少计数

计数为整型指针类型

在这里插入图片描述
一个对象修改,另外一个对象也能看见

class string{public:string(char *str = ""):_pCount(new int (1)){//如果指针为空,则初始化位空字符串if (nullptr == str)str = "";//申请空间_str = new char[strlen(str) + 1];//拷贝数据strcpy(_str, str);}string(string& s)    //静态成员变量不能再在初始化列表中使用:_str(s._str)			//两个对象共用同一份资源, _pCount(s._pCount)   //两个对象共用一个计数{++(*_pCount);}//s2 = s1//s2原来的资源将不再使用---应该给原来的计数-1//			计数非0://          计数为0: 释放掉原来的资源//s2应该与s1共享同一份资源:计数++string& operator=(const string& s){//自己给自己赋值,不用做任何操作if (this != &s){//让当前对象与其管理的资源分离开if (0 == --*_pCount){delete[]_str;delete _pCount;}//与s共享资源_str = s._str;_pCount = s._pCount;++ (*_pCount);}return *this;}~string(){//每次释放对象计数都要减一,减完之后要看_count是不是0if (_str && 0 == -- *_pCount){delete[]_str;_str = nullptr;delete _pCount;_pCount = nullptr;}}private:char * _str;int* _pCount;};

引用计数也有缺陷
如果出现这种情况

void TestString()
{bite::string s1("hello");bite::string s2(s1);bite::string s3("world");bite::string s4(s3);s3 = s1;  //s3不需要释放原来的资源,因为还有s4在用s1 = s4;	//s4是最后使用资源的对象,所以需要释放
}

这种情况程序走到末尾,4个对象共用同一块空间,如果用[]运算符去修改对象s1的值,那么其他对象也都被修改

写时拷贝

所有对象共享一份资源时,读数据不用拷贝,一但有对象要修改,则单独为该对象拷贝一份资源
所以当出现所有写操作或者可能会引起写操作的方法,都会把当前对象修改掉,所以要分离对象’

namespace bite
{class string{public:string(char *str = ""):_pCount(new int (1)){//如果指针为空,则初始化位空字符串if (nullptr == str)str = "";//申请空间_str = new char[strlen(str) + 1];//拷贝数据strcpy(_str, str);}string(string& s)    //静态成员变量不能再在初始化列表中使用:_str(s._str)			//两个对象共用同一份资源, _pCount(s._pCount)   //两个对象共用一个计数{++(*_pCount);}//s2 = s1//s2原来的资源将不再使用---应该给原来的计数-1//			计数非0://          计数为0: 释放掉原来的资源//s2应该与s1共享同一份资源:计数++string& operator=(const string& s){//自己给自己赋值,不用做任何操作if (this != &s){//让当前对象与其管理的资源分离开if (0 == --*_pCount){delete[]_str;delete _pCount;}//与s共享资源_str = s._str;_pCount = s._pCount;++ (*_pCount);}return *this;}char& operator[](size_t index){//该操作可能会改变当前对象的内容//必须:分离当前对象if (GetRef() > 1){string strtemp(_str);//构造临时对象this->swap(strtemp); //当前对象与临时对象交换}return _str[index];}~string(){//每次释放对象计数都要减一,减完之后要看_count是不是0if (_str && 0 == -- *_pCount){delete[]_str;_str = nullptr;delete _pCount;_pCount = nullptr;}}void swap(string &s){std::swap(_str, s._str);std::swap(_pCount, s._pCount);}private://获取引用计数int GetRef(){return *_pCount;}private:char * _str;int* _pCount;};	
}void TestString()
{bite::string s1("hello");bite::string s2(s1);bite::string s3("world");bite::string s4(s3);s3 = s1;  //s3不需要释放原来的资源,因为还有s4在用s1 = s4;	//s4是最后使用资源的对象,所以需要释放s1[0] = 'H';char& rc = s1[0];rc = 'H';
}

在这里插入图片描述
写时拷贝单线程底下没有问题,但在多线程下可能会出错
在这里插入图片描述

~string(){//每次释放对象计数都要减一,减完之后要看_count是不是0if (_str && 0 == -- *_pCount){delete[]_str;_str = nullptr;delete _pCount;_pCount = nullptr;}}

线程1计数减过了但是时间片到了,还没来的及与0比较。线程2过来,发现资源还存在,而且线程2时间片充足,就会去释放资源。释放完后,线程1又开始执行,发现计数已经变为0,就会把资源再释放一次,也会造成代码崩溃

模拟实现string

namespace bite
{class string{public:typedef char* iterator;public:string(const char* str = ""){if (str == nullptr)str = "";//当前对象开辟空间_size = strlen(str);_capacity = _size ;_str = new char[_capacity + 1];//拷贝元素strcpy(_str, str);}//放入n个字符chstring(size_t n, char ch):_size(n), _capacity(n), _str(new char[n + 1])//此处不能new char[_capacity],因为成员变量初始化,只跟声明顺序有关,_str先于_capacity声明,所以//先初始化{memset(_str, ch, n);_str[n] = '\0';	//最后一个位置设置为\0}//[begin,end)string(char* begin, char* end){_size = end - begin;_capacity = _size;_str = new char[_size + 1];strncpy(_str, begin, _size);_str[_size] = '\0';}string(const string& s):_size(s._size), _capacity(s._size){_str = new char[_capacity + 1];strcpy(_str, s._str);}string& operator=(const string& s){if (this != &s){int len = strlen(s._str) ;char * p = new char[len + 1];strcpy(p, s._str);delete[]_str;_str = p;_size = len;_capacity = len;}return *this;}~string(){if (_str){delete[]_str;_str = nullptr;_capacity = 0;_size = 0;}}//容量相关操作size_t size()const{return _size;}size_t capacity()const{return _capacity;}bool empty()const{return 0 == _size;}void resize(size_t newsize,char ch){size_t oldsize = _size;if (newsize > oldsize){//有效元素增多//多出的元素再空余空间能否放的下if (newsize > _capacity){reserve(newsize);}memset(_str + _size, ch, newsize-oldsize);}_size = newsize;_str[_size] = '\0';}void reserve(size_t newcapacity){size_t oldcapacity = _capacity;if (newcapacity > oldcapacity){//申请新空间char * temp = new char[newcapacity + 1];//拷贝元素strcpy(temp, _str);//释放旧空间delete[]_str;//指向新空间_str = temp;_capacity = newcapacity;}}//元素访问相关操作char& operator[](size_t index){assert(index < _size);return _str[index];}const char& operator[]( int index){assert(index < _size);return _str[index];}//元素修改操作void push_back(char ch){if (_size == _capacity)reserve(_capacity * 2);_str[_size++] = ch;_str[_size] = '\0';}string& operator+=(const char ch){push_back(ch);return *this;}string& operator+=(const string s);bool operator==(const string s);bool operator!=(const string s);bool operator>=(const string s);bool operator<=(const string s);bool operator>(const string s);bool operator<(const string s);friend ostream& operator<< (ostream& _cout, const bite::string& s){_cout << s.c_str();return _cout;}friend istream operator>>(istream _cin, string s);//迭代器iterator begin(){return _str;}iterator end(){return _str + _size;}//特殊操作size_t find(char ch, size_t pos = 0){for (size_t i = pos; i < _size; i++){if (ch == _str[i])return i;}return npos;}size_t rfind(char ch, size_t pos = npos){if (pos == npos)pos = _size - 1;for (int i = pos; i >= 0; i--){if (ch == _str[i])return i;}return npos;}string substr(size_t pos = 0, size_t n = npos){if (n == npos)n = _size;string temp(_str + pos, _str + n + pos);return temp;}const char* c_str()const{return _str;}private:size_t _capacity;  //当前空间有多大size_t _size;		//当前string里有多少个有效字符char *_str;static size_t npos;};size_t string::npos = -1;
}

要使用范围for进行打印,必须要给出begin()和end()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383148.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

详解vector容器(应用+模拟实现,vector相关练习题)

vector容器 动态的顺序表&#xff0c;数组。 vector操作 vector操作及其概念 构造 vector<int>v1;vector<int>v2(10, 5);vector<int>v3(v2);int array[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };vector<int>v4(array, array sizeof(array) / sizeof(a…

详解list容器(应用+模拟实现)

list容器 带头结点的双向循环链表 list操作 list容器的概念及其操作 构造和销毁 list<int>L1;list<int>L2(10, 5);vector<int>v{ 1, 2, 3, 4, 5, 6, 7, 8, 9 };list<int>L3(v.begin(), v.end());list<int>L4(L3);元素访问 cout << L3.…

vector和list容器有哪些区别

这个问题的本质还是在问顺序表和链表的区别 底层结构不同 vector容器list容器一段连续的空间带头结点的双向循环链表 元素访问方式 vector容器list容器支持随机访问—O(1)不支持随机访问—O(N)需要扩容不需要扩容任意位置插入元素----O(N)–搬移元素O(1) 迭代器不同 vector…

复习栈和队列,详解最小栈,栈的弹出压入序列,逆波兰表达式求值

栈和队列的概念 栈:吃进去吐出来 对列&#xff1a;吃进去拉出来 数据结构中的栈和内存中的区别 数据结构中的栈具有后进先出的特性&#xff0c;而内存中的栈是一个内存空间&#xff0c;只不过这个内存空间具与数据结构的栈具有相同的特性。 栈和队列操作 栈和队列基本操作…

CentOS7关闭防火墙

firewalld的基本使用 启动 systemctl start firewalld关闭 systemctl stop firewalld查看状态 systemctl status firewalld 开机禁用 systemctl disable firewalld开机启用 systemctl enable firewalldCentOS 7的服务管理 启动一个服务 systemctl start firewalld.service关闭…

详解优先级队列priority_queue(应用+模拟实现)

优先级队列的概念 优先队列是一种容器适配器&#xff0c;根据严格的弱排序标准&#xff0c;它的第一个元素总是它所包含的元素中最大的此上下文类似于堆&#xff0c;在堆中可以随时插入元素&#xff0c;并且只能检索最大堆元素(优先队列中位于顶部的元 素)。优先队列被实现为容…

c++中容器适配器

什么是容器适配器 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff0c;该中模式是将一个类的接口转换成客户希望的另外一个接口。 stack模拟封装 template<class T,class Container deque<T>>cl…

模拟实现priority_queue优先级队列

优先级队列 无参构造 priority_queue():c(){}区间构造 区间构造需要用到迭代器&#xff0c;而迭代器每个容器的类型不一样&#xff0c;所以用模板给出&#xff0c;初始化列表&#xff0c;把用户给进来的元素空间起始位置&#xff0c;放到优先级队列中底层空间的位置&#xf…

详解malloc,calloc,realloc原理及其模拟实现

malloc原理 malloc它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时&#xff0c;它沿连接表寻找一个大到足以满足 用户请求所需要的内存块。然后&#xff0c;将该内存块一分为二&#xff08;一块的大小与用户请求的大小相等&#xff0c;另一块的大…

c++动态内存管理题目

malloc/free和new/delete的区别 malloc/free和new/delete的共同点是&#xff1a;都是从堆上申请空间&#xff0c;并且需要用户手动释放。不同的地方是&#xff1a; malloc和free是函数&#xff0c;new和delete是操作符malloc申请的空间不会初始化&#xff0c;new可以初始化ma…

私人博客定制----封装数据库接口

封装MySQLAPI 我们先把初始化句柄和断开句柄进行一个封装 static MYSQL* MySQLInit(){ //1.初始化一个Mysql句柄建立连接 MYSQL* connect_fd mysql_init(NULL); //2.和数据库建立连接 if (mysql_real_connect(connect_fd, "127.0.0.1", "root&quo…

私人博客定制---服务器接口封装

实现服务器接口 我们用一个http服务器作为底层&#xff0c;但是c中并没有先成的http服务器&#xff0c;所以我在GitHub上找到一个牛人写的http服务器&#xff0c;拿来直接用&#xff0c;节省本项目开发的时间 这是服务器的链接地址 上面有详细的使用方法&#xff0c;本文就不再…

私人博客定制

项目背景 可行性方面 需求分析&#xff1a; 详细设计&#xff1a; 数据库设计 博客管理API的设计 标签相关API 服务器端的实现 对数据库操作进行封装 对服务器操作进行封装 客户端实现 具体操作 使用markdown 具体实现 测试 项目效果展示 维护 完整代码 项目…

初识c++中的函数模板

函数模板 函数模板概念 函数模板:编译器生成代码的一个规则。函数模板代表了一个函数家族&#xff0c;该函数模板与类型无关&#xff0c;在使用时被参数化&#xff0c;根据实参类型产生函数的特定类型版本。 函数模板格式 //要让这个函数与类型无关 //Add函数模板 template…

深入理解c++中的函数模板

非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名称。 非类型形参&#xff0c;就是用一个常量作为类(函数)模板的一个参数&#xff0c;在类(函数)模板中可将该参数当成常量来使…

c++中的IO流

c语言中的IO操作 标准类型的输入输出: 输入------>数据来源是通过键盘进行输入输出------>程序中的数据输出到控制台 c语言中: scanf:输入 printf:输出 两个函数的相同点 1 —格式串 2 —不定参数 两个函数的缺陷 1 —用户要提供数据的格式—用户要记忆大量的格式串—…

201301 JAVA2~3级---走格子

请编写一个函数&#xff08;允许增加子函数&#xff09;&#xff0c;计算n x m的棋盘格子&#xff08;n为横向的格子数&#xff0c;m为竖向的格子数&#xff09;沿着各自边缘线从左上角走到右下角&#xff0c;总共有多少种走法&#xff0c;要求不能走回头路&#xff0c;即&…

复习Linux基本操作----常见指令

Linux基本操作 ls命令 ls(list):相当于windows上的文件资源管理器 语法&#xff1a; ls [选项][目录或文件] 功能&#xff1a;对于目录&#xff0c;该命令列出该目录下的所有子目录与文件。对于文件&#xff0c;将列出文件名以及其他信息。 常用选项&#xff1a; -a 列出目…

复习Linux基础操作---权限操作

shell命令以及运行原理 Linux严格意义上说的是一个操作系统&#xff0c;我们称之为“核心&#xff08;kernel&#xff09;“ &#xff0c;但我们一般用户&#xff0c;不能直接使用kernel。而是通过kernel的“外壳”程序&#xff0c;也就是所谓的shell&#xff0c;来与kernel沟…

【剑指offer】_01 (二维数组中的查找)

题目描述 在一个二维数组中&#xff08;每个一维数组的长度相同&#xff09;&#xff0c;每一行都按照从左到右递增的顺序排序&#xff0c;每一列都按照从上到下递增的顺序排序。请完成一个函数&#xff0c;输入这样的一个二维数组和一个整数&#xff0c;判断数组中是否含有该…