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

news2024/11/18 7:25:47

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];//<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获取第一个字符前一个位置的迭代器
范围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.普通对象的迭代器(可读可写)——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

  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()
{
	// 范围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] << " ";
	}
}
  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;

	//头删2
	s.erase(0, 1);
	cout << s << endl;
}

运行结果:

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

运行结果:

  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 += "%%";
		else
			tmp += 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记得三连支持哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2136613.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

十二,Spring Boot 异常处理(自定义异常页面,全局异常,自定义异常)

十二&#xff0c;Spring Boot 异常处理(自定义异常页面&#xff0c;全局异常&#xff0c;自定义异常) 文章目录 十二&#xff0c;Spring Boot 异常处理(自定义异常页面&#xff0c;全局异常&#xff0c;自定义异常)1. 基本介绍2. 自定义异常页面3. 全局异常4. 自定义异常5. 补充…

LineageOS刷机教程

版权归作者所有&#xff0c;如有转发&#xff0c;请注明文章出处&#xff1a;https://cyrus-studio.github.io/blog/ LineageOS 是一个基于 Android 开源项目&#xff08;AOSP&#xff09;的开源操作系统&#xff0c;主要由社区开发者维护。它起源于 CyanogenMod 项目&#xff…

数据库索引底层数据结构之B+树MySQL中的页索引分类【纯理论知识,干货分享,面试必备】

目录 1、索引简介 1.1 什么是索引 1.2 使用索引的原因 2、索引中数据结构的设计 —— B树 2.1 哈希 2.2 二叉搜索树 2.3 B树 2.4 最终选择之——B树 2.4.1 B树与B树的对比(面向索引)【面试题】 3、MySQL中的页 3.1 页的使用原因 3.2 页的结构 3.2.1 页文件头和页文件…

解锁定位服务:Flutter应用中的高德地图定位

前言 在现代移动应用开发中&#xff0c;定位服务已成为一项基本功能&#xff0c;它使得应用能够获取用户的地理位置信息&#xff0c;为用户提供更加个性化的服务。 Flutter 作为跨平台的移动应用开发框架&#xff0c;支持集成多种服务&#xff0c;包括定位服务。 本文将介绍如…

HR8870:可PWM控制,4.5A直流有刷电机驱动数据手册

HR8870芯片描述 HR8870是一款直流有刷电机驱动器&#xff0c;适用于打印机、电器、工业设备以及其他小型机器。两个逻辑输入控制H桥驱动器&#xff0c;该驱动器由四个N-MOS组成&#xff0c;能够以高达4.5A的峰值电流双向控制电机。利用电流衰减模式&#xff0c;可通过对输入进行…

故障码格式解析

中&#xff0c;诊断故障码&#xff08;DTC, Diagnostic Trouble Code&#xff09;是由一个字母前缀和三个后续字符组成的。这些字母前缀根据故障所属的系统类别来区分&#xff0c;具体如下&#xff1a; B0 -- B3&#xff1a;表示车身系统&#xff08;Body&#xff09;的故障码…

Linux CTF逆向入门

1.ELF格式 我们先来看看 ELF 文件头&#xff0c;如果想详细了解&#xff0c;可以查看ELF的man page文档。 关于ELF更详细的说明&#xff1a; e_shoff&#xff1a;节头表的文件偏移量&#xff08;字节&#xff09;。如果文件没有节头表&#xff0c;则此成员值为零。 sh_offset&…

Qt 菜单、工具栏 的基本使用

效果 代码 #include "mainwindow.h" #include "ui_mainwindow.h" #include<QToolBar> #include<QDebug> #include<QPushButton>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupU…

【JAVA入门】Day45 - 压缩流 / 解压缩流

【JAVA入门】Day45 - 压缩流 / 解压缩流 文章目录 【JAVA入门】Day45 - 压缩流 / 解压缩流一、解压缩流二、压缩流 在文件传输过程中&#xff0c;文件体积比较大&#xff0c;传输较慢&#xff0c;因此我们发明了一种方法&#xff0c;把文件里的数据压缩到一种压缩文件中&#x…

【LLMs对抗性提示:提示泄漏、非法行为、DAN、Waluigi效应、 游戏模拟器、防御策略————】

对抗性提示 目录 对抗性提示 提示注入 提示泄漏 非法行为 DAN Waluigi效应 GPT-4模拟器 游戏模拟器 防御策略 在指令中添加防御 参数化提示组件 引用和其他格式 对抗提示检测器 模型类型 参考文献 Adversarial prompting是提示工程中的一个重要主题&#xff0c…

每日OJ_牛客_NC313 两个数组的交集

目录 牛客_NC313 两个数组的交集 解析代码 牛客_NC313 两个数组的交集 两个数组的交集_牛客题霸_牛客网 class Solution { public:/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** * param nums1 int整型vector * pa…

统计/nginx/access.log中每个ip的访问次数,按高到低排列

/nginx/access.log具体内容长这样&#xff1a; 第一个元素就是ip。 awk {print $1} /nginx/access.log | sort | uniq -c | sort -r首先&#xff0c;awk {print $1} /nginx/access.log 从 /nginx/access.log文件的每行中提取出第一个字段。然后&#xff0c;sort 对提取出的第…

【有哪些坑】Apollo配置中心FAQ常见问题列表

使用某个框架之前&#xff0c;得先看看前辈们踩过的坑。 他人的间接经验 -> 自己的直接经验 前车之鉴&#xff0c;后事之师。比喻前人失败了&#xff0c;后人应该从中吸取教训&#xff0c;避免再犯同样的错误。 常见问题回答 1. Apollo是什么&#xff1f; Apollo&#xff…

关于STM32项目面试题01:电源

博客的风格是&#xff1a;答案一定不能在问题的后面&#xff0c;要自己想、自己背&#xff1b;回答都是最精简、最精简、最精简&#xff0c;可能就几个字&#xff0c;你要自己自信的展开。 面试官01&#xff1a;说说你知道的开关电源的拓扑结构&#xff1f; 面试官02&#xff1…

Nacos下载和启动

Nacos是什么&#xff1f; 一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台 下载 https://github.com/alibaba/nacos/releases/tag/2.1.1启动 将下载好的Nacos解压缩&#xff0c;然后到bin目录下打开cmd 输入指令&#xff1a;startup.cmd -m standalone 出…

Apache DolphinScheduler 跨工作流复杂依赖功能详解

大家好&#xff0c;我叫高楚枫&#xff0c;来自阿里云 EMR 团队的开发工程师&#xff0c;同时也是 Apache DolphinScheduler 的 PMC 成员之一。 今天非常高兴能在这里和大家分享关于跨工作流复杂依赖的功能详解。 引言 在现代的数据处理和调度过程中&#xff0c;工作流的依赖…

STL_string 常用的用法

string里常用的函数与讲解使用 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ &#xff08;点击进入c关于string 的各个函数的全面讲解使用/英文版&#xff09; Iterators&#xff08;迭代器&#xff09;: begin与end&#xff1a; …

门磁模块详解(防盗感应开关 STM32)

目录 一、介绍 二、程序设计 main.c文件 gate_guard.h文件 gate_guard.c文件 三、实验效果 四、资料获取 项目分享 一、介绍 MC-38常闭式门磁开关是作为IO开关输入数字信号的&#xff0c;原理是合在一起信号是导通的 , 配合有线主机使用 不能单独使用。适用于非铁质&a…

RK3588镜像打包制作,替换文件系统

1.在开发板上安装async apt-get async 2.在另一台linux机器上执行命令拷贝文件系统 注意&#xff1a; 这里使用root权限或者账户 mkdir rootfs rsync -avx root192.168.1.3:/ rootfs 3.制作空镜像文件 先去开发板上验证自己的系统使用了多少空间&#xff0c;然后输入命令制…

grafana升级指南

已有grafana在使用&#xff0c;需要升级新版本的grafana&#xff0c;操作如下&#xff1a; 1.先把之前的grafana文件夹整个备份 2.在grafana官网下载OSS的zip版本&#xff0c;不要msi版本 3.在原来的grafana文件夹里&#xff0c;把新版本的文件夹都复制进来&#xff0c;但是…