欢迎来到CILMY23的博客
🏆本篇主题为:深入探索C++之std::string:不止于字符串
🏆个人主页:CILMY23-CSDN博客
🏆系列专栏:Python | C++ | C语言 | 数据结构与算法 | 贪心算法 | Linux
🏆感谢观看,支持的可以给个一键三连,点赞关注+收藏。
✨写在前头:
了解完模板和STL后,我们要开始研究std命名空间中的string,作为我们STL学习的开端,string对我们重新认识C语言中的字符串有很大意义。在上文中我们说过容器(Containers),容器是用来存储数据的对象,如数组、链表、树结构等。STL提供了多种类型的容器,本期我们将了解string类,虽然string并没有被归为容器,但它也和容器类似。
string
一、string的前瞻
1.1 C语言中的字符串
C语言中,字符串是以'\0'结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数, 但是这些库函数与字符串是分离开的,不太符合OOP(面向对象编程)的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。
我写C语言字符串系列篇的时候还是深有体会的,稍不留神就越界访问导致出错,有时候还难排查出来。
1.2 string的基本概念
string 类是 C++ 标准库中的一部分,它提供了对字符序列的封装,使得字符序列的操作变得既简单又安全。
☄️☄️文档阅读
我们来看看文档是怎么说的:文档的阅读网站我放在了文章末尾,在这里我分了两部分的文档基本阅读
🍀🍀图一:
🍀🍀图二:
☄️☄️信息翻译
- 字符串是表示字符序列的类
- 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作 单字节字符字符串的设计特性。
- string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信 息,请参阅basic_string)。
- string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits 和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
- 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。
☄️☄️总结
1. string是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
4. 不能操作多字节或者变长字符的序列。
⚠⚠ 在使用string类时,必须包含 #include 头文件以及 using namespace std;
二、string类的常用接口
💫String 类的默认成员函数
string的默认成员函数我们在这里主要看以下三部分:
🍃构造函数(⭐)
进入构造函数的文档界面,我们看到有七个构造函数,其中重点掌握的,也是最常用的,我在图片中已经标出来了。剩下的了解就差不多了。
在这里我们要了解第三个,前面两个很容易理解,但是第三个是什么呢?npos又是什么呢?
我们来看声明和定义,文档在底下给了我们的定义
string (const string& str, size_t pos, size_t len = npos)
解析我在图片中标出来了,大家可以研究研究。
substring constructor是指字串构造。
现在主要的问题是 npos 是个什么东西? 文档给我们提供了链接,我们可以点击查看(链接)
这里一看我们就知道了,原来 npos 是 size_t (无符号整数)的最大值,这就涉及原码反码和补码了(不懂的小伙伴可以看看链接)。
-1 的补码是32个1,也就是2的32次方-1,但是string构造函数并不会开到这么多空间,所以就只会判定拷贝直到str的末尾。
🍭🍭实际操作
写了这么多,我们来实际操作一下。在vs 2019上对这几个构造函数进行使用:记得包含头文件<string>
string s0;
string s1("CILMY23");
string s2(s1,2,3);
string s3(s2);//拷贝构造
string s4(s1, 3, string::npos);//从第三个位置开始,拷贝到str字符串末尾
string s5(10, '*');
string s6("CILMY23", 2);
结果如下:
🍃析构函数
对析构函数我们不必关注太多,稍微看看就行
在学习的时候,我们知道析构函数的功能即可
🍃赋值运算符重载
在这里赋值运算符重载主要给了三种方式
这三种方式的实操如下:
🍭🍭实际操作
string s7;s7 = s2; //将s2拷贝给s7
s7 = "CILNY23";//将常量字符串赋值给s7
s7 = '*';//将单字符*赋值给s7
结果如下:
赋值运算符重载用的最多的还是前两种,一般第三种还是挺少用的。
💫String 类的容量操作
string 的容量操作一共有以下操作:
我们重点学习:size,empty,clear,reserve,resize
🎈size(⭐),length,capacity,max_size
在string类中,size和length是一样的,都是返回字符串的长度。,它们表示的是字符串中,有效字符的个数,而capacity表示的是容量,max_size,它返回 string 类型对象最多包含的字符数。也就是string类型支持的最大字符数,超过这个数,将无法支持,编译器会拋出 length_error 异常。
我们知道在字符串的末尾是有\0的,在这里是不计入size和length的。
为什么这里会相同呢?
这就不得不提及STL和String的先后问题了,其实是String比STL先出现的,所以String在刚刚的网站中我们是在Container中是找不到String的,但是它跟容器差不多,所以就增加了一个size接口,起初最先我们使用的是length,size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
说完了size和length,我们接下来看看capacity
capacity返回的是开辟空间的大小,它是一个const成员函数,this指针指向的内容是不可以改变的 ,它和size是不一样的,有可能等于size,也可能比size还多
我们可以看一下在vs上它是如何扩容的,
size_t sz = s1.capacity();
int n;
for (n = 0; n < 1000; n++)
{s1.push_back('*');if (sz != s1.capacity()){sz = s1.capacity();cout << "capacity changed->" << s1.capacity() << endl;}}
结果如下:
我们可以看到它的扩容机制大概是按1.5倍来扩容的
而g++底下的扩容机制是按照两倍扩容的。
所以说具体的这些细节,是不确定的。
🍭🍭实际操作
string s1("hello CILMY23");cout << s1.size() << endl;
cout << s1.length() << endl;
cout << s1.capacity() << endl;
cout << s1.max_size() << endl;
cout << s1.npos << endl;
结果如下:
但是我这里max_size可能是受到了系统或者编译器的限制,在我查阅很多文档后,都说它比npos还小1个,这里得考虑字符'\0'。所以最大长度是不确定的,一般给的是当前平台最大值。
🎈empty
文档介绍:这是一个负责测试string是否为空表,如果是空就返回true,否则返回false
这个函数较为简单就不进行实操演示了
🎈clear(⭐)
clear函数主要是删除这个string中包含的字符,让它变成一个空表,我们知道空表的判定,是长度length为0,所以这里的clear是不会动capacity容量的,是只会清除有效字符。
clear()
被调用,string
对象仍然存在,它的内存没有被释放,但其内容被清空了。
🎈reserve(⭐)
它的作用是请求改变容器的容量。具体来说,它会预分配足够多的内存空间以便存储至少指定数量的元素,这个数量由函数的参数指定。这意味着使用 reserve
可以减少由于添加新元素导致的多次内存重新分配操作,从而提高程序性能。
需要注意的是,reserve 函数只改变容器的容量(即可存储的元素数量,而不必进行再分配),但不改变容器的大小(即实际存储的元素数量)。也就是说,即使调用了 reserve,size() 和 length() 函数的返回值也不会因此改变;reserve 也不会影响容器中已有元素的内容。
🍭🍭实际操作
void Teststring2()
{string s;// 测试reserve是否会改变string中有效元素个数s.reserve(100);cout << s.size() << endl;cout << s.capacity() << endl;// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小s.reserve(50);cout << s.size() << endl;cout << s.capacity() << endl;
}// 利用reserve提高插入数据的效率,避免增容带来的开销
结果如下:
但是你开100的空间,编译器不一定会只给你一百个空间,在g++当中,确实是开了一百空间,如果看会不会缩容,就是缩小空间的话,vs是不会缩小的。也就是reserve是比capacity大才会扩容。
🎈resize(⭐)
reserve会比resize用的更加广泛些,让我们来看看resize吧
resize重载了两个函数:
resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字
符个数增多时;resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的
元素空间。
注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
🍭🍭实际操作
void Teststring1()
{string s("hello, world");cout << s.size() << endl;cout << s.length() << endl;cout << s.capacity() << endl;// 将s中有效字符个数增加到10个,多出位置用'a'进行填充// “aaaaaaaaaa”s.resize(10, 'a');cout << s.size() << endl;cout << s.capacity() << endl;// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充// "aaaaaaaaaa\0\0\0\0\0"// 注意此时s中有效字符个数已经增加到15个s.resize(15);cout << s.size() << endl;cout << s.capacity() << endl;cout << s << endl;// 将s中有效字符个数缩小到5个s.resize(5);cout << s.size() << endl;cout << s.capacity() << endl;cout << s << endl;
}
结果如下:
🎈shrink_to_fit(⭐)
它说的是减少字符串所占用的空间以匹配它的实际内容大小。会请求字符串对象减少其容量,以适应它的实际大小,释放未使用的内存。
🍭🍭实际操作
假设你有一个字符串 s
,如果你在对它进行了多次修改之后,希望确保它不占用额外的空间,你可以调用shrink_to_fit
std::string s = "aaaaaa";// ... 对字符串s进行了一些操作,可能使其大小发生了变化s.shrink_to_fit(); // 释放未使用的内存
💫string类对象的访问及遍历操作
类对象的访问和遍历操作主要有以下两个部分,重点部分是operator[],和迭代器(也就是begin,end)。
⛵⛵类对象的访问
🌠opertor[](⭐)
运算符重载后的[],支持了两个版本,它的作用是返回pos所在字符串中指向的位置,如果pos和字符串的长度相等,那它就会返回一个‘\0’。
这两个版本主要的差别不是很大,前一个的权限是可读可写,后一个的权限是只读,它们构成了函数重载,是因为this指针被const修饰了。
🍭🍭实际操作
我们进入实际操作更能理解重载后的[], 它采用的是 【下标】的形式
void Teststring3()
{string s1("hello cilmy");const string s2("Hello cilmy");cout << s1 << " " << s2 << endl;cout << s1[0] << " " << s2[0] << endl;s1[0] = 'H';cout << s1 << endl;// s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
}
我们使用它就像使用数组一样方便快捷,并且可以特定位置返回修改对应位置的字符。
结果:
🌠at
在平常使用中,我们更经常使用 [] ,但 at 的功能和它是类似的,同样都是重载了两个对应的函数,也是返回对应下标的字符,区别不同的点在于,at在访问字符串时会执行范围检查。如果尝试访问的位置超出了字符串的当前长度,它会抛出一个异常,从而避免了潜在的未定义行为。
🍭🍭实际操作
at的实际操作如下所示: 采用 at(下标) 的形式
//at 和 [] 的区别
void test4()
{string str = "hello";try {// 安全的访问方法,但可能抛出异常char ch1 = str.at(10);}catch (const std::out_of_range& e) {// 处理越界访问cout << "越界访问异常: " << e.what() << '\n';}// 不安全的访问方法,但不会抛出异常,在越界时行为未定义char ch2 = str[10];
}
🌠back和front
back和front看起来是C++11才有的,目前大部分编译器都支持这个版本,它实际上是为了规范才提供的,要跟其他容器保持一致,back和front的返回无非就是字符串的最后一个字符和第一个字符。在这里就不做展开讲解了,看看文档就好啦。
⛵⛵string类的遍历
string类的遍历支持三种方式,一种是迭代器,一种是重载后的[],一种是范围for,但范围for的本质还是迭代器实现的,而且迭代器的遍历是主流的一种方式,更经常使用迭代器,此外我们还会涉及到逆置输出字符串,那就让我们进入实操看看吧。
🍭🍭实际操作
🌠[](⭐)
假设我们有个字符串s,采用 size() + [] 的方式进行遍历字符串s,但是注意,这个可不同于C语言中的 [] ,那样是等价于 *()
string s("hello CILMY23");
//[]的遍历
for (int i = 0; i < s.size(); i++)
{cout << s[i] << " ";cout << s.operator[](i) << " ";
}
cout << endl;//修改
for (int i = 0; i < s.size(); i++)
{s[i] += 1;cout << s[i] << " ";
}cout << endl;//如果你不想修改,那就用const版本const string s2("hello CILMY23");
for (int i = 0; i < s.size(); i++)
{//无法修改s2[i] += 1;cout << s2[i] << " ";
}cout << endl;}
🌠迭代器(⭐)
迭代器它的行为有点类似指针
string str("hello CILMY23");
//第二个遍历方式,迭代器+begin()+end()
string::iterator it = str.begin();while (it != str.end())
{cout << (*it) << " ";++it;
}cout << endl;
在这里我们就要补充迭代器的begin位置和end位置了,begin是h,end是‘\0’
同样它也能修改字符内容
//修改字符
string::iterator it1 = str.begin();
while (it1 != str.end())
{*it1 -= 1;cout << (*it1) << " ";++it1;
}cout << endl;
从使用的角度上,[]确实是方便,但是更通用的还是迭代器,迭代器是容器的核心访问,它能访问更多的情况。
🌠 范围for
它的本质其实也是一个迭代器
string str1("hello CILMY23");for (auto e : str1)
{cout << e << " ";
}cout << endl;
推荐在正向遍历的时候使用,反正也是简洁使用。
💫string类对象的修改操作
类对象的修改操作一共有以下这么多:
1️⃣ push_back
push_back 的功能是在尾部插入一个字符。但是要注意的是,它在设计上,是真的只能插入一个字符
我们来看实际操作:
string s("hello");s.push_back('C');
s.push_back('I');
s.push_back('L');
s.push_back('M');
s.push_back('Y');cout << s << endl;
2️⃣ append
我们可以看到append一共重载了六个, append
是 string
的一个成员函数,用于向字符串的末尾添加内容。
我们来看实际操作下的append
void test7()
{// 创建一个string对象s,并初始化为"hello"string s("hello");// 向s追加字符串"CILMY23",s变为"helloCILMY23"s.append("CILMY23");cout << s << endl; // 输出:helloCILMY23// 向s追加了10个'*'字符,s变为"helloCILMY23**********"s.append(10,'*');cout << s << endl; // 输出:helloCILMY23**********// 创建另一个空的string对象s1string s1;// 向s1追加字符串"xx hello CILMY23 xx",s1变为"xx hello CILMY23 xx"s1.append("xx hello CILMY23 xx");// std::string的begin和end函数返回指向字符串第一个字符和尾后字符的迭代器,respectively// 这里,通过迭代器向s追加s1的部分内容:"x hello CILMY23 x"// ++s1.begin() 移动到s1的第二个字符,--s1.end()移动到s1的最后一个有效字符s.append(++s1.begin(), --s1.end());// 输出s此时的内容:helloCILMY23**********x hello CILMY23 xcout << s << endl;// 注意:第三次追加实际上省略了s1字符串的首尾两个'x'
}
当然我们在实际操作中,更喜欢用 重载后的+=
3️⃣ operator+=(⭐)
根据以下的文档说明,我们可以直接在字符串末尾添加一个字符串,或者单个字符,又或者是另一个字符对象。
🍭🍭实际操作
void test8()
{string s("hello");string s1("xxxxxx");s += ' ';s += "CILMY23";s += s1;cout << s << endl;
}
+=使用起来简单方便,清楚明白,在使用中,我个人还是更偏向使用+=的。
4️⃣ insert
insert这个函数,重载形式也蛮多的,在C++中,std::string 类同样提供了 insert 函数来在字符串中的指定位置插入内容。与 append 相比,insert 提供了更高的灵活性,它允许你在字符串的任何位置添加字符或字符串。
ℹ️ℹ️具体介绍
insert(size_type pos, const std::string& str):
//在当前字符串的 pos 位置插入字符串 str。insert(size_type pos, const std::string& str, size_type subpos, size_type sublen):
//将字符串 str 从索引 subpos 开始的 sublen 长度的子字符串插入到当前字符串的 pos 位置。insert(size_type pos, const char* s):
//将字符串 s 插入到当前字符串的 pos 位置。insert(size_type pos, const char* s, size_type n):
//将字符串 s 的前 n 个字符插入到当前字符串的 pos 位置。insert(size_type pos, size_type n, char c):
//在当前字符串的 pos 位置插入 n 次字符 c。insert(iterator p, char c):
//在迭代器 p 指定的位置插入一个字符 c。insert(iterator p, size_type n, char c):
//在迭代器 p 指定的位置插入 n 次字符 c。insert(iterator p, InputIterator first, InputIterator last):
//在迭代器 p 指定的位置插入另一个字符串或字符数组,该字符串由 first 到 last 指定的范围决定。
这个主要作为了解即可,不需要过于在意去记忆。insert最经常用的还是insert(size_type pos, const char* s),切记不要多用,因为它的底层是挪动数据,这样会造成代码效率低下。
5️⃣ assign
assign:v. 分派,布置(工作、任务);分配(某物);指派,派遣;确定(价值、功能、时间、地点);转让(财产、权利)
assign的意思如上,在C++标准库中,std::string 的 assign 函数用于给字符串赋新值。这个函数替换字符串的当前内容,可以从多种不同类型的源赋值,例如从另一个 string、从字符串,或是直接从字符数组来赋值。
ℹ️ℹ️具体介绍
assign(const std::string& str):
//用字符串 str 的内容替换当前字符串的内容。assign(const std::string& str, size_type subpos, size_type sublen):
//用 str 的一个子串替换当前字符串的内容,这个子串从 subpos 开始,长度为 sublen。assign(const char* s):
//用字符串 s 的内容替换当前字符串的内容。assign(const char* s, size_type n):
//用字符串 s 的前 n 个字符替换当前字符串的内容。assign(size_type n, char c):
//用 n 个重复的字符 c 替换当前字符串的内容。assign(InputIterator first, InputIterator last):
//用两个迭代器 first 和 last 指定范围内的字符替换当前字符串的内容。
大概就介绍到这里,erase 用的还是比较多的。
6️⃣ erase (⭐)
erase还是挺好用的,erase 函数用来删除字符串中的一部分内容,是一个非常有用的成员函数,允许多种不同的用法以适应不同的需求。
ℹ️ℹ️具体操作
string str = "Hello, World!";// 用法1: 删除从位置3开始的5个字符
str.erase(3, 5);
cout << str << '\n'; // 输出: Hel, World!// 重置字符串
str = "Hello, World!";// 用法2: 删除位置为0的字符
auto it = str.erase(str.begin());
cout << str << '\n'; // 输出: ello, World!
cout << *it << '\n'; // 输出e,即下一个字符// 重置字符串
str = "Hello, World!";// 用法3: 删除第3个字符到第6个字符之间的所有字符,包括第3个,不包括第6个
str.erase(str.begin() + 2, str.begin() + 6);
cout << str << '\n'; // 输出: He World!
当然它的底层也是通过挪动数据实现的,能不用尽量不用。
7️⃣replace
在C++中,string 的 replace 成员函数允许我们替换字符串中的某部分内容。这个函数非常灵活,提供了多种重载版本。
ℹ️ℹ️具体操作
string str = "I like C++ programming.";// 示例 1: 使用另一个字符串替换
str.replace(7, 3, "Python");
cout << str << '\n'; // 输出: I like Python programming.// 示例 2: 使用迭代器范围和字符串替换
str.replace(str.begin(), str.begin() + 6, "He loves");
cout << str << '\n'; // 输出: He loves Python programming.// 示例 3: 使用C风格字符串替换部分内容
str.replace(8, 6, "Java", 4);
cout << str << '\n'; // 输出: He loves Java programming.// 示例 4: 用字符替换一部分内容
str.replace(str.begin(), str.begin() + 8, 4, 'X');
cout << str << '\n'; // 输出: XXXX Java programming.
这里就不多做演示了,感兴趣的读者可以自己实验一下
💫string类对象的字符串操作
💧find(⭐)
如果我们想覆盖字符串,删除,替换,需要找到一个位置的时候,find()就显得极其重要。find 函数用于在字符串内查找子串或字符的第一个出现位置。如果找到了匹配项,它就返回匹配项的下标;如果没有找到,它则返回 npos,这是一个特别定义的常量,表示不存在的位置。
find(const std::string& str, size_type pos = 0) const:
从位置 pos 开始搜索字符串 str。
find(const char* s, size_type pos = 0) const:
从位置 pos 开始搜索以空字符结尾的字符串 s。
find(const char* s, size_type pos, size_type n) const:
从位置 pos 开始,搜索字符串 s 的前 n 个字符。
find(char c, size_type pos = 0) const:
从位置 pos 开始,搜索字符 c。
ℹ️ℹ️具体操作
string str = "We are looking for the term in this string.";// 查找子串
size_t found = str.find("term");
if (found != std::string::npos)cout << "Found 'term' at index: " << found << '\n';// 查找C风格的字符串
found = str.find("the term");
if (found != std::string::npos)cout << "Found 'the term' at index: " << found << '\n';// 查找单个字符
found = str.find('t');
if (found != std::string::npos)cout << "Found 't' at index: " << found << '\n';// 从某一位置后开始查找
found = str.find('i', 10);
if (found != std::string::npos)cout << "Found 'i' after index 10 at index: " << found << '\n';// 查找不存在的字符串
found = str.find("nonexistent");
if (found == std::string::npos)cout << "Did not find 'nonexistent'.\n";
💧rfind
在C++中,string 类提供了 rfind 函数用于从字符串的末尾开始向前搜索子串或者字符的最后一次出现的位置。如果找到了匹配项,它返回该匹配项的起始下标,如果没有找到,它返回npos。
rfind(const std::string& str, size_type pos = npos) const:
从位置 pos 开始向前搜索字符串 str 的最后一次出现。
rfind(const char* s, size_type pos = npos) const:
从位置 pos 开始向前搜索字符串 s 的最后一次出现。
rfind(const char* s, size_type pos, size_type n) const:
从位置 pos 开始向前搜索字符串 s 的前 n 个字符的最后一次出现。
rfind(char c, size_type pos = npos) const:
从位置 pos 开始(默认为字符串的末尾)向前搜索字符 c 的最后一次出现。
ℹ️ℹ️具体操作
string str = "The rain in Spain falls mainly in the plain.";// 查找子串的最后一次出现
size_t found = str.rfind("in");
if (found != std::string::npos)cout << "Last occurrence of 'in' found at index: " << found << '\n';// 查找字符的最后一次出现
found = str.rfind('a');
if (found != std::string::npos)cout << "Last occurrence of 'a' found at index: " << found << '\n';// 查找子串的最后一次出现,但不超过索引 10
found = str.rfind("in", 10);
if (found != std::string::npos)cout << "Last occurrence of 'in' before index 10 found at index: " << found << '\n';// 查找不存在的字符串
found = str.rfind("xyz");
if (found == std::string::npos)cout << "'xyz' not found.\n";
rfind 用于找到特定的字符或者子串在字符串中最后一次出现的位置。如果需要从字符串的结尾向前查找,rfind 是个理想的选择。如果想要找字符串中某个子串或字符的第一个出现,应当使用 find 函数。
💧c_str
c_str() 返回一个指向正规C字符串的指针,常量,即以空字符结束的字符数组。这个方法用于获取一个C风格的字符串版本,通常是为了与需要传统C字符串的C语言API兼容。
这是一个非常实用的功能,因为它允许 string 对象以C风格的字符串形式与那些早期设计的或者以纯C编写的接口(APIs)交互。例如,一些系统调用和库函数要求传入一个以 NULL 终止的字符数组(char* 或 const char*),这时你可以使用 c_str() 函数。
ℹ️ℹ️具体操作
string str = "Example string";// 使用c_str()获取C风格的字符串const char* cstyle_str = str.c_str();// 输出C风格的字符串cout << cstyle_str << endl;
在上面的例子中,c_str() 返回了一个指向以 NULL 结尾的字符串 'Example string\0' 的指针。这个指针可以被传递给任何需要C风格字符串的函数。
需要注意的一点是,由 c_str() 返回的字符数组的生存期与它所属的 string 对象相同,并且如果在 c_str() 调用后对原字符串对象进行了修改(除了添加字符到字符串的末尾以外),返回的字符指针可能就不再有效。因此,最好是在需要使用C风格字符串的时候才调用 c_str(),并且避免在之后修改字符串。
💧substr (⭐)
substr 函数用于从字符串中提取一个子串。这个方法非常灵活,允许你指定开始位置和需要提取的子串的长度。如果不指定长度,则默认提取从开始位置到字符串末尾的所有字符。
这里是 substr 函数的声明:
std::string substr(size_type pos = 0, size_type len = npos) const;
- pos 参数指定了子串开始的位置,默认值为0。
- len 参数指定了子串的长度。默认值 npos 是一个特殊的值,表示直到字符串的末尾。
这个函数会返回一个新的 string 对象,包含从 pos 开始、长度为 len 的子串。如果 pos 是字符串的长度或更大,函数会抛出out_of_range 异常。如果 pos 加上 len 超出了字符串的末尾,那么只会提取到字符串的末尾为止的子串。
ℹ️ℹ️具体操作
string text = "Hello, World!";// 提取从位置6开始到末尾的所有字符
string sub1 = text.substr(6);
cout << "sub1: " << sub1 << endl; // 输出 "World!"// 提取从位置0开始的5个字符
string sub2 = text.substr(0, 5);
cout << "sub2: " << sub2 << endl; // 输出 "Hello"// 尝试从超出字符串长度的位置开始提取子串
try {string sub3 = text.substr(50, 2);
}
catch (out_of_range& e) {cout << "Caught an out_of_range exception: " << e.what() << endl;// 捕获异常
}
// 如果len参数超出字符串的末尾,则从pos到字符串末尾的所有字符都被提取
string sub4 = text.substr(7, 20);
cout << "sub4: " << sub4 << endl; // 输出 "World!"}
这里的捕获异常大家先了解就可以了,具体的之后我们会详细讲解。
💫string类对象的非成员函数重载
🌼swap(⭐)
在这里我们重点关注swap函数就好了。
swap 成员函数用于将两个字符串对象的内容进行交换。这个操作是高效的,通常只涉及指针和大小值的交换,而非真正的字符数据复制,因此即使字符串的内容很长,使用 swap 也不会导致大量的数据复制。
文档如下:
ℹ️ℹ️具体操作
🌼operator+
operator+实现字符串的连接操作。这个操作符可以用来连接两个 string 对象,或者一个 string 对象与一个字符串(const char* 类型),或者一个 string 对象与一个字符(char 类型)。operator+ 会创建一个新的 string 对象来存储连接后的字符串结果。
文档如下:
ℹ️ℹ️具体操作
string str1 = "Hello, ";
string str2 = "World!";// 使用operator+连接两个std::string对象
string result1 = str1 + str2;
cout << result1 << endl; // 输出:Hello, World!// 使用operator+连接std::string对象和C风格字符串
const char* cstr = " Have a nice day!";
string result2 = result1 + cstr;
cout << result2 << endl; // 输出:Hello, World! Have a nice day!// 使用operator+连接string对象和单个字符
char ch = '!';
string result3 = result1 + ch;
cout << result3 << endl; // 输出:Hello, World!!
operator+ 对于 string 对象的操作通常生成一个新的字符串,包含操作符两边操作数连接后的结果。当使用 operator+ 进行字符串连接时,如果连接涉及多个操作(例如,多个 + 操作符的串联),每个连接操作都可能会生成一个临时的字符串对象,这可以增加额外的内存分配和复制的开销。在处理很多字符串连接操作的时候,使用成员函数 append 或 operator+= 可以提高效率。
🌼getline
getline 是一个标准库函数,它从输入流中读取文字直到遇到换行符(默认是 '\n'),并将读取的内容(不包括换行符)存入一个 string 对象。这个函数能够处理不定长度的输入,非常适合用于从 cin 或任何其他输入流(如 ifstream 文件输入流)读取一行文本。
文档如下:
getline 函数的声明如下:
std::istream& getline(std::istream& is, std::string& str, char delim = '\n');
- is 是输入流的引用。
- str 是将输入内容存储的 string 对象的引用。
- delim 是可选的分隔符,默认为换行符 '\n',当遇到此字符时,getline 停止读取。如果提供此参数,getline 将会使用该字符作为行的终止符。
ℹ️ℹ️具体操作
string line;cout << "Please enter your name: ";
getline(std::cin, line);cout << "Hello, " << line << "!" << endl;
该程序提示用户输入名字,然后读取一行输入并存入 line 字符串,接着输出名字。
如果输入字符超过 string 的最大长度,getline 会导致 length_error 异常。但由于 string 可以处理非常大的字符串,所以在实际程序中发生这种情况的概率非常低。getline 函数是处理输入中的字符串行非常有用的工具,它确保了即使输入中含有空格也能够正确得到整行的字符串。
🌼operator<< 和 operator>>
流提取 和 流插入,其实就是和我们使用cout << /(cin >>)
在做输入操作/(输出)的时候一样,控制台会先去等待我们输入一个值/(通过去缓冲区中拿取数据,然后将其显示在控制台上)
它们支持直接进行对string对象的插入和提取。
三、string类的接口一览图
接口一览:从文档中整理了一份全部接口的思维导图
文档网站:
cplusplus.com/reference/string/string/?kw=stringhttps://cplusplus.com/reference/string/string/?kw=string
字符编码:
【Python】python编程初探2---字符编码,输入和输出,初识数据类型-CSDN博客https://blog.csdn.net/sobercq/article/details/137061735
文档学习小结:
sequences :n. 序列,顺序;继起的事,是sequence的复数形式
features : n.产品特点,特征;容貌;嘴脸(feature 的复数)
specifically : adv. 特意,专门地;明确地,具体地;具体来说,确切地说;局限性地;专门;
instantiation : n. 实例化
handle : v. 拿;处理,应付;操纵;触(球);经营,管理
default : adj. 默认的
variable :adj. 易变的,多变的;时好时坏的;可变的,可调节的;(数)(数字)变量的;(植,动)变异的,变型的;(齿轮)变速的
assignment : n. 作业,任务;(工作等的)分配,指派;(财产、权利的)转让,在默认成员函数中,意指赋值。
substring :n. 子串;子链
constructor :n. 构造函数;构造器;建造者
portion :n. (某物的)一部分;(尤指餐馆中食物的)一份,一客;(责任、过失、职责等的)一份,一部分;<法律>(根据法律赠与或遗留给继承人的)一份财产;<古> 命运,天数
spans : v. 跨越;持续;贯穿(span 的第三人称单数)
consecutive : adj. 连续的,不间断的
assign:v. 分派,布置(工作、任务);分配(某物);指派,派遣;确定(价值、功能、时间、地点);转让(财产、权利)
🛎️感谢各位同伴的支持,本期C++就讲解到这啦,如果你觉得写的不错的话,可以给个一键三连,点赞,关注+收藏,若有不足,欢迎各位在评论区讨论。