【C++】string类 - 库中的常见使用
作者主页:lightqjx
本文专栏:C++
目录
前言(简单了解STL)
一、为什么要学习string类
二、 string类的简介
三、string的库中常见使用
1. 常见构造函数
2. 对容量的操作
3. 访问及遍历操作
(1)常见操作
(2)string的迭代器
4. string类对象的增加操作
5. 对string对象进行查找
6. string类非成员函数
四、总结
前言(简单了解STL)
概念
STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。
发展
STL最初是惠普实验室完成的,这是原始版本(HP版本),HP版本是所有STL实现版本的始祖;后来继承自HP版本,又出现了P. J. 版本、RW版本、SGI版本。
其中的需要注意的是SGI版本:需要由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。所以后面文章学习STL要阅读的部分源代码,主要参考的就是这个版本。
STL的六大组件
参照下图:
所以接下来关于string的学习,其实就是对容器的第一步学习,也是对STL的第一步学习。
缺陷(简单了解)
- STL库的更新太慢了。这个得严重吐槽,上一版靠谱是C++98,中间的C++03基本一些修订。C++11出来已经相隔了13年,STL才进一步更新。
- STL现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
- STL极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
- STL的使用会有代码膨胀的问题,比如使用vector/vector/vector这样会生成多份代码,当然这是模板语法本身导致的。
接下来,开始进入正题:认识string类并学习string类的常见使用。
一、为什么要学习string类
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数(如strlen、strcpy等等),但是这些库函数与字符串是分离开的,不太符合面向对象编程(Object Oriented Programming,简称OOP)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
在OJ题型中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。
二、 string类的简介
文档查阅链接:string - C++ Reference
如图是在文档中对string的部分介绍。简单总结一下就是:
- string是一个类,表示字符串的字符串类
- 该类的接口与常规容器的接口基本相同,添加了一些专门用来操作string的常规操作。
- string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
- 不能操作多字节或者变长字符的序列。
- 在使用string类时,必须包含头文件:#include<string>以及使用命名空间:using namespace std;
三、string的库中常见使用
string类中的可以使用的函数有许多,本次的使用讲解只是将库中常见使用进行列举,来逐渐掌握string的使用。
1. 常见构造函数
库中的构造函数有:
其中的所有函数在文当中都有解释,链接为:string::string - C++ Reference
现在来看看string类的实例化,测试代码:
#include<string>
#include <iostream>
using namespace std;void test1()
{string s1; // 默认构造函数:string();string s2("Hello world"); // 用C字符串来构造string类对象string s3(s2); // 拷贝构造string s4(10, 'x'); // string类对象中包含10个字符xstring s5("Hello world", 4); // 复制"Hello world"的前4个string s6(s2, 4, 3); // 复制s2对象的下标为4的位置开始的后面3个字符// 复制s2对象的下标为3的位置开始的后面100个字符,此时s2长度不够,// 则会使用缺省参数npos = -1,将剩余的字符全部拷贝string s7(s2, 3, 100);cout << "s1:" << s1 << endl;cout << "s2:" << s2 << endl;cout << "s3:" << s3 << endl;cout << "s4:" << s4 << endl;cout << "s5:" << s5 << endl;cout << "s6:" << s6 << endl;cout << "s7:" << s7 << endl;
}
int main()
{test1();return 0;
}
除此之外,还可以使用赋值运算符重载来实例化:
链接:string::operator= - C++ Reference
它可以使用string对象,C字符串,和字符来进行赋值:
#include<string>
#include <iostream>
using namespace std;
void test2()
{string s1("Hello world");string s2;s2 = s1;cout << s2 << endl; // string& operator= (const string& str);s2 = "abcdefg";cout << s2 << endl; // string& operator= (const char* s);s2 = 'x';cout << s2 << endl; // string& operator= (char c);}int main()
{test2();return 0;
}
2. 对容量的操作
在库中常见对容量的操作如下:
对于size、length、capacity,都是没参数的,且都只有一个函数,使用时需要注意:
- size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小(即:capacity不会变)。
使用代码如下:
#include<string>
#include <iostream>
using namespace std;
void test3()
{string s1("Hello world");cout << "s1 :"<< s1 << endl;cout << "s1.size():" << s1.size() << endl;cout << "s1.length():" << s1.length() << endl;cout << "s1.capacity():" << s1.capacity() << endl;s1.clear();cout << "-----------执行s1.clear()后-------------" << endl;cout << "s1 :" << s1 << endl;cout << "s1.size():" << s1.size() << endl;cout << "s1.length():" << s1.length() << endl;cout << "s1.capacity():" << s1.capacity() << endl;
}
int main()
{test3();return 0;
}
对empty和clear进行测试:
使用:
#include<string>
#include <iostream>
using namespace std;
void test4()
{string s1("Hello world");cout << "s1:" << s1 << endl;cout << "s1.size():" << s1.size() << endl;cout << "s1.capacity():" << s1.capacity() << endl;cout << "s1.empty():" << s1.empty() << endl;s1.clear();// 清除s1中的数据,但容量不变cout << "s1:" <<s1 << endl;cout << "s1.size():" << s1.size() << endl;cout << "s1.capacity():" << s1.capacity() << endl;cout << "s1.empty():" << s1.empty() << endl;
}
int main()
{test4();return 0;
}
对于resize,有多个函数,来看库中:
其中,resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
使用代码如下:
#include<string>
#include <iostream>
using namespace std;
void test5()
{string s1("Hello world");cout << "s1:" << s1 << endl;cout << "s1.size():" << s1.size() << endl;cout << "s1.capacity():" << s1.capacity() << endl << endl;s1.resize(5);//n比当前长度小cout << "s1:" << s1 << endl;cout << "s1.size():" << s1.size() << endl;cout << "s1.capacity():" << s1.capacity() << endl << endl;s1.resize(10, 'x');//n比当前长度大,并且指定字符xcout << "s1:" << s1 << endl;cout << "s1.size():" << s1.size() << endl;cout << "s1.capacity():" << s1.capacity() << endl << endl;s1.resize(100);//n比当前长度大,不指定字符cout << "s1:" << s1 << endl;cout << "s1.size():" << s1.size() << endl;cout << "s1.capacity():" << s1.capacity() << endl << endl;
}
int main()
{test5();return 0;
}
可以扩容的还有一个函数reserve,在库中只有一个函数:
reserve(size_t n = 0) :为string预留空间,不改变有效元素个数,当n小于string的底层空间总大小时,reserver不会改变容量大小;如果n大于当前的字符串容量,该函数会促使容器将其容量增加到n个字符或更大(不同平台,扩容的大小可能不同)。
使用代码如下:
#include<string>
#include <iostream>
using namespace std;
void test6()
{string s1("Hello world");cout << "s1.size():" << s1.size() << endl;cout << "s1.capacity():" << s1.capacity() << endl << endl;s1.reserve(5);//n比当前长度小cout << "s1:" << s1 << endl;cout << "s1.size():" << s1.size() << endl;cout << "s1.capacity():" << s1.capacity() << endl << endl;s1.reserve(50);//n比当前长度大cout << "s1.size():" << s1.size() << endl;cout << "s1.capacity():" << s1.capacity() << endl << endl;
}
int main()
{test6();return 0;
}
3. 访问及遍历操作
对对象进行访问及遍历的常用函数:
(1)常见操作
在C语言阶段,我们常常是使用方括号+下标([ ]+下标)的方法来遍历字符数组的,在C++中,也是可以这样写的:
#include<string>
#include <iostream>
using namespace std;
void test7()
{// C语言char str[] = "Hello world";str[1]++; // C++string s1("Hello world");for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << ' ';}cout << endl;
}int main()
{test7();return 0;
}
其实C++中的 [ ] + 下标是 [ ] 操作符重载:s1[i] --> s1.operator[ ](i) 。
“char& operator[] (size_t pos);”o普通对象可以使用;
“const char& operator[] (size_t pos) const;”普通对象和const对象都可以使用。
(2)string的迭代器
在C++中,std::string
的迭代器是一种用于遍历和访问字符串中字符的对象。迭代器提供了类似于指针的功能,允许你逐个访问字符串中的每个字符。迭代器可能是指针,也可能不是指针,在string中的迭代器其实就是指针。
begin() 和 end():
- begin() 返回指向字符串第一个字符的迭代器。
- end() 返回指向字符串末尾(最后一个字符的下一个位置)的迭代器。
rbegin() 和 rend():
- rbegin() 返回指向字符串最后一个字符的反向迭代器。
- rend() 返回指向字符串开头之前的反向迭代器。
iterator是像指针一样的类型,有可能是指针,也可能不是指针;iterator是正向的迭代器,而reverse_iterator则是反向的迭代器。要注意:const_iterator和const_reverse_iterator类型的迭代器都是只能读不能写的。
它的使用方法如下:
#include<string>
#include <iostream>
using namespace std;
void test8()
{string s1("Hello world");string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}cout << endl;// 反向迭代器:反向打印string::reverse_iterator rit = s1.rbegin();while (rit != s1.rend()){// 读cout << *rit << " ";++rit;}cout << endl;
}
int main()
{test8();return 0;
}
返回for也是可以实现访问遍历的,如果是要对数据进行修改,则要用引用,如下所示:
#include<string>
#include <iostream>
using namespace std;
int main()
{string s1("Hello world");for (auto& ch : s1){//改ch++;}for (auto ch : s1){//读cout << ch << " ";}return 0;
}
其实范围for的底层是用迭代器实现的。
4. string类对象的增加操作
对对象进行修改的常用函数:
对于push_back,在库中只有一个函数:
使用如下:
#include<string>
#include <iostream>
using namespace std;
int main()
{string s1("Hello world");s1.push_back('x');s1.push_back('y');s1.push_back('z');cout << s1 << endl;return 0;
}
对于append,在库中有:
代码的使用:
#include<string>
#include <iostream>
using namespace std;
void test9()
{string s1("Hello world");string s2("abcdefgh");s1.append(s2);cout << s1 << endl;string s3("Hello world");s3.append(s2,3,4);cout << s3 << endl;string s4("Hello world");s4.append("1234567");cout << s4 << endl;string s5("Hello world");s5.append("1234567", 3);cout << s5 << endl;string s6("Hello world");s6.append(3, 'x');cout << s6 << endl;
}
int main()
{test9();return 0;
}
对于oprevator+=,在库中有:
可以追加的有:string对象中的字符串,常量字符串,单个字符。并且它还可以自己根据条件扩容的
#include<string>
#include <iostream>
using namespace std;
void test10()
{string s1("Hello world");string s2("abcdefgh");s1 += s2;cout << s1 << endl;string s3("Hello world");s3 += "123456789";cout << s3 << endl;string s4("Hello world");s4 += 'x';cout << s4 << endl;
}
int main()
{test10();return 0;
}
在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
上面三种都是在原数据上进行追加,还可以指定位置进行插入,可以使用insert函数。它的函数重载也有很多:
我们以其中的第2个为例,是在pos位置插入str对象中的字符串中的从第subpos位置开始的sublen个字符:
#include<string>
#include <iostream>
using namespace std;
int main()
{string s1("Hello world");string s2("0123456789");s1.insert(5, s2, 3, 8); // 向s1中的第5个下标位置插入s2的下标为3开始的后8个字符cout << s1 << endl;return 0;
}
其余的重载函数可以根据文档进行查阅:string::insert - C++ Reference
5. 对string对象进行查找
常用函数有:
c_str可以返回C格式的字符串的指针,在C++中如果想用一些只能用C语言的字符串接口,就可以使用c_str来转化。
对于find的库函数有:
这里只介绍一种,比如第一个,给了缺省值0,它是在这个成员函数调用的对象中向后去找与str对象中的字符串匹配的子字符串的首次出现位置。找到后就返回。如果没找到,就返回npos。npos是string的静态成员变量,在库中是:
npos是无符号类型,并且默认值为-1。返回时会转化为全1的二进制,所以返回的就是最大的整型
void test11()
{string s1("Hello world");string s2("world");string s3("well");cout << s1.find(s2, 0) << endl;cout << s1.find(s3, 0) << endl;
}
int main()
{test11();return 0;
}
运行结果:
find是向后查找,而也有一个向前查找的函数rfind:
这里我们也只开看第一个:
对于substr,在库中可以看到:
它的功能是在当前对象中从pos位置开始,截取n个字符,然后将其返回
6. string类非成员函数
几个常用的非成员函数:
库中operator+中函数重载有:
它常常是用来连接对象中的字符串的。如以下代码所示:
void test12() {string s1("Hello");string s2(" ");string s3("world");string s4;s4 = s1 + s2 + s3;cout << s4 << endl; } int main() {test12();return 0; }
运行之后就是Hello world。
对应对流插入和流提取的运算符重载由于是在string这个头文件中实现了的,所以才会支持string这个类的对象的输入输出。它们的使用方式和内置类型是几乎相同的。
对于getline,在库中:
第一个:从输入流 is 中读取数据到字符串 str,直到遇到分隔符 delim。分隔符 delim 会被提取但不会被存储到 str 中。
第二个:从输入流 is 中读取数据到字符串 str,直到遇到换行符 '\n'。换行符会被提取但不会被存储到 str 中。
对于字符串时可以比较大小的,对于string类也是一样,所以在库中就会有对其进行比较大小的函数:
它们和str系列中的strcmp差不多,都是按照其ASCLL码来比较的。
四、总结
以上内容是关于string中的常见库函数的使用,虽然并没有完全介绍,但介绍了一些常见的使用函数,算是对string类进行了初步了解了,关于string的有关的函数还有很多,如下图所示:
如果要更加深入的使用string类中的,可以根据文档来进一步学习了解,C++文档链接:string - C++ Reference
感谢各位观看!希望能多多支持!