公司动态
【 C++ 】string类的常用接口说明
目录1、string类的基础知识2、string类的常用接口说明2.1、string类的成员函数constructor构造函数destructor析构函数operator赋值string类对象的容量操作迭代器string类的元素访问string类对象的遍历操作string类对象的修改操作2.2、sring类非成员函数1、string类的基础知识字符串是表示字符序列的类标准的字符串类提供了对此类对象的支持其接口类似于标准字符容器的接口但添加了专门用于操作单字节字符字符串的设计特性。string类是使用char(即作为它的字符类型使用它的默认char_traits和分配器类型(关于模板的更多信息请参阅basic_string)。string类是basic_string模板类的一个实例它使用char来实例化basic_string模板类并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。注意这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列这个类的所有成员(如长度或大小)以及它的迭代器将仍然按照字节(而不是实际编码的字符)来操作。总结string是表示字符串的字符串类该类的接口与常规容器的接口基本相同再添加了一些专门用来操作string的常规操作。string包含的头文件是#includestringstring是属于std命名空间的using namespace std;string类是管理动态增长字符数组这个字符串以\0结尾2、string类的常用接口说明2.1、string类的成员函数constructor构造函数函数名称功能说明1、string()重点无参构造空的string类对象即空字符串2、string(const char* s)重点带参的常量字符串初始化3、string(const strings)重点拷贝构造函数4、string (const char* s, size_t n)对一个字符串的前n个初始化5、string (size_t n, char c)用 n 个 c 初始化6、string (const string str, size_t pos, size_t len npos)从pos位置处取len长度的字符进行拷贝构造1、string() 无参构造string s1;//无参初始化2、string(const char* s) 带参构造string s2(hello world!);//常量字符串初始化3、string(const strings) 拷贝构造string s2(hello world!);//常量字符串带参构造 string s3(s2);//拷贝构造 string s4 s2;//也是拷贝构造 定义新对象时用是拷贝构造给已存在的对象用才是赋值运算符重载。这两种场景编译器会自动匹配不同的函数。5、string (size_t n, char c) 用n个c初始化string s6(5, k);//用5个k初始化 cout s6 endl;//kkkkk6、string (const string str, size_t pos, size_t len npos) 从pos位置处取len长度的字符进行拷贝构造string s2(hello world!);//带参拷贝构造 string s7(s2, 6, 5);//从s2字符串的第6个位置往后取5个字符初始化 cout s7 endl;//worlddestructor析构函数前面已经说过string类是管理动态增长字符数组对于动态申请的空间需要用到析构函数把它释放掉。不过这里我们无需操作因为编译器会帮我们默认调用构造函数。operator赋值void test_string2() { string s1(hello); string s2(xxx); s1 s2;//string s1 kkk;//c-string s1 y;//character }string类对象的容量操作函数名称功能说明1、size重点返回字符串有效字符长度2、length返回字符串有效字符长度3、max_size返回字符串最大长度4、capacity返回空间总大小5、reserve重点为字符串预留空间6、resize重点将有效字符的个数拆成n个多出的空间用字符c填充7、clear重点清空有效字符8、empty重点检测字符串释放为空串是返回true否则返回false9、shrink_to_fit请求字符串减小其容量以适应其大小1、sizesize返回的是有效字符的个数size_t size() const;int main () { string str (Test string); cout str.size() endl;//11 return 0; }2、lengthsize_t length() const;length和size无本质之差返回的都是字符串的长度。void test_string1() { string s(hello world); cout s.length() endl;//11 }但是受到历史背景的影响还是推荐用size合意。3、max_sizesize_t max_size() const;max_size返回的就是最大值4、capacitysize_t capacity() const;capacity返回的就是容量大小void test_string6() { string s(hello world); cout s.capacity() endl;//15 }5、reservevoid reserve (size_t n 0);reserve的特性请求将字符串容量适应计划的大小更改为最多 n 个字符的长度。如果 n 大于当前字符串容量则该函数会导致容器将其容量增加到 n 个字符或更大。在所有其他情况下它被视为收缩字符串容量的非约束性请求容器实现可以自由地进行优化并使字符串的容量大于n。此函数对字符串长度没有影响并且无法更改其内容。利用reserve进行提前预留空间可以减少扩容带来的损耗。如下没有reserve预留空间很明显普通版本的尾插会进行多次扩容而频繁扩容会带来效率损失可以加上reserve预留空间进行优化很清晰明了加上了reserve提前预留空间大大减少了扩容的频次从而避免效率上的损失。reserve对于扩容当n大于对象当前的capacity时将当前对象的capacity扩大为n或大于nreserve对于缩容当n小于对象当前的capacity时不同编译器下处理的情况是不一样的VS下什么也不做Linux下缩容到size大小如下测试代码int main() { string s(hello world); cout s endl; cout s.size() endl; cout s.capacity() endl; //reverse(n)当n大于对象当前的capacity时将当前对象的capacity扩大为n或大于n s.reserve(20); cout s endl; cout s.size() endl; cout s.capacity() endl; //reverse(n)当n小于对象当前的capacity时 s.reserve(5); cout s endl; cout s.size() endl; cout s.capacity() endl; return 0; }我们在VS下和Linux下分别进行测试6、resizeresize特性将字符串大小调整为 n 个字符的长度。如果 n 小于当前字符串长度则当前值将缩短为其前 n 个字符并删除超出第 n 个字符的字符。如果 n 大于当前字符串长度则通过在末尾插入所需数量的字符以达到 n 大小来扩展当前内容。如果指定了 c则新元素将初始化为 c 的副本否则它们是值初始化字符空字符。void test() { string s1(hello world); //resize(n)n小于对象当前的size时将size缩小到n s1.resize(4); cout s1 endl; //hell cout s1.size() endl; //4 cout s1.capacity() endl; //15 string s2(hello world); //resize(n)n大于对象当前的size时将size扩大到n扩大的字符默认为\0 s2.resize(20); cout s2 endl; //hello world cout s2.size() endl; //20 cout s2.capacity() endl; //31 string s3(hello world); //resize(n, char)n大于对象当前的size时将size扩大到n扩大的字符为char s3.resize(20, x); cout s3 endl; //hello worldxxxxxxxxx cout s3.size() endl; //20 cout s3.capacity() endl; //31 }7、clearclear的本质就是清掉所有空间。void test() { string s(hello world); cout s endl;//hello world s.clear(); cout s.size() endl;//0 cout s endl;//空 }8、emptyvoid test() { string s(hello world); cout s endl;//hello world s.clear();//清空有效字符 if (s.empty()) cout empty endl;//empty }迭代器迭代器就是像指针一样的东西函数名称功能说明1、begin将迭代器返回到开头2、end将迭代器返回到末尾3、rbegin返回一个逆序迭代器它指向容器c的最后一个元素4、rend返回一个逆序迭代器它指向容器c的第一个元素前面的位置1、begin2、endvoid test_string3() { string s1(hello); string::iterator it s1.begin(); while (it ! s1.end()) { cout *it ;//h e l l o it; } }3、rbeginrbegin是一种逆向迭代器rbegin的特性如下返回指向字符串最后一个字符即其反向开头的反向迭代器。反向迭代器向后迭代增加它们会将它们移动到字符串的开头。rbegin 指向成员末尾将指向的字符之前的字符。要留心反向迭代器的是往反方向走区别于正向迭代器而其根本原因等后续讲到模拟实现再深究。4、rendrend的特性如下返回一个反向迭代器该迭代器指向字符串的第一个字符被视为其反向结尾前面的理论元素。string:rbegin 和 string:rend 之间的范围包含字符串的所有字符顺序相反。5、const正向迭代器普通迭代器是可读可写的因此针对const修饰的特殊情况下我们不能如下操作因为我Func里的s1是const修饰的而Func里的迭代器又是可读可写的版本属于权限放大因此要进行修正//const正向迭代器 void Func(const string s) { //记得加上const_使其对于const的函数 string::const_iterator it s.begin(); /* 或者使用auto自动推导类型 auto it s.begin(); */ while (it ! s.end()) { //*it 1; 不能写 cout *it ; it; } }6、const反向迭代器//const反向迭代器 void Func(const string s) { string::const_reverse_iterator rit s.rbegin(); /* 或者使用auto自动推导类型 auto rit s.rbegin(); */ while (rit ! s.rend()) { //*rit 1; 不能写 cout *rit ; rit; } }string类的元素访问函数名称功能说明1、operator[ ]获取字符串的字符2、at获取字符串中的字符operator[ ]operator[ ] 是获取字符串中的字符。实际的场景如下void test_string_4() { string s1(hello); const string s2(world); s1[0] x; s2[0] y; //err }因为s2是const修饰的只读所以不能修改自然s2[0]就会出错而s1可读可写。string类对象的遍历操作函数名称功能说明1、operator[ ]重点返回pos位置的字符const string类对象调用2、begin endbegin获取一个字符的迭代器 end获取最后一个字符下一个位置的迭代器3、rbegin rendbegin获取一个字符的迭代器 end获取最后一个字符下一个位置的迭代器4、范围forC11支持更简洁的范围for的新遍历方式法1operator[ ]有了[ ]的运算符重载我们就可以实现像C语言一样的下标[ ]去访问。void test_string3() { //法一//下标[] string s1(hello); for (size_t i 0; i s1.size(); i) { //s1.operator[](i); cout s1[i] ; //h e l l o } }法2正向迭代器 begin endvoid test_string3() { //法二正向迭代器 string s1(hello); string::iterator it s1.begin(); while (it ! s1.end()) { cout *it ;//h e l l o it; } }法3反向迭代器 rbegin rendvoid test_string3() { //法三反向迭代器 string s1(hello); string::reverse_iterator rit s1.rbegin(); while (rit ! s1.rend()) { cout *rit ;//o l l e h rit; } }法4范围forvoid test_string3() { //法4范围for string s1(hello); for (auto ch : s1)//auto自动取s1里的字符自动 { cout ch ; } cout endl; }范围for可以自动帮助我们实现这一整套循环看着十分便捷。不过范围for的本质其实还是利用迭代器的原理。这里大家可以通过查看反汇编得知。string类对象的修改操作函数名称功能说明1、push_back在字符串后尾插字符c2、insert指定位置前插入3、append在字符串后追加一个字符串4、operator在字符串后追加字符串str5、erase删除字符或字符串6、swap交换7、c_str返回C格式字符串8、find从字符串pos位置开始往后找字符C返回该字符的位置9、substr在str中从pos位置开始截取n个字符然后返回10、rfind从字符串pos位置开始往前找字符C返回该字符的位置11、assign为字符串分配一个新值替换其当前内容12、replace替换字符串的一部分1、push_back尾插字符void push_back (char c);作用将字符c追加到字符串的末尾使其长度增加 1。#includeiostream #includestring using namespace std; int main() { string str(word); cout str endl; //world str.push_back(s); cout str endl; //worlds }2、insert指定位置插入#includeiostream #includestring using namespace std; int main() { string str(hello world); //头部插入一个字符 str.insert(0, 1, x); cout str endl; //xhello world //使用迭代器头插 str.insert(str.begin(), 1, y); cout str endl; //yxhello world //在第n个位置插入字符 str.insert(3, 1, x); cout str endl; //yxhxello world str.insert(str.begin() 3, 1, k); cout str endl; //yxhkxello world //头插一个字符串 str.insert(0, !!!!); cout str endl; //!!!!yxhkxello world }3、append追加字符串#includeiostream #includestring using namespace std; int main() { string s1(hello); string s2( every); //追加一个string对象 s1.append(s2); cout s1 endl; //hello every //追加一个常量字符串 s1.append(body); cout s1 endl; //hello everybody //用n个字符拼接 s1.append(3, !); cout s1 endl; //hello everybody!!! //插入一个迭代器区间 s1.append(s2.begin() 1, s2.end());//hello everybody!!!every cout s1 endl; }4、operator作用通过在字符串的当前值末尾追加其他字符来扩展字符串参数含义str一个字符串对象其值在末尾复制。s指向以空值终止的字符序列的指针。序列在字符串的末尾复制。c一个字符追加到字符串的当前值。#includeiostream #includestring using namespace std; int main() { string str(hello); str ; str world; cout str endl; //hello world string ptr !!!!; str ptr; cout str endl; //hello world!!!! }不同的编译器扩容机制不同string在VS下大概是1.5倍扩容在Linux下大概是2倍扩容相对比较合适开多了浪费开少了就会频繁导致扩容如下代码验证int main() { string s; size_t old s.capacity(); cout capacity changed: old \n; cout making s grow:\n; for (int i 0; i 1000; i) { //s.push_back(c); s c; if (old ! s.capacity()) { old s.capacity(); cout capacity changed: old \n; } } return 0; }VS环境下Linux环境下通过在VS环境下的测试结果我们发现在添加数据之前string的容量是15第一次扩容到31将近2倍扩容不同于后续的1.5倍扩容实际string类里的成员变量是如下的情况class string { public: //... private: char _buff[16]; char* _str; size_t _size; size_t _capacity; };5、erase删除#includeiostream #includestring using namespace std; int main() { string str(hello world); //头删 str.erase(str.begin()); cout str endl; //ello world //头删指定字符 str.erase(str.begin() 3); cout str endl; //ell world //从pos处位置删除n个字符 str.erase(2, 3); cout str endl; //elorld //利用缺省值只给定删除的位置往后全删 str.erase(2); cout str endl; //el str.erase(0); cout str endl; //空 }6、swap交换void swap (string str);注意通过str另一个字符串对象的内容交换容器的内容。长度可能不同。调用此成员函数后此对象的值是调用前 str的值str的值是调用前此对象具有的值。请注意存在一个具有相同名称的非成员函数swap使用与此成员函数类似的优化重载该算法。#includeiostream #includestring using namespace std; int main() { string s1(hello world); string s2(HELLO WORLD); s1.swap(s2); //库里的swap 效率高 本质交换指针 swap(s1, s2);//全局的swap 效率低 本质深拷贝 }7、c_str 返回C格式字符串#includeiostream #includestring using namespace std; int main() { string s1(hello); cout s1 endl; //hello cout s1.c_str() endl;//hello }C提供c_str接口本质是为了兼容C语言的语法如下的fopen打开文件的情景如果我有个C中string定义的字符串那么我用fopen时就需要掉c_str获得C风格的字符串形式才行int main() { string filename(Test.cpp); FILE* fout fopen(filename.c_str(), r); char ch fgetc(fout); while (ch ! EOF) { cout ch; ch fgetc(fout); } return 0; }8、find参数含义str另一个包含要搜索的主题的字符串。pos要在搜索中考虑的字符串中第一个字符的位置。如果此值大于字符串长度则该函数永远不会找到匹配项。注意第一个字符由值 0而不是 1表示值为 0 表示搜索整个字符串。s指向字符数组的指针。如果指定了参数 n 3则要匹配的序列是数组中的前 n 个字符。否则2则期望以空终止的序列要匹配的序列的长度由空字符的第一次出现确定。n要匹配的字符序列的长度。c要搜索的单个字符。返回值含义第一个匹配项的第一个字符的位置。如果未找到匹配项则该函数返回字符串npos。int main() { string s1(https://blog.csdn.net/); string s2(blog); //正向搜索string类对象 size_t pos1 s1.find(s2); cout pos1 endl; //8 //搜索字符 size_t pos2 s1.find(.); cout pos2 endl; //12 }9、substr参数含义pos要作为子字符串复制的第一个字符的位置。如果此值等于字符串长度则该函数返回空字符串。如果此值大于字符串长度则会抛出out_of_range。注 第一个字符由值 0而不是 1表示。len要包含在子字符串中的字符数如果字符串较短则使用尽可能多的字符。值 stringnpos表示在字符串末尾之前的所有字符。返回值具有此对象的子字符串的字符串对象。int main() { //要求取出文件的后缀 string file(string.cpp.tar.zip); size_t pos file.find(.); if (pos ! string::npos) { //string suffix file.substr(pos, file.size() - pos); string suffix file.substr(pos); cout file 后缀 suffix endl; //string.cpp.tar.zip后缀.cpp.tar.zip } else { cout 没有后缀 endl; } }如果我要取出最后一个后缀.zip就需要用到rfind来完成10、rfind参数含义str另一个包含要搜索的主题的字符串。pos字符串中最后一个字符的位置将被视为匹配的开始。任何大于或等于字符串长度的值包括字符串npos都意味着将搜索整个字符串。注 第一个字符由值 0而不是 1表示。s指向字符数组的指针。如果指定了参数 n 3则要匹配的序列是数组中的前 n 个字符。否则2则期望以空终止的序列要匹配的序列的长度由空字符的第一次出现确定。n要匹配的字符序列的长度。c要搜索的单个字符。int main() { //要求取出文件的后缀 string file(string.cpp.tar.zip); size_t pos file.rfind(.); if (pos ! string::npos) { //string suffix file.substr(pos, file.size() - pos); string suffix file.substr(pos); cout file 后缀 suffix endl; //string.cpp.tar.zip后缀.zip } else { cout 没有后缀 endl; } }int main() { //取出url的域名 string url1(https://cplusplus.com/reference/string/string/rfind/); string url2(https://blog.csdn.net/bit_zyx?spm1000.2115.3001.5343); //协议 域名 uri string url url1; //取协议 string protocol; size_t pos1 url.find(://); if (pos1 ! string::npos) { protocol url.substr(0, pos1); cout protocol: protocol endl; //https } else { cout 非法url endl; } //取域名 string domain; size_t pos2 url.find(/, pos1 3); if (pos2 ! string::npos) { domain url.substr(pos1 3, pos2 - (pos1 3)); cout domain: domain endl; //domain:cplusplus.com } //取uri string uri url.substr(pos2 1); cout uri: uri endl;//uri:reference/string/string/rfind/ }11、replace参数含义string从指定位置开始把一定长度子串换成给定 std::string 内容。substring从指定位置起将一定长度子串替换为另一 std::string 的指定子串 。c - string把指定位置和长度子串换成 C 风格字符串。buffer将指定位置和长度子串替换为字符数组前若干字符。fill用指定数量的同一字符替换指定位置和长度子串。range把指定迭代器范围内子串换成另一迭代器范围字符序列。initializer list将指定迭代器范围内子串替换为初始化列表字符 。int main() { string s1(2222222); string s2(3333333); s1.replace(0, 2, s2); cout s1 endl;//333333322222 return 0; }我们可以利用replace和find实现把一个字符串里所有的空格换成%int main() { string str(hello world hello xzy); size_t pos str.find( ); while (pos ! string::npos) { str.replace(pos, 1, %%); pos str.find( , pos 2); } cout str endl; //hello%%%%world%%hello%%xzy return 0; }这里只是为了演示replace的接口使用而这么做但是此方法效率太低大量移动数据可采用如下的方法进行优化空间换时间int main() { string str(hello world hello xzy); string ans; for (auto ch : str) { if (ch ) ans %%; else ans ch; } cout ans endl; //hello%%%%world%%hello%%xzy return 0; }2.2、sring类非成员函数函数功能说明1、operator尽量少用因为传值返回导致深拷贝效率低2、operator输入运算符重载3、operator输出运算符重载4、relational operators大小比较5、getline获取一行字符串1、operatorint main() { string s1(hello); string s2( world); //1、string类 string类 string s3 s1 s2; cout s3 endl; //hello world //2、string类 字符 s3 s1 !; cout s3 endl; //hello! //3、字符 string类 s3 ! s2; cout s3 endl; //! world //4、string类 字符串 s3 s1 CSDN; cout s3 endl; //helloCSDN //5、字符串 string类 s3 !!! s2; cout s3 endl; //!!! world }2、operator / operatoristream operator (istream is, string str); ostream operator (ostream os, const string str);int main() { string str; cin str; //hello world cout str endl; //hello }3、relational operatorsstring类对 、!、、、、这些运算符进行了重载并且支持string类和string类string类和字符串间的比较使用效果如下int main() { string s1(abcd); string s2(efgh); cout (s1 s2) endl;//0 cout (s1 s2) endl;//1 cout (s1 s2) endl;//0 cout (s1 s2) endl;//1 }注意这里string把这些运算符重载为了全局函数不像我们先前实现的日期类将其重载为成员函数重载为全局函数能帮助我们实现普通字符串和string类类型的比较因为成员函数的第一个参数默认是this指针这也就意味着实际调用时左操作数一定得是个string类类型才行就不支持普通字符串和string类对象之间的运算符比较了int main() { string s1(2222222222); string s2(3333333333); cout (s1 s2) endl; // 1 cout (s1 1111111111) endl; // 0 cout (1111111111 s2) endl; // 1 return 0; }4、getline将行从流转换为字符串从 is 中提取字符并将其存储到 str 中直到找到分隔字符 delim或换行符 \n表示 2。如果在 is 中到达文件末尾或者在输入操作期间发生其他错误则提取也会停止。如果找到分隔符则会提取并丢弃它即不存储它下一个输入操作将在它之后开始。int main() { string s1; getline(cin, s1); //hello world cout s1 endl;//hello world string s2; getline(cin, s2, r); //输入hello world cout s2 endl; //输出hello wo }