目录
一. string类
- 字符串是表示字符序列的类
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
- string类是使用char(即作为它的字符类型,使用它的默认char_traits)和分配器类型
- string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数
- 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然安装字节(而不是实际编码的字符)来操作
总结:
- string是表示字符串的字符串类
- 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
- string在底层实际是:basic_string模板类的别名,typedef basic_string<char,char_traits,allocator>string;
- 不能操作多字节或者变长字符的序列。
在使用string类时,必须包含#include头文件以及using namespace std;
二. string类的常用接口说明
1. string类对象的常见构造
(constructor)函数名称 | 功能说明 |
string() | 构造空的string类对象,即空字符串 |
string(const char* s) | 用C-string来构造string类对象 |
string(size_t n, char c) | string类对象中包含n个字符c |
string(const string& s) | 拷贝构造函数 |
void Teststring() {
//常见构造
string s1;//构造空的string类对象
string s2("hello");//用C格式字符串构造
string s3(s2);//拷贝构造
string s4 = s3;//拷贝构造
//常用构造
string s5("abcdefghijklmnopqrstuvwxyz");//用C格式字符串构造
//string (const string& str, size_t pos, size_t len = npos);
string s6(s5, 5, 10);//从s5中的下标为5处开始复制10个字符
string (const string& str, size_t pos, size_t len = npos);
string s7(s5, 5);//从s5中的下标为5处开始复制,复制长度为npos个,即直到全部复制到s7中
//最长复制一个整形的最大值长度
//string (const char* s, size_t n);
string s8("213241", 5);//从下标为0开始复制字符串里的5个字符到s8中
//string (size_t n, char c);
string s9(2, 'x');//用2个x初始化
//string s10(s5, 30, 100);//越界抛出异常
//string (const string& str, size_t pos, size_t len = npos);
string s11(s5, 4, 200);//长度不够全部只能将全部字符串复制即截止
}
string类型会被其它字符串或字符覆盖
//string类型会被其他的字符串或者字符覆盖
void test3() {
string s1("hello");
string s2("xxx");
s1 = s2;//用s2将s1覆盖
s1 = "yyy";//用字符串将s1覆盖
s1 = 'y';//用字符将s1覆盖
cout << s1 << endl;
}
2. string类对象的容量操作
函数名称 | 功能说明 |
size | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回空间总大小 |
empty | 检测字符串释放为空串,是返回true,否则返回false |
clear | 清空有效字符 |
reserve | 为字符串预留空间 |
resize | 将有效字符的个数该成n个,多出的空间用字符c填充 |
// size/clear/resize
void Teststring1() {
//注意:string类对象支持直接用cin和cout进行输入和输出
string s("hello world!!!");
cout << s.length() << endl;//返回字符串长度
cout << s.size() << endl;//返回字符串长度,常用,因为适用性强
cout << s.max_size() << endl;//可放入字符串最大的长度
cout << s.capacity() << endl;//字符串的容量
cout << s << endl;
//将s中的字符串清空,注意情况时只是将size清0,不改变底层空间的大小
s.clear();
cout << s.size() << endl;
cout << s.capacity() << endl;
//无初始化扩容,将s1中有效字符个数增加到100个,多出位置用'x'进行填充
string s1;
s1.reserve(100);//扩容不初始化
s1.resize(100, 'x');//扩容初始化
cout << s1 << endl;
//查看在vs下的扩容情况
size_t sz = s.capacity();
cout << "capacity:" << " " << sz << endl;
for (size_t i = 0; i < 1000; ++i) {
s.push_back('x');
//不等于说明扩容了
if (sz != s.capacity()) {
sz = s.capacity();
cout << "capacity:" << " " << sz << endl;
}
}
//有初始化扩容
string s2("hello");
s2.reserve(100);//只扩容
s2.resize(100, 'x');//扩容完后面补x
cout << s2 << endl;
//在vs下这两个函数不会缩容
s2.reserve(10);//容量不变
s2.resize(10);//将s2中有效字符个数缩小到10个size,容量不变,只在s2后打印五个x
cout << s2 << endl;
}
void Teststring2() {
string s;
//测试reserve是否会改变string中有效元素个数
s.reserve(100);
cout << s.size() << endl;
cout << s.capacity() << endl;
//测试reverse参数小于string的底层空间大小时,是否会将空间缩小
s.reverse(50);
cout << s.size() << endl;
cout << s.capacity() << endl;
}
//利用reserve提高插入数据的效率,避免增容带来的开销
void TestPushBack() {
string s;
size_t sz = s.capacity();
cout << "making s grow:\n";
for(int i = 0; i < 100; ++i) {
s.push_back('c');
if(sz != s.capacity()) {
sz = s.capacity();
cout << "capacity changed:" << sz << '\n';
}
}
}
注意:
- size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般清空下基本都是用size()。
- clear()只是将string中有效字符清空,不改变底层空间大小。
- resize(size_t n)与resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
- reserve(size_t res_arg = 0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reverser不会改变容量大小。
3. string类对象的访问及遍历操作
函数名称 | 功能说明 |
operator[] | 返回pos位置的字符,const string类对象调用 |
begin+end | begin获取一个字符的迭代器+end获取最后一个字符下一个位置的迭代器 |
rbegin+rend | begin获取一个字符的迭代器—+end获取最后一个字符下一个位置的迭代器 |
范围for | c++支持更简洁的范围for的新遍历方式 |
//字符串的遍历
void test4() {
//第一种方式,下标+[]
string s1("hello world!");
//size是字符串容量函数,获取字符串的长度,不包含\0
for (size_t i = 0; i < s1.size(); ++i) {
//这里的[]是被string重载过的,注意越界会抛出异常
//s1.operator[].(i);
cout << s1[i] << " ";
}
cout << endl;
//第二种方式,迭代器 -------像指针一样的东西或者就是指针
//为了方便访问像树或者链表的数据结构,还可以访问顺序表类似的数据结构
string s2("hello");
string::iterator it = s2.begin();//begin获取第一个字符的迭代器
//end获取最后一个字符即(\0处)下一个位置的迭代器,begin和end的区间范围是左闭右开
while (it != s2.end()) {
cout << *it << " ";
++it;
}
cout << endl;
//第三种方式,范围for ------原理:编译器替换成迭代器,反汇编查看可知
string s3("world!");
for (auto ch : s3) {
cout << ch << " ";
}
cout << endl;
}
//使用迭代器遍历vector
void test5() {
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
vector<int>::iterator vit = v.begin();
while (vit != v.end()) {
cout << *vit << " ";
++vit;
}
cout << endl;
}
(1)operator[]重载问题和at的重载问题
//管理以'\0'结尾的动态增长字符数组,关于operator[]重载问题
namespace a {
class string {
public:
//可读可写重载,值修改返回对象,这里返回引用是方便修改字符串中的字符
char& operator[](size_t pos) {
assert(pos <= _size);
return _str[pos];
}
//只读重载,对应const不可修改的类型
const char& operator[](size_t pos) const {
assert(pos <= _size);
return _str[pos];
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
}
//operator[]和at的两种重载
void test6() {
string s1("hello");
const string s2("hello");
//s1[6];//越界使用断言一定报错,和数组不同
//s2[6];同理
//s1.at(6);//越界抛出异常
//s2.at(6);//同理
s1[0] = 'x';
//s2[0] = 'x';//const修饰不能修改
s1.at(0) = 'y';//s2同理
}
(2)迭代器
//四种迭代器:可读可写正向迭代器,可读可写反向迭代器,只读正向迭代器,只读反向迭代器
//begin和end迭代器的比较一般使用!=,除了一些特殊情况使用<,标准化
//begin获取第一个字符的迭代器,end获取最后一个字符即(\0处)下一个位置的迭代器,begin和end的区间范围是左闭右开,rbegin和rend同理
void test7() {
string s1("hello world!");
//可读可写正向迭代器
string::iterator sit = s1.begin();
while (sit != s1.end()) {
(*sit) += 1;
cout << *sit << " ";
++sit;
}
cout << endl;
cout << s1 << endl;
//可读可写反向迭代器
string::reverse_iterator rsit = s1.rbegin();
while (rsit != s1.rend()) {
(*rsit) -= 1;
cout << *rsit << " ";
++rsit;
}
cout << endl;
cout << s1 << endl;
const string s2("hello world");
//只读正向迭代器
string::const_iterator sit2 = s2.begin();
while (sit2 != s2.end()) {
//(*sit2) += 1;//报错
cout << *sit2 << " ";
++sit2;
}
cout << endl;
//只读反向迭代器
string::const_reverse_iterator rsit2 = s2.rbegin();
while (rsit2 != s2.rend()) {
//(*rsit2) -= 1;//报错
cout << *rsit2 << " ";
++rsit2;
}
cout << endl;
}
4. string类对象的修改操作
函数名称 | 功能说明 |
push_back | 在字符串后尾插字符c |
append | 在字符串后追加一个字符串 |
operator+= | 在字符串后追加字符串str |
c_str | 返回C格式字符串 |
find+npos | 从字符串pos位置开始往后找字符c,后悔该字符在字符串中的位置 |
rfind | 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置 |
substr | 在str中从pos位置开始,截取n个字符,如何将其返回 |
//string转化成C语言字符串
void test4() {
string s("hello wordl!");
cout << s << endl;
cout << s.c_str() << endl;//c_str()返回C语言的字符串,为了更好的与C语言适应
}
//find、rfind、substr
void test5() {
//利用find、rfind和substr取出文件后缀
string file1("string.cpp");
string file2("string.c");
string file3("string.c.tar.zip");//这里针对多个含有标记的可以使用rfind,找最后一个标记
string& file = file1;//这里使用引用方便更改
size_t pos = file.find('.');//查找字符,找到返回 该字符下标,否则返回npos
//size_t pos = file.rfind('.');//查找字符,找到返回 该字符下标,否则返回npos
string suffix = file.substr(pos);//不传返回字符串长度默认是npos
if (pos != string::npos) {
cout << suffix << endl;
}else {
cout << "无后缀" << endl;
}
//npos是string里面的一个静态成员变量
//static const size_t npos = -1;
//利用substr和find取出url中的协议,域名,uri(资源)
string url1("http://www.cplusplus.com/reference/string/string/find/");
string url2("https://leetcode.cn/problems/reverse-only-letters/submissions/");
string& url = url1;//方便更改
//找出协议、域名、uri
string protocol;//协议
size_t pos1 = url.find("://", 0);
if (pos1 != string::npos) {
protocol = url.substr(0, pos1);
cout << protocol << endl;
}
else {
cout << "false" << endl;
}
string domain;//域名
size_t pos2 = url.find('/', pos1+3);//从w开始找,找到/停下
if (pos2 != string::npos) {
domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));//第一个参数传起始位置,第二个参数传长度
cout << domain << endl;
}
else {
cout << "false" << endl;
}
string uri;//资源
uri = url.substr(pos2 + 1);
cout << uri << endl;
}
//push_back、append和+=
void test6() {
//连接字符串并扩容
string s1;
s1.push_back('x');//只能用来连接字符
s1.append("hello");//连接字符串
string s2("world");
s1.append(s2);//连接字符串
cout << s1 << endl;
//常用为以下方法,使用重载后的+=
string s3;
s3 += 'x';
s3 += "hello";
string s4("world");
s3 += s4;
cout << s3 << endl;
cout << s3.c_str << endl; //以C语言的方式打印字符串
}
注意:
1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reverse把空间预留好。
5. string类非成员函数
函数 | 功能说明 |
operator+ | 尽量少用,因为传值返回,导致深拷贝效率低 |
operator>> | 输入运算符重载 |
operator<< | 输出运算符重载 |
getline | 获取一行字符串,输入时不会受到空格的影响,只会受到换行符的影响 |
relational operators | 大小比较,接口很多,过于冗杂 |