C++ string类的模拟实现

模拟实现string类不是为了造一个更好的轮子,而是更加理解string类,从而来掌握string类的使用

string类的接口设计繁多,故而不会全部涵盖到,但是核心的会模拟实现 

库中string类是封装在std的命名空间中的,所以在模拟实现中我们也可以用命名空间来封装

string类的实现可以在.h头文件中,.cpp文件中再来实际操作string类对象

完整的代码实现:

namespace djx
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}string(const char* str = ""):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}//传统写法/*string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}*/void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//现代写法string(const string& s):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);swap(tmp);}//传统写法/*	string& operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this;}*///现代写法1/*string& operator=(const string& s){if (this != &s){string tmp(s);swap(tmp);}return *this;}*///现代写法2string& operator=(string s){swap(s);return *this;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str()const{return _str;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* s){size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str+_size, s);_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* s){append(s);return *this;}/*void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}int end = _size;while (end >=(int) pos){_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;}*/void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size+1;while (end >pos){_str[end] = _str[end-1];end--;}_str[pos] = ch;_size++;}void insert(size_t pos, const char* s){assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}int end = _size;while (end >=(int) pos){_str[end + len] = _str[end];end--;}strncpy(_str + pos, s, len);_size += len;}void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{size_t begin = pos + len;while (begin <= _size){_str[begin - len] = _str[begin];begin++;}_size -= len;}}bool operator<(const string& s)const{return strcmp(_str, s._str) < 0;}bool operator==(const string& s)const{return strcmp(_str, s._str) == 0;}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);}void resize(size_t n,char ch='\0'){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);while (_size < n){_str[_size] = ch;_size++;}_str[_size] = '\0';}}size_t find(char ch, size_t pos = 0){for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;//没有找到}size_t find(const char* s, size_t pos=0){const char* p = strstr(_str+pos, s);if (p){return p - _str;}else{return npos;}}string substr(size_t pos, size_t len = npos){string s;size_t end = pos + len;if (len == npos || pos + len >= _size){len = _size - pos;end = _size;}s.reserve(len);for (size_t i = pos; i < end; i++){s += _str[i];}return s;}size_t size()const{return _size;}size_t capacity()const{return _capacity;}void clear(){_str[0] = '\0';_size = 0;}private:char* _str;size_t _size;size_t _capacity;public:const static size_t npos;//const static size_t npos=-1;//特例//const static double npos = 1.1;  // 不支持};const size_t string::npos = -1;ostream& operator<<(ostream& out, const string& s){for (auto e : s){out << e;}return out;}istream& operator>>(istream& in, string& s){s.clear();char buff[129];size_t i = 0;char ch;ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 128){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}
}

 

构造函数和析构函数:

namespace djx
{class string{public:string(const char* str = "")//构造函数:_size(strlen(str))//在对象中的成员都要走初始化列表(成员们定义的地方),_capacity(_size){_str = new char[_capacity + 1];//多开一个空间给'\0'strcpy(_str, str);}~string()//析构函数{delete[] _str;//释放空间+调用对象的析构函数(用于清理申请的资源)_str = nullptr;_size = _capacity = 0;}const char* c_str()const//返回c格式的字符串,加const用以修饰this指针,让const对象可以调用,非const对象也是可以调用的{return _str;}private:char* _str;//指向存储字符串的空间size_t _size;//有效字符的个数size_t _capacity;//存储有效字符的容量};
}

测试:

string类的对象可以重载流插入,流提取运算符,但现在我们还没有实现,又想查看string类中字符串的内容,可以用c_str 得到_str

void test1()
{djx::string s("hello");cout << s.c_str() << endl;djx::string s2;cout << s2.c_str() << endl;
}

构造函数:

1 库中string实现:对于无参的string,初始化为空字符串,对于有参的string,则初始化为参数内容

所以可以给构造函数缺省参数,缺省值是"",注意不能是" " 因为空格也是有效字符,也没必要是"\0",因为常量字符串自动会带有一个'\0'

2 _size 和_capacity是不算'\0'的大小的,它只是一个标识字符,因为c语言需要'\0'作为字符串的结束标志,c++不能抛弃c,所以'\0'被保留下来了

   实际在开空间时也是需要为'\0'预留一个空间的

string的三种遍历方式:

operator[]重载:

#include<assert.h>
namespace djx
{class string{public:string(const char* str = ""):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str()const{return _str;}char& operator[](size_t pos)//非const对象调用,返回引用,可读可写{assert(pos < _size);return _str[pos];}const char& operator[](size_t pos)const//const对象调用,只读不可写{assert(pos < _size);return _str[pos];}size_t size()const//const对象可以调用,非const对象也可以调用{return _size;}size_t capacity()const{return _capacity;}private:char* _str;size_t _size;size_t _capacity;};
}

测试:

void test2()
{djx::string s("hello");for (size_t i = 0; i < s.size(); i++){cout << s[i];}cout << endl;
}

 迭代器:

string类的迭代器可以看作是指针,因为它完美契合指针的行为

namespace djx
{class string{public:typedef char* iterator;//非const对象的迭代器typedef const char* const_iterator;//const对象的迭代器iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}string(const char* str = ""):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str()const{return _str;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}size_t size()const{return _size;}size_t capacity()const{return _capacity;}private:char* _str;size_t _size;size_t _capacity;};
}

测试:

void test3()
{djx::string s("hello");cout << s.c_str() <<endl;djx::string::iterator it = s.begin();while (it != s.end()){(*it)++;//写cout << *it;//读it++;}cout << endl;
}

 范围for:

范围for的底层原理是迭代器,所以有了迭代器就可以使用范围for

测试:

void test4()
{djx::string s("hello");cout << s.c_str() << endl;for (auto &e : s)//不加引用就是只读{e++;//写cout << e;//读}cout << endl;
}

插入和删除操作:

push_back:

 尾插一个字符,插入之前需要检查是否需要扩容,扩容扩至原来的2倍

注意:_size==_capacity 可能是第一次插入,双方都是0,那么2*_capacity就是0,所以如果是第一次插入的扩容,可以扩4个空间

扩容可以使用reserve:

1 开n个空间,实际上reserve要开n+1个空间,因为要存'\0'

2 拷贝数据到新空间

3 释放旧空间

4 指向新空间


append:

 插入之前要检查是否需要扩容:原有效字符个数+要插入的有效字符个数>_capacity则扩容

operator+=:

目前阶段的完整代码:

#include<assert.h>
namespace djx
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}string(const char* str = ""):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str()const{return _str;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* s){size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str+_size, s);_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* s){append(s);return *this;}size_t size()const{return _size;}size_t capacity()const{return _capacity;}private:char* _str;size_t _size;size_t _capacity;};
}

测试:

 

void test5()
{djx::string s("hello");cout << s.c_str() << endl;s.push_back(' ');s.append("world");cout << s.c_str() << endl;s += '#';s += "!!!!!!!!!";cout << s.c_str() << endl;djx::string s2;s2 += '#';s2 += "!!!!!!!!!!";cout << s2.c_str() << endl;
}

insert:

 插入一个字符,版本1:

在pos位置插入一个字符,就需要从'\0'开始直到pos位置,将这些位置上的数据全部向后挪动一位

不要忘记把'\0'一并移走 

注意:如果是头插,pos==0,且end是size_t类型,那么循环的结束条件是end<pos

即end<0,但由于end是size_t类型是不会<0的,让end写成int类型,那么end可以达到-1,为避开隐式类型转换,将end由int提升为size_t,所以pos也要强制为int类型,这样当pos为0时,end可以达到-1,-1<0结束循环 

 版本2:

 版本1利用强转来解决循环条件的结束问题,版本2不强转:

将end作为最后一个要挪动的数据的最终位置,end==pos+1的位置时,pos位置上的值已经挪到pos+1上了,当end==pos位置时,结束循环

插入常量字符串:

 可以尾插,所以pos可以是_size

1 检查原有效字符个数+要插入的有效字符格式是否>_capacity,大于则扩容

2 从'\0'位置开始,一直到pos位置上的数据,将它们全部向后挪动len步

3 将常量字符串拷贝到_str+pos的位置,只要拷贝len个,不要使用strcpy全部拷贝,因为会拷贝到'\0'

erase:

有两种情况:

一:从pos位置开始,有多少删多少

1 当没有给len传参时,len使用缺省值npos,(size_t类型的-1,很大的数字,但是一个字符串不会有那么大,所以npos通常可以理解为有多少要多少),即从pos位置开始全部删除

2  传参给len,但若pos+len>=_size (pos+len是要删除的最后一个数据的下一个位置),也是从pos位置开始全部删除

方法:在pos位置给'\0'

二:从pos位置开始,删除len个字符 

pos+len是合法的,那么从pos+len位置开始一直到'\0',要将它们全部向前移动len步,覆盖效果

不要忘记移动'\0'

 len给一个缺省值npos(const修饰的静态变量,值为-1)

 静态成员变量不在对象中,不走初始化列表,在类外定义,给初始值-1

但是 const修饰的静态整型成员变量是一个特例,可以在声明的地方给缺省值

其实在常规情况下,成员变量声明的地方给缺省值,就代表它们在走初始化列表的时候可以被初始化

流插入和流提取重载:

流插入:

    ostream& operator<<(ostream& out, const string& s){for (auto e : s){out << e;}return out;}

会有cout<<s<<s2的情况,所以有ostream类型的返回值 ,out出了作用域还在所以可以用传引用返回提高效率

流提取:

        void clear(){_str[0] = '\0';_size = 0;}
    istream& operator>>(istream& in, string& s){s.clear();//在向string类的对象输入内容时,要情况原有的所有数据char buff[129];size_t i = 0;char ch;ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 128){buff[i] = '\0';s += buff;i = 0;//从头再来}ch = in.get();}if (i != 0)//可能字符串的长度没有达到128个{buff[i] = '\0';s += buff;}return in;}

会有cin>>s>>s2的情况,所以有istream类型的返回值

在输入的字符串很长的情况下,若是提取一个字符便+=到string类的对象中,难免会有多次扩容,

若以为了提高效率,可以开一个能存储129个字符的buff数组,提取的字符先存储到buff数组中

当有了128个字符时,加入'\0',再将buff数组中的所有字符以字符串的形式一并+=到string类的对象中 

buff数组就像一个蓄水池,水满了就全部拿走,然后再接着蓄水,满了再全部拿走

注意:cin和scanf一样,不能提取到空格或者'\n',scanf中用getchar可以提取任意字符,那在cin中,有get可以提取任意字符

测试:

void test6()
{djx::string s("hello world");cout << s.c_str() << endl;//没有重载流插入运算符之前,打印string类对象的内容s.insert(5, '%');cout << s.c_str() << endl;s.insert(s.size(), '%');cout << s.c_str() << endl;s.insert(0, '%');cout << s.c_str() << endl;djx::string s2("hello world");s2.insert(5, "abc");cout << s2 << endl;//重载流插入运算符之后,打印string类对象的内容s2.insert(0, "xxx");cout << s2 << endl;s2.erase(0, 3);cout << s2 << endl;s2.erase(5, 100);cout << s2 << endl;s2.erase(2);cout << s2 << endl;}

void test7()
{djx::string s("hello world");cout << s << endl;cin >> s;cout << s << endl;
}

 

 

 目前为止的完整代码:

#include<assert.h>
namespace djx
{class string{public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}string(const char* str = ""):_size(strlen(str)),_capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str()const{return _str;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* s){size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str+_size, s);_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* s){append(s);return *this;}/*void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}int end = _size;while (end >=(int) pos){_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;}*/void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size+1;while (end >pos){_str[end] = _str[end-1];end--;}_str[pos] = ch;_size++;}void insert(size_t pos, const char* s){assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}int end = _size;while (end >=(int) pos){_str[end + len] = _str[end];end--;}strncpy(_str + pos, s, len);_size += len;}void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{size_t begin = pos + len;while (begin <= _size){_str[begin - len] = _str[begin];begin++;}_size -= len;}}size_t size()const{return _size;}size_t capacity()const{return _capacity;}void clear(){_str[0] = '\0';_size = 0;}private:char* _str;size_t _size;size_t _capacity;public:const static size_t npos;//const static size_t npos=-1;//特例//const static double npos = 1.1;  // 不支持};const size_t string::npos = -1;ostream& operator<<(ostream& out, const string& s){for (auto e : s){out << e;}return out;}istream& operator>>(istream& in, string& s){s.clear();char buff[129];size_t i = 0;char ch;ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 128){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}
}

关系运算符重载:

        bool operator<(const string& s)const{return strcmp(_str, s._str) < 0;}bool operator==(const string& s)const{return strcmp(_str, s._str) == 0;}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);}

resize:

        void resize(size_t n,char ch='\0'){if (n <= _size)//删除效果{_str[n] = '\0';_size = n;}else//插入效果{reserve(n);while (_size < n){_str[_size] = ch;_size++;}_str[_size] = '\0';}}

ch的缺省值给'\0',若是没有传参给ch,那么就用'\0'填充 

分三种情况:

n<=_size:删除效果,保留前n个有效字符

_size<n<_capacity : 无需扩容,直接插入

n>_capacity :先扩容,再插入

测试:

void test8()
{djx::string s("hello world");cout << s<< endl;s.resize(5);cout << s << endl;s.resize(25, 'x');cout << s << endl;
}

 find:

查找一个字符:

        size_t find(char ch, size_t pos = 0){for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;//没有找到}

pos为0,若没有传参给pos则默认从头开始找

查找字符串:

	    size_t find(const char* s, size_t pos=0){const char* p = strstr(_str+pos, s);//指向匹配字符串的首字符if (p){return p - _str;}else{return npos;//找不到}}

测试:

void test()
{djx::string s("hello world");size_t i = s.find("world");cout << i << endl;
}

 

 

substr:

        string substr(size_t pos, size_t len = npos){string s;size_t end = pos + len;if (len == npos || pos + len >= _size)//从pos位置开始有多少取多少{len = _size - pos;end = _size;}s.reserve(len);//提前开空间,避免多次扩容for (size_t i = pos; i < end; i++){s += _str[i];}return s;//传值返回}

拷贝构造:

传统写法和现代写法在效率上区别不大,只是代码简洁性的区分

传统写法:

        //传统写法string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}

自己开和s一样大的空间,拷贝数据

现代写法:

        void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//现代写法string(const string& s):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);//调用构造函数swap(tmp);}

 

 

让构造函数去生成tmp,再与tmp交换

当tmp销毁时,调用tmp的析构函数会把this指向的对象,它申请的资源给释放掉

注意:this指向的对象中的成员变量都要走初始化列表(是它们定义的地方)

那么如果不给this指向对象的成员变量初始化,那么this指向的对象中的_str指向的是一块随机的空间,是野指针,不能随意释放这块不属于自己的空间

所以需要在初始胡列表的地方给this指向对象中的成员变量初始化

赋值重载:

传统写法:

        //传统写法string& operator=(const string& s){if (this != &s){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);delete[] _str;//释放旧空间_str = tmp;//指向新空间_size = s._size;_capacity = s._capacity;}return *this;}

1 tmp指向一块与s一模一样的空间

2 释放_str指向的空间

3 _str指向tmp指向的空间

现代写法:

版本1:

        //现代写法1string& operator=(const string& s){if (this != &s){string tmp(s);//调用拷贝构造生成tmpswap(tmp);}return *this;}

版本2:

 

//现代写法2string& operator=(string s)//拷贝构造生成s{swap(s);return *this;}

 s销毁,会释放原本由*this对象申请的空间

测试:

void test9()
{djx::string s("test.cpp.tar.zip");size_t i = s.find('.');cout << i << endl;djx::string sub = s.substr(i);cout << sub << endl;djx::string s2("https://legacy.cplusplus.com/reference/string/string/rfind/");//分割 协议、域名、资源名djx::string sub1;djx::string sub2;djx::string sub3;size_t i1 = s2.find(':');if (i1 != djx::string::npos){sub1 = s2.substr(0, i1);}else{cout << "找不到i1" << endl;}size_t i2 = s2.find('/', i1 + 3);if (i2 != djx::string::npos){sub2 = s2.substr(i1+3, i2-(i1+3));}else{cout << "找不到i2" << endl;}sub3 = s2.substr(i2 + 1);cout << sub1 << endl;cout << sub2 << endl;cout << sub3 << endl;
}

 注意1:

 

 substr是传值返回,s对象出了作用域之后就销毁了,会生成一个临时对象,由s拷贝构造生成

若是我们没有写拷贝构造函数,编译器默认生成的拷贝构造函数对于对象中内置类型的成员只会完成浅拷贝(值拷贝),即临时对象和s对象管理同一块空间,但是s出了作用域之后会调用析构函数,这块空间会被释放掉

 

substr返回的临时对象再拷贝构造给sub对象,没写拷贝构造函数,编译器生成的拷贝构造依然是值拷贝,那么sub对象和临时对象管理同一块空间,临时对象任务完成后,销毁,再次对已经释放的空间进行释放,导致程序崩溃

这里还涉及编译器优化的问题:

substr拷贝构造生成临时对象,再由临时对象拷贝构造生成sub,连续的拷贝构造动作,编译器直接一步优化为一个拷贝构造:让s在销毁前先拷贝构造生成sub,即使是优化了,也因为浅拷贝的问题导致程序崩溃-> sub 和s管理同一块资源空间,s销毁,释放一次这块空间,等sub销毁时,再一次释放这块空间

所以拷贝构造必须由我们来写,完成深拷贝,让每个对象有自己独立的资源空间 

注意2 :

 

解决拷贝构造问题后,由原来的浅拷贝变为了深拷贝,那么substr返回的临时对象就有和s一模一样的独立资源空间

但是若是我们没有写赋值重载函数,那么编译器默认生成的赋值重载函数对于对象中的内置类型的成员只会浅拷贝,即sub1和临时对象管理同一块空间,且sub1原来管理的资源空间丢失,导致内存泄漏,且临时对象销毁,对它管理的资源空间释放一次,当sub1销毁,又对这块空间释放一次,程序崩溃

 

 所以我们也要显示写赋值重载函数,完成深拷贝,让每个对象有自己独立的,与赋值对象一模一样的资源空间,而不是和其他对象共享同一块资源空间的管理

完结撒花~

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

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

相关文章

使用PostgreSQL构建强大的Web应用程序:最佳实践和建议

PostgreSQL是一个功能强大的开源关系型数据库,它拥有广泛的用户群和活跃的开发社区。越来越多的Web应用选择PostgreSQL作为数据库 backend。如何充分利用PostgreSQL的特性来构建健壮、高性能的Web应用?本文将给出一些最佳实践和建议。 一、选择合适的PostgreSQL数据类型 Pos…

【Vue】Mixin 混入

Vue Mixin 混入 1.简介 混入&#xff08;mixin&#xff09;提供了一种非常灵活的方式&#xff0c;来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项&#xff08;如data、methods、mounted等等&#xff09;。当组件使用混入对象时&#xff0c;所有混入对象的…

鸿蒙剥离 AOSP 不兼容 Android 热门问题汇总,不吹不黑不吵

上周发了一篇 《鸿蒙终于不套壳了&#xff1f;纯血 HarmonyOS NEXT 即将到来》的相关资讯&#xff0c;没想到大家「讨&#xff08;fa&#xff09;论&#xff08;xie&#xff09;」的热情很高&#xff0c;莫名蹭了一波流量&#xff0c;虽然流量对我来说也没什么用&#xff0c;但…

私密数据采集:隧道爬虫IP技术的保密性能力探究

作为一名专业的爬虫程序员&#xff0c;今天要和大家分享一个关键的技术&#xff0c;它能够为私密数据采集提供保密性能力——隧道爬虫IP技术。如果你在进行敏感数据采集任务时需要保护数据的私密性&#xff0c;那么这项技术将是你的守护神。 在进行私密数据采集任务时&#xff…

图像去雨-雨线清除-图像处理-(计算机作业附代码)

背景 多年来&#xff0c;图像去雨已经被广泛研究&#xff0c;使用传统方法和基于学习的方法。然而&#xff0c;传统方法如高斯混合模型和字典学习方法耗时&#xff0c;并且无法很好地处理受到严重雨滴影响的图像块。 算法 通过考虑雨滴条状特性和角度分布&#xff0c;这个问…

【Leetcode】98. 验证二叉搜索树

一、题目 1、题目描述 给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下: 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。示例1: 输入:root = …

马上七夕到了,用各种编程语言实现10种浪漫表白方式

目录 1. 直接表白&#xff1a;2. 七夕节表白&#xff1a;3. 猜心游戏&#xff1a;4. 浪漫诗句&#xff1a;5. 爱的方程式&#xff1a;6. 爱心Python&#xff1a;7. 心形图案JavaScript 代码&#xff1a;8. 心形并显示表白信息HTML 页面&#xff1a;9. Java七夕快乐&#xff1a;…

QT的布局与间隔器介绍

布局与间隔器 1、概述 QT中使用绝对定位的布局方式&#xff0c;无法适用窗口的变化&#xff0c;但是&#xff0c;也可以通过尺寸策略来进行 调整&#xff0c;使得 可以适用窗口变化。 布局管理器作用最主要用来在qt设计师中进行控件的排列&#xff0c;另外&#xff0c;布局管理…

Android 远程真机调研

背景 现有的安卓测试机器较少&#xff0c;很难满足 SDK 的兼容性测试及线上问题&#xff08;特殊机型&#xff09;验证&#xff0c;基于真机成本较高且数量较多的前提下&#xff0c;可以考虑使用云测平台上的机器进行验证&#xff0c;因此需要针对各云测平台进行调研、比较。 …

服装定制小程序

如今&#xff0c;人们对时尚的追求已不仅仅停留在传统的购买与穿搭上&#xff0c;而是更加注重个性化和定制化的需求。为满足这一需求&#xff0c;乔拓云网推出了一款创新的服装定制小程序&#xff0c;为用户提供定制专属时尚的全新旅途。 通过进入【乔拓云】后台&#xff0c;用…

Ordinals 之后,以太坊铭文协议 Ethscriptions 如何再塑 NFT 资产形态

随着加密市场的发展&#xff0c;NFT 赛道逐渐形成了其独有的市场。但在加密熊市的持续影响下&#xff0c;今年 NFT 赛道的发展充满坎坷与挑战。据 NFTGO 数据显示&#xff0c;截至 8 月 7 日&#xff0c;与去年相比&#xff0c;NFT 市值总计约 56.4 亿美元&#xff0c;过去 1 年…

搜狗拼音暂用了VSCode及微信小程序开发者工具快捷键Ctrl + Shit + K 搜狗拼音截图快捷键

修改搜狗拼音的快捷键 右键--更多设置--属性设置--按键--系统功能快捷键--系统功能快捷键设置--取消Ctrl Shit K的勾选--勾选截屏并设置为Ctrl Shit A 微信开发者工具设置快捷键 右键--Command Palette--删除行 微信开发者工具快捷键 删除行&#xff1a;Ctrl Shit K 或…

无脑入门pytorch系列(四)—— scatter_

本系列教程适用于没有任何pytorch的同学&#xff08;简单的python语法还是要的&#xff09;&#xff0c;从代码的表层出发挖掘代码的深层含义&#xff0c;理解具体的意思和内涵。pytorch的很多函数看着非常简单&#xff0c;但是其中包含了很多内容&#xff0c;不了解其中的意思…

深入理解内存 —— 函数栈帧的创建与销毁

前言 一位优秀的程序员&#xff0c;必须对内存的分布有深刻的理解&#xff0c;在初学编程的时候&#xff0c;往往有诸如以下很多问题困扰着初学者&#xff0c;而通过今天的分享&#xff0c;我们就可以通过自己的观察&#xff0c;将这些问题统统解决掉 局部变量是怎么创建的&…

keepalived集群

keepalived概述 keepalived软件就是通过vrrp协议来实现高可用功能。 VRRP通信原理 VRRP就是虚拟路由冗余协议&#xff0c;它的出现就是为了解决静态路由的单点故障。 VRRP是通过一种竞选一种协议机制来将路由交个某台VRRP路由器。 VRRP 用IP多播的方式&#xff08;多播地…

C语言中常见的一些语法概念和功能

常用代码&#xff1a; 程序入口&#xff1a;int main() 函数用于定义程序的入口点。 输出&#xff1a;使用 printf() 函数可以在控制台打印输出。 输入&#xff1a;使用 scanf() 函数可以接收用户的输入。 条件判断&#xff1a;使用 if-else 语句可以根据条件执行不同的代码…

【力扣每日一题】2023.8.15 字符中的查找与替换

目录 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 代码&#xff1a; 题目&#xff1a; 示例&#xff1a; 分析&#xff1a; 题目很长&#xff0c;简而言之就是检查字符串中对应索引的位置是否有特定的字符串&#xff0c;如果有&#xff0c;那么替换&#xff0c;返…

Ceph如何操作底层对象数据

1.基本原理介绍 1.1 ceph中的对象(object) 在Ceph存储中&#xff0c;一切数据最终都会以对象(Object)的形式存储在硬盘&#xff08;OSD&#xff09;上&#xff0c;每个的Object默认大小为4M。 通过rados命令&#xff0c;可以查看一个存储池中的所有object信息&#xff0c;例如…

Optional的基础运用

Optional的基础运用 简介代码示例 简介 代码示例 package org.example;import org.junit.Test;import java.util.Optional;public class OptionalTest {Testpublic void advance() {String str "hello";str null;// of(T t):封装数据t生成Optional对象&#xff0c…