浅拷贝+引用计数--写时拷贝---模拟实现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;只不过这个内存空间具与数据结构的栈具有相同的特性。 栈和队列操作 栈和队列基本操作…

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

优先级队列的概念 优先队列是一种容器适配器&#xff0c;根据严格的弱排序标准&#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;判断数组中是否含有该…

再谈c++中的多态

何为多态 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会产生出不同的状态。 多态的实现 在继承的体系下 基类中必须有虚函数(被virtual关键字修饰的成员函数)&#xff0c;在派生类中必须…

再谈c++中的继承

继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序设计的层次结构&#xff0c;体现了…

红黑树概念及其相关操作的实现

红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但它并不像AVL树一样&#xff0c;每个结点绑定一个平衡因子。但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或Black。 通过 对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c…

模拟实现STL中map和set容器

红黑树的迭代器 //红黑树的迭代器 template<class T> struct RBTreeIterator {typedef RBTreeNode<T>Node;typedef RBTreeIterator<T> Self; public:RBTreeIterator(Node* pNode nullptr):_pNode(pNode){}//具有指针操作T& operator*(){return _pNode-…

排序上---(排序概念,常见排序算法,直接插入,希尔排序,直接选择排序,堆排序)

排序的概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递增或递减的排列起来的操作。稳定性&#xff1a;假定在待排序的记录序列中&#xff0c;存在多个具有相同的关键字的记录&#xff0c;若经过排序&…

排序下---(冒泡排序,快速排序,快速排序优化,快速排序非递归,归并排序,计数排序)

排序上 排序上 交换类排序 基本思想&#xff1a;所谓交换&#xff0c;就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置&#xff0c;交换排序的特点是&#xff1a;将键值较大的记录向序列的尾部移动&#xff0c;键值较小的记录向序列的前部移动。 冒泡…

哈希的概念及其操作

哈希概念 顺序结构以及平衡树中&#xff0c;元素关键码与其存储位置之间没有对应的关系&#xff0c;因此在查找一个元素时&#xff0c;必须要经过关键码的多次比较。顺序查找时间复杂度为O(N)&#xff0c;平衡树中为树的高度&#xff0c;即O( Log2N)&#xff0c;搜索的效率取决…