一、概述
在编程过程中,我们常常与字符串打交道,从我们写的第一个"hello world!",到后面提升难度写查询字符串中是否存在子串,这些过程中均包含着对字符串的处理,在C中利用#include "string.h"就可以利用这个库里的许多模板,如strlen、strcpy....总之有很多函数供大家使用。相应的在C++中标准模板库也(STL)也实现了string类来供大家使用。下面就来详细介绍C++中的string类。
C++是面向对象的高级语言,所以其中的这里实现的string也只是它里面的一个类。既然是类,那么我们就要知道,我们是使用的是他的接口。
二、常用接口
1、构造字符串
函数 | 功能 |
string |
构造空的
string
类对象,即空字符串
|
string(const char* s) |
用
C-string
来构造
string
类对象
|
string(size_t n,char) |
string
类对象中包含
n
个字符
c
|
string(const string&s) |
拷贝构造函数
|
使用场景如下,这下就可以理解了。这是string类对象的常见构造
注意在使用string类时,必须包含#include头文件以及using namespace std;
void Teststring()
{
string s1; // 构造空的string类对象s1
string s2("hello world!"); // 用C格式字符串构造string类对象s2
string s3(10, 'x'); // 创建一个由10个'x'字符组成的字符串 "xxxxxxxxxx"
string s4(s2); // 拷贝构造s4
}
2、string类对象的容量操作
其实这一部分就是string类里实现好的成员函数。我们调用的时候也很简单,就是 对象.方法 就行了
函数 | 功能 |
size |
返回字符串有效字符长度
|
length |
返回字符串有效字符长度
|
capacity |
返回空间总大小
|
empty |
检测字符串释放为空串,是返回
true
,否则返回
false
|
clear |
清空有效字符
|
reserve |
为字符串预留空间
|
resize |
将有效字符的个数改成
n
个,多出的空间用自己给的字符
填充
|
下面我们用代码来理解这些函数(分析均写在函数注释里、请耐心看完)
void Teststring1()
{
string s("hello, bit!!!");
cout << s.size() << endl;//结果是13
cout << s.length() << endl;//结果是13
//size()与length()方法底层实现原理完全相同,
//引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size
//所以也不难理解为什么C++这两个函数为什么作用一样还都存在了。
cout << s.capacity() << endl;//结果是15
//其实capacity()返回的就是std::string对象当前分配的存储空间的大小,
//这个大小至少足够存储当前字符串,可能还有额外的空间以减少将来添加更多字符时的
//重新分配次数。一个std::string对象的容量通常是实现定义的,这意味着它依赖于
//你的C++标准库的实现和可能的内存分配策略。一些实现可能初始分配比实际字符串长度更多的
//空间,以优化追加操作;而其他实现可能正好分配等同于字符串长度的空间。
//从英文单词上也可以理解它的意思。capacity 名词 容积,容纳能力。
cout << s << endl;
// 注意:string类对象支持直接用cin和cout进行输入和输出
// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
s.clear();
cout << s.size() << endl; //结果是0
cout << s << endl; //结果是空
cout << s.capacity() << endl; //还是15
// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
// “aaaaaaaaaa”
s.resize(10, 'a');
cout << s.size() << endl;//结果是10
cout << s.capacity() << endl;//结果是15
// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
// "aaaaaaaaaa\0\0\0\0\0"
// 注意此时s中有效字符个数已经增加到15个
s.resize(15);
cout << s.size() << endl;//结果是15
cout << s.capacity() << endl;//结果是15
cout << s << endl;
// 将s中有效字符个数缩小到5个
s.resize(5);
cout << s.size() << endl;//结果是5
cout << s.capacity() << endl;//结果是15
cout << s << endl;//aaaaa
}
这一段代码后,上面的函数都讲完了,还差个empty和reserve,empty很好理解就是检测字符串释放为空串,是返回true,否则返回false,我们再来分析分析reserve。
reserve的作用是为字符串预留空间。
void Teststring2()
{
string s;
// 测试reserve是否会改变string中有效元素个数
s.reserve(100);
cout << s.size() << endl;//是0
cout << s.capacity() << endl;//是111
// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
s.reserve(50);
cout << s.size() << endl;//是0
cout << s.capacity() << endl;//是111
//根据C++标准,reserve()的实现并不要求减小容量,如果参数小于当前容量。
//换句话说,reserve()保证至少有指定数量的空间,但如果已经有更多的空间,它不会减少这个空间。
}
// 利用reserve提高插入数据的效率,避免突然增容带来的时间开销
看到这里,大家对函数可能都已经理解了,但是对capacity()好像感觉还不是那么到位,我在举两个例子让大家来判断一下。
void TestPushBack()
{
string s;
size_t sz = s.capacity();
//在32位系统中,size_t通常被定义为unsigned int,而在64位系统中,
//它通常被定义为unsigned long long或unsigned long,这取决于编译器和操作系统。
cout << "making s grow:\n";
for (int i = 0; i < 100; ++i)
{
s.push_back('c');
//push_back是一个成员函数,广泛应用于C++标准库中的容器类
//这个函数的作用是向容器的末尾添加一个新元素。
if (sz != s.capacity())//如果容量变了就输出出来
{
sz = s.capacity();
cout << "capacity changed: " << sz << '\n';
}
}
}
//分析一下,这段代码通过不断地向一个std::string对象s添加字符来观察其容量的变化。
//在C++中,std::string(以及其他动态容器,如std::vector)的容量指的是
//它在需要重新分配内存之前可以容纳的元素数量。当你向std::string添加元素时,
//如果当前容量不足以容纳新的元素,其容量将会自动增加。
void TestPushBackReserve()
{
string s;
s.reserve(100);
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';
}
}
}
//那再看这段代码,手到擒来,Reserve已经预留好空间了,而且还很大,所以一直没有
//当前容量不足以容纳新的元素,故这段代码不会输出什么东西
3、string类对象的访问及遍历操作
这个也是字符串中也是十分常用的操作。
函数 | 功能 |
operator[] |
返回
pos
位置的字符,
const string
类对象调用
|
begin+end |
begin
获取一个字符的迭代器
+
end
获取最后一个字符下一个位置的迭
代器
|
rbegin+rend |
begin
获取一个字符的迭代器
+
end
获取最后一个字符下一个位置的迭
代器
|
范围for |
C++11
支持更简洁的范围
for
的新遍历方式
|
光看这些名字肯定是一头雾水,我们上代码欣赏一下它的作用。
这个写在前面大家先理解一下。
void Teststring1()
{
string s1("hello Bit");
const string s2("Hello Bit");
cout << s1 << " " << s2 << endl;
cout << s1[0] << " " << s2[0] << endl;
s1[0] = 'H';
cout << s1 << endl;
// s2[0] = 'h'; 代码编译失败,因为const类型对象不能修改
}
(关于这个const string不清楚的 参见大佬文章关于C/C++中const的用法-CSDN博客 本文不细述)
void Teststring2()
{
string s("hello Bit");
// 3种遍历方式:
// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,
// 另外以下三种方式对于string而言,第一种使用最多
// 1. for+operator[]
for (size_t i = 0; i < s.size(); ++i)
cout << s[i] << endl;
// 2.迭代器
string::iterator it = s.begin();
while (it != s.end())
{
cout << *it << endl;
++it;
}
string::reverse_iterator rit = s.rbegin();
while (rit != s.rend())
cout << *rit << endl;
//在C++中,迭代器是一种访问容器中元素的机制,它提供了类似于指针的接口。
//迭代器封装了对容器元素的访问方式,使得你可以通过迭代器遍历容器中的元素,
//而不需要知道容器的内部表示。
//当你使用*it时,你实际上是在解引用迭代器it。
//解引用迭代器的操作返回对应于迭代器当前位置的元素的引用。
//这就像使用指针一样,其中指针指向某个元素,你需要解引用指针来获取该元素的值。
// 3.范围for
for (auto ch : s)
cout << ch << endl;
}
4、string类对象的修改操作
函数 | 功能 |
push_back |
在字符串后尾插字符
c
|
append |
在字符串后追加一个字符串
|
operator+= |
在字符串后追加字符串
str
|
c_str |
返回
C
格式字符串
|
find+npos |
从字符串
pos
位置开始往后找字符
c
,返回该字符在字符串中的位置
|
rfind |
从字符串
pos
位置开始往前找字符
c
,返回该字符在字符串中的位置
|
substr |
在
str
中从
pos
位置开始,截取
n
个字符,然后将其返回
|
上代码理解。
void Teststring()
{
string str;
str.push_back(' '); // 在str后插入空格
str.append("hello"); // 在str后追加一个字符"hello"
str += 'b'; // 在str后追加一个字符'b'
str += "it"; // 在str后追加一个字符串"it"
cout << str << endl;
cout << str.c_str() << endl; // 以C语言的方式打印字符串
// 获取file的后缀
string file("string.cpp");
size_t pos = file.rfind('.');
string suffix(file.substr(pos, file.size() - pos));
cout << suffix << endl;
// npos是string里面的一个静态成员变量
// static const size_t npos = -1;
// 取出url中的域名
string url("http://www.cplusplus.com/reference/string/string/find/");
cout << url << endl;
size_t start = url.find("://");
if (start == string::npos)
{
cout << "invalid url" << endl;
return;
}
start += 3;
size_t finish = url.find('/', start);
string address = url.substr(start, finish - start);
cout << address << endl;
// 删除url的协议前缀
pos = url.find("://");
url.erase(0, pos + 3);
cout << url << endl;
}
结果
5、string类非成员函数
C++的std::string
类有一些与之关联的非成员函数,这些函数提供了额外的功能,而不直接属于任何一个std::string
对象。以下是一些常用的非成员函数,也比较杂乱无章吧,更多的用法都是等待读者去发现使用。这里仅举例子。
大小比较
交换两个字符串的内容