目录
2.10 Non-member function overloads
2.10.3 relational operators(string)
一、模拟实现接口总览
Member functions
Memberfunctions//构造函数string(constchar*str="") //析构函数~string() //拷贝构造 -- 现代写法string(conststring&s) //赋值重载 -- 现代写法2string&operator=(strings) //iteratorsiteratorbegin() iteratorend() //Capacitysize_tsize()constsize_tcapacity()constboolempty()const//更改容量大小voidreserve(size_tn) //调整字符串大小voidresize(size_tn, charch='\0') //清空字符串voidclear() //Element accesschar&operator[](size_tpos) constchar&operator[](size_tpos)const//modify//尾插一个字符voidpush_back(charc) //尾插一个字符串voidappend(constchar*str) //+= 一个字符string&operator+=(charc) //+= 一个字符串string&operator+=(constchar*str) //交换两个字符串voidswap(string&s) //String operationsconstchar*c_str()const// 返回字符c 在string中第一次出现的位置size_tfind(charc, size_tpos=0)const// 返回子串s在string中第一次出现的位置size_tfind(constchar*s, size_tpos=0)const//在pos位置上插入字符c,并返回该字符的位置string&insert(size_tpos, charc) //在pos位置上插入字符串s,并返回该字符的位置string&insert(size_tpos, constchar*s) //删除pos位置上的元素,并返回该元素的下一个位置string&erase(size_tpos, size_tlen=npos)
Non-member function overloads
Non-memberfunctionoverloadsostream&operator<<(ostream&out, conststring&s) istream&operator>>(istream&in, string&s)
上面只是挑一些常用的进行模拟实现,要把自己实现的写在自己命名的命名空间里面,否则与库中的 string 会产生冲突
注:string 类模拟实现,最主要是实现 string 类的构造、拷贝构造、赋值运算符重载以及析构函数,面试也经常喜欢考这几个
二、string模拟实现
2.1 构造函数
构造函数的参数设置成缺省参数,不传参默认构造空字符串
//构造函数string(constchar*str="") { _size=strlen(str);//字符串大小_capacity=_size;//构造时,容量大小默认与字符串大小相同_str=newchar[_capacity+1];//为字符串开辟空间(多开一个用于存放'\0')strcpy(_str, str);//将C字符串拷贝到已开好的空间}
2.2 析构函数
string 类的析构函数需要我们自己写,因为每个 string 类对象都开辟有一块空间,不释放会造成内存泄漏
//析构函数~string() { delete[] _str; //释放_str指向的空间_str=nullptr; _size=_capacity=0; }
2.3 拷贝构造函数
这里需要注意浅拷贝和深拷贝的问题
浅拷贝:
也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规
简单说就是 新拷贝出来的对象的指针 和 原对象的指针 指向的内存空间是同一块空间,其中一个对象的改动会对另一个对象造成影响,进行析构的时候会对同一份空间释放两次,造成非法访问
深拷贝:
如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出,一般情况都是按照深拷贝方式提供
深拷贝:原对象 与 新拷贝对象 互相独立,其中任何一个对象的改动不会对另外一个对象造成影响
很明显,string 类的拷贝构造需要深拷贝,拷贝构造有两种写法,传统写法和现代写法,一般比较推荐现代写法
2.3.1 传统写法
开辟一块和原对象空间大小一样的空间,然后将原对象的字符串拷贝过去,接着把原对象的其他成员变量也赋值过去即可
//拷贝构造函数//传统写法string(conststring&s) { _size=s._size; _capacity=s._capacity; _str=newchar[_capacity+1];//申请空间strcpy(_str, s._str);//拷贝字符串到新空间}
2.3.2 现代写法
现代写法与传统写法的思想不同:复用构造函数构造一个 tmp对象,对 tmp 初始化的对象是原对象的字符串,然后再将 tmp对象 与 新拷贝对象的数据交换即可,新拷贝对象先用初始化列表设置为空,新拷贝对象并没有开辟空间。新拷贝对象的 _str 与原对象的 _str 指向的也不是同一块空间,是互相独立的
//拷贝构造 -- 现代写法string(conststring&s) :_str(nullptr) , _size(0) ,_capacity(0) { stringtmp(s._str);//复用构造函数,构造 tmp对象swap(tmp);//交换 }
swap 函数是后面实现的,并不是使用库中的 swap
2.4 赋值运算符重载
与拷贝构造函数类似,赋值运算符重载函数的模拟实现也涉及深浅拷贝问题,赋值运算符重载也是进行深拷贝
2.4.1 传统写法
赋值运算符重载函数的传统写法与拷贝构造函数的传统写法类似,只是左值的 _str 在开辟新空间之前需要先将原来的空间释放掉,并且在进行操作之前还需判断是否是自己给自己赋值,若是自己给自己赋值,则无需进行任何操作
//赋值重载//传统写法string&operator=(conststring&s) { if (this==&s)//检查自我赋值 { return*this; } delete[] _str; _size=s._size; _capacity=s._capacity; _str=newchar[_capacity+1]; strcpy(_str, s._str); return*this;//返回左值,目的是为了支持连续赋值}
2.4.2 现代写法
现代写法 -- 1
复用拷贝构造函数,拷贝构造一个 tmp对象,对 tmp 初始化的对象是原对象,然后将该 tmp对象与 新拷贝对象进行交换交换,图就不画了,与上面的拷贝构造现代写法图类似
//赋值重载 -- 现代写法1string&operator=(conststring&s) { if (this==&s)//检查自我赋值 { return*this; } stringtmp(s);//复用拷贝构造函数,用s拷贝构造出对象tmpswap(tmp); return*this; }
现代写法 -- 2
也是复用拷贝构造,但是参数不再是传引用传参,而是传值形参,而传值传参则会自动调用拷贝构造函数,但是这种写法无法检查自我赋值的情况,但是这种情况几乎不会出现,推荐这种写法
//赋值重载 -- 现代写法2string&operator=(strings)//传值传参自动调用拷贝构造,比如,s1 = s2,先把s2拷贝到s,s再交换给this,也就是s1{ swap(s); return*this; }
2.5 iterator
string 类中的迭代器实际上就是指针,只是给指针起了一个别名叫 iterator 而已
typedef char* iterator;//迭代器
2.5.1 begin
直接返回字符串第一个元素的地址
iteratorbegin() { return_str; }
2.5.2 end
直接返回字符串中最后一个字符的后一个字符的地址(即’\0’的地址)
iteratorend() { return_str+_size; }
范围 for 的底层就是迭代器
2.6 Capacity
2.6.1 size
size 用于获取字符串当前的有效长度(不包括’\0’)
size_tsize()const{ return_size; }
2.6.2 capacity
capacity函数用于获取字符串当前的容量
size_tcapacity()const{ return_capacity; }
2.6.2 empty
判断有效字符串是否为空,为空返回 true,否则返回 false
boolempty()const{ return_size==0; }
2.6.3 reserve
reserve 规则:
- n 大于原字符串的 capacity,此时 reserve 函数会将capacity 扩容到 n
- n 小于等于原字符串的 capacity,capacity 容量不变 (不缩容)
//更改容量大小voidreserve(size_tn) { if (n>_capacity)//n大于现有容量,增容,n小于现有容量则不改变容量 { char*tmp=newchar[n+1]; strcpy(tmp, _str); delete[] _str; _str=tmp; _capacity=n;//更新容量 } }
2.6.4 resize
resize 规则:
- n 小于原字符串的 size,此时 resize 函数会将原字符串的 size 改为 n,但不会改变 capacity
- 大于原字符串的 size,但小于其 capacity,此时 resize 函数会将 size 后面的空间全部设置为字符 c 或默认的‘\0’
- 大于原字符串的 capacity,此时 resize 函数会将原字符串扩容,然后将size 后面的空间全部设置为字符 c 默认的‘\0’
//调整字符串大小voidresize(size_tn, charch='\0') { if (n>_size)//n大于原字符串长度,增容,末尾追加字符 { reserve(n); for (size_ti=_size; i<n; i++) { _str[i] =ch; } _size=n; _str[_size] ='\0'; } else//小于等于 { _str[n] ='\0'; _size=n; } }
2.6.5 clear
clear 函数用于清空有效字符串
//清空有效字符串voidclear() { _str[0] ='\0'; _size=0; }
2.7 Element access
2.7.1 operator[ ]
[ ]运算符的重载是为了让 string对象能像C字符串一样,通过 [ ] +下标 的方式获取字符串对应位置的字符,就像数组可以可以通过下标访问元素
//可读可写char&operator[](size_tpos) { assert(pos<_size); return_str[pos]; } //只读constchar&operator[](size_tpos)const{ assert(pos<_size); return_str[pos]; }
2.8 Modify
2.8.1 push_back
push_back函数的作用就是在当前字符串的后面尾插上一个字符,尾插之前首先需要判断是否需要增容,若需要,则调用reserve函数进行增容,然后再尾插字符,注意尾插完字符后需要在该字符的后方设置上’\0’,否则打印字符串的时候会出现非法访问,因为尾插的字符后方不一定就是’\0’
//尾插一个字符voidpush_back(charc) { if (_size==_capacity)//判断是否需要增容 { size_tnewCapacity=_capacity==0?4 : _capacity*2; reserve(newCapacity); } _str[_size] =c; ++_size; _str[_size] ='\0'; }
2.8.2 append
append 函数的作用是在当前字符串的后面尾插一个字符串
//尾插一个字符串voidappend(constchar*str) { size_tlen=strlen(str); if (_size+len>_capacity)//判断是否需要增容 { reserve(_size+len); } strcpy(_str+_size, str);//将str尾插到字符串后面_size+=len; }
2.8.3 operator+=
+= 一个字符直接复用 push_back
//+= 一个字符string&operator+=(charc) { push_back(c); return*this; }
+= 一个字符串直接复用 append
//+= 一个字符串string&operator+=(constchar*str) { append(str); return*this; }
2.9 String operations
2.9.1 c_str
c_str 函数用于获取对象C类型的字符串
constchar*c_str()const{ return_str; }
2.9.2 find
在字符串中查找一个字符
// 返回字符c 在string中第一次出现的位置size_tfind(charc, size_tpos=0)const{ assert(pos<_size); while (pos<_size) { if (_str[pos] ==c) { returnpos;//找到目标字符,返回其下标 } pos++; } returnnpos;//没有找到目标字符,返回npos}
在字符串中查找一个子串
// 返回子串s在string中第一次出现的位置size_tfind(constchar*s, size_tpos=0)const{ assert(pos<_size); constchar*ptr=strstr(_str+pos, s); if (ptr==nullptr)//没有找到 { returnnpos; } else { returnptr-_str;//返回字符串第一个字符的下标 } }
2.9.3 insert
insert函数的作用是在字符串的任意位置插入字符或是字符串
任意位置插入字符:
//在pos位置上插入字符c,并返回该字符的位置string&insert(size_tpos, charc) { assert(pos<=_size); if (_size==_capacity)//判断是否需要增容 { size_tnewCapacity=_capacity==0?4 : _capacity*2; reserve(newCapacity); } size_tend=_size+1; while (pos<end)//挪动数据 { _str[end] =_str[end-1]; end--; } _str[pos] =c; _size++; return*this; }
任意位置插入字符串:
/在pos位置上插入字符串s,并返回该字符的位置string&insert(size_tpos, constchar*s) { assert(pos<=_size); size_tlen=strlen(s); if (_size+len>_capacity) { reserve(_size+len); } size_tend=_size+len; while (pos+len-1<end) { _str[end] =_str[end-len]; end--; } strncpy(_str+pos, s, len); _size+=len; return*this; }
2.9.3 erase
erase函数的作用是删除字符串任意位置开始的 len 个字符,分两种情况:
- pos位置及其之后的有效字符都需要被删除
- pos位置及其之后的有效字符只需删除一部分
//删除pos位置上 len 个的元素string&erase(size_tpos, size_tlen=npos) { assert(pos<_size); if (len==npos||pos+len>=_size)//说明pos位置及其后面的字符都被删除 { _str[pos] ='\0'; _size=pos; } else//说明pos位置及其后方的有效字符需要保留一部分 { strcpy(_str+pos, _str+pos+len); _size-=len; } return*this; }
2.10 Non-member function overloads
2.10.1 operator<<
重载 <<运算符是为了让string对象能够像内置类型一样使用 <<运算符直接输出打印,实现直接使用对象进行遍历即可可输出
ostream&operator<<(ostream&out, conststring&s) { for (size_ti=0; i<s.size(); ++i) { out<<s[i]; } returnout; }
2.10.2 operator>>
重载 >>运算符是为了让 string对象能够像内置类型一样使用>>运算符直接输入,输入前我们需要先将对象的C字符串置空,然后从标准输入流读取字符,直到读取到 ’ ‘ 或是 ’\n’ 便停止读取
istream&operator>>(istream&in, string&s) { s.clear();//清空charbuff[128] = { '\0' }; size_ti=0; charch=in.get();//读取一个字符while (ch!=' '&&ch!='\n')//当读取到的字符不是空格或'\n'的时候继续读取 { if (i==127)//满了 { s+=buff; i=0; } buff[i++] =ch; ch=in.get();//继续读取字符 } if (i>0) { buff[i] ='\0'; s+=buff; } returnin; }
2.10.3 relational operators(string)
关系运算符有 >、>=、<、<=、==、!= 这六个,但是对于 C++中任意一个类的关系运算符重载,我们均只需重载其中的两个,剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现
三、string模拟实现代码
(1)string.h
usingnamespacestd; namespacefy{ classstring { public: //构造函数string(constchar*str="") { _size=strlen(str);//字符串大小_capacity=_size;//构造时,容量大小默认与字符串大小相同_str=newchar[_capacity+1];//为字符串开辟空间(多开一个用于存放'\0')strcpy(_str, str);//将C字符串拷贝到已开好的空间 } //析构函数~string() { delete[] _str; //释放_str指向的空间_str=nullptr; _size=_capacity=0; } //拷贝构造函数传统写法//string(const string& s)//{// _size = s._size;// _capacity = s._capacity;// _str = new char[_capacity + 1];//申请空间// strcpy(_str, s._str);//拷贝字符串到新空间//}//拷贝构造 -- 现代写法string(conststring&s) :_str(nullptr) , _size(0) ,_capacity(0) { stringtmp(s._str);//复用构造函数,构造 tmp对象swap(tmp);//交换 } //赋值重载传统写法//string& operator=(const string& s)//{// if (this == &s)//检查自我赋值// {// return *this;// }// delete[] _str;// _size = s._size;// _capacity = s._capacity;// _str = new char[_capacity + 1];// strcpy(_str, s._str);// // return *this;//返回左值,目的是为了支持连续赋值//}赋值重载--现代写法1//string& operator=(const string& s)//{// if (this == &s)//检查自我赋值// {// return *this;// }// string tmp(s);//复用拷贝构造函数,用s拷贝构造出对象tmp// swap(tmp);// return *this;//}//赋值重载 -- 现代写法2string&operator=(strings)//传值传参自动调用拷贝构造,比如,s1 = s2,先把s2拷贝到s,s再交换给this,也就是s1 { swap(s); return*this; } //-------------------------------------------------------------------//iteratorstypedefchar*iterator;//迭代器iteratorbegin() { return_str; } iteratorend() { return_str+_size; } //-------------------------------------------------------------------//Capacitysize_tsize()const { return_size; } size_tcapacity()const { return_capacity; } boolempty()const { return_size==0; } //更改容量大小voidreserve(size_tn) { if (n>_capacity)//n大于现有容量,增容,n小于现有容量则不改变容量 { char*tmp=newchar[n+1]; strcpy(tmp, _str); delete[] _str; _str=tmp; _capacity=n;//更新容量 } } //调整字符串大小voidresize(size_tn, charch='\0') { if (n>_size)//n大于原字符串长度,增容,末尾追加字符 { reserve(n); for (size_ti=_size; i<n; i++) { _str[i] =ch; } _size=n; _str[_size] ='\0'; } else//小于等于 { _str[n] ='\0'; _size=n; } } //清空有效字符串voidclear() { _str[0] ='\0'; _size=0; } //-------------------------------------------------------------------//Element access//可读可写char&operator[](size_tpos) { assert(pos<_size); return_str[pos]; } //只读constchar&operator[](size_tpos)const { assert(pos<_size); return_str[pos]; } //-------------------------------------------------------------------//modify//尾插一个字符voidpush_back(charc) { if (_size==_capacity)//判断是否需要增容 { size_tnewCapacity=_capacity==0?4 : _capacity*2; reserve(newCapacity); } _str[_size] =c; ++_size; _str[_size] ='\0'; } //尾插一个字符串voidappend(constchar*str) { size_tlen=strlen(str); if (_size+len>_capacity)//判断是否需要增容 { reserve(_size+len); } strcpy(_str+_size, str);//将str尾插到字符串后面_size+=len; } //+= 一个字符string&operator+=(charc) { push_back(c); return*this; } //+= 一个字符串string&operator+=(constchar*str) { append(str); return*this; } //交换两个字符串voidswap(string&s) { std::swap(_str, s._str); std::swap(_size, s._size); std::swap(_capacity, s._capacity); } //-------------------------------------------------------------------//String operationsconstchar*c_str()const { return_str; } // 返回字符c 在string中第一次出现的位置size_tfind(charc, size_tpos=0)const { assert(pos<_size); while (pos<_size) { if (_str[pos] ==c) { returnpos;//找到目标字符,返回其下标 } pos++; } returnnpos;//没有找到目标字符,返回npos } // 返回子串s在string中第一次出现的位置size_tfind(constchar*s, size_tpos=0)const { assert(pos<_size); constchar*ptr=strstr(_str+pos, s); if (ptr==nullptr)//没有找到 { returnnpos; } else { returnptr-_str;//返回字符串第一个字符的下标 } } //在pos位置上插入字符c,并返回该字符的位置string&insert(size_tpos, charc) { assert(pos<=_size); if (_size==_capacity)//判断是否需要增容 { size_tnewCapacity=_capacity==0?4 : _capacity*2; reserve(newCapacity); } size_tend=_size+1; while (pos<end)//挪动数据 { _str[end] =_str[end-1]; end--; } _str[pos] =c; _size++; return*this; } //在pos位置上插入字符串s,并返回该字符的位置string&insert(size_tpos, constchar*s) { assert(pos<=_size); size_tlen=strlen(s); if (_size+len>_capacity) { reserve(_size+len); } size_tend=_size+len; while (pos+len-1<end) { _str[end] =_str[end-len]; end--; } strncpy(_str+pos, s, len); _size+=len; return*this; } //删除pos位置上 len 个的元素string&erase(size_tpos, size_tlen=npos) { assert(pos<_size); if (len==npos||pos+len>=_size)//说明pos位置及其后面的字符都被删除 { _str[pos] ='\0'; _size=pos; } else//说明pos位置及其后方的有效字符需要保留一部分 { strcpy(_str+pos, _str+pos+len); _size-=len; } return*this; } private: char*_str; size_t_size; size_t_capacity; conststaticsize_tnpos=-1; }; //-------------------------------------------------------------------//Non-member function overloadsostream&operator<<(ostream&out, conststring&s) { for (size_ti=0; i<s.size(); ++i) { out<<s[i]; } returnout; } istream&operator>>(istream&in, string&s) { s.clear();//清空charbuff[128] = { '\0' }; size_ti=0; charch=in.get();//读取一个字符while (ch!=' '&&ch!='\n')//当读取到的字符不是空格或'\n'的时候继续读取 { if (i==127)//满了 { s+=buff; i=0; } buff[i++] =ch; ch=in.get();//继续读取字符 } if (i>0) { buff[i] ='\0'; s+=buff; } returnin; } //relational operators(string)booloperator>(conststring&s1, conststring&s2) { returnstrcmp(s1.c_str(), s2.c_str()) >0; } booloperator==(conststring&s1, conststring&s2) { returnstrcmp(s1.c_str(), s2.c_str()) ==0; } booloperator>=(conststring&s1, conststring&s2) { returnoperator==(s1, s2) ||operator>(s1, s2);//复用 } booloperator<(conststring&s1, conststring&s2) { return!operator>=(s1, s2); } booloperator<=(conststring&s1, conststring&s2) { return!operator<(s1, s2); } booloperator!=(conststring&s1, conststring&s2) { return!operator==(s1, s2); } }
(2)Test.cpp
voidTest_string1() { //构造fy::strings1; fy::strings2("abcd"); //拷贝构造fy::strings3=s2; fy::strings4(s2); //赋值重载s1=s2;//编译器自动转换成 s1.operator(s2)} voidTest_string2()//iterators{ fy::strings1("abcdef"); fy::string::iteratorit=s1.begin(); for (it; it<s1.end(); ++it) { cout<<*(it); } cout<<endl; fy::strings2("12345"); fy::string::iteratorit2=s2.begin(); for (it2; it2<s2.end(); ++it2) { cout<<*(it2); } cout<<endl; } voidTest_string3()//Capacity{ fy::strings1("hello world"); cout<<s1.empty() <<endl; cout<<s1.size() <<endl; cout<<s1.capacity() <<endl; s1.reserve(5); cout<<s1.capacity() <<endl; s1.reserve(20); cout<<s1.capacity() <<endl; fy::strings2("hello world"); cout<<s2.size() <<endl; cout<<s2.capacity() <<endl; //s2.resize(6, 'x');s2.resize(15, 'x');//调试查看,<<这里暂时没有实现fy::strings3("aaaaa"); s3.clear(); } voidTest_string4()//Element access{ fy::strings1("hello world"); //可以使用 [] 访问字符串的每一位for (size_ti=0; i<s1.size(); ++i) { cout<<s1[i]; } cout<<endl; } voidTest_string5()//modify{ fy::strings1("hello"); s1.push_back(' '); s1.push_back('w'); s1.append("orld"); s1+='!'; s1+="xxxx"; } voidTest_string6()//String operations{ fy::strings1("hello helloxxss"); cout<<s1.find('o') <<endl; cout<<s1.c_str() <<endl; cout<<s1.find('xxs') <<endl; cout<<s1.c_str() <<endl; s1.insert(11, 'w'); cout<<s1.c_str() <<endl; s1.insert(6, "xxxxxxxx"); cout<<s1.c_str() <<endl; s1.erase(6, 10); cout<<s1.c_str() <<endl; } voidTest_string7()//Non - member function overloads{ fy::strings1("hello"); cout<<s1<<endl;//测试<<fy::strings2; cin>>s2;//测试>>cout<<s2<<endl; fy::strings3("aaa"); fy::strings4("aaa"); if (s3==s4) cout<<"s3==s4"<<endl; } intmain() { Test_string7(); return0; }
----------------我是分割线---------------
文章到这里就结束了,下一篇即将更新