一、string类
1、 string是表示字符串的字符串类。
2、 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3、 string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string。
4、 不能操作多字节或者变长字符的序列。在使用string类时,必须包含#include<string>头文件以及using namespace std。
那么接下来我们就来模仿标准库里的string类来进行一个模拟实现,进而更深层次地理解string容器。 (为了与标准库区分,我们在自己的命名空间里来实现)
首先根据源码,我们可以知道string中的主义成员变量我们可以写成 char* _str(字符数组),size_t _size(数据个数),size_t _capacity(容量大小)。
二、构造函数、拷贝构造函数及析构函数
1、构造函数
string的构造函数分为无参构造和有参构造,通过无参构造的对象会被默认生成一个空字符串,因此我们可以带一个缺省值。没有参数时就直接构造一空串。
string(const char* str = "") :_str(new char[strlen(str) + 1]) , _size(strlen(str)) , _capacity(strlen(str)) { strcpy(_str, str); }
注:在开空间时,我们需要多开一个空间,因为strlen算出的大小不包含 ‘\0’ ,因此我们需要给 ‘\0’留一个空间。
2、拷贝构造函数
拷贝构造函数是默认成员函数,如果不写编译器会自动生成,对于内置类型完成浅拷贝,对于自定义类型调用其拷贝构造函数完成拷贝。对于string类型来说,如果不自己写拷贝构造函数会导致浅拷贝问题。
如果仅仅依靠编译器自己提供的拷贝构造函数,就会像上图一样两个对象指向同一块空间。这就是我们所说的浅拷贝,浅拷贝有两大缺点:1、两个对象在析构时都会调用自己的析构函数,这样同一块空间就会被析构两次;2、一个对象的数据改变,另一个对象的数据也改变了。
因此我们自己实现一个拷贝构造函数尤为重要。我们需要进行深拷贝。
string(const string& str) :_str(new char[str._capacity+1]) ,_size(str._size) ,_capacity(str._capacity) { strcpy(_str, str._str); }
上面的这种实现方法是比较常见的一种写法,但是我们还有一种更加简便的写法。我们可以通过已经实现了的构造函数传一个常量字符串,即下面的 str . _str,来创建一个l临时对象,然后将这个临时对象的成员与自己交换,这样也完成了拷贝构造。
void swap(string& s) { ::swap(_str, s._str); ::swap(_size, s._size); ::swap(_capacity, s._capacity); } string(const string& str) :_str(nullptr) ,_size(0) ,_capacity(0) { string tmp(str._str); swap(tmp); }
但是,为了写成这个拷贝构造函数我们还写了一个swap函数,这这么就简便了呢?
那是因为通过查阅标准库我们发现swap函数也是一个string类中提供了的函数,因此我们不仅简便了拷贝构造函数的写法,还又完成了一个函数的实现。并且,因为tmp是一个局部对象,因此在出作用域后就会自动调用析构函数,所以交换后还可以清理掉原来的空间,一举两得。
注:蓝色的swap函数是我们自己写的,而黄色的,前面写明了域的swap函数是C++库中的一个现成的函数。这两个一定要区别开来。
3、析构函数
我们可以使用delete直接释放掉 _str的空间。
~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; }
三、string类中对容量操作的成员函数
1、size
它的功能是返回字符串有效字符的长度。我们可以直接返回其成员变量中的 _size。
size_t size()const { return _size; }
2、capacity
它的功能是返回返回空间总大小 。我们可以直接返回其成员变量中的 _capacity
size_t capacity()const { return _capacity; }
3、reserve
它的功能是为字符串预留空间
void reserve(size_t n) { if (n > _capacity) { char* tmp = new char[n + 1]; strcpy(tmp, _str); delete[] _str; _str = tmp; _capacity = n; } }
4、resize
它的功能是将有效字符的个数改成n个,多出的空间用字符 ch 填充。
_size < n <_capacity 先预留 n 大小的空间。直接用字符 ch 填充 n - _size 位置,记住要在最后加上 ‘\0’
_size > n 直接将 n 位置的置成 ’\0‘
n > _capacity 先预留 n 大小的空间,剩下的空间用字符 ch 填充,记住要在最后加上 ‘\0’。
void resize(size_t n, char ch = '\0') { if (n > _size) { reserve(n); for (size_t i = _size; i < n; i++) { _str[i] = ch; } _size = n; _str[_size] = '\0'; } else { //删除数据 _str[n] = '\0'; _size = n; } }
5、clear
它的作用是清空有效字符
void clear() { _str[0] = '\0'; _size = 0; }
6、empty
它的作用是检测字符串释放为空串,是返回true,否则返回false
bool empty()const { return _size == 0; }
四、string类中对象的增删查改操作
1、push_back
在字符串后尾插字符c
void push_back(char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size] = ch; _size++; _str[_size] = '\0'; }
2、append
在字符串后追加一个字符串
void append(const char* str) { size_t len = strlen(str); if (_capacity < len + _size) { reserve(len + _size); } strcpy(_str + _size, str); _size += len; }
3、c_str
返回C格式字符串
const char* c_str()const { return _str; }
补充:C语言字符串和 string类字符串的区别(下面我们来通过一段代码来看看他们的区别)
void test_string8() { string s1("hello"); s1 += '\0'; s1 += "De Bruyne"; cout << s1 << endl; cout << s1.c_str() << endl; }
代码运行结果如下:
通过上面的运行结果我们可以看出C语言的字符串一定是通过 ’\0‘ 结束的;而string 的字符串中 ’\0‘ 不一定就是字符串结束的标志。
4、find
npos是string类的静态成员变量,静态成员变量要在类外定义的。我们一般将它设为公共权限,并赋值为 -1。
在字符串中寻找一个字符
size_t find(char ch, size_t pos = 0)const { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (ch == _str[i]) { return i; } } return npos; }
在字符串中寻找字符串
size_t find(const char* sub, size_t pos = 0)const { assert(pos < _size); const char* ptr = strstr(_str + pos, sub); if (ptr == nullptr) { return npos; } else { return ptr - _str; } }
5、substr
它的作用是在str中从pos位置开始,截取n个字符,然后将其返回
string substr(size_t pos, size_t len = npos)const//取子串 { assert(pos < _size); size_t reallen = len; if (reallen == npos || reallen + pos > _size) { reallen = _size - pos; } string sub; for (size_t i = pos; i < reallen + pos; i++) { sub += _str[i]; } return sub; }
6、insert
插入一个字符
string& 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++; return *this; }
插入一个字符串
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 end = _size + len; while (pos + len <= end) { _str[end] = _str[end - len]; end--; } strncpy(_str + pos, str, len); _size += len; return *this; }
7、erase
void erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || len >= _size) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } }
五、string对于重要的运算符重载
1、赋值运算符的重载
编译器默认生成的赋值重载也会导致浅拷贝,所以我们需要实现深拷贝。
string& operator=(const string& str) { if (this != &str) { char* tmp = new char[str._capacity + 1]; strcpy(tmp, str._str); delete[] _str; _str = tmp; _size = str._size; _capacity = str._capacity; } return *this; }
和拷贝构造函数一样,我们也可以用简便写法来实现赋值运算符的重载。(注意不要自己给自己赋值)。
string& operator=(const string& str) { if (this != &str) { string tmp(str); swap(tmp); } return *this; }
2、流插入 <<
类外定义
ostream& operator<<(ostream& out, const string& s) { for (size_t i = 0; i < s.size(); i++) { out << s[i]; } return out; }
3、流提取 >>
istream& operator>>(istream& in, string& s) { s.clear(); char ch; ch = in.get(); const size_t N = 32; char buff[N]; size_t i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == N - 1) { buff[i] = '\0'; s += buff;/*先将输入的字符存在buff数组中,然后一次性+=到s中,这样就避免了输入的 字符串过大,使s频繁扩容的现象。*/ i = 0; } ch = in.get(); } buff[i] = '\0'; s += buff; return in; }
4、下标访问 [ ]
普通对象:可读可写
char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; }
const对象:可读不可写
char operator[](size_t pos) const { assert(pos < _size); return _str[pos]; }
5、加等一个字符 +=
我们可以直接复用push_back来实现一个字符的加等
string& operator+=(char ch) { push_back(ch); return *this; }
6、加等字符串 +=
我们可以直接复用append来实现字符串的加等
string& operator+=(const char* str) { append(str); return *this; }
7、大于 >
bool operator>(const string& s)const { return strcmp(_str, s._str) > 0; }
8、等于 ==
bool operator==(const string& s)const { return strcmp(_str, s._str) == 0; }
9、小于 <
bool operator<(const string& s)const { return !(_str > s._str) && !(_str == s._str); }
10、大于等于 >=
bool operator>=(const string& s)const { return !(_str < s._str); }
11、小于等于 <=
bool operator<=(const string& s)const { return !(_str > s._str); }
12、不等于 !=
bool operator!=(const string& s)const { return !(_str == s._str); }
六、迭代器与范围for
1、迭代器
迭代器作为STL的六大组件之一,它的作用十分重要。而在string中迭代器的本质就是一个 char* 或 const char* 的指针。
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; }
2、范围for
范围for是C++11支持的更简洁的新遍历方式。但是其实它没有什么高大上的,它的本质还是去调用迭代器来实现遍历的。因此我们只要实现了迭代器后,就可以直接使用范围for,根本不用自己去实现。
我们从下面的汇编代码的红色标记处可以看出范围for和迭代器的底层实现都是去call的begin()和end(),因此范围for的底层就是迭代器。
3、结果对比
void test_string9() { string s("hello world"); string::iterator it = s.begin(); while (it != s.end()) { cout << *it << " "; it++; } cout << endl; for (auto e : s) { cout << e << " "; } cout << endl; }
七、完整代码
string.h
#define _CRT_SECURE_NO_WARNINGS #pragma once #include<iostream> using namespace std; #include<cstring> #include<cassert> namespace zdl { 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; } void swap(string& s) { ::swap(_str, s._str); ::swap(_size, s._size); ::swap(_capacity, s._capacity); } //构造函数 string(const char* str = "") :_str(new char[strlen(str) + 1]) , _size(strlen(str)) , _capacity(strlen(str)) { strcpy(_str, str); } /*string() :_str(new char[1]) , _size(0) , _capacity(0) { _str[0] = '\0'; }*/ //拷贝构造函数:要深拷贝 string(const string& str) :_str(new char[str._capacity+1]) ,_size(str._size) ,_capacity(str._capacity) { strcpy(_str, str._str); } /*简便写法 string(const string& str) :_str(nullptr) ,_size(0) ,_capacity(0) { string tmp(str._str); swap(tmp); }*/ //赋值 string& operator=(const string& str) { if (this != &str) { char* tmp = new char[str._capacity + 1]; strcpy(tmp, str._str); delete[] _str; _str = tmp; _size = str._size; _capacity = str._capacity; } return *this; } /*简便写法 string& operator=(const string& str) { if (this != &str) { string tmp(str); swap(tmp); } return *this; } */ //析构函数 ~string() { delete[] _str; _str = nullptr; _size = _capacity = 0; } const char* c_str()const { return _str; } size_t size()const { return _size; } size_t capacity()const { return _capacity; } 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* str) { size_t len = strlen(str); if (_capacity < len + _size) { reserve(len + _size); } strcpy(_str + _size, str); _size += len; } string& operator+=(char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; } string& 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++; return *this; } 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 end = _size + len; while (pos + len <= end) { _str[end] = _str[end - len]; end--; } strncpy(_str + pos, str, len); _size += len; return *this; } void erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || len >= _size) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } } void clear() { _str[0] = '\0'; _size = 0; } size_t find(char ch, size_t pos = 0)const { assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (ch == _str[i]) { return i; } } return npos; } size_t find(const char* sub, size_t pos = 0)const { assert(pos < _size); const char* ptr = strstr(_str + pos, sub); if (ptr == nullptr) { return npos; } else { return ptr - _str; } } string substr(size_t pos, size_t len = npos)const//取子串 { assert(pos < _size); size_t reallen = len; if (reallen == npos || reallen + pos > _size) { reallen = _size - pos; } string sub; for (size_t i = pos; i < reallen + pos; i++) { sub += _str[i]; } return sub; } void resize(size_t n, char ch = '\0') { if (n > _size) { reserve(n); for (size_t i = _size; i < n; i++) { _str[i] = ch; } _size = n; _str[_size] = '\0'; } else { //删除数据 _str[n] = '\0'; _size = n; } } bool operator>(const string& s)const { return strcmp(_str, s._str) > 0; } bool operator==(const string& s)const { return strcmp(_str, s._str) == 0; } private: char* _str; size_t _size; size_t _capacity; public: static size_t npos; }; size_t string::npos = -1; ostream& operator<<(ostream& out, const string& s) { for (size_t i = 0; i < s.size(); i++) { out << s[i]; } return out; } istream& operator>>(istream& in, string& s) { s.clear(); char ch; ch = in.get(); const size_t N = 32; char buff[N]; size_t i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == N - 1) { buff[i] = '\0'; s += buff;/*先将输入的字符存在buff数组中,然后一次性+=到s中,这样就避免了输入的字符串过大,使s频繁扩容的现象。*/ i = 0; } ch = in.get(); } buff[i] = '\0'; s += buff; return in; } void test_string1() { string s1("hello world"); string s2(s1); string s3; cout << "s1: " << s1 << endl; cout << "s2: " << s2 << endl; cin >> s3; cout << "s3: " << s3 << endl; string s4("ZD"); s4 += 'L'; cout << "s4: " << s4 << endl; s4 += " "; s4 += "and"; s4 += " "; s4 += "De Bruyne"; cout << "s4: " << s4 << endl; } void test_string2() { string s5("hello"); s5.append("ZDL 17"); cout << "s5: " << s5 << endl; } void test_string3() { string s6("hello"); s6.insert(0, '#'); cout << "s6: " << s6 << endl; } void test_string4() { string s7("hello"); s7.insert(2, "ZDL"); cout << "s7: " << s7 << endl; } void test_string5() { string s8("helloeeeeeelllooo"); s8.erase(2, 2); cout << "s8: " << s8 << endl; } void test_string6() { string s9("hello world ZDL waiting for love"); string s10 = s9.substr(6, 5); cout << "s10: " << s10 << endl; } void test_string7() { string s11("hello world ZDL waiting for love"); string s12("shfshf"); cout << "比较: " << (s11 > s12) << endl; string s13("De Bruyne"); string s14("De Bruyne"); cout << "s13 与 s14: " << (s13 == s14) << endl; } void test_string8() { string s1("hello"); s1 += '\0'; s1 += "De Bruyne"; cout << s1 << endl; cout << s1.c_str() << endl; } void test_string9() { string s("hello world"); string::iterator it = s.begin(); while (it != s.end()) { cout << *it << " "; it++; } cout << endl; for (auto e : s) { cout << e << " "; } cout << endl; } }
test.cpp
#include"string.h" int main() { zdl::test_string1(); cout << "------------------------------" << endl; zdl::test_string2(); cout << "------------------------------" << endl; zdl::test_string3(); cout << "------------------------------" << endl; zdl::test_string4(); cout << "------------------------------" << endl; zdl::test_string5(); cout << "------------------------------" << endl; zdl::test_string6(); cout << "------------------------------" << endl; zdl::test_string7(); cout << "------------------------------" << endl; zdl::test_string8(); cout << "------------------------------" << endl; zdl::test_string9(); return 0; }