从C语言到C++_13(string的模拟实现)深浅拷贝+传统/现代写法(中):https://developer.aliyun.com/article/1513673
4.7 find() 的实现
find:查找字符
如果遍历完整个字符串都没找到,就返回 npos(找到库的来)。
这个 npos 我们可以在成员变量中定义:
size_t find(const char ch) const { for (size_t i = 0; i < _size; i++) { if (ch == _str[i])// 找到了 { return i; // 返回下标 } } return npos;// 找不到 } private: char* _str; size_t _size; size_t _capacity; public: const static size_t npos = -1;// const static 语法特殊处理,直接可以当成定义初始化
find:查找字符串
这里我们可以用 strstr 去找子串,如果找到了,返回的是子串首次出现的地址。
如果没找到,返回的是空。所以我们这里可以做判断,如果是 nullptr 就返回 npos。
如果找到了,就返回对应下标,子串地址 - 开头,就是下标了。
size_t find(const char* str, size_t pos = 0) const { const char* ptr = strstr(_str + pos, str); if (ptr == nullptr) { return npos; } else { return ptr - _str; // 减开头 } }
4.8 erase() 的实现
string& erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size)// 如果pos后面的都删完了,注意len == npos 不能忽略,因为npos + len 有可能重回到 1 { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } return *this; }
测试find 和 erase:
5. 传统写法和现代写法
对于拷贝构造的深拷贝,传统写法就是本本分分分地去完成深拷贝:
string(const string& s)/传统写法 :_str(new char[s._capacity + 1]) , _size(s._size) , _capacity(s._capacity) { strcpy(_str, s._str); }
5.1 拷贝构造的现代写法
现在我们来介绍一种现代写法,它和传统写法本质工作是一样的,即完成深拷贝。
现代写法的方式不是本本分分地去按着 Step 一步步干活,而是 "投机取巧" 地去完成深拷贝
void swap(string& s)// s和*this换 { ::swap(s._str, _str);//注意这里要加域作用符,默认是全局的,不然就是自己调自己了 ::swap(s._size, _size); ::swap(s._capacity, _capacity); } string(const string& s)/现代写法 :_str(nullptr) , _size(0) , _capacity(0) { string tmp(s._str); swap(tmp);// tmp和*this换 }
现代写法的本质就是复用了构造函数。
我想拷贝,但我又不想自己干,我把活交给工具人 tmp 来帮我干。
值得注意的是如果不给原_str赋空指针,那么它的默认指向会是个随机值。和tmp交换后,tmp 是一个局部对象,我们把 s2 原来的指针和 tmp 交换了,那么 tmp 就成了个随机值了。tmp 出了作用域要调用析构函数,对随机值指向的空间进行释放,怎么释放?都不是你自己的 new / malloc 出来的,你还硬要对它释放,就可能会引发崩溃。但是 delete / free 一个空,是不会报错的,因为会进行一个检查。所以是可以 delete 一个空的,我们这里初始化列表中把 nullptr 给 _str,是为了交换完之后, nullptr 能交到 tmp 手中,这样 tmp 出了作用域调用析构函数就不会翻车了。
5.2 赋值重载的现代写法
赋值重载的传统写法:
string& operator=(const string& s)/传统写法 { if (this != &s) { char* tmp = new char[s._capacity + 1];// 开辟新的空间 strcpy(tmp, s._str);// 赋值到tmp delete[] _str;// 释放原有空间 _str = tmp;// tmp赋值到想要的地方,出去tmp就销毁了 _size = s._size; _capacity = s._capacity; } return *this; }
赋值重载的现代写法:
string& operator=(const string& s)/现代写法 { if (this != &s) { string tmp(s); swap(tmp);// tmp和*this换 } return *this; }
比上面的拷贝构造的现代写法还要压榨tmp,交换完之后,正好让 tmp 出作用域调用析构函数,属实是一石二鸟的美事。把 tmp 压榨的干干净净,还让 tmp 帮忙把屁股擦干净(释放空间)。
用上面的测试简单测一下:
总结:
现代写法在 string 中体现的优势还不够大,因为好像和传统写法差不多。
但是到后面实现 vector、list 的时候,会发现现代写法的优势真的是太大了。
现代写法写起来会更简单些,比如如果是个链表,传统写法就不是 strcpy 这么简单的了,
你还要一个一个结点拷贝过去,但是现代写法只需要调用 swap 交换一下就可以了。
现代写法更加简洁,只是在 string 这里优势体现的不明显罢了,我们后面可以慢慢体会。
6. operator 运算符重载
6.1 六个比较运算符重载
学日期类的时候就说过,只需实现 > 和 ==,剩下的都可以复用解决:
而且> 和 ==可以直接用strcmp ,剩下的复用,你不想用strcmp时剩下的也不用改了:
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// 养成this指针写在前面的习惯 { 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); }
6.2 流插入和流提取重载
我们当时实现日期类的流插入和流提取时,也详细讲过这些,当时讲解了友元。
在友元那一章我们说过 "占参问题" ,这里就不再多做解释了。
如果我们重载成成员函数,第一个位置就会被隐含的 this 指针占据。
这样实现出来的流插入必然会不符合我们的使用习惯,所以我们选择在全局实现。
在全局里不存在隐含的 this 指针了。
而且我们已经有operator [ ] 可以访问私有成员了,所以不需要设置成友元函数:
流插入很简单:
ostream& operator<<(ostream& out, string& s) { for (size_t i = 0;i < s.size();++i) { out << s[i]; } return out; }
但是流提取是这么简单吗?下面的代码有什么问题?:
istream& operator>>(istream& in, string& s) { char ch; in >> ch; while (ch != ' ' && ch != '\n') { s += ch; in >> ch; } return in; }
我们发现这样输入空格和换行也终止不了程序,因为cin会自动忽略空格和换行。
有什么办法?cin有一个get的成员函数,可以获取每一个字符,现在我们查下文档会用就行,
后面我们还会详细的讲解IO流,流提取普通实现:
istream& operator>>(istream& in, string& s) { char ch; ch = in.get(); while (ch != ' ' && ch != '\n') { s += ch; ch = in.get(); } return in; }
这样实现的流提取有一个缺陷:频繁的 += 效率低,能想到什么办法优化?
以下是类似库里面的实现:(思路类似缓冲区)
istream& operator>>(istream& in, string& s)// 流插入优化(类似库里面的) { char ch; ch = in.get(); const size_t N = 32; char buff[N];// C++11支持的变长数组 size_t i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == N - 1)// 如果buff的容量满了 { buff[i] = '\0';// 在后面放\0, s += buff;// += 到 s 上 i = 0;// 把 i 重新变成0 用来再次使用buff数组 } ch = in.get(); } buff[i] = '\0';// 处理一下buff剩余的 s += buff; return in; }
简单测试下:
void test_string6() { string s1; string s2; cin >> s1 >> s2; cout << s1 << endl << s2 << endl; cout << (s1 > s2) << endl; cout << (s1 == s2) << endl; cout << (s1 >= s2) << endl; cout << (s1 < s2) << endl; cout << (s1 <= s2) << endl; cout << (s1 != s2) << endl; }
7. 完整代码:
string.h:
#pragma once #include<iostream> #include<string> #include<assert.h> using namespace std; namespace rtx { class string { public: string(const char* s = "") { _size =strlen(s);// 因为要算多次strlen 效率低 且放在初始化列表关联到声明顺序 所以不用初始化列表 _capacity = _size; _str = new char[_size + 1];// 开_size+1大小的空间(多开一个放\0) strcpy(_str, s); } //string(const string& s)/传统写法 // :_str(new char[s._capacity + 1]) // , _size(s._size) // , _capacity(s._capacity) //{ // strcpy(_str, s._str); //} void swap(string& s)// s和*this换 { ::swap(s._str, _str);//注意这里要加域作用符,默认是全局的,不然就是自己调自己了 ::swap(s._size, _size); ::swap(s._capacity, _capacity); } string(const string& s)/现代写法 :_str(nullptr) , _size(0) , _capacity(0) { string tmp(s._str); swap(tmp);// tmp和*this换 } //string& operator=(const string& s)/传统写法 //{ // if (this != &s) // { // char* tmp = new char[s._capacity + 1];// 开辟新的空间 // strcpy(tmp, s._str);// 赋值到tmp // delete[] _str;// 释放原有空间 // _str = tmp;// tmp赋值到想要的地方,出去tmp就销毁了 // _size = s._size; // _capacity = s._capacity; // } // return *this; //} string& operator=(const string& s)/现代写法 { if (this != &s) { string tmp(s); swap(tmp);// tmp和*this换 } return *this; } ~string() { delete[] _str; _str = nullptr; } const char* c_str() const { return _str; } size_t size() const { return _size; } char& operator[](size_t pos) { assert(pos < _size); return _str[pos]; } const char& operator[](size_t pos) const { assert(pos < _size); return _str[pos]; } typedef char* iterator; iterator begin() { return _str; } iterator end() { return _str + _size; } typedef const char* const_iterator; const_iterator begin() const { return _str; } const_iterator end() const { return _str + _size; } void reserve(size_t new_capacity) { if (new_capacity > _capacity) { char* tmp = new char[new_capacity + 1];// 开新空间 strcpy(tmp, _str);// 搬运 delete[] _str; // 释放原空间 _str = tmp;// 没问题,递交给_str _capacity = new_capacity;// 更新容量 } } void push_back(const char ch) { if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } _str[_size++] = ch;// 在_size位置放字符后++ _str[_size] = '\0';// 易漏 //insert(_size, ch); } void append(const char* str) { int len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } strcpy(_str + _size, str);// 首字符+_size大小就是\0位置 _size += len; //insert(_size, str); } string& operator+=(const char ch) { push_back(ch); return *this; } string& operator+=(const char* str) { append(str); return *this; } string& insert(size_t pos, const char ch) { assert(pos <= _size); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } for (size_t i = _size + 1;i > pos; --i)// 挪动数据,+1是挪动\0 { _str[i] = _str[i - 1]; } _str[pos] = ch; ++_size; return *this; } string& insert(size_t pos, const char* str) { assert(pos <= _size); int len = strlen(str); if (_size + len > _capacity) { reserve(_size + len); } for (size_t i = _size + len ;i > pos + len - 1; --i)// 挪动数据,画图注意边界,参考上面inser字符的len == 1 { _str[i] = _str[i - len];// 首先看\0 _size+len-len就是\0的位置 } strncpy(_str + pos, str, len); _size += len; return *this; } void resize(size_t new_capacity, const char ch = '\0') { if (new_capacity > _size)// 插入数据 { reserve(new_capacity); //for (size_t i = _size; i < new_capacity; ++i) //{ // _str[i] = ch; //} memset(_str + _size, ch, new_capacity - _size);// 上面的for循环即memset的功能 _str[new_capacity] = '\0'; _size = new_capacity; } else// 删除数据 { _str[new_capacity] = '\0'; _size = new_capacity; } } size_t find(char ch) const { for (size_t i = 0; i < _size; i++) { if (ch == _str[i])// 找到了 { return i; // 返回下标 } } return npos;// 找不到 } size_t find(const char* str, size_t pos = 0) const { const char* ptr = strstr(_str + pos, str); if (ptr == nullptr) { return npos; } else { return ptr - _str; // 减开头 } } string& erase(size_t pos, size_t len = npos) { assert(pos < _size); if (len == npos || pos + len >= _size)// 如果pos后面的都删完了,注意len == npos 不能忽略,因为npos + len 有可能重回到 1 { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } return *this; } 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// 养成this指针写在前面的习惯 { 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); } private: char* _str; size_t _size; size_t _capacity; public: const static size_t npos = -1;// const static 语法特殊处理,直接可以当成定义初始化 }; ostream& operator<<(ostream& out, string& s) { for (size_t i = 0;i < s.size();++i) { out << s[i]; } return out; } //istream& operator>>(istream& in, string& s)// 流插入普通实现 //{ // char ch; // ch = in.get(); // while (ch != ' ' && ch != '\n') // { // s += ch; // ch = in.get(); // } // return in; //} istream& operator>>(istream& in, string& s)// 流插入优化(类似库里面的) { char ch; ch = in.get(); const size_t N = 32; char buff[N];// C++11支持的变长数组 size_t i = 0; while (ch != ' ' && ch != '\n') { buff[i++] = ch; if (i == N - 1)// 如果buff的容量满了 { buff[i] = '\0';// 在后面放\0, s += buff;// += 到 s 上 i = 0;// 把 i 重新变成0 用来再次使用buff数组 } ch = in.get(); } buff[i] = '\0';// 处理一下buff剩余的 s += buff; return in; } void test_string1() { string s1("hello world"); string s2(s1); string s3("!!!"); s1 = s3; cout << s1.c_str() << endl; cout << s2.c_str() << endl; cout << s3.c_str() << endl; string s4; cout << s4.c_str() << endl; } void test_string2() { string s1("hello world"); string s2; for (size_t i = 0; i < s1.size(); i++) { cout << s1[i] << " "; } cout << endl; s1[0] = 'x'; for (size_t i = 0; i < s1.size(); i++) { cout << s1[i] << " "; } cout << endl; } void test_string3() { string s1("hello world"); string::iterator it = s1.begin(); while (it != s1.end()) { cout << *it << " ";// 读 it++; } cout << endl; it = s1.begin(); while (it != s1.end()) { (*it)++;// 写,++的优先级比*高,+=就低,可以 *it += 1; cout << *it << " "; it++; } cout << endl; for (auto& e : s1) { cout << e << " "; } cout << endl; } void test_string4() { string s1("hello world"); cout << s1.c_str() << endl; s1.push_back('!'); cout << s1.c_str() << endl; s1.push_back('R'); cout << s1.c_str() << endl; s1.append("abcd"); cout << s1.c_str() << endl; s1 += 'e'; s1 += "fgh"; cout << s1.c_str() << endl; s1.insert(0, 'x'); s1.insert(6, 'T'); cout << s1.c_str() << endl; s1.insert(6, "PPPPPPPPPP"); cout << s1.c_str() << endl; s1.insert(0, "PPPPPPPPPP"); cout << s1.c_str() << endl; s1.resize(100,'x'); cout << s1.c_str() << endl; } void test_string5() { string s1("hello world"); string s2(s1); string s3 = s1; cout << s2.c_str() << endl << s3.c_str() << endl; cout << s1.find('d') << endl;// 打印d的下标:6 cout << s1.find("world") << endl;// 打印了w的下标:10 cout << s1.find("wold") << endl;// 打印了npos:4294967295 cout << s1.find("world", 9) << endl;// 打印了npos:4294967295 s1.erase(9, 2);// 从下标9开始删除2个字符 cout << s1.c_str() << endl; s1.erase(s1.find('o'), 2);// 找到o,其下标为4,从下标4开始删除2个字符 cout << s1.c_str() << endl; s1.erase(5);// 从下标5开始删完 cout << s1.c_str() << endl; } void test_string6() { string s1; string s2; cin >> s1 >> s2; cout << s1 << endl << s2 << endl; cout << (s1 > s2) << endl; cout << (s1 == s2) << endl; cout << (s1 >= s2) << endl; cout << (s1 < s2) << endl; cout << (s1 <= s2) << endl; cout << (s1 != s2) << endl; } }
Test.c:
#define _CRT_SECURE_NO_WARNINGS 1 #include "string.h" int main() { try { rtx::test_string6(); } catch (const exception& e) { cout << e.what() << endl; } return 0; }
本章完。
这篇博客两万多个字了......刚开始接触确实有点累der
不过STL的实现都是类似的,以后的学习就轻松多了,
想笑~ 来伪装掉下的眼泪~