当前位置: 首页 > news >正文

C++:STL详解(一)string类的基本介绍与使用方式

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类的特性

  1. string类是表示字符串的字符串类;
  2. 该类的接口与常规容器的接口基本相同,再添加了一些转么能用来操作string的常规操作。
  3. string在底层实际是:basic_string 模板类的别名,typedef basic_string<char,char_trait , alloctor> string;
  4. 不能操作多字节或者变长字符的序列。

注意: 在使用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);
  • 函数运用:
  1. string(); 无参常用
#include <iostream>
using namespace std;
int main()
{string s1;//构造空字符串cout << s1 << endl;return 0;
}

运行结果:

  1. string(const string& str);
#include <iostream>
using namespace std;
int main()
{string s1("Hello world");string s2(s1);cout << s2 << endl;return 0;
}

运行结果:

  1. 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;
}

运行结果:

  1. string(const char* s, size_t n);
#include <iostream>
using namespace std;
int main()
{string s1("Hello world!", 3);cout << s1 << endl;return 0;
}

运行结果:

  1. string(size_t n, char c);
#include <iostream>
using namespace std;
int main()
{string s1(6, 'A');cout << s1 << endl;return 0;
}

运行结果:

  1. 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的值发生了变化。我们发现sizelength的值是一样的。
#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指向的空间,这样可以避免小块内存的数据被存入堆上。 (原始视图里面可以看真实的底层情况)
  • 问题:扩容一般会出现两个问题:一次性扩容太多,会出现空间资源的浪费;扩容太少,又会出现频繁扩容的情况,毕竟扩容是有消耗的。因此,引入reservereverse。跳转到相应位置学习。
//string的底层结构:
class string
{
private:char  _buff[16];//<16char* _str;//>=16size_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);//扩容至少20cout << 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并不包含\0cout << "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获取第一个字符前一个位置的迭代器
范围forC++11支持更加简洁的范围for遍历
autoauto在C++98和C++11的不同遍历

3.3.1 operator[]访问+遍历(⭐)

  1. 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;
};
  1. 运用:
void TestString1()
{string s("hello world!");cout << s << endl;// 1.下标+[]重载for (size_t i = 0; i < s.size(); i++){cout << s[i] << " ";}cout << endl;//换行
}

运行结果:

  1. 可以用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;//换行
}

运行结果:

  1. 我们还可以把数据进行++操作后,再进行--操作。
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;//换行
}

运行结果:

  1. 但是上面这种重载后的[]的用法和我们最初使用的数组中的[]是不一样的。
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

  1. 对于上面的代码:
    • string_str指向这一串数据。
    • 使用迭代器定义litlit有点像指针,但是它并不是指针。
    • 规定end()返回最后一个有效字符的下一个位置。
    • 规定begin()是返回开始位置的迭代器。

① 普通对象的迭代器(可读可写)—— iterator

void TestIterator1()
{// 1.普通对象的迭代器(可读可写)——iteratorstring 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_iteratorstring::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_iteratorconst 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_iteratorconst string s2("Hello bailetian!");string::const_reverse_iterator rcit = s2.rbegin();// 类型名称太长:autowhile (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

  1. C++11引入基于范围的for循环,范围for分为两个部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围;
  2. 范围for的底层就是迭代器,可以从汇编进行观察(汇编里面看见了begin、end等迭代器内容);
  3. 迭代器是可以进行修改的;
  4. 范围for可以作用到数组和容器对象上进行遍历。
  • 注意:在迭代器中使用了*it += 2;进行+的操作,在范围for里面进行-的操作:这里的代码有一个大坑。加入下面的代码就可以观察到:迭代器里面进行修改s就改变了,但是范围for里面进行修改s却没有发生改变。
    • 这里主要的原因就是:for这一部分的原理就是底层变化成迭代器后,相当于将*it赋值给ch,而ch本身只是某个字符的一份拷贝,即局部变量。修改局部变量时,并不会修改对应的字符。而迭代器就类似于指针。
cout << "s:" << s << endl;//在最后一行加入此代码
- 如果想要进行修改,就使用引用。此时`ch`变成`*it`里面每个字符的别名。
for(auto& ch : s)

3.3.4 auto的遍历(for)

  1. cpp98的遍历
//测试c++98和c++11的数组遍历对比——范围for用于数组遍历、容器遍历
void TestTraverse98()
{// 范围forint 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] << " ";}
}
  1. 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。
  1. 早期auto修饰的变量是自动变量,可以进行自动释放。后来栈上的变量都可以进行自动释放。因此此功能废弃了。
  2. C++11用auto做自动推导,用来进行类型的声明,相当于自动占位。
  3. auto存在的意义/价值:用auto来推导并替换变量的类型名称,简化代码。但是这种用法牺牲了代码的可读性:要求对被替换的内容比较熟悉,替换后能知道它是什么东西。
#include <map>
map<string, string>dict;
// map<string, string >::iterator mit = dict.begin();
auto mit = dict.begin();//替换后简化代码
// 当我们不熟悉map<string, string >时,可读性就降低了不知道auto替换的是什么类型
  1. auto不能定义无类型/ 类型不清楚的(auto通过内容推导类型,但是现在无法通过内容进行推导了)
  2. auto不能定义数组。
  3. 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);
  1. insert谨慎使用,避免效率低下。因为头插、中间插入都有可能引起数据的挪动。
void TestString3()
{string s("BaiLetian!");s.insert(0, "Hello,");//头插(0位置)cout << s << endl;
}
  • 运行结果:

  1. insert的设计比较混乱:使用时容易混乱。
  2. 频繁调用会使得空间的性能大大降低。

3.4.5 erase

功能:支持某个pos位置后的删除len个字符,如果不传入len参数,那么就会删除掉pos位置后面的所有字符。

  1. 头删:
void TestErase1()
{string s("ABCDEFGHIJK");cout << s << endl;//头删1(迭代器)(删除一个字符)s.erase(s.begin());cout << s << endl;//头删2s.erase(0, 1);cout << s << endl;
}

运行结果:

  1. 尾删
void TestErase2()
{string s("ABCDEFGHIJK");cout << s << endl;//尾删1s.erase(--s.end());cout << s << endl;//尾删2s.erase(s.size() - 1, 1);cout << s << endl;
}

运行结果:

  1. 中间位置删除
void TestErase3()
{string s("ABCDEFGHIJK");cout << s <<endl;s.erase(3, 2);cout << s << endl;
}

运行结果:

  1. 一般情况下,string的尾插只增加不删除。
  2. 频繁调用erase会使得空间的性能大大降低。

3.4.6 replace

功能:替换,pos位置开始的len个字符替换成……(string/char*/两个迭代器之间的一部分/全部)。

  1. 中间的某个位置后开始的几个字符替换成一个字符串。
void TestRepalce1()
{string s("Hello world!");cout << s << endl;s.replace(6, 6, "BaiLetian!");cout << s << endl;
}

运行结果:

  1. replace的效率也不高,频繁调用会使得空间的性能大大降低。

3.4.7 find

  1. 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;
}

运行结果:

注意:如果是进行大量的字符串替换,极有可能会出现:替换次数多了,会不止一次地进行扩容

  1. 改进:遍历原来的字符串,如果出现目标字符,就进行替换。唯一的缺点就是牺牲了空间。
void TestFind1()
{string s("Hello BaiLetian! Hello Qianye! ");cout << s << endl;string tmp;for (auto ch : s){if (ch == ' ')tmp += "%%";elsetmp += ch;}cout << tmp << endl;
}
  1. 还有一种更加高效的方式:使用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记得三连支持哦!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 【查看谷歌浏览器的个人文件路径】
  • 【Java面试】第九天
  • OpenCV结构分析与形状描述符(24)检测两个旋转矩形之间是否相交的一个函数rotatedRectangleIntersection()的使用
  • 性能诊断的方法(五):架构和业务诊断
  • 高教社杯数模竞赛特辑论文篇-2016年A题:基于极值优化的系泊系统设计
  • 消息中间件有哪些常见类型
  • Redis实现发布/订阅功能(实战篇)
  • 高度可定制的电竞鼠标,雷柏VT1 PRO MAX体验
  • docker拉取 jdk 8
  • 八股文知识汇总(常考)
  • 架构师备考的一些思考(四)
  • 【Hue导入Hive文件类型数据(自动建表)】
  • 网站如何防范BOT流量?
  • 【FATFS】f_mount函数详细解析
  • Python+Pytest框架,“api_key.py文件怎么编写“?
  • [ JavaScript ] 数据结构与算法 —— 链表
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • ES6系统学习----从Apollo Client看解构赋值
  • java 多线程基础, 我觉得还是有必要看看的
  • mysql 5.6 原生Online DDL解析
  • react-core-image-upload 一款轻量级图片上传裁剪插件
  • Spring核心 Bean的高级装配
  • VirtualBox 安装过程中出现 Running VMs found 错误的解决过程
  • 扑朔迷离的属性和特性【彻底弄清】
  • 强力优化Rancher k8s中国区的使用体验
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • LIGO、Virgo第三轮探测告捷,同时探测到一对黑洞合并产生的引力波事件 ...
  • 容器镜像
  • ​14:00面试,14:06就出来了,问的问题有点变态。。。
  • (1)Android开发优化---------UI优化
  • (152)时序收敛--->(02)时序收敛二
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (搬运以学习)flask 上下文的实现
  • (初研) Sentence-embedding fine-tune notebook
  • (回溯) LeetCode 78. 子集
  • (力扣题库)跳跃游戏II(c++)
  • (图)IntelliTrace Tools 跟踪云端程序
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (一)项目实践-利用Appdesigner制作目标跟踪仿真软件
  • (转)Sublime Text3配置Lua运行环境
  • (转)程序员疫苗:代码注入
  • (自用)交互协议设计——protobuf序列化
  • *(长期更新)软考网络工程师学习笔记——Section 22 无线局域网
  • *算法训练(leetcode)第四十七天 | 并查集理论基础、107. 寻找存在的路径
  • .chm格式文件如何阅读
  • .NET Core 项目指定SDK版本
  • .Net Remoting常用部署结构
  • .net 中viewstate的原理和使用
  • .NET/ASP.NETMVC 大型站点架构设计—迁移Model元数据设置项(自定义元数据提供程序)...
  • .NetCore实践篇:分布式监控Zipkin持久化之殇
  • .NET序列化 serializable,反序列化
  • .NET中的十进制浮点类型,徐汇区网站设计
  • ?php echo ?,?php echo Hello world!;?
  • @manytomany 保存后数据被删除_[Windows] 数据恢复软件RStudio v8.14.179675 便携特别版...
  • @RequestMapping 和 @GetMapping等子注解的区别及其用法