✨ Blog’s 主页: 白乐天_ξ( ✿>◡❛)
🌈 个人Motto:实践是检验真理的唯一标准!!!敲代码需要勤快点!!!!
💫 欢迎来到我的学习笔记!
参考文章:【C++】深入浅出STL之string类
一、C/C++中的字符串
1.1 C语言中的字符串
首先我们需要思考:为什么要学习string
类?
string
意为字符串
(相当于一个字符顺序表)。在C语言中,字符串是由一系列字符组成,并以'\0'
作为结束标志的字符数组
,我们想要存储字符串时就要用到字符数组。为了操作方便成员的标准库中提供了一些str系列的库函数。- 但是这些库函数与字符串是分离开的,不太符合
OPP
的思想,而且底层空间需要用户自己去管理,稍不注意可能会越界访问。
1.2 string类的使用场景
- 在
OJ
中,有关字符串的题目基本是以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类**,很少有人使用C语言标准库中的字符串操作函数**。 - ……
因此,在C++中专门设计了一个与字符串有关的类。我们知道,C++是面向对象的,我们可以在类里面去写各种成员函数来对外提供操作字符串的接口,这个类就是<u>string类</u>
。
二、初识string类
2.1 概述
+ 常用学习文档网站:[cplusplus.com](https://legacy.cplusplus.com/)(非官方网站) + 根据string类的文档我们可以看出string的确是一个类,由**类模板**`basic_string`实例化得来的。根据上面文档的内容,可知string
相当于一种字符顺序表/字符数组
,可以发生动态增长。
- 由下图可知,
basic_string类
是一个类模板。
- 该类模板可以实例化出很多的模板类(如下图),其中就包括我们要学习的
string类
。
- 从上图我们还可以看见除了
string
的其他的类,这些都是编码引起的。我们比较常见的ASCII编码
(全称:【美国信息交换标准代码】)主要是大小写中英字母、数字、标点符号等128个字符。下图是ASCII码的映射表,一个ASCII码值就对应一个字符。- 其实在VS编译器下的内存中我们可以发现:
- 但是ASCII不能表示出其他国家的字符例如中国汉字,为了表示出其他各国字符,推出了万国码(
Unicode
),也叫统一码。我国也推出一套编码字符集,叫做:GBK
。在【GB2312-80】中就存储了很多有关汉字的规则。
2.2 string类的特性
- string类是表示字符串的字符串类;
- 该类的接口与常规容器的接口基本相同,再添加了一些转么能用来操作string的常规操作。
- string在底层实际是:
basic_string
模板类的别名,typedef basic_string<char,char_trait
,alloctor> string;
。 - 不能操作多字节或者变长字符的序列。
注意: 在使用string类时,必须包含#include
头文件以及using namespace std;
。
三、string类的接口
对于接口类的学习,我们只需要熟悉比较常用的即可,其他的接口可以根据类似的方法去学习。
3.1 string类的默认成员函数
函数名称 | 功能说明 |
---|---|
constructor | 构造函数 |
destructor | 析构函数 |
operator= | 赋值重载 |
3.1.1 构造函数--自动调用
- 构造函数
constructor
一共有7个重载形式,其中比较重点的有以下三个函数。
- 函数原型:
string(); //
string(const string& str); // str全部拷贝
string(const string& str, size_t pos, size_t len = npos);// str从pos开始拷贝len个字符(拷贝一部分)
string(const char* s); //
string(const char* s, size_t n);
string(size_t n, char c);
template <class InputIterator>
string(InputIterator first, InputIterator last);
- 函数运用:
string();
无参常用
#include <iostream>
using namespace std;
int main()
{
string s1;//构造空字符串
cout << s1 << endl;
return 0;
}
运行结果:
string(const string& str);
#include <iostream>
using namespace std;
int main()
{
string s1("Hello world");
string s2(s1);
cout << s2 << endl;
return 0;
}
运行结果:
string(const string& str, size_t pos, size_t len = npos);
拷贝str
字符串中从pos
位置开始的len
个字符。
// 拷贝str字符串中从pos位置开始的len个字符
//string (const string& str, size_t pos, size_t len = npos);
//npos:在文档中查看:无符号整数的最大值
//npos是一个缺省值,根据文档:static const size_t npos = -1,npos存储整型最大值(缺省值),
//因此字符串是最大整型数据的长度(4G),它想表示:字符串有多长,就取多长
#include <iostream>
using namespace std;
int main()
{
string s1("Hello world");
string s2(s1, 6, 5);
cout << s2 << endl;
return 0;
}
运行结果:
有一个参数npos
,在文档中查看,得知它是无符号整形的最大值
。npos
是一个缺省参数,在函数中不传入参数值就表示无论字符串有多长,他就会取多长。
继续查看下面的文档,意思是:**从pos位置的len个长度取拷贝字符串的一部分(如果str字符串太短或者len为npos则直接到达字符串末尾)。**由此可知,即使不传入npos参数,字符串也会拷贝到末尾。
我们可以打印查看npos
的值。下面是在VS编译器debug X64
环境下的输出结果。
cout << string::npos << endl;
18446744073709551615
debug X86
环境下的运行结果如下:
4294967295
Linux平台下的g++编译器环境下,结果又是不一样的。
4. string(const char* s);
#include <iostream>
using namespace std;
int main()
{
string s1("Hello world!");
cout << s1 << endl;
return 0;
}
运行结果:
string(const char* s, size_t n);
#include <iostream>
using namespace std;
int main()
{
string s1("Hello world!", 3);
cout << s1 << endl;
return 0;
}
运行结果:
string(size_t n, char c);
#include <iostream>
using namespace std;
int main()
{
string s1(6, 'A');
cout << s1 << endl;
return 0;
}
运行结果:
template <class InputIterator>
string(InputIterator first, InputIterator last);
3.1.2 赋值重载
- 赋值重载的内容我在C++:缺省参数|函数重载|引用|const引用中讲过,可以跳转去了解一下。
string& operator= (const string& str); // 将一个string对象赋值给到另一个
string& operator= (const char* s); // 将一个字符串赋值给到string对象
string& operator= (char c); // 将一个字符赋值给到string对象
3.2 string类对象的常见容量操作
下面我将介绍string类中有关容量的一些操作。
函数名称 | 功能说明 |
---|---|
size | 返回字符串有效字符长度 |
length | 返回字符串有效字符长度 |
capacity | 返回总空间大小 |
max_size | 返回字符串的最大长度 |
resize | 将有效字符的个数改成n个,多出的空间用字符c填充 |
reserve | 为字符串预留空间 |
clear | 清空有效字符 |
empty | 检测字符串释放为空串,是返回true,否则返回false |
shrink_to_fit | 收缩到合适大小 |
首先介绍一下【size】的内容!
① size
+ `size`表示当前字符串已经存放了多少数据,`capacity`表示当前字符串可容纳的空间数。 + 我们在VS编译器下进行观察,可以发现str明明是一个空字符出纳,里面并没有数据,而VS为它开辟了大小默认15的空间。其实这里本应该是16,只不过`\0`也占了一个大小。- 然后去构建一个具体的字符串来进行观察,发现
size
的值发生了变化。我们发现size
和length
的值是一样的。
#include <string>
#include <iostream>
using namespace std;
int main()
{
string s("Hello world!");
cout << s.size() << endl;
cout << s.length() << endl;
cout << s.capacity() << endl;
cout << s << endl;
return 0;
}
运行结果:
- 因此我们开始在文档中查看他们的相关性质,发现他们的性质也是一样的。
- 我们通过观察文档发现,C++容器中并没有
string
,这是因为:在STL的诞生历史中,string
类并不属于STL,它是STL之前就存在的东西,属于C++标准库里面的东西。
- 根据向前兼容,由于
string
更早使用,而且使用length
来定义长度接口,因此即使STL
出来了也会有人在用string
的东西比如length
,因此没有淘汰掉过去的东西。而且在“数”里面,length
就不是很合理,而STL中的size
就具有通用性。向前兼容、向前发展,因此,旧的要兼容新的,继续保留,存在即合理!
② capacity
下面介绍一些【capacity】相关的内容。
capacity
表示当前字符串可容纳的空间数。它可以用于容量获取,容量没有算入\0
,空间大小比实际容量多一个。
void TestPushBack()
{
string s;
size_t sz = s.capacity();// 保存最初的容量
cout << "capacity changed: " << sz << "\n";
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编译器环境下的运行结果:实际空间其实还要加一,给
\0
。(第一次是2倍扩容,后续是1.5倍扩容)- Linux–g++4.8平台下的运行结果:(视频截图)(扩容呈标准的2倍趋势)
- 为什么在不同的平台环境下
capacity
扩容的趋势特点是不一样的呢?- 因为在不同平台下的STL库是不一样的;在VS中,使用不同版本的VS大小也是不一样的。
-
VS
下做了特殊处理:数据大小小于16的数据存入内部的buff
中;大于等于16时,就存入string
中_str
指向的空间,这样可以避免小块内存的数据被存入堆上。 (原始视图里面可以看真实的底层情况)
- 问题:扩容一般会出现两个问题:一次性扩容太多,会出现空间资源的浪费;扩容太少,又会出现频繁扩容的情况,毕竟扩容是有消耗的。因此,引入
reserve
和reverse
。跳转到相应位置学习。
//string的底层结构:
class string
{
private:
char _buff[16];//<16
char* _str;//>=16
size_t _size;
size_t _capacity;
};
③ max_size
下面将介绍【max_size】的相关内容。void Test_Max_Size()
{
string s("Hello world!");
cout << s.size() << endl;
cout << s.max_size() << endl;
}
- 我们在不同的平台下进行测试,发现结果各不相同。
- VS编译器X86环境下的运行结果:
Linux平台下的运行结果和上面是不一样的。
④ clear
接下来是【clear】的相关内容!- clear一般是不会清理容量的,一般都是清理数据。
void TestClear1()
{
string s("Hello BaiLetian!");
cout << "s.size()=" << s.size() << endl;
cout << "s.capacity()=" << s.capacity() << endl << endl;
s.clear();
cout << "s.size()=" << s.size() << endl;
cout << "s.capacity()=" << s.capacity() << endl << endl;
}
运行结果:
⑤ empty
- 判断字符是否为空字符,若已经清空,返回1;若没有清,返回0。
void TestClear()
{
string s("Hello");
cout << "size: " << s.size() << endl;
cout << "capacity:" << s.capacity() << endl;
cout << s.empty() << endl;
s.clear();
cout << "size: " << s.size() << endl;
cout << "capacity:" << s.capacity() << endl;
cout << s.empty() << endl;
}
运行结果:
⑥ reserve
reserve
:保留、预留,一般不会缩容。根据文档:- 提前开辟空间,避免扩容。(扩容忙有消耗,频繁扩容消耗很大)
reserve
一般不会缩容的:增加容量到n,或者reserve
扩容有一个特例:申请扩容容量较小,不会进行扩容;申请扩容容量较大,才会进行扩容。
void TestString2()
{
string s("Hello world!XXXXXXXXXXXX");
cout << s.size() << endl;
cout << s.capacity() << endl << endl;
s.reserve(20);//扩容至少20
cout << s.size() << endl;
cout << s.capacity() << endl <<endl;
s.reserve(28);
cout << s.size() << endl;
cout << s.capacity() << endl << endl;
s.reserve(40);
cout << s.size() << endl;
cout << s.capacity() << endl << endl;
//申请容量较小,不会动;申请容量较大,才会扩容
}
VS编译器的运行结果:(VS不会缩容,会牺牲空间)
* Linux-g++环境下的运行结果:(对齐的,会进行缩容)
reverse
:反转、逆置。
void TestPushBack()
{
//观察容量变化
string s;
s.reserve(100);//功能:开辟100 个容量的空间,实际上可以开辟比它还要大好几倍的量的空间(做整数倍对齐)
size_t sz = s.capacity();// 保存最初的容量,这里的capacity并不包含\0
cout << "capacity changed: " << sz << "\n";
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";
}
}
}
VS下的reserve
用法:(实际上开辟的空间数量比给出的数字还要大)VS需要保持整数倍对齐。
- Linux的g++,reserve的用法:
⑦ resize
⑧ shrink_to_fit
- capacity减少到size。
3.3 string类对象的访问及其遍历操作
函数名称 | 功能说明 |
---|---|
operator[ ] | 访问(并可以修改)**pos** 位置的字符,const string类对象调用 |
begin + end + 迭代器 | begin获取第一个字符的迭代器+end获取最后一个字符串下一个位置的迭代器 |
rbegin + rend + 迭代器 | rbegin获取最后一个字符的迭代器+rend获取第一个字符前一个位置的迭代器 |
范围for | C++11支持更加简洁的范围for遍历 |
auto | auto在C++98和C++11的不同遍历 |
3.3.1 operator[]访问+遍历(⭐)
operator[]
:访问pos位置的字符,可以通过它来遍历数据。下面是它的两种形式。(主要与运算符重载这部分知识有关)(这部分知识在7月17日末尾讲了)
char& operator[] (size_t pos); //可读可写版本
const char& operator[] (size_t pos) const; //只读版本
class string
{
public:
char& operator[](size_t i)
{
return _str[i];//返回引用:不是为了减少拷贝,而是为了修改数据
}
private:
char* _str;
size_t _size;
size_t _capacity;
};
- 运用:
void TestString1()
{
string s("hello world!");
cout << s << endl;
// 1.下标+[]重载
for (size_t i = 0; i < s.size(); i++)
{
cout << s[i] << " ";
}
cout << endl;//换行
}
运行结果:
- 可以用
operator[]
来修改pos
位置的数据。
void TestString1()
{
string s("hello world!");
cout << s << endl;
for (size_t i = 0; i < s.size(); i++)
{
s[i] += 1;//
cout << s[i] << " ";
}
cout << endl;//换行
}
运行结果:
- 我们还可以把数据进行
++
操作后,再进行--
操作。
void TestString1()
{
string s("hello world!");
cout << s << endl;
for (size_t i = 0; i < s.size(); i++)
{
s[i] += 1;//
cout << s[i] << " ";
}
cout << endl;//换行
}
运行结果:
- 但是上面这种重载后的
[]
的用法和我们最初使用的数组中的[]
是不一样的。
string s("abcdef");
char s2[] = "hello world";
s[1]++; // -> operator[](1)++
s2[1]++; // -> *(s2 + 1)++
3.3.2 四种迭代器(用于遍历)
1. 迭代器有四种:普通对象的迭代器、普通对象的反向迭代器、const对象的迭代器、const对象的反向迭代器。 2. 迭代器提供了访问所有容器的通用的访问方式,所有的容器都可以用它来访问。例如链表:#include <list>
void TestList()
{
list<int> lt = { 1,2,3,4,5,6,7 };
list<int>::iterator lit = lt.begin();
while (lit != lt.end())
{
cout << *lit << " ";
++lit;
}
cout << endl;
}
int main()
{
TestList();
return 0;
}
运行结果:
1 2 3 4 5 6 7
- 对于上面的代码:
string
中_str
指向这一串数据。- 使用迭代器定义
lit
,lit
有点像指针,但是它并不是指针。 - 规定
end()
返回最后一个有效字符的下一个位置。 - 规定
begin()
是返回开始位置的迭代器。
① 普通对象的迭代器(可读可写)—— iterator
void TestIterator1()
{
// 1.普通对象的迭代器(可读可写)——iterator
string s2("Hello bailetian!");
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
运行结果:
H e l l o b a i l e t i a n !(有空格)
② 普通对象的反向迭代器(只读)——reverse_iterator
void TestIterator2()
{
string s2("Hello bailetian!");
// 2.普通对象的反向(r)迭代器——const_iterator
string::reverse_iterator rit = s2.rbegin();
while (rit != s2.rend())
{
cout << *rit << " ";
++rit;//仍然是 ++ 操作,但是它是反的,倒着输出打印
}
cout << endl;
}
运行结果:
! n a i t e l i a b o l l e H
③ const对象的迭代器——const_iterator
void TestIterator3()
{
// 3.const对象的迭代器:只读不写(不可修改指向的内容)——reverse_iterator
const string s2("Hello bailetian!");
string::const_iterator cit = s2.begin();
while (cit != s2.end())
{
//*cit += 2;// error c3892: “cit”: 不能给常量赋值(*cit是常量)
cout << *cit << " ";
++cit;
}
cout << endl;
}
运行结果:
H e l l o b a i l e t i a n !
④ const对象的反向迭代器——const_reverse_iterator
void TestIterator4()
{
// 4.const对象的(是“的”而不是“修饰”)反向(r)迭代器——const_reverse_iterator
const string s2("Hello bailetian!");
string::const_reverse_iterator rcit = s2.rbegin();
// 类型名称太长:auto
while (rcit != s2.rend())
{
cout << *rcit << " ";
++rcit;
}
cout << endl;
}
运行结果:
! n a i t e l i a b o l l e H
3.3.3 范围for的遍历(auto)
void TestFor1()
{
string s("ABCDEF");
string::iterator it = s.begin();//使用迭代器定义指针
while (it != s.end())//有点像指针
{
*it += 2;//在这里进行迭代器的修改(迭代器可以进行修改)
cout << *it << " ";
++it;
}
cout << endl;
// 3.范围for
// 自动赋值,自动迭代,自动判断结束
for (auto ch : s)// auto在c++11表示自动推导,在这里自动推导识别s的类型
//这里是一个冒号,而不是两个
{
ch -= 2;//在这里对刚才的修改做出恢复
cout << ch << " ";
}
cout << endl;
cout << "s:" << s << endl;
}
- 运行结果:
C D E F G H
C D E F G H
- C++11引入基于范围的for循环,范围for分为两个部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围;
- 范围for的底层就是迭代器,可以从汇编进行观察(汇编里面看见了
begin、end
等迭代器内容); - 迭代器是可以进行修改的;
- 范围for可以作用到数组和容器对象上进行遍历。
- 注意:在迭代器中使用了
*it += 2;
进行+
的操作,在范围for里面进行-
的操作:这里的代码有一个大坑。加入下面的代码就可以观察到:迭代器里面进行修改s
就改变了,但是范围for
里面进行修改s
却没有发生改变。- 这里主要的原因就是:for这一部分的原理就是底层变化成迭代器后,相当于将
*it
赋值给ch
,而ch
本身只是某个字符的一份拷贝,即局部变量。修改局部变量时,并不会修改对应的字符。而迭代器就类似于指针。
- 这里主要的原因就是:for这一部分的原理就是底层变化成迭代器后,相当于将
cout << "s:" << s << endl;//在最后一行加入此代码
- 如果想要进行修改,就使用引用。此时`ch`变成`*it`里面每个字符的别名。
for(auto& ch : s)
3.3.4 auto的遍历(for)
cpp98
的遍历
//测试c++98和c++11的数组遍历对比——范围for用于数组遍历、容器遍历
void TestTraverse98()
{
// 范围for
int array[] = { 1, 2, 3, 4, 5 };
// c++98的遍历:数组遍历
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
array[i] *= 2;
}
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout << array[i] << " ";
}
}
cpp11
的遍历
void TestTraverse11()
{
int array[] = { 1, 2, 3, 4, 5 };
// c++11的遍历:auto+范围for——是用于遍历
for (auto& e : array)// 一个:,而不是两个:
e *= 2;//如果是大点的对象,则使用引用,e是array[0]、array[1]……的别名
for (auto e : array)
cout << e << " " << endl;
}
- 上面两段代码运行结果都是一样的:
2
4
6
8
10
3.3.5 auto的扩展
C++11提供的auto。- 早期auto修饰的变量是自动变量,可以进行自动释放。后来栈上的变量都可以进行自动释放。因此此功能废弃了。
- C++11用auto做自动推导,用来进行类型的声明,相当于自动占位。
- auto存在的意义/价值:用auto来推导并替换变量的类型名称,简化代码。但是这种用法牺牲了代码的可读性:要求对被替换的内容比较熟悉,替换后能知道它是什么东西。
#include <map>
map<string, string>dict;
// map<string, string >::iterator mit = dict.begin();
auto mit = dict.begin();//替换后简化代码
// 当我们不熟悉map<string, string >时,可读性就降低了不知道auto替换的是什么类型
- auto不能定义无类型/ 类型不清楚的(auto通过内容推导类型,但是现在无法通过内容进行推导了)
- auto不能定义数组。
- auto不能做参数,给缺省值也不支持。
3.4 string类对象的修改操作
下面是对string类对象的修改操作。
函数名称 | 功能说明 |
---|---|
push_back | 在字符串的末尾加入一个字符 |
append | 在字符串的末尾加入一个字符 |
operator+= | 在字符串后面追加字符串str |
insert | 在指定位置插入字符货字符串等操作 |
assign | 使用指定字符串替换原字符串 |
erase | 删除字符串中的一部分 |
replace | 替换指定区间的字符串 |
pop_back | 删除字符串的最后一个字符 |
swap | 收缩到合适大小 |
3.4.1 push_back
![](https://i-blog.csdnimg.cn/direct/fe19241dc17447b2a3958222b2163b78.png#pic_center)尾插一个字符,尾插一个字符串使用的是下面的append
。
void TestString()
{
string s("Hello world!");
s.push_back(' ');
s.push_back('X');
cout << s << endl;
}
运行结果:
3.4.2 append
string& append (const string& str); // 追加一个string对象
// 追加一个string对象中的指定字符串长度
string& append (const string& str, size_t subpos, size_t sublen);
string& append (const char* s); // 追加一个字符串
string& append (const char* s, size_t n); // 追加字符串中的前n个字符串
string& append (size_t n, char c); // 追加n个字符
append
支持尾插一个字符串。根据文档一共有6种重载形式。
void TestString()
{
string s("Hello world,");
s.push_back(' ');
s.append("BaiLetian!");
cout << s << endl;
}
运行结果:
3.4.3 operator+=
其实,`operator+=` 不仅可读性强,还可以实现`append`和`push_back`的功能。void TestAppendPopback()
{
string s("Hello world!");
s.push_back(' ');
s.push_back('X');
cout << s << endl;
s += 'Y';
s += ",BaiLetian!";
cout << s << endl;
}
运行结果:
3.4.4 insert
// 在指定位置插入一个string对象
string& insert (size_t pos, const string& str);
// 在指定位置插入一个string对象里的一部分
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
// 在指定位置插入一个字符串
string& insert (size_t pos, const char* s);
// 在指定位置插入一个字符串的前n个字符
string& insert (size_t pos, const char* s, size_t n);
// 在指定位置插入n个字符
string& insert (size_t pos, size_t n, char c);
// 在指定迭代器的位置插入n个字符
void insert (iterator p, size_t n, char c);
// 在指定迭代器的位置插入一个字符,并且返回一个迭代器的位置
iterator insert (iterator p, char c);
insert
谨慎使用,避免效率低下。因为头插、中间插入都有可能引起数据的挪动。
void TestString3()
{
string s("BaiLetian!");
s.insert(0, "Hello,");//头插(0位置)
cout << s << endl;
}
- 运行结果:
insert
的设计比较混乱:使用时容易混乱。- 频繁调用会使得空间的性能大大降低。
3.4.5 erase
功能:支持某个pos
位置后的删除len
个字符,如果不传入len
参数,那么就会删除掉pos
位置后面的所有字符。
- 头删:
void TestErase1()
{
string s("ABCDEFGHIJK");
cout << s << endl;
//头删1(迭代器)(删除一个字符)
s.erase(s.begin());
cout << s << endl;
//头删2
s.erase(0, 1);
cout << s << endl;
}
运行结果:
- 尾删
void TestErase2()
{
string s("ABCDEFGHIJK");
cout << s << endl;
//尾删1
s.erase(--s.end());
cout << s << endl;
//尾删2
s.erase(s.size() - 1, 1);
cout << s << endl;
}
运行结果:
- 中间位置删除
void TestErase3()
{
string s("ABCDEFGHIJK");
cout << s <<endl;
s.erase(3, 2);
cout << s << endl;
}
运行结果:
- 一般情况下,string的尾插只增加不删除。
- 频繁调用
erase
会使得空间的性能大大降低。
3.4.6 replace
功能:替换,pos
位置开始的len
个字符替换成……(string/char*/两个迭代器之间
的一部分/全部)。
- 中间的某个位置后开始的几个字符替换成一个字符串。
void TestRepalce1()
{
string s("Hello world!");
cout << s << endl;
s.replace(6, 6, "BaiLetian!");
cout << s << endl;
}
运行结果:
replace
的效率也不高,频繁调用会使得空间的性能大大降低。
3.4.7 find
- 从
pos
位置查找,例如下面的运用(将字符串中的空格替换成某个字符串):
void TestFind()
{
string s("Hello BaiLetian! Hello Qianye! ");
size_t pos = s.find(' ');//查找空格字符,返回字符的位置前一个字符的数据位置(第几个字符)
while (pos != string::npos)
{
s.replace(pos, 1, "%%");//找到目标后,将该一个字符用%%替换
//pos = s.find(' ');//从pos位置重新开始找空格并进行替换(找到了又继续从头开始找),改进城下面的
pos = s.find(' ', pos + 2);//找到后从后面两个位置开始找(因为后面两个已经被替换了)
}
cout << s << endl;
}
运行结果:
注意:如果是进行大量的字符串替换,极有可能会出现:替换次数多了,会不止一次地进行扩容
- 改进:遍历原来的字符串,如果出现目标字符,就进行替换。唯一的缺点就是牺牲了空间。
void TestFind1()
{
string s("Hello BaiLetian! Hello Qianye! ");
cout << s << endl;
string tmp;
for (auto ch : s)
{
if (ch == ' ')
tmp += "%%";
else
tmp += ch;
}
cout << tmp << endl;
}
- 还有一种更加高效的方式:使用
swap
函数。也可以使用reserve
。
3.4.8 assign
3.4.9 pop_back
功能:尾删一个字符。
void TestPopBack()
{
string s("abcdef");
cout << s << endl;
s.pop_back();
cout << s << endl;
}
运行结果:
3.4.10 swap
根据文档可知:swap
涉及到了深浅拷贝的问题
3.5 类对象的其他字符串操作
函数名称 | 功能说明 |
---|---|
c_str | 返回C格式字符串 |
substr | 在str中从pos位置开始,截取n个字符,然后将其返回 |
find | 在str中从pos位置开始往后找字符c,返回该字符在字符串中的位置 |
rfind | 在str中从pos位置开始往前找字符c,返回该字符在字符串中的位置 |
find_first_of | 从前往后找第一个匹配的字符 |
find_last_of | 从后往前找第一个匹配的字符 |
find_first_not_of | 从前往后找第一个不匹配的字符 |
find_last_not_of | 从后往前找第一个不匹配的字符 |
3.5.1 c_str
功能:返回底层字符串的指针。意义:它可以兼容C语言,因此可以使用C语言标准库里的各种函数等等。
void Testcstr()
{
string file;
cin >> file;
FILE* fout = fopen(file.c_str(), "r");
char ch = fgetc(fout);
while (ch!=EOF)
{
cout << ch;
ch = fgetc(fout);
}
fclose(fout);
}
在控制板上输入“test.cpp"回车:
运行结果:
3.5.2 find
功能:找一个字符或者字符串。void TestFind()
{
string s("test.cpp.zip");
size_t pos = s.find('.');
cout << pos << endl;
}
运行结果:
3.5.3 rfind
功能:倒着找一个字符或者字符串。void TestRfind()
{
string s("test.cpp.zip");
size_t pos = s.rfind('.');
cout << pos << endl;
}
运行结果:
3.5.4 substr
功能:pos位置开始的len个字符单独构造一个string并且返回该字符/字符串。
void TestFind()
{
string s("test.cpp");
size_t pos = s.find('.');
string suffix = s.substr(pos);//得到 . 后面的字符串
cout << suffix << endl;
}
运行结果:
3.5.5 find_first_of
功能:寻找匹配到的任意的字符。注意:不能你如果看名称猜测该关键词的作用,有出入。名字相当于“find_any_of”这个名字就比较好理解一些。
void TestFindFirstof()
{
string s("abcdefghijklmn");
size_t found = s.find_first_of("ajm");
while (found != string::npos)
{
s[found] = '*';
found = s.find_first_of("ajm", found + 1);
}
cout << s << endl;
}
运行结果:
观察发现:只要是字符串"ajm"
中出现的一个个字符,都被替换成了字符*
,由此可知,find_first_of
匹配的是字符串里面的任意一个字符。
3.5.6 find_last_of
![](https://img-blog.csdnimg.cn/img_convert/b3caae3bd63901d6faba8f42b71cb25f.png)功能:名字变化“rfind_nay_of”就更好理解一些。
Linux中避免字符串里面的非转义字符变成转义字符,多加一个\
符号。
3.5.7 find_first_not_of
3.5.8 find_last_not_of
喜欢的uu记得三连支持哦!