C++ string类
目录1. 为什么要学习string类1.1 C语言中的字符串1.2 两个面试题2. 标准库中的string类2.1 string类2.2 string类的常用接口说明3. string类的模拟实现3.1 经典的string类问题3.2 浅拷贝3.3 深拷贝3.3.1 传统版写法的String类3.3.2 现代版写法的String类3.4 写时拷贝4. 扩展阅读1. 为什么要学习string类1.1 C语言中的字符串C语言中字符串是以\0结尾的一些字符的集合为了操作方便C标准库中提供了一些str系列的库函数 但是这些库函数与字符串是分离开的不太符合OOP的思想而且底层空间需要用户自己管理稍不留神可 能还会越界访问。1.2 两个面试题字符串转整形数字字符串相加在OJ中有关字符串的题目基本以string类的形式出现而且在常规工作中为了简单、方便、快捷基本 都使用string类很少有人去使用C库中的字符串操作函数。2. 标准库中的string类2.1 string类string类的文档介绍1. 字符串是表示字符序列的类2. 标准的字符串类提供了对此类对象的支持其接口类似于标准字符容器的接口但添加了专门用于操作 单字节字符字符串的设计特性。3. string类是使用char(即作为它的字符类型使用它的默认char_traits和分配器类型(关于模板的更多信 息请参阅basic_string)。4. string类是basic_string模板类的一个实例它使用char来实例化basic_string模板类并用char_traits 和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。5. 注意这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列这个 类的所有成员(如长度或大小)以及它的迭代器将仍然按照字节(而不是实际编码的字符)来操作。总结1. string是表示字符串的字符串类2. 该类的接口与常规容器的接口基本相同再添加了一些专门用来操作string的常规操作。 比特就业课3. string在底层实际是basic_string模板类的别名typedef basic_string string;4. 不能操作多字节或者变长字符的序列。 在使用string类时必须包含#include头文件以及using namespace std;2.2 string类的常用接口说明1. string类对象的常见构造(constructor)函数名称功能说明string()(重点)构造空的string类对象即空字符串string(const char* s)重点用C-string来构造string类对象string(size_t n, char c)string类对象中包含n个字符cstring(const strings)重点拷贝构造函数void Teststring() { string s1; // 构造空的string类对象s1 string s2(hello bit); // 用C格式字符串构造string类对象s2 string s3(s2); // 拷贝构造s3 }2. string类对象的容量操作函数名称功能说明size重点返回字符串有效字符长度length (重点)返回字符串有效字符长度capacity返回空间总大小empty (重点)检测字符串释放为空串是返回true否则返回falseclear (重点)清空有效字符reserve (重点)为字符串预留空间**resize (重点)将有效字符的个数该成n个多出的空间用字符c填充注意1. size()与length()方法底层实现原理完全相同引入size()的原因是为了与其他容器的接口保持一 致一般情况下基本都是用size()。2. clear()只是将string中有效字符清空不改变底层空间大小。3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个不同的是当字 符个数增多时resize(n)用0来填充多出的元素空间resize(size_t n, char c)用字符c来填充多出的元素空间。注意resize在改变元素个数时如果是将元素个数增多可能会改变底层容量的大 小如果是将元素个数减少底层空间总大小不变。4. reserve(size_t res_arg0)为string预留空间不改变有效元素个数当reserve的参数小于 string的底层空间总大小时reserver不会改变容量大小。3. string类对象的访问及遍历操作函数名称功能说明operator[] 重 点返回pos位置的字符const string类对象调用begin endbegin获取一个字符的迭代器 end获取最后一个字符下一个位置的迭 代器rbegin rendbegin获取一个字符的迭代器 end获取最后一个字符下一个位置的迭代器范围forC11支持更简洁的范围for的新遍历方式4. string类对象的修改操作函数名称功能说明push_back在字符串后尾插字符cappend在字符串后追加一个字符串operator (重点)在字符串后追加字符串strc_str (重点)返回C格式字符串find npos (重点)从字符串pos位置开始往后找字符c返回该字符在字符串中的位置rfind从字符串pos位置开始往前找字符c返回该字符在字符串中的位置substr在str中从pos位置开始截取n个字符然后将其返回注意1. 在string尾部追加字符时s.push_back(c) / s.append(1, c) / s c三种的实现方式差不多一般情况下string类的操作用的比较多操作不仅可以连接单个字符还可以连接字符串。2. 对string操作时如果能够大概预估到放多少字符可以先通过reserve把空间预留好。5. string类非成员函数函数功能说明operator尽量少用因为传值返回导致深拷贝效率低operator (重点)输入运算符重载operator (重点)输出运算符重载getline (重点)获取一行字符串 大小比较relational operators (重点)大小比较6. vs和g下string结构的说明注意下述结构是在32位平台下进行验证32位平台下指针占4个字节。vs下string的结构 string总共占28个字节内部结构稍微复杂一点先是有一个联合体联合体用来定义string中字 符串的存储空间当字符串长度小于16时使用内部固定的字符数组来存放当字符串长度大于等于16时从堆上开辟空间union _Bxty { // storage for small buffer or pointer to larger one value_type _Buf[_BUF_SIZE]; pointer _Ptr; char _Alias[_BUF_SIZE]; // to permit aliasing } _Bx;这种设计也是有一定道理的大多数情况下字符串的长度都小于16那string对象创建好之后内部已经有了16个字符数组的固定空间不需要通过堆创建效率高。其次还有一个size_t字段保存字符串长度一个size_t字段保存从堆上开辟空间总的容量最后还有一个指针做一些其他事情。 故总共占1644428个字节。g下string的结构G下string是通过写时拷贝实现的string对象总共占4个字节内部只包含了一个指针该指 针将来指向一块堆空间内部包含了如下字段空间总大小字符串有效长度引用计数struct _Rep_base { size_type _M_length; size_type _M_capacity; _Atomic_word _M_refcount; };指向堆空间的指针用来存储字符串。3. string类的模拟实现3.1 经典的string类问题上面已经对string类进行了简单的介绍大家只要能够正常使用即可。在面试中面试官总喜欢让学生自己来模拟实现string类最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。大家看下以 下string类的实现是否有问题// 为了和标准库区分此处使用String class String { public: /*String() :_str(new char[1]) {*_str \0;} */ //String(const char* str \0) 错误示范 //String(const char* str nullptr) 错误示范 String(const char* str ) { // 构造String类对象时如果传递nullptr指针可以认为程序非 if (nullptr str) { assert(false); return; } _str new char[strlen(str) 1]; strcpy(_str, str); } ~String() { if (_str) { delete[] _str; _str nullptr; } } private: char* _str; }; // 测试 void TestString() { String s1(hello bit!!!); String s2(s1); }说明上述String类没有显式定义其拷贝构造函数与赋值运算符重载此时编译器会合成默认的当用s1构 造s2时编译器会调用默认的拷贝构造。最终导致的问题是s1、s2共用同一块内存空间在释放时同一块 空间被释放多次而引起程序崩溃这种拷贝方式称为浅拷贝。3.2 浅拷贝浅拷贝也称位拷贝编译器只是将对象中的值拷贝过来。如果对象中管理资源最后就会导致多个对象共 享同一份资源当一个对象销毁时就会将该资源释放掉而此时另一些对象不知道该资源已经被释放以为 还有效所以当继续对资源进项操作时就会发生发生了访问违规。3.3 深拷贝如果一个类中涉及到资源的管理其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情 况都是按照深拷贝方式提供。3.3.1 传统版写法的String类class String { public: String(const char* str ) { // 构造String类对象时如果传递nullptr指针可以认为程序非 if (nullptr str) { assert(false); return; } _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) { char* pStr new char[strlen(s._str) 1]; strcpy(pStr, s._str); delete[] _str; _str pStr; } return *this; } ~String() { if (_str) { delete[] _str; _str nullptr; } } private: char* _str; };3.3.2 现代版写法的String类class String { public: String(const char* str ) { if (nullptr str) { assert(false); return; } _str new char[strlen(str) 1]; strcpy(_str, str); } String(const String s) :_str(nullptr) { String strTmp(s._str); swap(_str, strTmp._str); } // 对比下和上面的赋值那个实现比较好 String operator(String s) { swap(_str, s._str); return *this; } /* String operator(const String s) { if(this ! s) { String strTmp(s); swap(_str, strTmp._str); } return *this; } */ ~String() { if (_str) { delete[] _str; _str nullptr; } } private: char* _str; };3.4 写时拷贝写时拷贝就是一种拖延症是在浅拷贝的基础之上增加了引用计数的方式来实现的。引用计数用来记录资源使用者的个数。在构造时将资源的计数给成1每增加一个对象使用该资源就给 计数增加1当某个对象被销毁时先给该计数减1然后再检查是否需要释放资源如果计数为1说明该 对象时资源的最后一个使用者将该资源释放否则就不能释放因为还有其他对象在使用该资源。写时拷贝写时拷贝在读取是的缺陷4. 扩展阅读面试中string的一种正确写法STL中的string类怎么了