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…...

大数据bug-sqoop(二:sqoop同步mysql数据到hive进行字段限制。)
一:sqoop脚本解析。 #!/bin/sh mysqlHost$1 mysqlUserName$2 mysqlUserPass$3 mysqlDbName$4 sql$5 split$6 target$7 hiveDbName$8 hiveTbName$9 partFieldName${10} inputDate${11}echo ${mysqlHost} echo ${mysqlUserName} echo ${mysqlUserPass} ec…...

Windows小记
一、域控制器升级的先决条件验证失败。 新建域时,本地 Administrator 帐户将成为域 Administrator 帐户。无法新建域,因为本地 Administrator 帐户密码不符合要求。 目前,本地 Administrator 帐户不需要密码。我们建议你使用网络用户命令行工…...

centos安装elasticsearch7.9
安装es 下载elasticsearch安装包解压安装包,并修改配置文件解压进入目录修改配置文件 添加用户,并修改所有者切换用户,运行es如何迁移旧版本的数据 下载elasticsearch安装包 下载地址如下,版本号可以替换成自己想要的。 这里需要注意一点&am…...

221、仿真-基于51单片机的智能啤酒发酵罐多点温度压力水位排水加水检测报警系统设计(程序+Proteus仿真+配套资料等)
毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 编辑 四、程序源码 资料包括: 需要完整的资料可以点击下面的名片加下我,找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方…...

C语言好题解析(三)
目录 选择题一选择题二选择题三选择题四编程题一编程题二 选择题一 以下程序段的输出结果是()#include<stdio.h> int main() { char s[] "\\123456\123456\t"; printf("%d\n", strlen(s)); return 0; }A: 12 B: 13 …...

OpenCV之remap的使用
OpenCV中使用remap实现图像的重映射。 重映射是指将图像中的某一像素值赋值到指定位置的操作:g(x,y) f ( h(x,y) ), 在这里, g( ) 是目标图像, f() 是源图像, 而h(x,y) 是作用于 (x,y) 的映射方法函数。为了完成映射过程, 需要获得一些插值为…...

leetcode 377. 组合总和 Ⅳ
2023.8.17 本题属于完全背包问题,乍一看和昨天那题 零钱兑换II 类似,但细看题目发现:今天这题是排列问题,而“零钱兑换II”是组合问题。排列问题强调顺序,而组合顺序不强调顺序。 这里先说个结论:先遍历物品…...

C++笔记之花括号和圆括号初始化区别,列表初始化和初始化列表区别
C笔记之花括号和圆括号初始化区别,列表初始化和初始化列表区别 code review! 文章目录 C笔记之花括号和圆括号初始化区别,列表初始化和初始化列表区别1.花括号{}进行初始化和圆括号()进行初始化2.列表初始化(list initialization࿰…...

git报错Add correct host key
想克隆备份的笔记库,失败。 测试连接github报错如下。 $ ssh -T gitgithub.comWARNING: POSSIBLE DNS SPOOFING DETECTED! The RSA host key for github.com has changed, and the key for the corresponding IP address 140.82.121.4 is unknown. This c…...

Kvm配置ovs网桥
环境:部署在kvm虚拟环境上(让虚拟机和宿主机都可以直接从路由器获取到独立ip) 1、安装ovs软件安装包并启动服务(一般采用源码安装,此处用yum安装) yum install openvswitch-2.9.0-3.el7.x86_64.rpm syste…...