4、string类对象修改操作
- 使用示例1:
void Teststring6() { string str; cout << str << endl; str.push_back('c'); // 在str后插入字符 cout << str << endl; str.append("hello"); // 在str后追加一个字符"hello" cout << str << endl; str += '\0'; // 在str后追加结束符 str += "cole"; // 在str后追加一个字符串"cole" cout << str << endl;//即使遇到‘\0’也不会停止,只有把字符串给完全遍历完了才会停止遍历 cout << str.c_str() << endl; // 以C语言的方式打印字符串(遇到\0就停止) }
- 使用示例2:
void Teststring7() { // 获取file的后缀 string file("string.cpp"); size_t pos = file.rfind('.');//从后往前找字符. string suffix(file.substr(pos, file.size() - pos));//拷贝pos位置之后的字符串 cout << suffix << endl; // 取出url中的域名 string url("http://www.cplusplus.com/reference/string/string/find/"); cout << url << endl; size_t start = url.find("://");//从前往后找字符串 if (start == string::npos)//没找到返回npos { cout << "invalid url" << endl; return; } start += 3; size_t finish = url.find('/', start);//再往后找结束位置 string address = url.substr(start, finish - start);//拷贝子串 cout << address << endl; // 删除url的协议前缀 pos = url.find("://"); url.erase(0, pos + 3); cout << url << endl; }
注:npos是string里面的一个静态成员变量
static const size_t npos = -1;
注意:
在string尾部追加字符时,s.push_back© / s.append(1, c) / s += ‘c’ 三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串
对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好(提高效率)
5、string类非成员函数
注:对上述函数只是了解一下,对于其他的操作函数可以查阅文档了解
三、模拟实现string类
注:这里我们只是模拟实现string的一些常用接口,并非要完全复刻,学习下string类的底层,让对string类的理解更深一点就行了
1、实现string类接口展示
注:模拟时为了避免与C++本身提供的string类造成命名冲突,我们选择在命名空间里进行实现
namespace cole { class string { friend ostream& operator<<(ostream& _cout, const cole::string& s); friend istream& operator>>(istream& _cin, cole::string& s); friend istream& getline(istream& _cin, string& s); public: typedef char* iterator; typedef const char* const_iterator; string(const char* str = ""); string(const string& s); string& operator=(const string& s); ~string(); void swap(string& s); // iterator iterator begin(); iterator end(); const_iterator begin()const; const_iterator end()const; // modify void push_back(char c); string& operator+=(char c); void append(const char* str); string& operator+=(const char* str); void clear(); const char* c_str()const; // capacity size_t size()const; size_t capacity()const; bool empty()const; void resize(size_t n, char c = '\0'); void reserve(size_t n); // access char& operator[](size_t index); const char& operator[](size_t index)const; //relational operator bool operator<(const string& s); bool operator<=(const string& s); bool operator>(const string& s); bool operator>=(const string& s); bool operator==(const string& s); bool operator!=(const string& s); // 返回c在string中第一次出现的位置 size_t find(char c, size_t pos = 0) const; // 返回子串s在string中第一次出现的位置 size_t find(const char* s, size_t pos = 0) const; // 在pos位置上插入字符c/字符串str,并返回该字符 string& insert(size_t pos, char c); string& insert(size_t pos, const char* str); // 删除pos位置上的长度个字符 string& erase(size_t pos, size_t len=npos); private: char* _str; size_t _capacity; size_t _size; static const size_t npos; }; //初始化静态私有成员变量 const size_t string::npos = -1; }
2、深浅拷贝问题
一般来说对于只有内置类型的成员变量,我们可以选择编译器提供的拷贝构造函数和赋值重载函数(自己不写),编译器提供的这两个函数能够完成浅拷贝(拷贝值),但是对于存在指针成员变量的类,浅拷贝是不行的
- 示例:
namespace cole { class string { public: string(const char* str = "") { // 断言不为空指针 assert(str); _str = new char[strlen(str) + 1]; strcpy(_str, str); } ~string() { if (_str) { delete[] _str; _str = nullptr; } } private: char* _str; }; // 测试 void Teststring() { string s1("hello cole"); string s2(s1); } } int main() { cole::Teststring(); return 0; }
- 结果:
- 解释:
即值拷贝(浅拷贝)让s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,由此对于涉及资源的管理,我们需要自己以深拷贝的方式写拷贝构造函数和赋值重载函数
3、string类深拷贝写法
- 深拷贝概念:
给每个对象独立分配资源,保证多个对象之间不会因为共享资源而造成多种错误以及程序释放崩溃的问题
- 传统式:比较常规,易于理解
class string { public: //构造函数 string(const char* str = "") { // 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下 assert(str); _str = new char[strlen(str) + 1]; strcpy(_str, str); } //拷贝构造函数 string(const string& s) : _str(new char[strlen(s._str) + 1]) { strcpy(_str, s._str); } //赋值 string& operator=(const string& s) { if (this != &s)//避免自己赋值自己(没有意义) { delete[]_str; _str = new char[strlen(s._str) + 1]; strcpy(_str, s._str); } return *this; } //析构函数 ~string() { delete[]_str; _str = nullptr; } private: char* _str; };
- 现代式:简洁,效果一致
class string { public: //构造函数 string(const char* str = "") { if (nullptr == str)//处理空指针 str = ""; _str = new char[strlen(str) + 1]; strcpy(_str, str); } //拷贝构造 string(const string& s) : _str(nullptr)//将自己赋值为空 { string strTmp(s._str);//调用构造函数,生成一个具有相同内容的string swap(_str, strTmp._str);//将string对象的各成员变量相互交换 }//拷贝构造结束strTmp会先析构再销毁 //赋值重载 string& operator=(string s)//传值参数,会调用拷贝构造,生成一个具有相同内容的string { swap(_str, s._str);//将string对象的各成员变量相互交换 return *this; }//赋值重载结束束s会先析构再销毁 //析构函数 ~string() { delete[] _str; _str = nullptr; } private: char* _str; };
4、string类其他常用接口模拟
- 实现代码:
ostream& operator<<(ostream& _cout, const string& s) { for (auto ch : s)//会替换成string的迭代器 { _cout << ch; } return _cout; } istream& operator>>(istream& _cin, string& s) { s.clear();//清理 char ch; ch = _cin.get();//能够接收空格和结束符 while (ch != ' ' && ch != '\n') { s += ch; ch = _cin.get(); } return _cin; } istream& getline(istream& _cin, string& s) { s.clear(); char ch; ch = _cin.get(); while (ch != '\n') { s += ch; ch = _cin.get(); } return _cin; } //构造函数 string::string(const char* str="") { if(str==nullptr) str=""; _str = new char[strlen(str) + 1]; _size = strlen(str); _capacity = _size; strcpy(_str, str); } //交换string void string::swap(string& s) { ::swap(_str, s._str); ::swap(_size, s._size); ::swap(_capacity, s._capacity); } //拷贝构造 string::string(const string& s) :_str(nullptr) , _size(0) , _capacity(0) { string tmp(s._str); swap(tmp); } //赋值重载 string& string::operator=(const string& s)//传引用 { //避免自己给自己赋值 if (this != &s) { string tmp(s._str); swap(tmp); } return *this; } //析构函数 string::~string() { delete[] _str; _str = nullptr; _size = 0; _capacity = 0; } // iterator(普通对象) string::iterator string::begin() { return _str; } string::iterator string::end() { return _str + _size; } // iterator(const对象) string::const_iterator string::begin()const { return _str; } string::const_iterator string::end()const { return _str + _size; } // modify //尾插字符 void string::push_back(char c) { 满空间就扩容 //if (_size == _capacity) //{ // reserve(_capacity == 0 ? 4 : _capacity * 2); //} //_str[_size] = c; //_size++; insert(_size, c); } string& string::operator+=(char c) { push_back(c); return *this; } //追加字符串 void string::append(const char* str) { /*size_t len = _size + strlen(str); if (len > _capacity) { reserve(len); } strcpy(_str + _size, str); _size = len;*/ insert(_size, str); } string& string::operator+=(const char* str) { append(str); return *this; } void string::clear() { _str[0] = '\0'; _size = 0; } const char* string::c_str()const { return _str; } size_t string::size()const { return _size; } size_t string::capacity()const { return _capacity; } bool string::empty()const { return _size == 0; } //开空间+初始 void string::resize(size_t n, char c) { if (n < _size) { _str[n] = '\0'; _size = n; } else { if (n > _capacity) { reserve(n); } int pos = _size; while (pos < n) _str[pos++] = c; _str[n] = '\0'; _size = n; } } //开空间 void string::reserve(size_t n) { //需要的空间大于容量就开空间 if (n > _capacity) { char* tmp = new char[n + 1]; strncpy(tmp, _str, _size + 1);//strcpy不能将\0后的内容也拷贝 delete[] _str; _str = tmp; _capacity = n; } } // access char& string::operator[](size_t index) { assert(index < _size); return _str[index]; } const char& string::operator[](size_t index)const { assert(index < _size); return _str[index]; } //relational operator bool string::operator<(const string& s) { return strcmp(_str, s._str) < 0; } bool string::operator<=(const string& s) { return *this < s || *this == s; } bool string::operator>(const string& s) { return !(*this <= s); } bool string::operator>=(const string& s) { return !(*this < s); } bool string::operator==(const string& s) { return strcmp(_str, s._str); } bool string::operator!=(const string& s) { return !(*this == s); } // 返回c在string中第一次出现的位置 size_t string::find(char c, size_t pos) const { //pos的合理性 assert(pos < _size); for (size_t i = pos; i < _size; i++) { if (_str[i] == c) return i; } //没找到 return npos; } // 返回子串s在string中第一次出现的位置 size_t string::find(const char* s, size_t pos) const { //pos的合理性 assert(pos < _size); const char* ret = strstr(_str + pos, s); if (ret) return ret - _str; //没找到 return npos; } // 在pos位置上插入字符c/字符串str string& string::insert(size_t pos, char c) { assert(pos <= _size); if (_size == _capacity) { reserve(_capacity == 0 ? 4 : _capacity * 2); } char* end = _str + _size; while (end >= _str + pos) { *(end + 1) = *end; end--; } _str[pos] = c; _size++; return *this; } string& string::insert(size_t pos, const char* str) { assert(pos <= _size); size_t len1 = strlen(str); size_t len2 = len1 + _size; if (len2 > _capacity) { reserve(len2); } char* end = _str + _size; while (end >= _str + pos) { *(end + len1) = *end; end--; } strncpy(_str + pos, str, len1); _size += len1; return *this; } // 删除pos位置上的长度个字符 string& string::erase(size_t pos, size_t len) { assert(pos < _size); size_t leftlen = _size - pos - 1; if (len >= leftlen) { _str[pos] = '\0'; _size = pos; } else { strcpy(_str + pos, _str + pos + len); _size -= len; } return *this; }