C++必修:STL用法之string

Betty’s Sweet 2024-07-22 14:05:03 阅读 98

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++学习

贝蒂的主页:Betty’s blog

1. C/C++中的字符串

1.1. C语言中的字符串

在 C 语言中,字符串是由字符组成的字符数组,以空字符 <code>'\0' 作为结束标志。由于数组特点,字符串的大小在定义数组时就已经确定,无法更改。

//数组大小为20

char str[20] = "hello betty!\n";

当然我们可以通过动态内存分配来来解决这个问题,但无疑非常繁琐。

void Test1()

{

char* str = NULL;

int len = 0;

// 初始分配一些内存

str = (char*)malloc(10 * sizeof(char));

if (str == NULL) {

perror("malloc fail");

return 1;

}

strcpy(str, "Hello");

len = strlen(str);

// 根据需要扩展字符串

str = (char*)realloc(str, (len + 6) * sizeof(char));

if (str == NULL) {

perror("realloc fail");

return 1;

}

strcat(str, " World");

printf("%s\n", str);

//最后释放内存

free(str);

}

1.2. C++中的字符串

虽然C++兼容C语言,在C++中仍然可以使用C语言的字符串,但是C++自己实现了一个关于处理字符串的类–string,它提供了许多方便的操作和功能,使得字符串的处理更加安全和高效。下面是一个简单的string的使用:

void Test2()

{

string str = "hello betty!";

cout << str << endl;

//改变第一个字符

str[0]++;

cout << str << endl;

//在末尾添加一个字符

str += 'e';

cout << str << endl;

//在末尾添加一个字符串

str += " hello";

cout << str << endl;

}

img

相较于C语言的字符串,C++的字符串明显方便的多。接下来我们将详细介绍C++<code>string类的特点与用法。

2. string的接口

C++为我们提供了丰富的string接口,我们可以通过对象来调用,为了方便我们学习我们可以通过查询相关文档辅助–string类的接口介绍

img

2.1. string的迭代器

迭代器(Iterator)是一种用于遍历容器中元素的工具。它提供了一种统一的方式来访问容器中的元素,而无需关心容器的具体实现细节。对于我们迭代器,我们在使用时将其当做指针使用即可。

在<code>string类中,我们就可以通过迭代器来访问其具体元素,并且也为我们提供了相应的调用函数。

2.1.1. begin()与end()函数

begin()end()函数的使用方法具体如下:

函数声明:

iterator begin();const_iterator begin() const;

作用:返回指向字符串第一个字符的迭代器。

返回值:普通对象返回iterator迭代器,const 对象返回const_iterator迭代器。

函数声明:

iterator end();const_iterator end() const;

作用:返回指向字符串最后一个字符下一个位置的迭代器。返回值:普通对象返回iterator迭代器,const 对象返回const_iterator迭代器。

begin()end()具体指向情况如下图所示:

img

然后我们可以通过以下代码来演示效果:

<code>void Test3()

{

string s1 = "hello betty!";

//普通迭代器

string::iterator it = s1.begin();//指向第一个位置

while (it != s1.end())

{

cout << *it << " ";

++it;

}

cout << endl;

const string s2 = "hello betty!";//const对象

//const 反向迭代器 不能改变字符串中的值

string::const_iterator itt = s2.begin();//指向最后一个字符的下一个位置

while (itt != s2.end())

{

cout << *itt << " ";

++itt;

}

}

img

2.1.2. rbegin()与rend()函数

<code>rbegin()与rend()函数的使用方法具体如下:

函数声明:

reverse_iterator rbegin();const_reverse_iterator rbegin() const;

作用:返回指向字符串最后一个字符位置(即其反向开头)的反向迭代器。

返回值:普通对象返回iterator迭代器,const 对象返回const_iterator迭代器。

函数声明:

reverse_iterator rend();const_reverse_iterator rend() const;

作用:返回指向字符串第一个字符前面一个位置的反向迭代器。返回值:普通对象返回iterator迭代器,const 对象返回const_iterator迭代器。

rbegin()rend()具体指向情况如下图所示:

img

然后我们可以通过以下代码来演示效果:

<code>void Test4()

{

string s1 = "hello betty!";

//普通反向迭代器

string::reverse_iterator rit = s1.rbegin();//指向最后一个字符位置

while (rit != s1.rend())

{

cout << *rit << " ";

++rit;

}

cout << endl;

const string s2 = "hello betty!";//const对象

//const 反向迭代器 不能改变字符串中的值

string::const_reverse_iterator ritt = s1.rbegin();//指向最后一个字符的位置

while (ritt != s1.rend())

{

cout << *ritt << " ";

++ritt;

}

}

img

2.2. string的初始化与销毁

因为<code>string是一个类,所以我们在初始化时肯定调用其构造函数初始化。以下就是我们常见初始化的接口:

img

第一个使我们的默认构造函数,不需要传参。第二个使用的是拷贝构造来初始化。第三个是使用一个<code>string的某段区间初始化,其中pos字符串下标,npos是指无符号整数的最大值。第四个使用的是某个字符数组初始化。第五个使用的是某个字符数组前n个字符来初始化第六个使用的是nc字符初始化。第七个使用的是某段迭代器区间初始化。最后也能通过赋值运算符重载初始化。

下面是具体的代码示例:

void Test5()

{

//1. 使用我们的默认构造函数,不需要传参。

string s1;

s1 = "hello betty!";

//2. 使用的是拷贝构造来初始化。

string s2(s1);

//3. 使用一个string的某段区间初始化,其中pos是字符串下标,npos是指无符号整数的最大值。

string s3(s2, 1, 7);

//4. 使用的是某个字符数组初始化。

string s4("hello world!");

//5. 使用的是某个字符数组前n个字符来初始化

string s5("hello world!", 5);

//6. 使用的是n个c字符初始化。

string s6(7, 'a');

//7. 使用的是某段迭代器区间初始化。

string s7(s1.begin(), s1.end());

//赋值运算符重载初始化

string s8 = "hello betty!";

cout << s1 << endl;

cout << s2 << endl;

cout << s3 << endl;

cout << s4 << endl;

cout << s5 << endl;

cout << s6 << endl;

cout << s7 << endl;

cout << s8 << endl;

}

img

而由于<code>string是一个类,出了作用域会自动调用它的析构函数,所以不用显示调用。

2.3. string的容量操作

接下来我们将学习关于string类常见的容量操作:

函数名称 功能
size 返回字符串的有效长度
length 返回字符串的有效长度
capacity 返回字符串的容量大小
max_size 返回字符串的最大长度
clear 清空字符串
empty 检查是否为空串,是则返回ture,否则返回false
reserve 请求改变字符串的容量
resize 重新设置有效字符的数量,超过原来有效长度则用c字符填充
shrink_to_fit 收缩资字符串容量
2.3.1. 有效长度与容量大小

string类中,我们可以通过size()length()返回字符串的有效长度;capacity()返回字符串的容量,其具体效果如下图:

img

我们也可以通过代码来验证:

<code>void Test6()

{

string s("hello betty!");

cout << s.size() << endl;//有效长度

cout << s.length() << endl;//有效长度

cout << s.capacity() << endl;//容量大小

cout << s.max_size() << endl;//最大大小

}

img

其中有效长度<code>size以及容量大小capacity不包括\0。而max_size返回字符串最大容量,不同平台下大小可能不一样。而在VS2022下大小为2147483647,也就是INT_MAX的大小。

接下来我们可以来探究一下string的扩容机制

void TestCapacity()

{

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';

}

}

}

VS2022编译器中,string大概是以1.5倍扩容,但是在g++编译器中却是2倍扩容。所以扩容倍数是

img

不确定的,具体由不同编译器决定。

img

最后我们来谈谈<code>empty()函数,它主要用来判断字符串是否为空:

void TestEmpty()

{

string s1("");//空串

string s2("hello ");

if (s1.empty())

{

cout << "s1为空串" << endl;

}

else

{

cout << "s1不为空串" << endl;

}

if (s2.empty())

{

cout << "s2为空串" << endl;

}

else

{

cout << "s2不为空串" << endl;

}

}

img

2.3.2. 有效长度与容量操作

首先我们要介绍的就是<code>clear()函数,他能清空字符串,也就是改变有效长度size,但不会改变容量capacity

void TestClear()

{

string s1("hello world!");

cout <<"s1的有效长度为:"<< s1.size() << endl;

cout <<"s1的容量大小为:"<< s1.capacity() << endl;

s1.clear();

cout << "s1的有效长度为:" << s1.size() << endl;

cout << "s1的容量大小为:" << s1.capacity() << endl;

if (s1.empty())

{

cout << "s1是空串" << endl;

}

}

img

接下来我们将介绍两个可以改变容量的函数<code>reserve,resize。它们之间不小的差别,首先它们的接口如下

img

我假设字符串原来有效长度为<code>sz,那么如果n<szsz<n<capcityn>capacity。两个函数的效果有何不同呢?

void Test8()

{

string s1("hello world!");

cout << "reserve测试:" << endl;

cout << s1 << endl;

cout << "s1的有效长度为:" << s1.size() << endl;

cout << "s1的容量大小为:" << s1.capacity() << endl;

s1.reserve(5);

cout << s1 << endl;

cout << "s1的有效长度为:" << s1.size() << endl;

cout << "s1的容量大小为:" << s1.capacity() << endl;

s1.reserve(13);

cout << s1 << endl;

cout << "s1的有效长度为:" << s1.size() << endl;

cout << "s1的容量大小为:" << s1.capacity() << endl;

s1.reserve(25);

cout << s1 << endl;

cout << "s1的有效长度为:" << s1.size() << endl;

cout << "s1的容量大小为:" << s1.capacity() << endl;

cout << endl;

cout << "resize测试:" << endl;

string s2("hello world!");

cout << s2 << endl;

cout << "s2的有效长度为:" << s2.size() << endl;

cout << "s2的容量大小为:" << s2.capacity() << endl;

s2.resize(5);

cout << s2 << endl;

cout << "s2的有效长度为:" << s2.size() << endl;

cout << "s2的容量大小为:" << s2.capacity() << endl;

s2.resize(10,'x');

cout << s2 << endl;

cout << "s2的有效长度为:" << s2.size() << endl;

cout << "s2的容量大小为:" << s2.capacity() << endl;

s2.resize(25, 'x');

cout << s2 << endl;

cout << "s2的有效长度为:" << s2.size() << endl;

cout << "s2的容量大小为:" << s2.capacity() << endl;

}

img

通过上述实验,我们可以总结出以下规律:

当<code>n<sz时,reserve并不会发生任何改变,resize会删除有效字符到指定大小。当sz<n<capcity时,reserve并不会发生任何改变,resize会补充有效字符(默认为’\0)到指定大小。当n>capacity时,reserve会发生扩容,resize会补充有效字符(默认为’\0)到指定大小。

注意:不同平台下扩容效果可能会不同,但是这个规律是不会改变的。

最后我们来介绍一个C++11引入的一个可以缩容的函数shrink_to_fit**,**它的主要目的就是让有效长度size与容量capacity适配。

void Test9()

{

string s1("hello world!");

cout << s1 << endl;

cout << "s1的有效长度为:" << s1.size() << endl;

cout << "s1的容量大小为:" << s1.capacity() << endl;

s1.reserve(100);//先扩容

cout << "扩容后s1的有效长度为:" << s1.size() << endl;

cout << "扩容后s1的容量大小为:" << s1.capacity() << endl;

s1.shrink_to_fit();//再缩容

cout << "缩容后s1的有效长度为:" << s1.size() << endl;

cout << "缩容后s1的容量大小为:" << s1.capacity() << endl;

}

img

2.4. string的访问操作

接下来我们就来介绍<code>string常见的访问函数:

函数名称 功能
operator[] 返回指定位置的字符,越界则报错
at 返回指定位置的字符,越界则抛异常
back 返回字符串最后一个字符(不是’\0’)
front 返回字符串第一个字符

首先是operator[]这个运算符重载与at函数,它们的功能类似都是返回指定下标字符,并且char*类型返回char*类型,const char*类型返回const char*类型。

img

<code>void Test10()

{

string s1("hello betty!");

for (int i = 0; i < s1.size(); i++)

{

cout << s1[i] << " " ;

}

cout << endl;

for (int i = 0; i < s1.size(); i++)

{

cout << s1.at(i) << " ";

}

}

img

然后就是C++11引入的<code>front与back函数,但实用性不是特别大,大家只需要了解。

img

<code>void Test11()

{

string s1("hello betty!");

cout << s1.front() << endl;

cout << s1.back() << endl;

}

img

2.5. string的修改操作

<code>string关于修改的函数的接口都比较多,一一列举比较麻烦,这里我们只重点介绍常用的接口,剩下的大家具体使用时查官方文档即可。下面是常见的关于string修改的函数接口:

函数名称 功能
push_back 字符串后追加字符
operator+= 在字符串后追加字符或字符串
append 在字符串后追加字符串
insert 在指定位置追加字符或者字符串
assign 使用指定字符串替换原字符串
replace 用新字符串替换原字符串指定区间
pop_back 删除字符串最后一个字符
erase 删除字符串指定部分
swap 交换两个字符串
2.5.1. 字符串的增加

首先我们来介绍字符串的增加操作,在末尾添加字符我们可以使用push_back,在末尾添加字符串我们可以使用append,而operator+=既可以在末尾添加字符,也可以添加字符串,insert可以在任意位置追加字符或者字符串。

void Test12()

{

string s("hello betty!");

//追加一个!

s.push_back('!');

cout << s << endl;

s += '!';

cout << s << endl;

s.insert(0,1,'!');

cout << s << endl;

//追加一个字符串

s.append("hello ");

cout << s << endl;

s+= "world!";

cout << s << endl;

//在下标0处追加字符串

s.insert(0,"hello ");

cout << s << endl;

}

img

当然<code>append与insert的接口不止这些,下面是具体的的接口,需要时直接插文档即可

img

img

2.5.2. 字符串的替换

接下来我们来介绍两个字符串替换的函数<code>assign以及replace,其中assign是直接替换掉原字符串,而replace是替换原字符串的某段区间。

void Test13()

{

string s1("hello world!");

string s2;

//直接用s1替换s2

s2.assign(s1);

cout << s2 << endl;

//用s1的某段区间替换s2

s2.assign(s1, 6);

cout << s2 << endl;

string s3("i am betty!");

//用s1替换掉2下标长度为2的区间

s3.replace(2, 2, s1);

cout << s3 << endl;

//用字符数组前n个字符替换

s3.replace(0, 2, "hhhh", 2);

cout << s3 << endl;

}

img

当然<code>assign与replace的接口不止这些,下面是具体的的接口,需要时直接插文档即可

img

img

2.5.3. 字符串的删除

字符串的删除有支持删除最后一个字符的<code>pop_back,也有支持删除任意区间的erase

void Test14()

{

string s("hello betty!");

//删除最后一个字符

s.pop_back();

cout << s << endl;

//删除迭代器所指字符

s.erase(s.begin());

cout << s << endl;

//删除0下标长度为3的一段区间

s.erase(0, 3);

cout << s << endl;

//删除一段迭代器区间

s.erase(s.begin(), s.end() - 2);

cout << s << endl;

}

img

2.5.4. 字符串的交换

最后我们来介绍一个字符串的交换函数<code>swap,这个swap函数与算法库中的swap函数并不相同,算法库中的swap函数将string中每个具体值都交换。而string中的swap函数实现的是指针交换,效率明显高的多。

img

<code>void TestSwap()

{

string s("hello betty!");

string p("hello world!");

cout << s << endl;

cout << p << endl;

s.swap(p);

cout << s << endl;

cout << p << endl;

}

img

2.6. string的其他操作

除了上面操作外,<code>string还有一些格外补充操作,这里值挑几个常用的函数为大家介绍。·

函数名称 功能
c_str 返回C格式的字符串
substr 从字符串pos位置开始,截取n个字符,然后将其返回
find 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
find_first_of 在原字符串中,从前往后找匹配串中第一个匹配的字符
find_last_of 在原字符串中,从后往前找匹配串中第一个匹配的字符
find_first_not_of 在原字符串中,从前往后找匹配串中第一个不匹配的字符
find_last_not_of 在原字符串中,从后往前找匹配串中第一个不匹配的字符
getline 获取一行字符串
operator<< 流提取重载
operator>> 流插入重载

首先是substrfind函数,这两个函数可以结合使用

void Test15()

{

string s("hello betty!");

//在字符串s中寻找b

size_t pos = s.find('b');

//从下标6开始截取长度为6的字符串

string str = s.substr(pos, 6);

cout << str << endl;

}

img

<code>find_first_of,find_last_offind_first_not_offind_last_not_of的用法非常类似,我们就只以其中一种举例:

void Test16()

{

string str("Please, replace the vowels in this sentence by asterisks.");

//从str字符串中任意匹配aeiou

size_t found = str.find_first_of("aeiou");

//找不到返回npos

while (found != string::npos)

{

str[found] = '*';

//从下一个位置找

found = str.find_first_of("aeiou", found + 1);

}

cout << str << endl;

}

img

这里是<code>find与find_first_of详细的接口,大家可以根据实际需求参考使用

img

img

最后我们来介绍以下<code>getline,它与流插入都是读取字符串,但是区别就是getline遇见空格不会停止,而流插入会。这区别和C语言中scanfgets类似

img

img



声明

本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。