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 赋值重载

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记得三连支持哦!

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部