1.string类
1.1 在http://www.cplusplus.com/reference/string/string/ 中是这样解释的:
String class
Strings are objects that represent sequences of characters.
The standardstring
class provides support for such objects with an interface similar to that of a standard container of bytes, but adding features specifically designed to operate with strings of single-byte characters.
Thestring
class is an instantiation of the basic_string class template that useschar
(i.e., bytes) as its character type, with its default char_traits and allocator types (see basic_string for more info on the template).
Note that this class handles bytes independently of the encoding used: If used to handle sequences of multi-byte or variable-length characters (such as UTF-8), all members of this class (such as length or size), as well as its iterators, will still operate in terms of bytes (not actual encoded characters).
总结一下呢,就四点:
- string是表示字符串的字符串类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作的string的常规操作
- string在底层实际是:basic_string模板类的别名, typedef basic_string<char, char_traits, allocator> string;
- 不能操作多字节或者变长字符的序列
1.2 string类的常用接口说明
在这里我只介绍我实现string类的过程和一些方法,具体用法请参照上面的网站进行查阅。
-
四大成员函数
![四大成员函数](F:\C++\CPPStudyJourney\知识点总结\8. string类\四大成员函数.png)
这里只有三个为什么说是四大成员函数呢?因为拷贝构造函数在构造函数选项里。
在这里我自己模拟实现了一下string类,下面是我模拟的四大成员函数:
//Member functions// //成员函数写在类里,写在类里的函数默认是内联(inline)函数 String(const char *str = "") { _size = strlen(str); _capacity = _size > 15 ? _size : 15; _str = new char[_capacity + 1]; strcpy(_str, str); } ~String() { if (_str) { delete[] _str; _str = nullptr; _size = 0; _capacity = 0; } } String(const String& s) :_str(nullptr), _size(0), _capacity(0) { String tmp(s._str); Swap(tmp); } String& operator=(String s) { Swap(s); return *this; }
在这里重点说一下 拷贝构造函数和赋值运算符重载函数:
这两个函数都用了现代写法;
**拷贝构造函数:**先初始化一个空类型。然后再用默认构造函数构造一个跟 s一样的一个临时的string对象。再将原本初始化的空类型和这个临时的string对象交换,返回即可。系统会自动调用析构函数清理被交换后的临时string对象。
**赋值运算符重载函数:**这里直接用了传值的做法,在调用函数的时候会发生值拷贝,拷贝了一份临时的string对象,直接交换this对象和这个临时的对象,返回即可,也是一样,系统会自动调用析构函数清理资源。
-
string类对象的容量操作
![string类对象的容量操作](F:\C++\CPPStudyJourney\知识点总结\8. string类\string类对象的容量操作.png)
上代码!!!
//这思个因为比较短小,所以写在类里了 void clear() { _size = 0; _str[0] = '\0'; } int empty() const { if (_size == 0) return 1; return 0; } size_t size() const { return _size; } size_t capacity() const { return _capacity; } //其他的写在类外,所以有命名空间 void Gerald::String::reserve(size_t n) { if (n > _capacity) { char *new_str = new char[n + 1]; strcpy(new_str, _str); delete[] _str; _str = new_str; _capacity = n; } } void Gerald::String::resize(size_t n, char ch) { if (n < _size) { _size = n; _str[_size] = '\0'; } else if (n > _size) { if (n > _capacity) { reserve(n); } /*while (_size < n) { push_back(ch); _size++; }*/ memset(_str + _size, ch, n - _size);//这里直接可以用memset来拷贝字节 //不用上面的循环很麻烦 _size = n; _str[_size] = '\0'; } }
这六个是比较常用的,其他的要么不实用,要么就会有些冗余,这也是很多人吐槽string类的地方,这么一个类有一百多个接口,冗余太多了。
说明:
- size()和length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
- clear()只是将string中的有效字符清空,不改变底层空间大小。
- reserve()只能增容,如果参数比现在的容量小,则不做任何操作。
- resize()如果参数比size小,那么就改size大小,然后给size位置放’\0’;如果参数大于size小于容量,并且如果用户给了填入的字符,则会把size扩大到参数大小,并将扩大的地方填入用户给的字符,若没给,默认’\0’;如果参数大于容量,就扩容,然后做跟上一步一样的操作。
-
类的对象访问操作![类对象访问操作](F:\C++\CPPStudyJourney\知识点总结\8. string类\类对象访问操作.png)
在这里基本上只能用的上[]运算符重载
char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; }
-
对类对象的修改操作
![对类对象的修改操作](F:\C++\CPPStudyJourney\知识点总结\8. string类\对类对象的修改操作.png)
void Gerald::String::push_back(char ch) { if (_size == _capacity) { reserve(_capacity * 2); } _str[_size] = ch; _size++; //这一步比较重要,若忘记加了,则会出现读取字符串停不下来 _str[_size] = '\0'; } void Gerald::String::append(const char *str) { int len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_str + _size, str); _size += len; } Gerald::String& Gerald::String::operator+= (char ch) { push_back(ch); return *this; } Gerald::String& Gerald::String::operator+= (const char *str) { append(str); return *this; } size_t Gerald::String::find(char ch, size_t pos) { assert(pos < _size); for (pos; pos < _size; pos++) { if (_str[pos] == ch) return pos; } return npos; } size_t Gerald::String::find(const char *str, size_t pos) { assert(pos < _size); char *pstr = strstr(_str, str); if (pstr) { return pstr - str; } return npos; } void Gerald::String::Insert(size_t pos, const char ch) { if (_size == _capacity)//_size位置放的是'\0'要统一 { size_t new_size = _capacity == 0 ? 15 : 2 * _capacity; reserve(new_size); } size_t i = _size + 1; //这里+1可以在第一步直接将'\0'挪到最后面,所以在最后就不需要赋值'\0'了 for (i; i > pos; i--) { _str[i] = _str[i - 1]; } _str[pos] = ch; _size++; } void Gerald::String::Insert(size_t pos, const char *str) { size_t len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } size_t i = _size + len; for (i; i > pos + len - 1; i--) //i > pos + len - 1 极端位置 i = pos + len 可以把pos位置的数据挪过来 { //就算在字符串尾部插入,循环还是会把'\0'挪到最后面,不用担心最后没有加'\0'的问题 _str[i] = _str[i - len]; } strncpy(_str + pos, str, len); _size += len; } void Gerald::String::Erase(size_t pos, size_t len) { if (pos + len > _size)//直接将后面的字符串屏蔽 { _str[pos] = '\0'; _size = pos; return; } for (size_t i = pos; i < _size - len + 1; i++)//i < _size - len + 1可以直接将'\0'挪到缩减后的最后一个位置 { _str[i] = _str[i + len]; } _size -= len; } Gerald::String Gerald::String::substr(size_t pos, size_t len) { if (_size - pos < len) len = _size - pos; String sub; sub.reserve(len); for (size_t i = pos; i < pos + len; i++) { sub += _str[i]; } return sub; }