string接口模拟实现2
文章目录
- 浅拷贝
- insert插入一个字符
- insert插入一个字符串
- 删除erase
- find(查找一个字符)
- \\\\\\\find(查找一个字符串:子串substr)
- 查找的练习(网址)
- 赋值operator=
- 比较大小
- 流插入
- 流提取
浅拷贝
//浅拷贝string::string(const string& str){//开一样大的空间,一样大的值_str = new char[str._capacity+1];strcpy(_str, str._str);_size = str._size;_capacity = str._capacity;}
记得断言,pos<=_size
insert插入一个字符
//string.cpp中的内容void string::insert(size_t pos, char ch){assert(pos<_size);if (_size == _capacity){reserve(2 * _capacity);}size_t i = _size;for (i = _size; i >= pos; i--){_str[i+1] = _str[i]; //为什么是[i+1]=[i],而不是[i]=[i-1]呢?因为我们还需要将下标为_size的\0也往后挪一格,\0之后位置的下标是_size+1,也就是i+1}_str[pos] = ch;_size++;}
在上面案例的测试中,测试极端案例,比如头插会发生意外,当i=0的时候,我们已经将_str[1]=_str[0]了,此时i=0,若是再- -,i就会很大,因为i是unsigned int类型,所以0-1的结果是很大(绕环,成为很大的值)。
那i成为很大的值,那肯定大于pos,以至于无法出循环。
方法一:
想让i成为-1,那就让它的类型成为int。
int i = _size;
但是又有一个问题:i > pos
,i是int类型,pos是size_t类型。有一个知识点:当一个操作符两边的操作数类型不一样的时候,我们可以将范围小的向范围大的强转(在这里:无符号向有符号转换)for (i = _size; i >= (int)pos; i--)
void string::insert(size_t pos, char ch){assert(pos<_size);if (_size == _capacity){reserve(_capacity == 0?4: 2 * _capacity);}int i = _size;for (i = _size; i >= (int)pos; i--){_str[i+1] = _str[i];}_str[pos] = ch;_size++;}
方法二:
将i
初始化为_size+1
void string::insert(size_t pos, char ch){assert(pos<_size);if (_size == _capacity){reserve(_capacity == 0?4: 2 * _capacity);}size_t i = _size+1;for (i = _size + 1; i > pos; i--){_str[i] = _str[i-1];}_str[pos] = ch;_size++;}
insert插入一个字符串
void string::insert(size_t pos, const char* str){assert(pos<_size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);//这是新找了一个地方开空间}size_t i = _size+len;for (i = _size+ len; i >= pos+len; i--){_str[i] = _str[i-len];}size_t j = 0;for (j = 0; j < len; j++){_str[pos + j] = str[j];}}
这种方法也存在bug(越界),当我想在下标为0的位置插入(即头插一个字符串)
_str[i] = _str[i-len];
删除erase
可以画图自己理解
- npos是类的静态成员变量。静态成员变量不可以直接给缺省值,需要在类外面。(因为在声明处给值,是传给了初始化列表,而静态成员变量不会在初始化列表里面)
- 当声明和定义分离时,不要将
size_t string::npos = -1;
放在.h
里面,不然.h
会在string.cpp
和test.cpp
都展开。那npos这个变量会有两份,那两个链接的时候合并生成可执行程序,它们的符号表里面npos就会合并到一起,那怎么会有两个npos呢,一个成员不能定义两份,所以:静态成员变量当生命和定义分离时,需要将size_t string::npos = -1;
放在.cpp
里
- 如果删除的长度>len或者len没有传值,使用的缺省值npos,那么就是将pos以及之后的全部删掉。(即将pos的位置直接修改为‘\0’)
- 如果删除的长度<len,那么将len那一节去掉,再将pos+len之后的移过来。
void string::erase(size_t pos, size_t len) //0 npos
{//如果len大于pos后面字符的个数if (_size - pos <= len || len == npos){_str[pos] = '\0';}else{size_t i = 0;size_t s = _size - pos-len;while (i<=s){_str[pos] = _str[pos + len];pos++;i++;}_size -= len;}
void string::erase(size_t pos, size_t len) //0 npos{//如果len大于pos后面字符的个数if (_size - pos <= len || len == npos){_str[pos] = '\0';}else{size_t end = pos + len;while (end <= _size){_str[end - len] = _str[end];++end;}_size -= len;}}
find(查找一个字符)
记得断言:pos<_size
size_t string::find(char c, size_t pos)
{assert(pos < _size);size_t i = pos;while (i < _size){if (_str[i] == c)return i;i++;}return npos;
}
\\\\find(查找一个字符串:子串substr)
- 子串匹配即暴力匹配(strstr)
size_t string::find(const char* str, size_t pos){size_t len = strlen(str);assert(pos + len < _size);const char* p1 = strstr(_str, str);if (p1 == nullptr)return npos;else{//已知地址,如何获得它的下标//我们知道第一个元素的地址_str。p1-_str就是下标return p1 - _str;}}
这样也可以
查找的练习(网址)
void test6()
{//想取出来域名editor.csdn.nethou::string str1("https://editor.csdn.net/md?articleId=144002569");size_t m = str1.find(':') + 3; //m是域名中e的下标size_t n = str1.find('/', m); //n是从域名开始的下一个‘/’的下标if (m != hou::string::npos && n != hou::string::npos){hou::string domain = str1.substr(m, n - m);std::cout << domain.c_str() << std::endl;}
}
赋值operator=
这个两个操作数均已初始化过,都有各自的值,但是现在想让str2=str1.
让str2=str1,那么str2将有一部分空间被浪费。
让str2=str1,但是1比2大,装不下。
所以,第一步应该是将str2给释放掉,然后按照str1的大小来设置。不然如果之后再扩容的话,也是需要去找新的空间,然后将原来的释放掉。
string& string::operator=(const string& str)
{delete[]_str;_str = new char[str._capacity + 1];_size = str._size;_capacity = str._capacity;return *this;
}
上面写的有点不完善。如果是将自己赋值给自己,会成功吗?
当然不会。因为在刚开始,就已经将它自己给释放掉了。所以需要有条件。
比较大小
比较大小需要在全局实现。为什么呢?如果在类里面,则第一个参数就固定死了,一定是隐藏的this指针。而在全局的话,则第一个是左操作数,第二个是右操作数。
字符串如何比较呢?
是从头开始,一个字符一个字符的比较它们的ascll码值。
bool operator==(const string& left, const string& right){return (strcmp(left.c_str(), right.c_str()) == 0);}bool operator!=(const string& left, const string& right){return !(right == left);}bool operator<(const string& left, const string& right){return strcmp(left.c_str(), right.c_str()) < 0;}bool operator>(const string& left, const string& right){return strcmp(left.c_str(), right.c_str()) > 0;}bool operator<=(const string& left, const string& right){return !(left > right);}bool operator>=(const string& left, const string& right){return !(left < right);}
流插入
必须是引用返回,且是在全局实现这个函数
在实现流插入之前,我们需要实现size()
和operator[]
,这两个在是流插入时需要使用。
还有一个注意点是: ostream& operator<< (ostream& os, string& str);
第二个参数是const修饰的,所以不能修改。而str[i]是允许用户修改里面的内容的。因此,有两个解决办法:
(1.将const取消掉。
(2.实现一个函数:const char& operator[]( size_t pos) const;
.(最前面的const指的是返回值被const修饰,不能更改.最后的那个const的含义是:这个函数可以被const修饰的对象调用。比如,
hou::string str1("houhouohu");//str1这个对象是被const修饰的
std::cout<<str1[3]<<std::endl;
(3.把string的const迭代器实现出来(这个是为什么呢?看下图)。
//第一种方法 //cout<<str1;其中,cout是os,str1是strostream& operator<< (ostream& os, string& str){for (size_t i = 0; i < str.size(); i++){os << str[i];}return os;}
//第二种const char& string::operator[](size_t pos)const{return _str[pos];}ostream& operator<< (ostream& os, const string& str){for (size_t i = 0; i < str.size(); i++){os << str[i];}return os;}
流提取
是将内容搬到str里面。(一个字符一个字符的提取)
在此之前要实现+=。实现+=就相当于是push_back一个字符。但push_back的时候需要判断空间是否足够,可以考虑实现reverse,也可以当场实现开空间。
还要注意一个问题就是:cin和scanf都有一个共同的特点,就是,自动忽略空格和换行(它们会认为空格和换行是用来分割的,然后它们就直接忽略它了,不提取)。
由上图可以看出,当str1里面有内容,而我又cin了,在此之后,原本的内容会被清除,所以我们需要用到清楚clear()
void string::reverse(size_t n){if (n > _capacity){char* tmp = new char[n];strcpy(tmp, _str);delete[]_str;_str = tmp;_capacity = n;}}void string::push_back(char ch){if (_size == _capacity){reverse( _capacity == 0 ? 4 : 2 * _capacity);}_str[_size] = ch;_size++;}string& string::operator+=(const char ch){push_back(ch);return *this;}void string::clear(){//只是清楚内容_str[0] = '\0';_size = 0;}istream& operator>> (istream& is, string& str){char ch;// is >> ch;ch=is.get();while (ch != ' ' && ch != '\0'){str += ch;//该扩容就扩容,该加等就加等,是+=的事情了//is >> ch;ch=is.get();}return is;}