【C++】STL——string类详解


🚀 作者简介:一名在后端领域学习,并渴望能够学有所成的追梦人。
🚁 个人主页:不 良
🔥 系列专栏:🛸C++  🛹Linux
📕 学习格言:博观而约取,厚积而薄发
🌹 欢迎进来的小伙伴,如果小伙伴们在学习的过程中,发现有需要纠正的地方,烦请指正,希望能够与诸君一同成长! 🌹


STL简介

STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架。

STL的版本

  • 原始版本

Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本–所有STL实现版本的始祖。

  • P. J. 版本

由P. J. Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。

  • RW版本

由Rouge Wage公司开发,继承自HP版本,被C+ + Builder 采用,不能公开或修改,可读性一般。

  • SGI版本

由Silicon Graphics Computer Systems,Inc公司开发,继承自HP版 本。被GCC(Linux)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程 风格上看,阅读性非常高。我们后面学习STL要阅读部分源代码,主要参考的就是这个版本

STL的六大组件

STL提供六大组件,它们之间可以相互组合使用。

image-20230622102921683

1、容器(containers)
容器用来存放数据,包括各种数据结构,如string,vector,list,deque,set,map等。从实现的角度来看,STL容器是一种类模板(class template)。

2、算法(algorithms)
算法包括各种常用的sort,search,copy,erase, find等。从实现的角度来看,STL算法是一种函数模板(function template)。

3、迭代器(iterators)
迭代器作为“泛型指针”,扮演容器和算法之间的粘合剂,用来连接容器和算法。从实现角度来看,迭代器是一种将operator*,operator->,operator++,operator–等指针相关操作进行重载的类模板(class template)。所有的STL容器都带有自己专属的迭代器。原生指针也是一种迭代器。

所谓的原生指针就是我们定义的最普通的指针,形如 类型名 *指针名,类型名可以是基础类型int,double等,也可以是一个类。

当一个类将*和->操作符进行重载时,虽然也可以进行类似指针的操作,但是它已经不是原生指针。

4、仿函数(functors)
仿函数是让一个类看起来像一个函数。其实就是一种重载了operator()的类(class)或者类模板(class template)。

5、配接器(adapters)
一种用来修饰容器,仿函数或者迭代器的接口的东西。配接器修改类的接口,使原来不相互匹配的两个类可以相互匹配,进行合作。

6、空间配置器(allocators)
配置器主要负责空间的配置和管理。从实现角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的类模板(class template)。

内存池:当我们需要频繁的申请和释放空间的时候,要提高效率可以开一个内存池,不用每次都向操作系统中申请内存。内存池的内存也是从堆上申请来的。

STL的缺陷

  • STL 库的更新太慢,上一版靠谱的是 C++98,中间的 C++03 基本一些修订。C++11 出来已经相隔了 13 年,STL才进一步更新。
  • STL 现在都没有支持线程安全。并发环境下需要我们自己加锁。且锁的粒度是比较大的。
  • STL 极度的追求效率,导致内部比较复杂。比如类型萃取,迭代器萃取。
  • STL 的使用会有代码膨胀的问题,比如使用 vector/vector/vector 这样会生成多份代码,当然这是模板语法本身导致的。

标准库中的string类

string类介绍

C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

C语言中字符串数组不方便修改,所以C++提供了一个string类。由于历史原因string类比STL产生的早一些,所以string类中提供的函数比较冗余。

  • string 是表示字符串的字符串类。
  • 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作 string 的常规操作。
  • string类是被typedef出来的,string 在底层实际是basic_string 模板类的别名:typedef basic_string<char, char_traits, allocator> string;
  • string类不能操作多字节或者变长字符的序列。

在头文件<string>中包含多个类模板(Class templates)(basic_string、char_traits)和类实例化(Class instantiations)(string、u16string、wstring、u32string):

image-20230622110840894

image-20230622111402850

string管理的是char(1个字节)的数组;wstring管理的是wchar_t(2个字节)的数组;u16string管理的是char16_t(2个字节)的数组;u32string管理的是char32_t(4个字节)的数组。为什么会有这样的差异?因为现实世界中有管理不同字符数组的需求的。

文档中的一些主要模块如下图,我们平时学习STL时可以使用这个网站:Cplusplus

image-20230622155548695

string类对象的常见构造

string类实现了多个构造函数的重载,常用的构造函数如下:

(constructor)函数名称功能说明
string()构造空的string类对象,即空字符串
string(const char* s)用 C-string 来构造 string 类对象
string(const char* s, size_t n)复制s所指字符序列中的前n个字符
string(const string& s)拷贝构造函数
string(size_t n, char c)生成n个c字符的 string 类对象
string(const string& str, size_t pos, size_t len = npos)复制str中从字符位置pos开始并跨越len个字符的部分

最后一个如果不给len,缺省值是npos,在定义中虽然npos = -1,但是npos是无符号数,所以是整数的最大值。

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s1;
	string s2("hello world");
	string s3("hello world", 5);
	string s4(s2);//拷贝构造s2
	string s5(5, 'c');//生成5个c字符的string类对象
	string s6(s2, 6, 5);//从下标为6的位置开始向后复制5个字符
	//在string类中流插入和流提取已经重载过了,可以直接使用
	cout << s1 << endl; //空串
	cout << s2 << endl; //hello world
	cout << s3 << endl; //hello
	cout << s4 << endl; //hello world
	cout << s5 << endl; //ccccc
	cout << s6 << endl; //world
	return 0;
}

我们还可以这样:

string s = "hello world"

因为会发生隐式类型转换,单参数的构造函数支持隐式类型转换,const char *转换成string,编译器优化为直接构造,如果不想进行隐式类型转换我们可以在构造函数前加关键字explicit

string类对象容量操作

函数名称功能说明
size返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty检测字符串是否为空串,是返回true,否则返回false
clear清空有效字符
reserve为字符串预留空间
resize将有效字符的个数改成n个,多出的空间用字符c填充
max_size获取string对象的最大长度
shrink_to_fit(C++11)缩容,将capacity缩小至和size一样大小

size/length函数

  • 使用size函数或者length函数获取当前有效字符的个数
size_t size() const;
size_t length() const;

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello world"); 
	cout << s.size() << endl; // 11
	cout << s.length() << endl; // 11
}

size函数和length函数作用相同,因为string类产生的比较早,后来为了和其他的数据结构保持一致,所以增加了size函数。

max_size函数

  • 使用max_size函数获取string对象的最大长度
size_t max_size() const;

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
    string s("hello world"); 
    cout << s.max_size() << endl; // 2147483647
}

capacity函数

  • 使用capacity函数获取当前对象所分配的存储空间的大小
size_t capacity() const;

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
    string s("hello world"); 
    cout << s.capacity() << endl; // 15
}

VS下面的容量空间是不包含\0的。

扩展:string类的扩容规则:

我们通过下面的程序观察:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s;
	size_t sz = s.capacity();
	cout << "making s grow:" << sz << endl;
	for (int i = 0; i < 100; i++)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed :" << sz << "\n";
		}
	}
	return 0;
}

输出结果:

这个容量没有算上\0,从结果可以看出第一次扩容是2倍,往后都是1.5倍扩容。

刚开始的时候没有动态开辟数组,而是存到了string类中的一个buff数组中,数组大小是16,包含\0,如果内容小于16字节就存在buff数组中,大于16就存到ptr指向的空间上去。所以实际上并不是第一次扩容是2倍,而是第一次开空间的时候直接开到32,再往后以后都是1.5倍扩容。

image-20230622203552268

string类对象的结构其实就像下面代码一样:

class string{
private:
	char* _ptr;
	char _buf[16];
	size_t _size;
	size_t _capacity;
};

cout << sizeof(s) << endl;//28,所以string类里面存的就是上面列出来的。

所以可以认为VS编译器下string是1.5倍的扩容。

Linux g++下是2倍扩容:

image-20230622204058761

平台使用的STL的版本不一样,所以扩容规则也不同。

empty函数

  • 使用empty函数判断字符串是否为空
bool empty() const;

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
    string s("hello world"); 
    cout << s.empty() << endl; // true
}

clear函数

  • 使用clear函数清空有效字符,删除后对象变为空字符串
void clear();

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
    string s("hello world"); 
    cout << s.size() << endl; // 11
    cout << s.capacity() << endl;//15
    s.clear();
    cout << s.size() << endl; // 0 
    cout << s.capacity() << endl; //15
}

resize函数

  • 使用resize改变当前对象的有效字符的个数
void resize (size_t n);
void resize (size_t n, char c);

resize规则:扩容并且初始化

1.当n大于对象当前的size时,将size扩大到n,扩大的字符为c,若c未给出,则默认为’\0’。
2.当n小于对象当前的size时,将size缩小到n。

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello world");
	cout << s.size() << endl; // 11
	cout << s.capacity() << endl; //15
	cout << s << endl; //hello world
    
	//n小于对象当前的size时,将size缩小到n
	s.resize(5);
	cout << s.size() << endl;//5
	cout << s.capacity() << endl;//15
	cout << s << endl; //hello
    
	//n大于对象当前的size时,将size扩大到n,扩大的字符为c
	s.resize(12, 'x');
	cout << s.size() << endl;//12
	cout << s.capacity() << endl;//15
	cout << s << endl;//helloxxxxxxx
    
	//当n大于对象当前的size时,将size扩大到n,扩大的字符为c,若c未给出,则默认为’\0’
	s.resize(20);
	cout << s.size() << endl;//20
	cout << s.capacity() << endl;//31
	cout << s << endl;//helloxxxxxxx
}

注意:若给出的n大于对象当前的capacity,则capacity也会根据规则进行扩大。

reserve函数

  • 使用reserve改变当前对象的容量大小
void reserve (size_t n = 0);

reserve规则:
1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
 2、当n小于对象当前的capacity时,capacity不变。

reserve函数对字符串的size没有影响,并且无法更改其内容。可以通过reserve函数提前开空间,减少扩容,提高效率。

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello world");
	cout << s.size() << endl; // 11
	cout << s.capacity() << endl; //15

	//n小于对象当前的capacity时,capacity不变
	s.reserve(5);
	cout << s.size() << endl;//11
	cout << s.capacity() << endl;//15

	//n大于对象当前的capacity时,将capacity扩大到n或者大于n
	s.reserve(32);
	cout << s.size() << endl;//11
	cout << s.capacity() << endl;//47
}

shrink_to_fit函数

  • shrink_to_fit是缩容,将capacity缩小至和size一样(C++11)
void shrink_to_fit();

注意:

  • size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()
  • clear()只是将string中有效字符清空,不改变底层空间大小
  • resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时用’\0’来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变容量大小,如果是将元素个数减少,容量大小不变
  • reserve(size_t n = 0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的容量大小时,reserver不会改变容量大小。

string类对象的访问及遍历操作

函数名称功能说明
operator[pos]返回pos位置的字符,const string类对象调用
at(pos)返回pos位置的字符
begin+ endbegin获取第一个字符的迭代器 + end获取最后一个字符的下一个位置的迭代器
rbegin + rendrbegin获取最后一个字符的迭代器 + rend获取第一个字符的前一个位置的迭代器
范围forC++11支持更简洁的范围for的新遍历方式

operator[]重载运算符

  • operator[]

string类对[ ]运算符进行了重载,所以我们可以直接使用[ ]+下标访问对象中的元素。重载使用的引用返回,所以我们可以通过[ ]+下标修改对应位置的元素。

char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	const string s1;
	string s("hello world");
	for (int i = 0; i < s.size(); i++)
	{
		//[]+下标访问字符串中的元素
		cout << s[i];
	}
	cout << endl;
	for (int i = 0; i < s.size(); i++)
	{
		//修改字符串中对应位置的元素
		s[i] = 'x';
	}
	cout << s;
	cout << endl;
	return 0;
}

扩展:

成员函数后加const关键字后,我们称这个函数为常函数。

形式: void fun() const {};

特性:

  • 构造函数和析构函数不可以是常函数;

  • 可以使用数据成员,但是常函数内不可修改成员属性;

  • 常函数的this指针类型是const 类名* this,正常的成员函数this指针类型是类名* this。

实例化对象前加const称该对象为常对象。形式:const 类名 对象名;常对象只能调用常函数。

at函数

  • 使用at访问对象中的元素

at函数和[ ]+下标的作用相同,at函数使用的也是引用返回,所以我们也可以通过at函数修改对应位置的元素。

但是at和[]+下标的一个区别是:如果访问越界,[]+下标的方式会直接报错;而at是抛异常,异常可以被捕获。

char& at (size_t pos);
const char& at (size_t pos) const;

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	const string s1;
	string s("hello world");
	for (int i = 0; i < s.size(); i++)
	{
		//[]+下标访问字符串中的元素
		cout << s.at(i);
	}
	cout << endl;
	for (int i = 0; i < s.size(); i++)
	{
		//修改字符串中对应位置的元素
		s.at(i) = 'x';
	}
	cout << s;//xxxxxxxxxxx
	cout << endl;
	return 0;
}

迭代器

  • 使用迭代器访问对象中的元素

begin获取第一个字符的迭代器 + end获取最后一个字符下一个位置(即‘\0’)的迭代器:

iterator begin();
const_iterator begin() const;

iterator end();
const_iterator end() const;

rbegin获取最后一个字符的迭代器 + rend获取第一个字符的前一个位置的迭代器:

reverse_iterator rbegin();
const_reverse_iterator rbegin() const;

reverse_iterator rend();
const_reverse_iterator rend() const;

image-20230622191802734

迭代器区间是左闭右开,无论是正向迭代器还是反向迭代器遍历的时候都是通过++操作

使用正向迭代器begin和end访问修改元素:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello world");
	//使用正向迭代器访问元素
	string::iterator it1 = s.begin();
	while (it1 != s.end())
	{
		cout << *it1;
		it1++;
	}
	cout << endl;

	//使用正向迭代器访问元素并对其进行修改
	string::iterator it2 = s.begin();
	while (it2 != s.end())
	{
		*it2 += 1;
		it2++;
	}
	cout << s;//ifmmp!xpsme
	cout << endl;
	return 0;
}

使用反向迭代器begin和end访问修改元素:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello world");
	//使用反向迭代器访问元素
	string::reverse_iterator rit1 = s.rbegin();
	while (rit1 != s.rend())
	{
		cout << *rit1;
		rit1++;
	}
    //输出 dlrow olleh
	cout << endl;

	//使用反向迭代器访问元素并对其进行修改
	string::reverse_iterator rit2 = s.rbegin();
	while (rit2 != s.rend())
	{
		*rit2 += 1;
		rit2++;
	}
	cout << s;//ifmmp!xpsme
	cout << endl;
	return 0;
}

当类对象为const对象的时候我们可以使用const迭代器:

const类型的迭代器不能修改类对象的内容,即不能通过*迭代器方式修改字符串内容,但是可以修改迭代器本身(迭代器++)。

const_iterator begin() const;
const_iterator end() const;

const_reverse_iterator rbegin() const;
const_reverse_iterator rend() const;

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	//使用const正向迭代器访问元素
	const string s("hello world");
	string::const_iterator it1 = s.begin();
	while (it1 != s.end())
	{
		cout << *it1;
		it1++;
	}
	//输出 hello world
	cout << endl;
	//使用const反向迭代器访问元素
	string::const_reverse_iterator rit1 = s.rbegin();
	while (rit1 != s.rend())
	{
		cout << *rit1;
		rit1++;
	}
	//输出 dlrow olleh
	cout << endl;
	return 0;
}

范围for

  • 使用范围for访问对象中的元素

若是需要通过范围for修改对象的元素,则用于接收元素的变量类型必须是引用类型,否则只是对象元素的拷贝,对变量的修改不会影响到对象的元素。

范围for的使用是建立在迭代器的基础上的,当迭代器发生错误范围for也将不能再使用。

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello world");
	for (auto e : s)
	{
		cout << e;
	}
	//输出 hello world
	cout << endl;

	//变量类型使用引用类型
	for (auto& e : s)
	{
		e++;
		cout << e;
	}
	//输出 ifmmp!xpsme
	cout << endl;
	return 0;
}

string类对象的修改操作

函数名称功能说明
push_back在字符串后尾插字符c
pop_back(C++11)尾删
append在字符串后追加一个字符串
operator+=在字符串后追加字符或者字符串
c_str返回C格式字符串
find + npos从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
find_first_of从字符串第一个位置开始向后查找与字符串中相同的字符
find_last_of从字符串最后一个位置开始向前查找与字符串中相同的字符
substr在str中从pos位置开始,截取n个字符,然后将其返回
insert在字符串pos位置插入字符或字符串
swap交换两个字符串的内容
erase指定位置删除字符或字符串
replace从pos开始长度为len的内容替换成指定内容
copy将string的子字符串复制到字符数组中

push_back函数

  • 使用push_back进行尾插
void push_back (char c);

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello");
	cout << s << endl;//hello
	s.push_back(' ');
	s.push_back('w');
	s.push_back('o');
	s.push_back('r');
	s.push_back('l');
	s.push_back('d');
	cout << s << endl;//hello world
	return 0;
}

pop_back函数

  • 尾删
void pop_back();

示例:

#include <iostream>
#include <string>
using namespace std;
int main ()
{
  string s("hello world!");
  str.pop_back();
  cout << s << endl;//输出hello world
  return 0;
}

append函数

  • 使用append进行追加
//在原字符串后面追加字符串str
string& append (const string& str);
//在原字符串后面追加C字符串s
string& append (const char* s);
//在原字符串后面追加n个字符s
string& append (size_t n, char c);

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello");
	cout << s << endl;//hello
	string s1("world");
	在原字符串后面追加字符串s1
	s.append(s1);
	cout << s << endl;//helloworld

	//在原字符串后面追加C字符串c
	const char* c = "xxx";
	s.append(c);
	//s.append("xxx");
	cout << s << endl;//helloworldxxx

	//在原字符串后面追加n个字符s
	s.append(2,'4');
	cout << s << endl;//helloworldxxx44

	return 0;
}

operator+=重载运算符

  • 使用operator+=在字符串后追加字符或者字符串
//在原字符串后面追加字符串str
string& operator+= (const string& str);
//在原字符串后面追加C字符串s
string& operator+= (const char* s);
//在原字符串后面追加字符s
string& operator+= (char c);

示例:

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello");
	cout << s << endl;//hello
	string s1("world");
	在原字符串后面追加字符串s1
	s += s1;
	cout << s << endl;//helloworld

	//在原字符串后面追加C字符串c
	const char* c = "xxx";
	s +=c;
	//s += "xxx";
	cout << s << endl;//helloworldxxx

	//在原字符串后面追加n个字符s
	s += '4';
	cout << s << endl;//helloworldxxx4

	return 0;
}

c_str函数

  • 返回C格式字符串
const char* c_str() const;

c_str()返回const char* 类型,返回的字符串会以空字符结尾。

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello world");
	//作为自定义类型,调用的是重载之后的流插入
	cout << s << endl;//hello world

	//c_str返回的是const char*类型,是指针,为内置类型
	//内置类型调用的是库里面的流插入,char*类型按照C字符串去打印
	cout << s.c_str() << endl;//hello world
}

两者的区别是什么呢?如果我们按照重载流插入去打印,是按照string类的size大小去打印的;但是当转换成C字符串之后使用库里的流插入时,遇见\0就停止了。

#include <string>
#include <iostream>
using namespace std;
int main()
{
	string s("hello world");
	s += '\0';
	s += '\0';
	s += '\0';
	s += '6';
	s += '6';
	s += '6';
	cout << s << endl;//hello world666

	cout << s.c_str() << endl;//hello world
}

data函数和c_str函数作用相同,用法也一致。

find函数

  • 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置,如果找到了返回第一个字符匹配出现的位置,如果没有找到返回npos(因为字符串不会有npos那么长,所以不用担心比字符串npos大)
//从pos位置向后找与str匹配的第一个位置
size_t find (const string& str, size_t pos = 0) const;
//从pos位置向后找与s匹配的第一个位置
size_t find (const char* s, size_t pos = 0) const;
//从pos位置向后找与字符c匹配的第一个位置
size_t find (char c, size_t pos = 0) const;

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("http://www.cplusplus.com/reference/string/string/find/");

	//find (const string& str, size_t pos = 0)正向搜索与string对象所匹配的第一个位置
	string s2("www");
	//从pos(pos缺省值为0)开始在s1中搜索与s2对象所匹配的第一个位置
	cout << s1.find(s2) << endl; //7

	// find (const char* s, size_t pos = 0)正向搜索与字符串s所匹配的第一个位置
	const char* s = "cplusplus.com";
	//从pos(pos缺省值为0)开始在s1中搜索与s对象所匹配的第一个位置;
	cout << s1.find(s) << endl;  //11

	// find (char c, size_t pos = 0)正向搜索与字符char所匹配的第一个位置
	cout << s1.find(':') << endl; //4
	return 0;
}

rfind函数

  • rfind函数和find函数作用类似,但是rfind是反向搜索第一个匹配项,如果找到了返回第一个字符匹配出现的位置(从首字符开始向后所在位置),如果没有找到返回npos(因为字符串不会有npos那么长,所以不用担心比字符串npos大)
//从npos位置开始向前搜索字符串str,返回第一次出现的位置
size_t rfind (const string& str, size_t pos = npos) const;
//从npos位置开始向前搜索字符串s,返回第一次出现的位置
size_t rfind (const char* s, size_t pos = npos) const;
//从npos位置开始向前搜索字符c,返回第一次出现的位置
size_t rfind (char c, size_t pos = npos) const;

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("http://www.cplusplus.com/reference/string/string/find/");

	//find (const string& str, size_t pos = 0)正向搜索与string对象所匹配的第一个位置
	string s2("www");
	//从pos(pos缺省值为0)开始在s1中搜索与s2对象所匹配的第一个位置
	cout << s1.rfind(s2) << endl; //7

	// find (const char* s, size_t pos = 0)正向搜索与字符串s所匹配的第一个位置
	const char* s = "string";
	//从pos(pos缺省值为0)开始在s1中搜索与s对象所匹配的第一个位置;
	cout << s1.rfind(s) << endl;  //42

	// find (char c, size_t pos = 0)正向搜索与字符char所匹配的第一个位置
	cout << s1.rfind(':') << endl; //4
	return 0;
}

find_first_of函数

  • 从字符串首个位置开始向后查找与字符串中相同的字符,只要有相同字符就返回,和子串区分开;查找成功返回匹配位置,不同返回npos
//从字符串首个位置开始向后查找与字符串str中相同的字符
size_type find_first_of (const basic_string& str, size_type pos = 0) const;
//从字符串首个位置开始向后查找与字符串s中相同的字符
size_type find_first_of (const charT* s, size_type pos = 0) const;
//从字符串pos位置开始向后查找与字符串s中相同的字符
size_type find_first_of (const charT* s, size_type pos, size_type n) const;
//从字符串首个位置开始向后查找与字符c相同的坐标并返回
size_type find_first_of (charT c, size_type pos = 0) const;

示例1:

//从字符串首个位置开始向后查找与字符串str中相同的字符
size_type find_first_of (const basic_string& str, size_type pos = 0) const;

代码:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello world");
	string s2("hl");
	//在s1字符串中查找与s2字符串中匹配的字符
	//成功返回下标位置,失败返回npos
	size_t pos1 = s1.find_first_of(s2);
	while (pos1 != string::npos)
	{
		//将pos1位置处对应的字符替换成*
		s1[pos1] = '*';
		//从pos+1位置开始向后查找
		pos1 = s1.find_first_of(s2, pos1 + 1);
	}
	cout << s1 << endl; //*e**o wor*d
}

示例2:

//从字符串首个位置开始向后查找与字符串s中相同的字符
size_type find_first_of (const charT* s, size_type pos = 0) const;
//从字符串首个位置开始向后查找与字符c相同的坐标并返回
size_type find_first_of (charT c, size_type pos = 0) const;

代码:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello world");
	const char* s = "hl";
	size_t pos1 = s1.find_first_of(s);
	while (pos1 != string::npos)
	{
		//将pos1位置处对应的字符替换成*
		s1[pos1] = '*';
		//从pos+1位置开始向后查找
		pos1 = s1.find_first_of(s, pos1 + 1);
	}
	cout << s1 << endl; //*e**o wor*d
    //查找与字符e相同的字符并返回下标
    size_t pos2 = s1.find_first_of('e');
	cout << pos2 << endl;//1
}

find_last_of函数

从字符串最后一个位置开始向前查找与字符串中相同的字符

//从字符串最后位置开始向前查找与字符串str中相同的字符
size_type find_last_of (const basic_string& str, size_type pos = npos) const;
//从字符串最后一个位置开始向前查找与字符串s中相同的字符
size_type find_last_of (const charT* s, size_type pos = npos) const;
//从字符串pos位置开始向前查找与字符串s中相同的字符
size_type find_last_of (const charT* s, size_type pos, size_type n) const;
//从字符串最后一个位置开始向前查找与字符c相同的坐标并返回
size_type find_last_of (charT c, size_type pos = npos) const;

示例1:

//从字符串最后位置开始向前查找与字符串str中相同的字符
size_type find_last_of (const basic_string& str, size_type pos = npos) const;

代码:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello world");
	string s2("hl");
	//在s1字符串中查找与s2字符串中匹配的字符
	//成功返回下标位置,失败返回npos
	size_t pos1 = s1.find_last_of(s2);
	while (pos1 != string::npos)
	{
		//将pos1位置处对应的字符替换成*
		s1[pos1] = '*';
		//从pos+1位置开始向前查找
		pos1 = s1.find_last_of(s2, pos1 + 1);
	}
	cout << s1 << endl; //*e**o wor*d
}

示例2:

//从字符串pos位置开始向前查找与字符串s中相同的字符
size_type find_last_of (const charT* s, size_type pos, size_type n) const;
//从字符串最后一个位置开始向前查找与字符c相同的坐标并返回
size_type find_last_of (charT c, size_type pos = npos) const;

代码:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello world");
	const char* s = "hl";
	size_t pos1 = s1.find_last_of(s);
	while (pos1 != string::npos)
	{
		//将pos1位置处对应的字符替换成*
		s1[pos1] = '*';
		//从pos+1位置开始向后查找
		pos1 = s1.find_last_of(s, pos1 + 1);
	}
	cout << s1 << endl; //*e**o wor*d
    //查找与字符e相同的字符并返回下标
    size_t pos2 = s1.find_last_of('e');
	cout << pos2 << endl;//1
}

substr函数

  • 获取字符串中位置pos开始的长度为len的子串,返回值类型为string
string substr (size_t pos = 0, size_t len = npos) const;

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello world");
	string s2 = s1.substr(6, 5);
	cout << s2 << endl;//world
}

insert函数

  • 在字符串pos位置插入字符或字符串
//在pos位置插入字符串str
string& insert (size_t pos, const string& str);
//在pos位置插入字符串s
string& insert (size_t pos, const char* s);
//参数是迭代器,在迭代器指定的位置插入字符c
iterator insert (iterator p, char c);

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello");
	string s2("every");
	//插入字符串s2
	s1.insert(5,s2);
	cout << s1 << endl;//helloevery
    
	//find和insert配合,插入字符
	s1.insert(s1.find('y') + 1, "body");
	cout << s1 << endl;//helloeverybody
    
	//使用迭代器在字符串的后面插入字符
	s1.insert(s1.end(), '!');
	cout << s1 << endl;//helloeverybody!
}

swap函数

  • 使用string类中提供的swap函数完成两个string字符串的交换

底层是指针,改变的是指针的指向和size的值。

void swap (string& str);

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello");
	string s2("every");
	cout << s1 << endl;//hello
	cout << s2 << endl;//every
	s1.swap(s2);
	cout << s1 << endl;//every
	cout << s2 << endl;//hello
}

string类中提供了swap函数,算法库(头文件为algorithm)中也提供了一个swap函数:

void swap (string& x, string& y);

示例:

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
	string s1("hello");
	string s2("every");
	cout << s1 << endl;//hello
	cout << s2 << endl;//every
	swap(s1,s2);//使用算法库中的swap函数
	cout << s1 << endl;//every
	cout << s2 << endl;//hello
}

两者的区别是string类中提供的只能针对string类型,算法库中的swap函数无论什么类型都可以使用。

对于string对象来说string提供的swap函数更高效,直接改string指针的指向就可以了;库里面提供的函数交换时会产生一个临时对象,拷过去再赋值。

erase函数

  • 指定位置删除字符或字符串
//从位置pos开始删除len长度
string& erase (size_t pos = 0, size_t len = npos);
//删除迭代器指向位置的字符
iterator erase (iterator p);
//删除迭代器first开始到last-1之间的字符,迭代器是左闭右开,不包括last位置的字符
iterator erase (iterator first, iterator last);

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello everybody");
	//删除从0开始的6个字符
	s1.erase(0, 6);
	cout << s1 << endl;//everybody
	//删除迭代器指向位置的字符e
	s1.erase(s1.begin());
	cout << s1 << endl;//verybody
	s1.erase(s1.begin(),s1.end());
	cout << s1 << endl;//空字符串
}

replace函数

  • 从pos位置开始长度为len的内容替换成指定内容
//将从pos位置开始的长度为len的内容替换为字符串s
string& replace (size_t pos, size_t len, const char* s);
//将从pos位置开始的长度为len的内容替换为n个字符c
string& replace (size_t pos, size_t len, size_t n, char c);

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello everybody");
	s1.replace(6, 9, "world");
	cout << s1 << endl;//hello world
	s1.replace(0, 5, 4, 'I');//IIII world
	cout << s1 << endl;
}

copy函数

  • 将string的子字符串复制到字符数组中,返回拷贝字符长度
size_t copy (char* s, size_t len, size_t pos = 0) const;

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello everybody");
	char s[20];
	//将s1中从位置0开始长度为5的字符内容拷贝到字符数组s中
	size_t length = s1.copy(s, 5, 0);
	//copy函数仅仅只是拷贝
	//并不会在字符数组结尾处加\0,需要自己手动加
	s[length] = '\0';
	cout << s << endl;
}

string类的非成员函数

函数名称功能说明
operator+在字符串后添加指定字符或者字符串
operator>>输入运算符重载
operator<<输出运算符重载
getline获取一行字符串
relational operators大小比较

operator+

  • 在字符串后添加指定字符或者字符串

尽量少用,因为传值返回,导致深拷贝效率低

//两个参数是两个字符串类对象
string operator+ (const string& lhs, const string& rhs);
//两个参数一个是字符串类对象,一个是c字符串,交换位置也可以使用
string operator+ (const string& lhs, const char*   rhs);
string operator+ (const char*   lhs, const string& rhs);	
//两个参数一个是字符串类对象,一个是c字符,交换位置也可以使用
string operator+ (const string& lhs, char rhs);
string operator+ (char lhs, const string& rhs);

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello");
	string s2("world");
	const char* s3 = "everybody";
	//两个参数是string类型
	s1 = s1 + s2;
	cout << s1 << endl;//helloworld
	//参数一个是C字符串一个是string类型,和操作数位置无关
	s1 = s3 + s1;
	//s1 = s1 + s3;
	cout << s1 << endl;//everybodyhelloworld
	//参数一个是C字符一个是string类型,和操作数位置无关
	s1 = s1 + 'x';
	//s1 = 'x' + s1;
	cout << s1 << endl;//everybodyhelloworldx
}

operator>>和operator<<

istream& operator>> (istream& is, string& str);//重载流提取
ostream& operator<< (ostream& os, const string& str);//重载流插入

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello");
	string s2;
    //支持连续输入和输出,输入会覆盖之前的内容
	cin >> s1 >> s2;//输入hello world

	cout << s1 << " " << s2;//输出hello world
}

getline函数

  • 使用cin>>输入的时候遇到空格便会停止读取,所以当我们需要输入带有空格的字符串时可以使用getline函数,getline函数只有在遇到\n的时候才会停止读取。
//当读取到换行符\n时停止读取
istream& getline (istream& is, string& str);
//可以自己设置分隔符delim
istream& getline (istream& is, string& str, char delim);

示例:

使用>>:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1;
	//使用>>
	cin >> s1;//输入hello world
	cout << s1;//输出hello
}

使用getline函数:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1;
	//使用getline函数,当遇到换行时停止读取
	getline(cin, s1);//输入hello world
	cout << s1 << endl;//输出hello world
	//设置分隔符为!,当输入!或者换行时停止读取
	getline(cin, s1, '!');//输入hello world! nihao
	cout << s1 << endl;//输出hello world
}

输入的时候cin>>通过换行或者空格来区分多个值;当输入有空格的字符串时,最好使用getline,getline遇到换行结束。

关系运算符

因为string类中重载了大量的关系运算符relational operators,所以我们平时不用string中的compare函数比较。

//==重载运算符
bool operator== (const string& lhs, const string& rhs);
bool operator== (const char*   lhs, const string& rhs);
bool operator== (const string& lhs, const char*   rhs);
//!=重载运算符
bool operator!= (const string& lhs, const string& rhs);
bool operator!= (const char*   lhs, const string& rhs);
bool operator!= (const string& lhs, const char*   rhs);
// < 重载运算符
bool operator<  (const string& lhs, const string& rhs);
bool operator<  (const char*   lhs, const string& rhs);
bool operator<  (const string& lhs, const char*   rhs);
//<=重载运算符
bool operator<= (const string& lhs, const string& rhs);
bool operator<= (const char*   lhs, const string& rhs);
bool operator<= (const string& lhs, const char*   rhs);
// > 重载运算符
bool operator>  (const string& lhs, const string& rhs);
bool operator>  (const char*   lhs, const string& rhs);
bool operator>  (const string& lhs, const char*   rhs);
//>=重载运算符
bool operator>= (const string& lhs, const string& rhs);
bool operator>= (const char*   lhs, const string& rhs);
bool operator>= (const string& lhs, const char*   rhs);

示例:

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1("hello world");
	string s2("hello");
	char s3[] = "str";
    const char* s4 = "why";
    //上面四个字符串只要保证运算符中有一个string类即都可使用重载运算符
	cout << (s3 == s2) << endl;//0
	cout << (s1 != s2) << endl;//1
	cout << (s1 > s2) << endl;//1
	cout << (s1 >= s2) << endl;//1
	//……此处省略不一一列举
}
  • 32
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 31
    评论
评论 31
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不 良

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值