【STL学习】(1)string类

前言

本文将详细讲解STL中string类的常用的接口函数。

一、为什么学习string类?

1、字符串类型的重要性

在现实生活中有很多复杂类型是以字符串来表达的,比如我们在搜索引擎输入的“数据”,一个人的姓名、身份证号等等。

所以字符串类型是很重要的。

2、C语言中的字符串

C语言中,字符串是以‘\0’结尾的一些字符的集合。

  1. 在C语言中并没有字符串类型,但是我们可以使用字符数组或字符指针来表示。
  2. C标准库中提供了一些strxx系列的库函数管理字符串,但是这些库函数与字符串是分离的,不太符合OOP的思想。
  3. 并且字符串的底层空间需要用户自己管理,一不小心可能就会越界访问。

因此C++中引入了string——管理字符串的类。

在常规工作中,为了简单、方便、快捷,我们基本都会使用string类,很少有人去使用C库中的字符串操作函数,所以我们需要学习string。

3、怎样学习STL?

  1. 学习STL 不要想 着把每一个用法都掌握,因为STL的知识点太多了,所以我们只需掌握它一些常用的,其余不常用的了解有这么一个用法,使用时查看文档即可。
  2. 学习STL我们要学会查看文档,推荐两个查看文档的网站
    • 正统版本:优点更新快,缺点可读性差
    • 山寨版本:只更新到C++11,可读性好,推荐
  3. STL(standard template libaray-标准模板库)使用之前需要声明对应的头文件,并且展开命名空间std。

二、string类对象的常见接口函数

1、string类对象的常见构造

学习string,我们首先得学会它的构造。

我们知道一个类有多种初始化对象的方式,string也是如此,如下图:

在这里插入图片描述
tip:

  • 需要掌握的几个常见构造
    1. string():默认构造,构造一个空的string对象,即空字符串(0个字符)。
    2. string(const char* s):用C字符串来构造string对象。
    3. string(size_t n, char c):用连续n个字符的c来构造string对象。
    4. string(const string& str):拷贝构造函数,构造一个str的副本。
  • 只需要了解的几个构造函数
    1. string(const string& str, size_t pos, size_t len = npos):用str的部分序列来构造string对象,从pos位置开始,取len个字符。注意:如果len太大或者len为缺省值npos时,会把str从pos位置开始直到结束的字符序列用来构造string对象。(npos是string类的一个静态成员变量,因为npos = -1且又为无符号类型,所以它在内存中表示整形的最大值)在这里插入图片描述
    2. string(const char* s, size_t n):用s指向的字符数组的前n个字符来构造string对象。

代码示例:

#include<iostream>
#include<string>//使用string,要声明头文件
using namespace std;//展开命名空间

int main()
{
	//常用的构造函数
	//1、默认构造函数——构造一个空的string对象,即0个字符的空字符串
	string s1;
	//虽然string是自定义类型,但是string文件中operator<<,所以可以直接使用<<
	cout << s1 << endl;
	//2、使用C字符串来构造string对象
	string s2("hello string");
	cout << s2 << endl;
	//3、用连续n个字符的c来构造string对象
	string s3(10, '*');
	cout << s3 << endl;
	//4、拷贝构造——构造一个s2的副本
	string s4(s2);
	cout << s4 << endl;

	//其余需要只需了解即可
	//1、取s2中的hello来构造一个string对象,所以pos = 0;len = 5.
	string s5(s2, 0, 5);
	cout << s5 << endl;
	//取s2中的string来构造一个string对象,所以pos = 6,len >= 6或不传参,使用npos缺省值
	string s6(s2, 6);
	cout << s6 << endl;

	//string的operator=支持三种类型的直接赋值
	s1 = s5;//string
	cout << s1 << endl;
	s1 = "abc";//const char*
	cout << s1 << endl;
	s1 = 'a';//char
	cout << s1 << endl;
	return 0;
}

tip:

  1. 注意:因为某些历史原因,也为了与C兼容,所以C++语言中的字符串字面值并不是标准库类型string的对象。切记,字符串字面值与string是不同的类型。
  2. 因为析构函数会自动调用,所以析构我们就不做讲解了。
  3. string的operator=支持三种类型的直接赋值,如下图在这里插入图片描述

2、string类对象的容量操作

在这里插入图片描述
掌握:string对象的常用容量操作

函数名称功能说明
size返回字符串有效字符长度(注:不包含\0)
capacity返回当前为字符串分配的存储空间,以字节为单位
clear清空字符串的有效字符,使之成为空字符串
reserve申请n个字符的字符串容量,使用场景:当我们知道字符串需要的容量,一次reserve扩容,避免多次扩容
resize调整字符串的长度 ,将有效字符的个数改成n个,多出的空间用字符c填充
empty检测字符串是否为空串,是返回true,否则返回false

(1)size与length成员函数

size和length两个成员函数的功能一样,都是返回字符串有效字符长度。

代码示例:

int main()
{
	string s("hello string");
	//计算s的长度
	cout << s.size() << endl;
	cout << s.length() << endl;
	return 0;
}

运行结果:

在这里插入图片描述
tip:

  1. 字符串的有效字符长度不包含\0,因为\0是字符串的结束标志。
  2. string的产生比str早一些,严格来说string不属于STL容器,但是string又与容器属于同一类的,所以不从历史角度看也可以将其归为容器。
  3. size与length方法底层实现完全相同,引入size的原因是为了其他容器接口保持一致,一般情况下基本都是使用size。

(2)max_size成员函数(了解)

max_size返回字符串可以达到的最大长度。

tip:

  1. STL是一个规范,但它有很多版本,所以底层实现大同小异。
  2. max_size的文档只是说返回字符串可以达到的最大长度,但是并未说明具体说明,所以在不同版本下max_size不一样。
  3. max_size不靠谱,使用中无意义。

(3)capacity成员函数

capacity返回空间的总大小。

代码示例:

int main()
{
	string s("hello string");
	cout << s.size() << endl;
	//返回空间的总大小
	cout << s.capacity() << endl;
	size_t old = s.capacity();//返回值类型为无符号类型
	//观察每一次扩容空间的总大小
	for (int i = 0; i < 100; ++i)
	{
		s += 'x';
		if (old != s.capacity())
		{
			cout << "扩容:" << s.capacity() << endl;
			old = s.capacity();
		}
	}
	return 0;
}

VS下的运行结果:

在这里插入图片描述
g++下的运行结果:

在这里插入图片描述
tip:

  • capacity返回当前为字符串分配的存储空间,以字节为单位。
  • capacity不一定等于字符串的长度,它可以大于或等于。(STL是一个规范,有许多版本,所以底层大同小异。)
  • capacity的代码在VS和g++下分别运行会发现:
    1. VS下capacity在开始会申请15个字节的空间,第一次扩容会capacity2倍扩容,之后capacity1.5倍扩容。
    2. g++下的capacity开始时是按需申请,size多大capacity就多大,capacity都是2倍扩容。
    3. VS是PJ版本STL,g++是SGI版本STL。

(4)clear成员函数

clear清空字符串的有效字符,使之成为空字符串(即长度为0)。

代码示例:

int main()
{
	string s("hello string");
	cout << "清空前:" << endl;
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	s.clear();
	cout << "清空后:" << endl;
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	return 0;
}

运行结果:

在这里插入图片描述

tip:

  • clear清空字符串的有效字符,不会影响capacity。

(5)reserve成员函数

我们知道扩容是有代价的,那有什么可以操作消除这个代价呢?

reserve申请一个长度为n的字符串容量。

代码示例:

int main()
{
	string s("hello");
	//申请100个字符的字符串容量
	s.reserve(100);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	return 0;
}

在这里插入图片描述
tip:

  • reserve申请n个字符的字符串容量:
    1. 如果n大于当前字符串容量,则该函数申请扩容到n或比n大。
    2. 如果n小于当前字符串容量,则该函数申请缩容,注意该申请是不具约束力的,即底层的实现缩容或不缩容都可以(了解:字符串有数据的不会缩容,无数据会缩容)。一般我们不会缩容,因为效率低。
  • reserve只是单纯的开空间,不会影响字符串的长度和内容。

(6)resize成员函数

resize将字符串大小调整为n个字符的长度。

代码示例:

int main()
{
	string s("hello");
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
	//将字符串大小调整为n个字符的长度。
	//情况1:n大于当前字符串的长度——开空间+填值初始化
	s.resize(100, 'x');
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
	//情况2:n小于当前字符串的长度——将当前值长度缩小到n,删除第n个字符以外的值
	s.resize(10);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
	return 0;
}

在这里插入图片描述

tip:

  • resize将字符串大小调整为n个字符的长度:
    1. 如果n大于字符串长度,则开空间+填值初始化(第二个参数为char类型,在扩容时用于填值初始化。注意:当不传第二个参数时,使用\0填值初始化)
    2. 如果n小于字符串长度,会删除n后的字符,但不会缩容。
  • resize与reserve的区别:
    1. resize是改变字符串的长度,reserve是改变字符串的容量。
    2. 如果长度大于容量,长度会影响容量,但是容量什么情况都不会影响长度。

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

函数名称功能说明
operator[]返回字符串pos位置的字符的引用
begin + endbegin获取第一个字符的迭代器+end获取最后一个字符下一个位置的迭代器
rbegin + rendrbegin获取最后一个字符的反向迭代器+rend获取第一个字符前一个位置的反向迭代器
范围forC++11支持更简洁的范围for的新遍历方式

(1)operator[]和at()成员函数

operator[]和at()的功能一样,都是返回字符串pos位置的字符的引用。

代码示例1:

int main()
{
	try 
	{
		string s("hello string");
		//operator[]和at()都是返回字符串pos位置字符的引用
		cout << s[0] << endl;
		cout << s.at(0) << endl;

		//operator[]和at()的区别
		//s[18];//operator[]越界访问是断言报错
		s.at(18);//at()越界是抛异常
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	return 0;
}

tip:

  1. at()是早期string没有很好支持运算符重载产生的,一般情况我们更喜欢使用operator[]。
  2. at()和operator[]的区别:at()越界是抛异常,operator[]越界是断言处理。
  3. 自定义类型的[]与内置类型的[]在底层有本质的区别:
    • 内置类型的[]是指针的解引用
    • 自定义类型的[]是调用operator[]成员函数
  4. operator[]:
    • operator[]必须是成员函数
    • 为了与下标的原始定义兼容通常以所访问元素的引用作为返回值,优点是下标可以出现在赋值运算符的任意一端
    • 如果一个类包含下标运算符,则它通常会定义两个版本:一个返回普通引用,另一个是类的常量成员并且返回常量引用
  5. 重载函数调用时,会走最匹配的。

代码示例2:遍历字符串

int main()
{
	string s("hello");
	//遍历
	for (size_t i = 0; i < s.size(); ++i)
	{
		cout << s[i] << " ";
	}
	cout << endl;
	return 0;
}

tip:

  1. 标准库类型限定使用的下标必须是size_t,而内置的下标运算符所用的索引不是无符号类型。
  2. string对象的下标必须大于等于0而小于s.size()。使用超过此范围的下标将引发不可预知的结果,以此类推,使用下标访问空string也会引发不可预知的结果。
  3. 使用下标运算符有一个前提,必须是连续的空间。

(2)迭代器

问题1: 迭代器是什么?

迭代器类似于指针类型,提供了对对象的间接访问,可以用来读写对象。

tip:

  1. 和指针类似,也能通过解引用迭代器来获取它所指示的元素,执行解引用的迭代器必须合法并确实指示着某个元素
  2. 注意:与指针类似,试图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为
  3. 指针也是迭代器。但是我们不容易得到尾后指针(尾后指针指向容器尾元素的下一位置的指针),所以引入了返回迭代器的成员的函数。
  4. 迭代器的对象是容器中的元素或string对象中的字符。

问题2: 为什么引入迭代器?

  1. 迭代器是通用的,任何容器都支持迭代器,并且用法是类似的;而[]只有其中少数几种容器才支持,不通用。(虽然从历史角度来看string不属于容器,但是string和容器属于同一类,所以string支持很多与容器类似的操作)
  2. 算法可以通过迭代器,去处理容器中的数据。(算法需要操作容器中的数据,但是算法不能直接访问容器,因为数据一般封装为私有的,且每种容器结构逻辑上也不一样,需要一种通用的方式——迭代器去操作容器中的数据)

问题3: 怎么使用迭代器?

和指针不一样的是,获取迭代器不是使用取地址符,有迭代器的类型同时拥有返回迭代器的成员。

代码示例1:使用迭代器

int main()
{
	string s("hello string");
	//使用迭代器:有迭代器的类型同时拥有返回迭代器的成员。
	//一般来说,我们不在意迭代器准确的类型到底是什么,所以使用auto自动推导迭代器的类型
	auto it1 = s.begin();//auto——》string::iterator
	auto it2 = s.end();//auto——》string::iterator

	auto it3 = s.cbegin();//auto——》string::const_iterator
	auto it4 = s.cend();//auto——》string::const_iterator

	auto it5 = s.rbegin();//auto——》string::reverse_iterator
	auto it6 = s.rbegin();//auto——》string::reverse_iterator

	auto it7 = s.crbegin();//auto——》string::const_reverse_iterator
	auto it8 = s.crbegin();//auto——》string::const_reverse_iterator
	return 0;
}

tip:

  1. begin成员负责返回指向第一个元素(或第一个字符)的迭代器。
  2. end成员负责返回指向容器(或string对象)“尾元素的下一位置(one past the end)”的迭代器。
    • 该迭代器指示的是容器的一个本不存在的“尾后(off the end)”元素。
    • 该迭代器没什么实际含义,仅是个标记而已,表示我们已经处理完了容器中的所有元素。
    • end成员返回的迭代器常被称作尾后迭代器(off-the-end iterator)或者简称为尾迭代器(end iterator)
  3. 如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。
  4. 一般来说,我们不在意迭代器准确的类型到底是什么,使用auto根据右边的表达式自动推导迭代器的类型。
  5. begin和end有很多版本:
    • 带r的版本返回反向迭代器
    • 以c开头的版本则返回const迭代器(以c开头版本是C++新标准引入的,用以支持auto与begin和end函数结合使用)
  6. 当auto与begin或end结合使用时,获得的迭代器类型依赖于容器类型——如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator。
  7. 建议:如果对象只需读操作而无需写操作的,最好使用cbegin和cend。

代码示例2:迭代器范围

int main()
{
	string s("hello string");
	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		//移动迭代器,获取下一个元素
		++it;
	}
	cout << endl;
	return 0;
}

tip:

  1. 一个迭代器范围(iterator range)由一对迭代器表示,两个迭代器分别指向同一个容器中的元素或者是尾元素之后的位置(one past the last element)。这两个迭代器通常被称为begin和end,或者是first和last,它们标记容器中元素的一个范围。
  2. 迭代器范围中的元素包含first所表示的元素以及从first开始直至last(但不包含last)之间的所有元素。
  3. 这种元素范围被称为左闭右开区间(left-inclusive interval),其标准数学描述为[begin,end]表示范围自begin开始,于end之前结束。
    • 迭代器begin和end必须指向相同的容器。
    • end可以与begin指向相同的位置,但不能指向begin之前的位置。
  4. 标准库使用左闭合范围是因为这种范围有三种方便的性质。假定begin和end构成一个合法的迭代器范围,则
    • 如果begin与end相等,则范围为空
    • 如果begin与end不等,则范围至少包含一个元素,且begin指向该范围中的第一个元素
    • 我们可以对begin递增若干次,使得begin==end
  5. 使用递增运算符将迭代器从一个元素移动到另一个元素。
  6. 注意:因为end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作。
  7. 泛型编程:C++程序员习惯性在for循环中使用!=而非<进行循环结束条件的判断,是因为所有标准库容器的迭代器都定义了==和!=,但是它们大多数都没有定义<运算符。因此,只要我们养成使用迭代器和!=的习惯,就不用在意用的到底是那种容器类型。

代码示例3:迭代器的两大意义

int main()
{
	//迭代器的两大意义:
	//1、迭代器是通用的,任何容器都支持迭代器,并且用法类似
	string s("hello");
	auto it = s.cbegin();//①迭代器只读,不能写;②auto根据表达式推导迭代器类型为string::const_iterator
	while (it != s.cend())
	{
		//cbegin与cend不等,范围非空
		cout << *it << " ";
		//移动迭代器,获取下一个元素
		++it;
	}
	cout << endl;

	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	auto vit = v.cbegin(); //①迭代器只读,不能写;②auto根据表达式推导迭代器类型为vector<int>::const_iterator
	while (vit != v.cend())
	{
		//cbegin与cend不等,范围非空
		cout << *vit << " ";
		//移动迭代器,获取下一个元素
		++vit;
	}
	cout << endl;

	list<int> lt;
	lt.push_back(1);
	lt.push_back(2);
	lt.push_back(3);
	auto lit = lt.cbegin(); //①迭代器只读,不能写;②auto根据表达式推导迭代器类型为vector<int>::const_iterator
	while (lit != lt.cend())
	{
		//cbegin与cend不等,范围非空
		cout << *lit << " ";
		//移动迭代器,获取下一个元素
		++lit;
	}
	cout << endl;

	//2、迭代器跟算法配合
	reverse(v.begin(), v.end());
	reverse(s.begin(), s.end());

	return 0;
}

总结:迭代器提供了一种统一的方式访问或修改容器数据,并且算法可以通过迭代器,去处理容器中的数据。

(3)范围for

在前面C++入门我们已经讲解过什么是范围for了。

范围for是C++11支持的一种更简洁的新遍历方式。

范围for的语法格式是:

for(declaration:expression)
	statement

tip:

  1. expression表示的必须是一个序列
    • 注意:这个序列的范围必须是确定的,例如用花括号括起来的初始化列表、数组或者容器或string等类型的对象。
  2. declaration是定义一个变量,序列中的每个元素都能转换成该变量的类型
    • 变量的类型我们一般使用auto类型说明符,auto关键字可以令编译器自动推导变量类型
    • 如果需要对序列中的元素执行写操作,循环变量必须声明成引用类型
  3. statement可以是一条单独的语句也可以是一个块。

代码示例:

int main()
{
	string s("hello string");
	//使用范围for遍历string对象
	//for的底层实现是迭代器
	for (auto e : s)
	{
		//读
		cout << e << " ";
	}
	cout << endl;

	for (auto &e : s)
	{
		//写
		++e;
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

tip:

  1. 范围for的底层实现是用迭代器完成的,所以只要支持迭代器就可以使用范围for
  2. 注意范围for只能正向遍历,不能反向遍历。
  3. 反向遍历,可以使用反向迭代器

代码示例:反向迭代器

int main()
{
	string s("hello string");
	//反向遍历
	auto it = s.rbegin();
	while (it != s.rend())
	{
		cout << *it << " ";
		//递增一个反向迭代器会移动到前一个元素
		++it;
	}
	return 0;
}

tip:

  1. rbegin成员负责返回指向尾元素(即最后一个)的迭代器。
  2. rend成员负责返回指向首元素的前一个位置的迭代器。
  3. 反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。
  4. 对于反向迭代器,递增(以及递减)操作的含义会颠倒过来。递增一个反向迭代器会移动到前一个元素;递减一个反向迭代器会移动到下一个元素。

3、string类对象的修改操作

函数名称功能说明
push_back在字符串后尾插字符c
append在字符串后追加一个字符串
operator+=在字符串后追加字符串str
c_str返回C格式字符串
find从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr在str中从pos位置开始,截取n个字符,然后将其返回

(1)push_back&append&operator+=成员函数

在这里插入图片描述
push_back:在字符串后尾插一个字符

代码示例:

int main()
{
	string s("hello string");
	//在字符串后尾插一个字符
	s.push_back('!');
	cout << s << endl;
	return 0;
}

在这里插入图片描述
append:一般是在字符串后追加一个字符串

代码示例:

int main()
{
	string s1;//空串
	string s2("hello string");
	//append的常见用法
	s1.append(s2);//在s1后追加一个string对象
	cout << s1 << endl;
	s1.clear();
	s1.append("66666");//在s1后追加一个c字符串
	cout << s1 << endl;
	s1.clear();

	//append的其他用法(了解即可)
	s1.append(s2, 5);//追加string对象的子串,子串从pos位置开始,取sublen个字符(sublen是缺省参数,默认为npos)
	cout << s1 << endl;
	s1.clear();
	s1.append("hello", 5);//追加s指向的字符数组的前n个字符
	cout << s1 << endl;
	s1.clear();
	s1.append(6, '6');//追加n个字符c
	cout << s1 << endl;
	s1.clear();
	return 0;
}

在这里插入图片描述
operator+=:在字符串后追加字符串

代码示例:

int main()
{
	string s1("hello");
	string s2("string");
	//operator+=不仅可以在字符串后追加字符串,还可以追加字符
	s1 += ' ';//追加一个字符
	s1 += s2;//追加一个string对象
	s1 += "666";//追加一个C字符串
	cout << s1 << endl;
	return 0;
}

tip:

  1. operator+=不仅可以追加单个字符,还可以追加字符串。
  2. 一般operator+=就可以满足我们字符串尾追加的大多数情况,并且operator+=的使用更人性化,所以一般追加我们使用operator+=即可。

(2)c_str成员函数

在这里插入图片描述
c_str: 返回C格式字符串。

问题: 为什么string对象需要返回C格式字符串?

因为很多C++程序在标准库出现之前就已经写成了,而且有一些C++程序实际上是与C语言或其他语言的接口程序,所以无法使用C++标准库。因此,现代C++程序不得不与那些充满数组或C格式字符串的代码衔接,为了使这一工作简单易行,C++专门提供了一组功能。

简单来说,就是与旧代码做衔接。

代码示例:

int main()
{
	string filename = "test.cpp";
	filename += ".zip";
	//与旧代码做衔接
	//例如:fopen函数的第一个参数类型为const char*
	FILE* fout = fopen(filename.c_str(), "r");
	return 0;
}

tip:

  1. C格式字符串使用一个指针存储,该指针指向一个以空字符结束的字符数组。
  2. 指针类型是const char*,从而确保我们不会改变字符数组的内容。
  3. 注意:我们无法保证c_str函数返回的数组一直有效,事实上,如果后续的操作改变了s的值就可能让之前返回的数组失去效用。
  4. 建议:如果执行完c_str函数后程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。

(3)find&rfind&substr成员函数

在这里插入图片描述
find: 从字符串pos位置开始往后找字符c(或字符串str),返回该字符(或字符串)在字符串中第一次出现的位置。

代码示例:

int main()
{
	string url = "https://legacy.cplusplus.com/reference/string/string/";
	// 协议  域名  资源名
	size_t pos1 = url.find("://");
	size_t pos2 = url.find('/', pos1 + 3);
	cout << pos1 << " " << pos2 << endl;
	return 0;
}

tip:

  1. 如果没有找到,返回string的静态成员变量npos。
  2. find常常与substr配合使用。
  3. 注意:pos参数默认为0,即默认从首字符开始找,但有些情况并不能从0开始,需要我们自己控制。

在这里插入图片描述
substr: 返回string对象一个子串,子串从pos开始,截取len个字符(len为缺省参数,默认值为npos)。

代码示例:

int main()
{
	string url = "https://legacy.cplusplus.com/reference/string/string/";
	// 协议  域名  资源名
	size_t pos1 = url.find("://");
	string protocol;
	if (pos1 != string::npos)
	{
		protocol = url.substr(0, pos1);
	}
	cout << protocol << endl;

	string domain;
	string uri;

	size_t pos2 = url.find('/', pos1 + 3);
	if (pos2 != string::npos)
	{
		domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));//len是一个左闭右开
		uri = url.substr(pos2 + 1);
	}
	cout << domain << endl;
	cout << uri << endl;
	return 0;
}

tip:

  1. 左闭右开的范围,相减可以得到长度。

在这里插入图片描述
rfind: 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置。

代码示例:

int main()
{
	string s("test.cpp.zip");
	//获取文件的后缀名
	size_t pos = s.rfind('.');
	string extension;
	if (pos != string::npos)
	{
		extension = s.substr(pos + 1);
	}
	cout << extension << endl;
	return 0;
}

tip:

  1. pos默认从最后一个字符开始往前找。(任何大于或等于字符串长度的值(包括string::npos)表示从最后一个字符位置开始)

加油站:string的其他不常用的修改操作(了解即可)

  1. assign:为字符串重新赋值,类似=运算符。
    在这里插入图片描述
    tip: 因为=运算符重载只有两个操作数,它只能有两个参数,所以assign的2、4、5、6不能重载。(但是实际中我们一般是将赋值内容构造成一个string对象,再通过operator=赋值)
  2. insert:在字符串中pos(或p)指定的字符之前插入额外字符。在这里插入图片描述
    tip: 因为string的底层是字符数组的顺序表,pos位置之前的插入需要挪动数据,效率太低,所以我们不易多用。
  3. erase:删除字符串的一部分。在这里插入图片描述
  4. replace:将字符串pos位置开始的len个字符(或[i1,i2)范围的字符)替换成字符串str(或字符串s的前n个)等。在这里插入图片描述
  5. **find_first_of:从pos位置开始往后在字符串中找与参数中指定的任何字符匹配的第一个字符,找到返回其位置,找不到返回string::npos。在这里插入图片描述
    tip:①find_last_of从pos往前;②find_first_not_of与find_first_of功能相反,找与参数中指定的任何字符都不匹配的第一个字符;③find_last_not_of。

4、string类非成员函数

函数功能说明
operator+尽量少用,因为传值返回,导致深拷贝效率低
operator>>输入运算符重载
operator<<输出运算符重载
getline获取一行字符串
relational operators大小比较

(1)relational operators关系运算符重载

在这里插入图片描述
代码示例:

int main()
{
	string s1("abc");
	string s2("abd");
	if (s1 == s2)
	{
		cout << "s1 == s2" << endl;
	}
	else
	{
		cout << "s1 != s2" << endl;
	}

	if (s1 <= s2)
	{
		cout << "s1 <= s2" << endl;
	}
	else
	{
		cout << "s1 > s2" << endl;
	}

	if (s1 >= s2)
	{
		cout << "s1 >= s2" << endl;
	}
	else
	{
		cout << "s1 < s2" << endl;
	}
	return 0;
}

(2)getline获取一行字符串

在这里插入图片描述
getline: 从流中获取一行到字符串,即从is中读取字符并将其存储到str中,直到找到分隔字符delim(或者对于(2)找到换行字符’\n’)。

字符串最后一个单词的长度

#include <iostream>
#include<string>
using namespace std;

int main() 
{
    string s;
     //读取一行字符
    getline(cin,s);
    //找到最后一个空格的位置
    size_t pos = s.rfind(' ');
    //左闭右开,相减得出范围
    cout << s.size() - (pos + 1) << endl;
    return 0;
}

tip:

  1. cin读到空格或换行就结束,getline读到换行或指定分隔符才结束。
  2. getline是非成员函数。
  3. getline如果找到分隔符,则提取并丢弃它(即不存储它,下一个输入操作将在它之后开始)。

加油站:将数值转换成字符串,将字符串转换成数值。

  1. 将数值转换成字符串。

    在这里插入图片描述
  2. 字符串转换成数值在这里插入图片描述

三、标准库中的string类

1、string类

  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性
  3. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(更多模板信息请参考basic_string)
  4. 注意,这个类独立于所使用的编码来处理字节:如果用于处理多字节或变长字节(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作

总结

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

2、basic_string类模板

在这里插入图片描述
tip: basic_string是string的一个类模板,适用于任何字符类型

为什么会将其设计为模板,这就需要我们了解编码了。

3、编码

  1. 在计算机中,所有的数据在存储和运算时都要使用二进制数来表示(因为计算机用高电平和低电平分别表示1和0)。
  2. 编码:确定一个字符和二进制数的对应规则,这就是编码。

(1)ASCII

计算机是老美发明,老美需要处理的字符只包括英文字母(大小写)、数字、标点符号、特殊字符等等,总共128个字符,所以老美就制作了一套ASCII编码。

在这里插入图片描述

tip:

  1. ASCII (American Standard Code for Information Interchange):美国信息交换标准代码是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。
  2. 标准ASCII使用一个字节存储一个字符,首位是0,总共可表示128个字符。

(2)GBK

ASCII对于老美还说完全够用了,但是我们中国文化博大精深,所以中国创造了一套GBK编码(汉字内码扩展规范,国标)。
tip:

  1. 汉字编码字符集,包含了2万多个汉字等字符,所以GBK中一个中文字符编码成两个字节的形式存储
  2. GBK兼容ASCII字符集

问题: GBK兼容ASCII,那中英混合,解码时如何区分是中文还是英文?

**GBK规定:汉字的第一个字节的第一位必须是1.(即看到是1两个字节为一个整体)

(3)Unicde

Unicode是国际组织制定的,可以容纳世界上所有文字、符号的字符集。

统一码(Unicode)是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

tip: UTF-8

  1. 是Unicode字符集的编码方案,采取可变长编码方案,共分四个长度区:1个字节,2个字节,3个字节,4个字节。
  2. 英文字符、数字等占一个字节(兼容ASCII),汉字字符占用3个字节。
  3. UTF-8编码格式在这里插入图片描述

(4)总结

在这里插入图片描述

tip:

  1. 字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码。
  2. 英文、数字一般不会乱码,因为很多字符集都兼容ASCII编码。
  3. 编码:把字符按照字符集编码成对应二进制数
  4. 解码:把二进制数按照字符集解码成字符
  5. 建议:使用UTF-8
  6. Windows下默认编码是GBK,Linux下默认的是UTF-8

3、类模板的实例化

在这里插入图片描述
tip: 现阶段我们学的string,是使用char实例化的。在这里插入图片描述

string还有许多操作,这里就不一一列举了,需要的时候查看文档即可。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

康奋威科技邀您到场参观2024长三角快递物流展

参展企业介绍 杭州康奋威科技股份有限公司创立于2005年&#xff0c;由国家“万人计划”专家任天挺先生创立并担任法人&#xff0c;是一家专业从事智能装备研发与制造的国家级高新技术企业。专注于自动化控制、机械设计、信息化方面的技术研究&#xff0c;主要为太阳能光伏、智…

一篇文章让你完全掌握使用Git推送代码到新版GitCode

Git推送代码到新版GitCode 前言一、安装git二、tortoise git的安装2.1 关于tortoise git2.2 tortoise git和tortoise git汉语包的下载2.3安装过程2.4配置tortoise git 三、创建GitCode项目关于READM文件关于.gitignore文件关于LICENS文件 四、配置GitCode和Git4.1克隆项目4.2配…

【十二】【算法分析与设计】滑动窗口(3)

30. 串联所有单词的子串 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words ["ab","cd","ef"]&#xff…

【Godot 3.5控件】用TextureProgress制作血条

说明 本文写自2022年11月13日-14日&#xff0c;内容基于Godot3.5。后续可能会进行向4.2版本的转化。 概述 之前基于ProgressBar创建过血条组件。它主要是基于修改StyleBoxFlat&#xff0c;好处是它几乎可以算是矢量的&#xff0c;体积小&#xff0c;所有东西都是样式信息&am…

Mysql学习--深入探究索引和事务的重点要点与考点

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

基于springboot+vue+Mysql的闲一品交易平台

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

2024,产品国际化改造

2024&#xff0c;我们的核心是国际化/信创/多租户/AI融合应用。 作为招投标与即将推进的项目需求&#xff0c;优先对产品进行国际化改造。 1.我们的思考 作为基础平台个性化定制的项目落地模式&#xff0c;我们必须兼顾平台与定制直接的平衡&#xff0c;使整个系统能快速在多…

力扣541. 反转字符串 II

思路&#xff1a;题目的意思就是每2k个字符进行一次循环访问&#xff0c;如果个数小于k就全部反转&#xff0c;如果大于k则只反转k个字符; class Solution {public String reverseStr(String s, int k) {char[] charArray s.toCharArray();int length charArray.length;//每…

DDR4总结最全纯干货分享

DDR存储器发展的主要方向一言以蔽之&#xff0c;是更高速率&#xff0c;更低电压&#xff0c;更密的存储密度&#xff0c;从而实现更好的性能。 DDR4 SDRAM&#xff08;Double Data Rate Fourth SDRAM&#xff09;&#xff1a;DDR4提供比DDR3/ DDR2更低的供电电压1.2V以及更高的…

学会这些Jmeter插件,才能设计出复杂性能测试场景

为什么要使用jmeter线程组插件呢&#xff1f; jmeter自带的线程组插件模拟的压测场景非常有限&#xff0c;当需要模拟复杂压测场景的时候&#xff0c;推荐大家使用jmeter线程组插件。 如何下载jmeter线程组插件呢&#xff1f; 早期版本的jmeter可以针对我们需要的扩展功能&a…

Docker-Container

Docker ①什么是容器②为什么需要容器③容器的生命周期容器 OOM容器异常退出容器暂停 ④容器命令清单总览docker createdocker rundocker psdocker logsdocker attachdocker execdocker startdocker stopdocker restartdocker killdocker topdocker statsdocker container insp…

宏集PLC如何应用于建筑的3D打印?

案例概况 客户&#xff1a;Rebuild 合作伙伴&#xff1a;ASTOR 应用&#xff1a;用于建筑的大尺寸3D打印 应用产品&#xff1a;3D混凝土打印机 一、应用背景 自从20世纪80年代以来&#xff0c;增材制造技术&#xff08;即3D打印&#xff09;不断发展。大部分3D打印技术应…

力扣---零钱兑换---动态规划

思路&#xff1a; 这是一道典型的动态规划问题&#xff08;希望下次不用提示&#xff0c;能直接认出来&#xff09;&#xff1a;我将g[i]定义为总金币为i所需的最少硬币个数。所以递推公式可以表示为&#xff1a;g[i]min(g[i-1],g[i-2],g[i-5])1&#xff0c;也就是g[i]min(g[i-…

简介:网络数据中心和数字孪生系统融合

前言 云服务器是在云中提供可扩展的计算服务&#xff0c;避免了使用传统服务器时需要预估资源用量及前期投入的情况。云服务器支持用户自定义一切资源&#xff1a;cpu、内存、硬盘、网络、安全等等&#xff0c;并可在访问量和负载等需求发生变化时轻松地调整它们。云服务器为业…

算法公式汇总

文章目录 三角函数定义式诱导公式平方关系两角和与差的三角函数积化和差公式和差化积公式倍角公式半角公式万能公式其他公式反三角函数恒等式 三角函数定义式 三角函数 定义式 余切&#xff1a; c o t A 1 t a n A \text { 余切&#xff1a;} \ cotA \frac{1}{tanA} 余切&a…

华为OD机22道试题

华为OD机试题 2.查找小朋友的好朋友位置 在学校中&#xff0c;N 个小朋友站成一队&#xff0c;第 i 个小朋友的身高为 height[i]&#xff0c;第 i 个小朋友可以看到第一个比自己身高更高的小朋友j&#xff0c;那么 j 是 i 的好朋友 (要求&#xff1a;j>i) 。 请重新生成一个…

springboot+itextpdf+thymeleaf+ognl根据静态模版文件实现动态生成pdf文件并导出demo

第一步&#xff1a;导入maven依赖 <!-- 导出为PDF依赖包 --><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId></dependency><dependency><groupId>com.itextpdf</groupId><art…

xAI开发的一款巨大型语言模型(HLM)--Grok 1

在xAI发布Grok的权重和架构之后&#xff0c;很明显大型语言模型&#xff08;LLM&#xff09;的时代已经过去&#xff0c;现在是巨大型语言模型&#xff08;HLM&#xff09;的时代。这个混合专家模型发布了3140亿个参数&#xff0c;并且在Apache 2.0许可下发布。这个模型没有针对…

C++关于类和对象的基础语法

前言&#xff1a; 介绍c中类和对象的基础语法和注意事项&#xff0c;这里是c入门的第一道坎&#xff0c;细节很多&#xff0c;在后面的更深的学习中还会反复提到。 目录 前言&#xff1a; 1.OO语言 2.类的定义 3.类的访问限定符与封装的引入 4.类的实例化 5.关键字this指…

Magic Copy:一键AI抠图,在浏览器中获得任何图像素材

Magic Copy&#xff1a;轻松一点&#xff0c;精准抠图&#xff0c;让创意无限放大&#xff01; - 精选真开源&#xff0c;释放新价值。 概览 Magic Copy&#xff08;AI智能抠图插件&#xff09;是一个创新型的浏览器扩展工具&#xff0c;其独特之处在于能够无缝集成于用户的网…