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

news2024/10/6 1:37:08

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


文章目录

  • STL简介
    • STL的版本
    • STL的六大组件
    • STL的缺陷
  • 标准库中的string类
    • string类介绍
    • string类对象的常见构造
    • string类对象容量操作
      • size/length函数
      • max_size函数
      • capacity函数
      • empty函数
      • clear函数
      • resize函数
      • reserve函数
      • shrink_to_fit函数
    • string类对象的访问及遍历操作
      • operator[]重载运算符
      • at函数
      • 迭代器
      • 范围for
    • string类对象的修改操作
      • push_back函数
      • pop_back函数
      • append函数
      • operator+=重载运算符
      • c_str函数
      • find函数
      • rfind函数
      • find_first_of函数
      • find_last_of函数
      • substr函数
      • insert函数
      • swap函数
      • erase函数
      • replace函数
      • copy函数
    • string类的非成员函数
      • operator+
      • operator>>和operator<<
      • getline函数
      • 关系运算符

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
	//……此处省略不一一列举
}

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

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

相关文章

混合策略改进的哈里斯鹰优化算法-附代码

混合策略改进的哈里斯鹰优化算法 文章目录 混合策略改进的哈里斯鹰优化算法1.哈里斯鹰优化算法2.改进哈里斯鹰优化算法2.1 初始化种群的改进2.1.1 初始种群多样化2.1.2 初始种群精英化 2.2 逃逸能量递减机制的改进2.4 拉普拉斯交叉算子策略 3.实验结果4.参考文献5.Matlab代码6.…

6.17 、Java初级:锁

1 同步锁 1.1 前言 经过前面多线程编程的学习,我们遇到了线程安全的相关问题,比如多线程售票情景下的超卖/重卖现象. 上节笔记点这里-进程与线程笔记 我们如何判断程序有没有可能出现线程安全问题,主要有以下三个条件: 在多线程程序中 有共享数据 多条语句操作共享数据 多…

移动web-渐变

渐变 使用场景&#xff1a;使用background-image属性实现渐变背景效果 代码&#xff1a;background-image: linear-gradient(参数1,参数2,参数3...); (默认的方位从上到下) 参数1 方位名词: to right, to left 角度deg: 直接写度数 参数2 颜色1 参数3 颜色2... 注意&#xff…

看完这篇 教你玩转渗透测试靶机vulnhub—Corrosion:1

Vulnhub靶机Corrosion:1渗透测试详解 Vulnhub靶机介绍&#xff1a;Vulnhub靶机下载&#xff1a;Vulnhub靶机安装&#xff1a;Vulnhub靶机漏洞详解&#xff1a;①&#xff1a;信息收集&#xff1a; Vulnhub靶机渗透总结&#xff1a; Vulnhub靶机介绍&#xff1a; vulnhub是个提…

canvas详解01-绘制基本图形

既然我们已经设置了 canvas 环境&#xff0c;我们可以深入了解如何在 canvas 上绘制。到本文的最后&#xff0c;你将学会如何绘制矩形&#xff0c;三角形&#xff0c;直线&#xff0c;圆弧和曲线&#xff0c;变得熟悉这些基本的形状。绘制物体到 Canvas 前&#xff0c;需掌握路…

软件工程——第5章总体设计知识点整理

本专栏是博主个人笔记&#xff0c;主要目的是利用碎片化的时间来记忆软工知识点&#xff0c;特此声明&#xff01; 文章目录 1.总体设计的基本目的&#xff1f; 2.总体设计的任务&#xff1f; 3.总体设计过程由哪两个阶段组成&#xff1f; 4.总体设计的步骤&#xff1f; 5…

【Linux从入门到精通】进程地址空间(虚拟地址 vs 物理地址)

本篇文章会围绕三个问题&#xff08;什么是地址空间&#xff1f;地址空间是如何设计的&#xff1f;为什么要有地址空间&#xff1f;&#xff09;进行展开讲述。其中主要是了解虚拟地址和物理地址的区别。希望本篇文章会对你有所帮助。 文章目录 一、什么是地址空间&#xff1f;…

《机器学习公式推导与代码实现》chapter6-k近邻算法

《机器学习公式推导与代码实现》学习笔记&#xff0c;记录一下自己的学习过程&#xff0c;详细的内容请大家购买作者的书籍查阅。 k近邻算法 k近邻(k-nearest neighbor, k-NN)算法是一种经典的分类算法。k近邻算法根据新的输入实例的k个最近邻实例的类别来决定其分类。所以k近…

rust abc(1): 最小环境搭建

文章目录 1. 目的2. 命令集合3. 安装或更新 rust3.1 命令3.2 运行结果 4. 包管理工具 Cargo5. 创建 Rust 的 Hello World 程序: 单个文件6. 创建 Rust 的 Hello World 工程&#xff1a; 基于 Cargo6.1 cargo new 创建工程6.2 cargo run6.3 完整输出6.4 解释 7. IDE/编辑器8. Re…

Jetson安装Anaconda(miniforge3)

1 miniforge3 miniforge集成了Anaconda的核心工具&#xff1a;conda。conda是一个包和环境管理工具。因此&#xff0c; miniforge里面的conda和Anaconda里面的conda完全一样&#xff1b;你能用Anaconda做的安装、升级、删除包等功能&#xff0c;miniforge都能做&#xff1b;你…

angular实现自定义模块路由懒加载;配置自定义模块路由及子路由

图片中绿色表示新建的文件;黄色表示被更改的文件; 1、创建一个新的项目 ng new angularlazyload2、创建一个用户模块,并配置路由 ng g module module/user --routing如图: 3 、在module/模块下创建user组件 ng g component module/user如图: 4、实现路由懒加载 依次…

java00——类和对象

在Java中一切皆对象 什么是对象&#xff1f; 一个人、一只猫、一条狗…这些就是一个对象&#xff1b; 每个对象都有属性和行为。 什么是类&#xff1f; 类即同类别&#xff0c;例如不论男人、女人、黑人、白人…&#xff0c;都是人类&#xff0c;即同一类事务的统称。 类的…

HTB-Sandworm

HTB-Sandworm 立足altas -> silentobserversilentobserver -> 完整的atalsatlas -> rootexploit 扫描最常用的1000个端口。 80会重定向到443。 去看看443有什么吧。 目录扫描可能不会起作用。在concat上面找到了一个有趣的东西。 “如果不知道怎么PGP&#xff1…

Axure教程—折叠面板

本文介绍利用Axure中的动态面板制作折叠面板 一、效果 预览地址&#xff1a;https://3k8az1.axshare.com 二、功能 1、点击标题展开面板内容 2、点击标题折叠面板 三、制作 从默认元件库拖入一个动态面板&#xff0c;设置两个状态&#xff0c;一个状态面板标题&#xff0c;一…

【MySQL】不允许你还不了解创建计算字段

&#x1f3ac; 博客主页&#xff1a;博主链接 &#x1f3a5; 本文由 M malloc 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f384; 学习专栏推荐&#xff1a;LeetCode刷题集&#xff01; &#x1f3c5; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指…

【开源与项目实战:开源实战】82 | 开源实战三(中):剖析Google Guava中用到的几种设计模式

上一节课&#xff0c;我们通过 Google Guava 这样一个优秀的开源类库&#xff0c;讲解了如何在业务开发中&#xff0c;发现跟业务无关、可以复用的通用功能模块&#xff0c;并将它们从业务代码中抽离出来&#xff0c;设计开发成独立的类库、框架或功能组件。 今天&#xff0c;…

【后端】使用TS编写任务管理系统----Express

文章目录 常见的后端框架安装并且声明文件库项目基本配置编写任务管理后端API添加任务查看任务设置任务完成状态删除任务 总结 node -v v16.13.0https://github.com/dL-hx/server-side 常见的后端框架 expresskoa… 安装并且声明文件库 $ npm i express $ npm i types/exp…

前端vue入门(纯代码)14

内容创作不易&#xff0c;各位帅哥美女&#xff0c;求个小小的赞&#xff01;&#xff01;&#xff01; 【15.给todoList案例添加编辑按钮】 本篇内容在TodoList案例的基础上添加个编辑按钮&#xff0c;要求&#xff1a; &#xff08;1&#xff09;点击编辑按钮后&#xff0c…

轻松学会研华屏幕下载和上传

&#x1f525;一个人走得远了&#xff0c;就会忘记自己为了什么而出发&#xff0c;希望你可以不忘初心&#xff0c;不要随波逐流&#xff0c;一直走下去&#x1f3b6; &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e; ✅ 如果觉得博主…