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销毁,又对这块空间释放一次,程序崩溃


所以我们也要显示写赋值重载函数,完成深拷贝,让每个对象有自己独立的,与赋值对象一模一样的资源空间,而不是和其他对象共享同一块资源空间的管理
完结撒花~
相关文章:
C++ string类的模拟实现
模拟实现string类不是为了造一个更好的轮子,而是更加理解string类,从而来掌握string类的使用 string类的接口设计繁多,故而不会全部涵盖到,但是核心的会模拟实现 库中string类是封装在std的命名空间中的,所以在模拟…...
Qt实现简单的漫游器
文章目录 Qt的OpenGL窗口GLSL的实现摄像机类的实现简单的漫游器 Qt的OpenGL窗口 Qt主要是使用QOpenGLWidget来实现opengl的功能。 QOpenGLWidget 提供了三个便捷的虚函数,可以重载,用来重新实现典型的OpenGL任务: paintGL:渲染…...
【c语言】文件操作
朋友们,大家好,今天分享给大家的是文件操作的相关知识,跟着我一起学习吧!! 🎈什么是文件 磁盘上的文件是文件。 但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件 程序文…...
【Unity】坐标转换经纬度方法(应用篇)
【Unity】坐标转换经纬度方法(应用篇) 解决地图中经纬度坐标转换与unity坐标互转的问题。使用线性变换的方法,理论上可以解决小范围内所以坐标转换的问题。 之前有写过[Unity]坐标转换经纬度方法(原理篇),在实际使用中,…...
element时间选择器el-date-picter使用disabledDate指定禁用的日期
需要的效果 <el-date-pickerclass"selectstyle"v-model"year"value-format"yyyy"type"year":picker-options"disabledCli"placeholder"选择年"> </el-date-picker>data() {return {disabledCli: {/…...
出学校干了 5 年外包,已经废了
如果不是女朋友和我提分手,我估计现在还没醒悟 本科大专,17年通过校招进入某软件公司做测试,干了接近5年的功能。 今年年初,感觉自己不能够在这样下去了,长时间呆在一个舒适的环境会让一个人堕落!而我已经…...
day-23 代码随想录算法训练营(19)part09
669.修剪二叉搜索树 思路一:根据二叉搜索树的特性进行中间值与去区间值判断,有三种情况:1.在区间中,所以左右子树都可能在区间中; 2.在区间外面的左侧,必然只有右子树可能存在区间中;3.在区间外…...
JVM编译优化
即时编译器 HotSpot虚拟机中内置了两个即时编译器,分别称为Client Compiler和Server Compiler,或者简称为C1编译器和C2编译器。Java8默认开启Server模式。用户可以使用“-client”或“-server”参数去指定编译模式。 C1编译器启动速度快,关注局部简单可靠的优化,比如方法…...
vue浏览器插件安装-各种问题
方法1:vue.js devtolls插件下载 https://blog.csdn.net/qq_55640378/article/details/131553642 下载地址: Tags vuejs/devtools GitHub npm install 或是 cnpm install 遇到的报错 设置淘宝镜像源(推荐使用nrm,这一步是为…...
maven工具-maven的使用-镜像仓库、本地仓、IDEA使用maven
Maven 一、为什么使用maven 添加第三方jar包jar包之间的依赖关系处理jar包之间的冲突获取第三方jar包将项目拆分成多个工程模块实现项目的分布式部署 二、maven简介 Maven项目对象模型(POM),可以通过一小段描述信息来管理项目的构建,报告和文档的…...
Mac鼠标增强工具Smooze Pro
Smooze Pro是一款Mac上的鼠标手势增强工具,可以让用户使用鼠标手势来控制应用程序和系统功能。 它支持多种手势操作,包括单指、双指、三指和四指手势,并且可以自定义每种手势的功能。例如,您可以使用单指向下滑动手势来启动Expos视…...
数据结构-单链表(C语言简单实现)
简介 以顺序结构进行数据存储时,它的特点就是可以用一组任意的存储单元存储数据元素,这组存储单元可以是连续的,也可以是不连续的,这些数据可以存在内存未被占用的任意位置。它也是有缺点的,就是在插入和删除时需要移…...
.netcore grpc身份验证和授权
一、鉴权和授权(grpc专栏结束后会开启鉴权授权专栏欢迎大家关注) 权限认证这里使用IdentityServer4配合JWT进行认证通过AddAuthentication和AddAuthorization方法进行鉴权授权注入;通过UseAuthentication和UseAuthorization启用鉴权授权增加…...
分布式 - 服务器Nginx:一小时入门系列之负载均衡
文章目录 1. 负载均衡2. 负载均衡策略1. 轮询策略2. 最小连接策略3. IP 哈希策略4. 哈希策略5. 加权轮询策略 1. 负载均衡 跨多个应用程序实例的负载平衡是一种常用技术,用于优化资源利用率、最大化吞吐量、减少延迟和确保容错配置。使用 nginx 作为非常有效的HT…...
Linux学习之基本指令二
-----紧接上文 在了解cat指令之前,我们首先要了解到Linux下一切皆文件,在学习c语言时我们就已经了解到了 对文件输入以及读入的操作(向显示器打印,从键盘读取数据),对于Linux下文件的操作,也是…...
神经网络基础-神经网络补充概念-41-梯度的数值逼近
概念 梯度的数值逼近是一种用于验证梯度计算正确性的方法,它通过近似计算梯度来与解析计算的梯度进行比较。虽然数值逼近在实际训练中不常用,但它可以用来检查手动或自动求导的实现是否正确。 代码实现 import numpy as np# 定义函数 f(x) x^2 def f…...
tornado在模板中遍历二维数组
要在Tornado模板中遍历一个二维数组,你可以使用Tornado的模板语法来实现迭代和显示数组中的每个元素。 以下是一个示例,演示如何在Tornado模板中遍历和显示二维数组的内容: template.html: <!DOCTYPE html> <html> <head&g…...
前端-初始化Vue3+TypeScript
如果使用如下命令初始化项目,项目很干净,很适合了解项目的各个结构。 npm init vitelatest如果使用如下命令初始化项目,是可以选择你需要的组件 npm init vuelatest...
龙蜥社区安全联盟(OASA)正式成立,启明星辰、绿盟、360 等 23 家厂商重磅加入
7 月 28 日,由启明星辰、绿盟、360、阿里云、统信软件、浪潮信息、中兴通讯|中兴新支点、Intel、中科院软件所等 23 家单位共同发起的龙蜥社区安全联盟(OASA,OpenAnolisSecurityAlliance)(以下简称“安全联…...
Flask-SQLAlchemy
认识Flask-SQLAlchemy Flask-SQLAlchemy 是一个为 Flask 应用增加 SQLAlchemy 支持的扩展。它致力于简化在 Flask 中 SQLAlchemy 的使用。SQLAlchemy 是目前python中最强大的 ORM框架, 功能全面, 使用简单。 ORM优缺点 优点 有语法提示, 省去自己拼写SQL,保证SQL…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
解析奥地利 XARION激光超声检测系统:无膜光学麦克风 + 无耦合剂的技术协同优势及多元应用
在工业制造领域,无损检测(NDT)的精度与效率直接影响产品质量与生产安全。奥地利 XARION开发的激光超声精密检测系统,以非接触式光学麦克风技术为核心,打破传统检测瓶颈,为半导体、航空航天、汽车制造等行业提供了高灵敏…...
小木的算法日记-多叉树的递归/层序遍历
🌲 从二叉树到森林:一文彻底搞懂多叉树遍历的艺术 🚀 引言 你好,未来的算法大神! 在数据结构的世界里,“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的,它…...
C++实现分布式网络通信框架RPC(2)——rpc发布端
有了上篇文章的项目的基本知识的了解,现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...
