【C++之STL】一文学会使用 string

文章目录

  • [1. STL导读](#1. STL导读)
    • [1. 1 什么是STL](#1. 1 什么是STL)
    • [1. 2 STL的版本](#1. 2 STL的版本)
    • [1. 3 STL六大组件](#1. 3 STL六大组件)
    • [1. 4 STL的重要性](#1. 4 STL的重要性)
    • [1. 5 STL的学习](#1. 5 STL的学习)
    • [1. 6 STL系列博客的规划](#1. 6 STL系列博客的规划)
  • [2. string](#2. string)
    • [2. 1 为什么学习string类?](#2. 1 为什么学习string类?)
    • [2. 2 标准库中的string](#2. 2 标准库中的string)
    • [2. 3 基本构造](#2. 3 基本构造)
    • [2. 4 尾插与输出运算符重载](#2. 4 尾插与输出运算符重载)
    • [2. 5 构造函数](#2. 5 构造函数)
    • [2. 6 赋值运算符重载](#2. 6 赋值运算符重载)
    • [2. 7 容量操作](#2. 7 容量操作)
    • [2. 8 元素访问](#2. 8 元素访问)
    • [2. 9 迭代器](#2. 9 迭代器)
    • [2. 10 修改](#2. 10 修改)
    • [2. 11 字符串操作](#2. 11 字符串操作)
    • [2. 12 不在类中的函数重载](#2. 12 不在类中的函数重载)
  • [3. 尾声](#3. 尾声)

1. STL导读

1. 1 什么是STL

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

1. 2 STL的版本

  1. 原始版本
    Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商业使用这些代码,无需付费。唯一的条件就是也需要向原始版本一样做开源使用。 HP 版本--所有STL实现版本的始祖。
  2. P.J. 版本
    由P.J.Plauger开发,继承自HP版本,被Windows Visual C++采用,不能公开或修改,缺陷:可读性比较低,符号命名比较怪异。
  3. RW版本
    由Rouge Wage公司开发,继承自HP版本,被C++ Builder 采用,不能公开或修改,可读性一般。
  4. SGI版本
    由Silicon Graphics Computer systems,inc公司开发,继承自HP版本。被GCC(Linux,devc++等)采用,可移植性好,可公开、修改甚至贩卖,从命名风格和编程风格上看,阅读性非常高。学习STL时需要阅读的源代码,主要参考的就是这个版本。

1. 3 STL六大组件

顺带一提:string并不是STL标准所规定的容器,实际上string的出现比STL早,可以说是STL容器标准制作的探路石,因此相对其它容器,string的接口显得较为臃肿,命名风格较为奇特,但这并不妨碍它的强大。

1. 4 STL的重要性

C++入门基础介绍(上)这篇博客中我提到过,C++的第一个正式版本的颁布曾因为STL的出现而延误:

C++的标准化工作于1989年开始,并成立了一个ANSI和IS0(International

StandardsOrganization)国际标准化组织的联合标准化委员会。1994年标准化委员会提出了第一个标准化草案。在该草案中,委员会在保持斯特劳斯特卢普最初定义的所有特征的时,还增加了部分新特征。

在完成C++标准化的第一个草案后不久,STL(Standard Template Library)诞生了,STL是惠普实验室开发的一系软件的统称。它是由Alexander Stepanov、MengLee和David RMusser在惠普实验室工作时所开发出来的。在通过了标准化第一个草案之后,联合标准化委员会又投票并通过了将STL包含到C++标准中的提议。STL对C++的扩展超出C++的最初定义范围。虽然在标准中增加STL是个很重要的决定,但也因此延缓了C++标准化的进程。

1997年11月14日,联合标准化委员会通过了该标准的最终草案。1998年,C++的ANSI/IS0标准被投入使用。

接近三年的等待,足以让任何人认识到它的重要性。

事实上也确实如此,且功利地说一说,在任何招聘C++开发岗位的笔试与面试的题目中,STL永远不会缺席,而在实际的开发中,STL也是贯穿整个C++项目的根脉

网上有句话说:"不懂STL,不要说你会C++"。STL是C++中的优秀作品,有了它,许多底层的数据结构以及算法都不需要自己重新造轮子,只有站在前人的肩膀上,才能健步如飞的快速开发。

1. 5 STL的学习

以下来自侯捷老师的《STL源码剖析》:

再功利地说:在找工作之前,一定要达到第二境界,而在工作中慢慢地到达第三境界。

扩充STL不是任何人可以直接传授的道理,而是一种慢慢沉淀出来的经验,作为初学者,不要盲目地尝试,因为那大概率只是浪费时间。

1. 6 STL系列博客的规划

STL是一系列十分标准,接口命名与使用都十分规范的标准库,在本系列博客中,将以容器和配接器的使用与模拟实现为主线,在其中穿插算法,仿函数以及迭代器。

容器的接口使用其实是十分相似的,因此本系列博客会着重于容器的模拟实现,仅在string时会详细介绍几乎所有接口,其它的容器只会介绍与string差异较大的接口。

2. string

2. 1 为什么学习string类?

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

string------对字符串的封装可以很好地解决这一问题,接口会自动向字符串的尾部添加'\0',不用自己操心'\0',也不需要担心越界访问(一般情况下),这使得无论是在OJ题还是实际开发中,都很少有人会使用C语言的字符串。

2. 2 标准库中的string

string类被包含在<string>的头文件中,同时也在std这个命名空间中。

2. 3 基本构造

string 类的成员其实和数据结构初阶中的顺序表是基本一样的,都是一个数组(只不过在 string 类中固定为char了),有一个指针,_size变量存储 string 中目前的数据个数,_capacity存储 string 中指针的容量。

cpp 复制代码
class string
{
private:
    char* _str;
    size_t _capacity;
    size_t _size;
};

2. 4 尾插与输出运算符重载

为了方便后文展示构造函数方便,先把这两个接口讲解一下。

  1. 尾插
    在STL库中,除了栈和队列这样的只能尾插(或者说是入队列和入栈)的容器之外,所有容器的尾插都是以push_back命名的,在 string 中就是:
cpp 复制代码
void push_back (char c);
  1. 输出运算符(>>)重载
cpp 复制代码
ostream& operator<< (ostream& os, const string& str);

string 并不是字符串,所以正常情况下不能直接通过输出运算符输出,但 string 类中对输出运算符进行了重载,使得其可以直接进行输出。

使用范例:

cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int main()
{
	string s;	//创建一个空的string实例化对象
	s.push_back('a');
	s.push_back('b');
	s.push_back('c');
	s.push_back('d');
	s.push_back('e');
	s.push_back('f');
	s.push_back('g');
	cout << s;
	return 0;
}

输出:

2. 5 构造函数

库函数的容器都提供了非常多种构造函数以让使用更方便,而在C++98中 string 提供了七种初始化方式,在C++11中扩充至九种,但多出来的两种,其中一种涉及右值引用,本文不作讲解,另一种这里只介绍用法不介绍原理。

  1. default (常用)
cpp 复制代码
string();

不传任何参数的构造函数,理所应当的,创建的是一个没有存储任何数据的空 string。

用法:

cpp 复制代码
#include<string>
using namespace std;
int main()
{
	string s;
	return 0;
}

这个 string 实例化对象 s 中的_size是0,但其容量不一定是零。

  1. copy (常用)
cpp 复制代码
string (const string& str);

拷贝构造,借助另一个 string 的实例化对象,拷贝构造出一个新的实例化对象。

cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int  main()
{
	string s;
	s.push_back('a');
	s.push_back('b');
	s.push_back('c');
	cout << s << endl;
	string s1(s);
	cout << s << endl;
	string s2 = s;
	cout << s2 << endl;
	return 0;
}

s1和s2在构造时都会以s为数据来源进行拷贝构造。

输出:

  1. from c-string(常用)
cpp 复制代码
string (const char* s);

从字符串中拷贝数据进行构造。

指针 s 指向的是一个字符串,无论是常量字符串还是字符数组,都必须以'\0'结尾才能保证正常构造。

cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int main()
{
	char str[] = "Hello World!";
	string s(str);
	cout << str << endl << s << endl;
	return 0;
}

输出:

  1. substring
cpp 复制代码
string (const string& str, size_t pos, size_t len = npos);

从 str 这个实例化对象中从 pos 位置(下标)开始拷贝走 len 个数据。

cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int main()
{
	string s("Hello World!");
	string s1(s, 1, 6);
	cout << s1;
	return 0;
}

运行结果:

  1. from buffer
cpp 复制代码
string (const char* s, size_t n);

根据字符串 s 的前 n 位构造出一个 string 实例化对象。

使用:

cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int main()
{
	char str[] = "Hello World!";
	string s(str, 5);
	cout << s << endl;
	return 0;
}

输出:

  1. fill (常用)
cpp 复制代码
string (size_t n, char c);

构造出存储了 n 个元素 c 的实例化对象。

cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int main()
{
	string s(10, 'a');
	cout << s;
	return 0;
}

输出:

  1. range (常用)
cpp 复制代码
template <class InputIterator>
	string  (InputIterator first, InputIterator last);

实际上是使用迭代器来构造对象。

迭代器是STL中一个十分重要的组件,对于 string 来说,它的迭代器就是对指针的封装,需要在介绍begin()end()接口之后才能使用。

  1. initializer_list
cpp 复制代码
string (initializer_list<char> il);

这里的initializer_list也是一种容器,关于它的具体说明会在vector的相关博客中讲解,这里只介绍其使用:

cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int main()
{
	string s({ '1','2','a','b' });
	cout << s << endl;
	return 0;
}

在创建实例化对象时,在对象名后面的括号中加上一对大括号,在其中写上一系列元素(注意对于 string,这里的元素必须是char类型的),并以逗号隔开。

输出:

2. 6 赋值运算符重载

因为赋值运算符重载和构造函数重合度较高,所以放在一起讲解。

C++11中 string 有5种赋值运算符重载,因为其中一种涉及右值引用,这里不做介绍。

  1. 拷贝赋值
cpp 复制代码
string& operator= (const string& str);

其使用和拷贝构造其实差不多,只是拷贝构造是在创建实例化对象时调用,而拷贝赋值是对一个已经实例化的对象的操作。

cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int main()
{
	string s("hello world");
	string s1("x");
	cout << s1 << endl;
	s1 = s;
	cout << s1 << endl;
	return 0;
}

输出:

  1. c-string
cpp 复制代码
string& operator= (const char* s);

和构造函数中的from c-string一样,略。

  1. character
cpp 复制代码
string& operator= (char c);

和c string是一样的,只是从字符串变成了字符,用于一些场景下的兼容。

cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int main()
{
	string s("Hello World!");
	s = 'a';
	cout << s;
	return 0;
}

输出:

  1. initializer_list
cpp 复制代码
string& operator= (initializer_list<char> il);

和构造函数里的 initializer_list 是一样的,略。

2. 7 容量操作

容量操作指对 string 对象中的_capacity_size进行访问或操作的接口。

  1. size()capacity()(重要)
    这两个接口根据它们的名称就可以推断出分别是返回_size_capacity的大小。
cpp 复制代码
size_t size() const;
size_t capacity() const;
//注:省略了关于异常的关键字,现阶段不用在意

在一些情况下,可能需要在类外面访问 string 对象的大小和容量,比如对 string 进行下标遍历,size()的大小就可以作为遍历次数。

不能直接访问对象中的 _size_capacity,一方面是因为这是私有成员变量;另一方面,就算声明了友元函数,C++标准中并没有规定_size_capacity的具体名称,因此不同编译器中的这两个变量的名称可能不同,如果直接在代码中访问成员变量,会使代码的可移植性变差。

  1. length()max_size()

这两个接口几乎没有什么用武之地。
size()length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()

后者是返回 string 对象可以达到的最大长度,这是由已知的系统或库实现限制推断出的字符串可以达到的最大潜在长度,但不能保证对象能够达到该长度,在达到该长度之前,它仍然可能无法在任何时候分配存储。所以它没有意义。

  1. clear()
    清除对象中存储的所有元素 ,即_size置为0。
cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int main()
{
	string s("Hello World!");
	s.clear();
	cout << s;
	return 0;
}

可以发现代码没有任何输出。

  1. resize()(重要)
cpp 复制代码
// resize()
void resize (size_t n);
void resize (size_t n, char c);

resize()是重新设置 string 对象的大小,如果新的_size小于原来的,就会直接将多余元素删除(实际上就是加一个'\0'),如果新的_size大于原来的,会使用第二个参数 c 去填补这些位置,如果没有第二个参数,默认是'\0'

如果新的_size大于原来的容量,还会进行扩容。

cpp 复制代码
#include<string>
#include<iostream>
using namespace std;
int main()
{
	string s("Hello World!");
	cout << s.size() << endl;
	s.resize(2 * s.size());
	cout << s.size() << " " << s << endl;
	s.resize(2 * s.size(), 'a');
	cout << s.size() << " " << s << endl;
	return 0;
}

输出:

  1. reserve() (重要)和shrink_to_fit()
cpp 复制代码
void reserve (size_t n = 0);	//注意reserve()是有缺省值的!

reserve()的作用是将 string 对象的容量扩大到n以上,具体是多少C++标准没有规定。
如果n<_capacity,这个函数不会有任何动作。

这个接口的意义是减少扩容带来的损耗 ,编译器在创建一个 string 实例化对象时,并不知道它最终会存储多少数据,因此使用过程中不可避免地需要扩容,而我们知道扩容是有效率损耗的(如果原来的地址后面的空间不足,就需要拷贝原数据到新地址),因此如果程序员已知 string 对象存储的最大值或是不低于某个值,就可以直接reserve()一下,以减少扩容。

shrink_to_fit()是请求字符串减小其容量以适应其大小,也就是释放没有存储数据的空间。

但是这个接口是非强制性的,C++标准只规定这个接口调用后,对象的容量不低于其大小就可以了,编译器可以在此基础上任意优化。

  1. empty() (重要)
    返回对象是否为空(即其长度是否为 0)。

2. 8 元素访问

  1. operator[]重载 (重要)
    string 是对字符串的封装,因此是无法直接使用下标访问操作符的,必须通过操作符重载
cpp 复制代码
      char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;

其重载的返回值均为下标指向元素的引用,只是如果对象被const修饰不可修改,那么其返回的引用也不可修改。

下标访问操作符不会检查pos指向的位置是否合法,如果 pos 大于对象的大小,会发生未定义行为。

所有容器的元素访问操作符都会提供 const 版本以兼容对象被 const 修饰的情况。

  1. at()

    at()与下标访问操作符几乎一样,只是at()会检查pos位置是否合法,如果非法就会抛出异常

  2. front()back()

分别返回对象中第一个元素和最后一个元素的引用,注意也会有 const 修饰的区别。

2. 9 迭代器

在 string 中,可以把迭代器这样简单地理解:

cpp 复制代码
typedef char* interator;
typedef const char* const_interator;	//注意const迭代器的写法,用_相连

当然,现代的迭代器是对指针的进一步封装,不过在早期,string 的迭代器就是这样的,所以我们可以先这么理解,使用上是没有差别的。

  1. auto (重要)
    auto关键字可以在创建变量自动识别其类型而无需显式写出,在迭代器这里使用的非常频繁。
    对于 string 来说,它的迭代器类型为:
cpp 复制代码
std::string s;
// std::string::iterator it1 = s.begin();
auto it1 = s.begin();

似乎也不是很长,但是如果是将来的一个数据结构 map,情况就不一样了:

cpp 复制代码
std::map<std::string, std::string> m;
// std::map<std::string, std::string>::iterator it2 = m.begin();
auto it2 = m.begin();

不需要管这个类型具体是什么,只需要知道这样写很麻烦,而auto可以帮助我们省去这个麻烦就可以了。

比如:

补充:

  1. 在早期C/C++中auto的含义是使用auto修饰的变量,是具有自动存储器的局部变量,但是后来这个东西变得不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
  2. 用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。
  3. auto不能作为函数的参数,可以做返回值,但是建议谨慎使用 。(会降低代码可读性)
  4. auto不能直接用来声明数组
  1. begin()end() (重要)

    这两个接口分别返回指向string对象第一个元素和最后一个元素的下一个位置 的迭代器。

    根据this指针是否被const修饰,返回的迭代器类型也会改变。

    正式因为end()指向的最后一个元素的下一个位置,也就是说end()的返回值的解引用没有意义,所以在使用迭代器区间进行构造/赋值时,都是左闭右开的。

  2. 范围 for (重要)

    如果我们要使用迭代器遍历一个string对象,原本应该是这样写的:

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s("Hello World!");
	for (auto it1 = s.begin(); it1 != s.end(); it1++)
	{
		cout << *it1 << " ";
	}
	return 0;
}

当然这里有两点需要注意:

在string类中,迭代器可以直接当做指针进行解引用,也可以进行±和自增自减的操作。

迭代器在遍历时结束条件最好不要写it1 < s.end(),因为其他容器的数据存储可能不是连续的,写成it1 != s.end()有助于代码风格的统一。

对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。

因此C++11中引入了基于范围的for循环。

for循环后的括号由冒号":"分为两部分,第一部分是范围内用于迭代的变量,第二部分则表示被选代的范围,自动迭代,自动取数据,自动判断结束。范围for可以作用到数组和容器对象上进行遍历范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

范围for不是 string 类的特权,事实上只要类支持迭代器,且命名为 iterator 和 const_iterator ,就可以自动地支持范围for。

范围for的使用为:

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s("Hello World!");
	for (auto i : s)
	{
		cout << i << " ";
	}
	return 0;
}

这里也有两点要注意:

用于迭代的变量的类型可以写成auto,也可以显式地写出来。

如果不加&引用符号,范围 for 在遍历时会把容器中的元素直接复制给迭代变量 ,迭代变量不是迭代器!

因为默认情况下会发生复制,如果迭代变量的类型不是基础变量,而是类实例化对象,可能会造成效率大幅降低,因此我们可以使用引用来规避复制:

cpp 复制代码
for (auto& i : s)
{
	cout << i << " ";
}

但是要注意,如果容器没有被 const 修饰,那么迭代变量 i 默认是可以修改数据的,如果不希望 i 有修改的权限,可以在 auto 前加一个 const :

cpp 复制代码
for (const auto& i : s)
{
	cout << i << " ";
}
  1. 反向迭代器
    除了上面说的两个迭代器之外, string 还支持反向迭代器,类型为:
cpp 复制代码
reverse_iterator
const_reverse_iterator

同时也有对应的rbegin()rend()

对于反向迭代器,string中最后一个数据就是开始,第一个数据是结束,因此 rbegin() 和 rend() 返回的分别是指向数据末尾和数据开始的上一个位置 的反向迭代器。

反向迭代器与指针有些差异,但仍然可以进行解引用,自增自减等操作,只不过反向迭代器自增后会指向原来数据的上一个数据。

2. 10 修改

  1. push_back()
    前面已经介绍过了。略。
  2. append()
    C++11中,append有7中重载:
cpp 复制代码
//string (1)	
string& append (const string& str);
//substring (2)	
string& append (const string& str, size_t subpos, size_t sublen);
//c-string (3)	
string& append (const char* s);
//buffer (4)	
string& append (const char* s, size_t n);
//fill (5)	
string& append (size_t n, char c);
//range (6)	
template <class InputIterator>
   string& append (InputIterator first, InputIterator last);
//initializer list(7)	
string& append (initializer_list<char> il);

和构造函数有一些共通之处,就不挨个解释了。

append() 的作用就是在原来字符串的后面追加元素(如字符串等)。

  1. operator+= (重要!!)
    append() 有很多重载,但是全部记下来似乎有些困难,但是不要紧,string 类重载了 += 运算符。
cpp 复制代码
//string (1)	
string& operator+= (const string& str);
//c-string (2)	
string& operator+= (const char* s);
//character (3)	
string& operator+= (char c);
//initializer list (4)	
string& operator+= (initializer_list<char> il);

operator+= 支持 append 中除了迭代器区间,fill,substring 之外的所有操作,大部分情况下 operator+= 就可以应付了。

举例:

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s("Hello World!");
	string s1("kkkk");
	s1 += s;
	cout << s1;
	return 0;
}

输出:

  1. assign()

为 string 对象分配一个新值,替换其当前内容。

其重载和构造函数也基本一致:

cpp 复制代码
// string (1)	
string& assign (const string& str);
// substring (2)	
string& assign (const string& str, size_t subpos, size_t sublen);
// c-string (3)	
string& assign (const char* s);
// buffer (4)	
string& assign (const char* s, size_t n);
// fill (5)	
string& assign (size_t n, char c);
// range (6)	
template <class InputIterator>
   string& assign (InputIterator first, InputIterator last);
// initializer list(7)	
string& assign (initializer_list<char> il);

使用起来的效果和先 clear() 再 append(xxx) 的效果是一样的。

可以当做内置类型的赋值运算符。

  1. insert()erase()
    C++11中,insert 可以插入的有:
cpp 复制代码
// string (1)	
string& insert (size_t pos, const string& str);
// substring (2)	
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
// c-string (3)	
string& insert (size_t pos, const char* s);
// buffer (4)	
string& insert (size_t pos, const char* s, size_t n);
// fill (5)	
string& insert (size_t pos,   size_t n, char c);
iterator insert (const_iterator p, size_t n, char c);
// single character (6)	
iterator insert (const_iterator p, char c);
// range (7)	
template <class InputIterator>
	iterator insert (iterator p, InputIterator first, InputIterator last);
// initializer list (8)	
string& insert (const_iterator p, initializer_list<char> il);

在这些函数重载中,有的使用的是数组下标的形式,有的是迭代器,不必刻意去记,在需要的时候查一下再使用就可以了。

插入时可以看做是向这个迭代器/下标指向元素之前插入。

erase 有三个重载

cpp 复制代码
// sequence (1)	
// 从pos下标开始删除len个元素
string& erase (size_t pos = 0, size_t len = npos);
// character (2)	
// 删除迭代器指向的元素
iterator erase (const_iterator p);
// range (3)	
// 删除first-last之间的所有元素,注意左闭右开
iterator erase (const_iterator first, const_iterator last);
  1. swap() (重要)
    用于交换两个 string 对象。
    使用:
cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s("Hello World!");
	string s1("kkkk");
	s.swap(s1);
	cout << s1 << " " << s;
	return 0;
}

需要注意的是,在算法库(<algorithm>)中也有一个通用的swap函数:

cpp 复制代码
#include<iostream>
#include<string>
#include<algorithm>
using namespace std;
int main()
{
	string s("Hello World!");
	string s1("kkkk");
	swap(s, s1);	//调用方式不一样
	cout << s1 << " " << s;
	return 0;
}

虽然这两个 swap 的结果是一样的,但是对于string来说,应该尽可能使用 string 类的成员函数中的 swap,这涉及到了深浅拷贝问题,我会在下一篇博客------string类的模拟实现中详细介绍这一点。

  1. replace()
    这个函数使用较少,自行参考cplusplus了解一下即可。

2. 11 字符串操作

  1. c_str() (重要)

    返回一个指向数组的指针,该数组包含以 null 结尾(即'\0')的字符序列(即 C 字符串),表示 string 对象的当前值。

    这个函数用于用于兼容一些string类没有实现的字符串的用法。

    注意这个指针可能会在string对象调用一些接口后失效(如扩容)。

  2. find()和rfind()

    find 用于在 string 对象中查找字符/字符串。

cpp 复制代码
// string (1)	
// 从pos位置开始查找与str相同的字符串,返回第一个元素的下标,如果没找到会返回string::npos(-1)
size_t find (const string& str, size_t pos = 0) const noexcept;
// c-string (2)	
// 和上面的相同,只是从string变成了字符串
size_t find (const char* s, size_t pos = 0) const;
// buffer (3)	
// 与2相同,只是只匹配s的前n个字符
size_t find (const char* s, size_t pos, size_type n) const;
// character (4)	
// 从pos位置开始查找字符 c
size_t find (char c, size_t pos = 0) const noexcept;

find是从前往后查找,而rfind是从后往前查找。

e.g.使用find()找出字符串中所有'c'的下标:

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s("Heclcloc Wcorld!c");
	size_t pos = s.find('c');
	while (pos != string::npos)
	{
		cout << pos << " ";
		pos++;	//注意要++一次,不然会死循环
		pos = s.find('c', pos);
	}
	return 0;
}
  1. find_first_of()
    在字符串中搜索与其参数中指定的任何字符匹配的第一个字符。
cpp 复制代码
// string (1)
// 从pos位置开始找与str中任意一个字符相同的字符	
size_t find_first_of (const string& str, size_t pos = 0) const noexcept;
// c-string (2)	
size_t find_first_of (const char* s, size_t pos = 0) const;
// buffer (3)	
// 只匹配s的前n个字符
size_t find_first_of (const char* s, size_t pos, size_t n) const;
// character (4)	
size_t find_first_of (char c, size_t pos = 0) const noexcept;

与之对应的还有一组接口:

cpp 复制代码
find_first_of()
// Find character in string (public member function )
find_last_of()
// Find character in string from the end (public member function )
find_first_not_of()
// Find absence of character in string (public member function )
find_last_not_of()
// Find non-matching character in string from the end (public member function )
  1. compare()
    比较函数。
cpp 复制代码
// string (1)	
//比较this和str是否相同
int compare (const string& str) const noexcept;
// substrings (2)	
// 比较区间,pos,len给this,subpos和sublen给str
int compare (size_t pos, size_t len, const string& str) const;
int compare (size_t pos, size_t len, const string& str,
             size_t subpos, size_t sublen) const;
// c-string (3)	
int compare (const char* s) const;
int compare (size_t pos, size_t len, const char* s) const;
// buffer (4)	
int compare (size_t pos, size_t len, const char* s, size_t n) const;

返回值:

value relation between compared string and comparing string
0 They compare equal
<0 Either the value of the first character that does not match is lower in the compared string , or all compared characters match but the compared string is shorter.
>0 Either the value of the first character that does not match is greater in the compared string , or all compared characters match but the compared string is longer.

2. 12 不在类中的函数重载

  1. operator<<operator>> (重要)
cpp 复制代码
istream& operator>> (istream& is, string& str);
ostream& operator<< (ostream& os, const string& str);

istreamostream分别是cincout的类型。

这两个重载使得 string 可以直接输入输出而不需要借助字符串。

使用:

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s;
	cin >> s;
	cout << s;
	return 0;
}

输入与输出:

  1. operator+

可以通过operator+=类比,只是不会修改 this 指针,而是直接返回相加后的值。

一般来讲不推荐使用,因为这个函数是传值返回,需要进行深拷贝,效率低。

  1. getline() (重要)
    我们时常会需要输入一个带有空格的字符串,在C语言中,带有空格的字符串只能通过循环调用getchar()来实现,但在 C++ 中提供了getline函数来解决这一问题。
cpp 复制代码
istream& getline (istream&  is, string& str, char delim);
istream& getline (istream&  is, string& str);

istream 是 cin 的类型,第一个参数可以传 cin,第二个参数传 string 对象,第三个参数可以指定输入的停止符,默认是'\n',也就是在默认情况下,getline 会读取一行输入,无论是否带有空格。

使用:

cpp 复制代码
#include<iostream>
#include<string>
using namespace std;
int main()
{
	string s;
	getline(cin, s);
	cout << s;
	return 0;
}

输入输出:

3. 尾声

其实对于STL来说,它们的接口并不需要死记硬背,并且大概率也背不下来。

只需要在最开始的时候了解一遍,在需要的时候如果不记得了,就可以在cplusplus上查一下,用的多了,自然而然就记住了,记不住就说明这个接口/重载不重要,不需要死磕。

谢谢你的阅读,喜欢的话来个点赞收藏评论关注吧!
我会持续更新更多优质文章

相关推荐
djk888814 分钟前
.net将List<实体1>的数据转到List<实体2>
数据结构·list·.net
数据小爬虫@38 分钟前
利用Python爬虫获取淘宝店铺详情
开发语言·爬虫·python
搬砖的小码农_Sky39 分钟前
C语言:结构体
c语言·数据结构
高 朗1 小时前
【GO基础学习】基础语法(2)切片slice
开发语言·学习·golang·slice
寒笙LED1 小时前
C++详细笔记(六)string库
开发语言·c++·笔记
IT书架1 小时前
golang面试题
开发语言·后端·golang
初遇你时动了情1 小时前
uniapp 城市选择插件
开发语言·javascript·uni-app
zongzi_4942 小时前
二次封装的天气时间日历选择组件
开发语言·javascript·ecmascript
kikyo哎哟喂3 小时前
Java 代理模式详解
java·开发语言·代理模式
_OLi_3 小时前
力扣 LeetCode 106. 从中序与后序遍历序列构造二叉树(Day9:二叉树)
数据结构·算法·leetcode