【C++】——string的功能介绍及使用

news2024/9/23 3:23:31

前言:

在上期,我们简单的介绍了关于 模板和STL ,今天我就带领大家学习一下关于 【string】类。本期,我们主要讲解的是关于 【string】的基本介绍以及【string】类的常用接口说明。有了以上的基本认识之后,在下期,我们将模拟实现一个【string】类。


目录

(一)为什么学习string类?

1、 C语言中的字符串

(a)string类的引出🔥

2、两个面试题(暂不做讲解)

(二)标准库中的string类

1、 string类(了解)

2、编码

(a)ascll码

(b)万国码

(c)GBK字库

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

1、遍历三剑客 🔥

(a)迭代器 begin()+end()

(b)迭代器 rbegin()+rend()

(c)for+[]

(d)范围for

2、单个字符

(四)string类对象的常见构造

(五)string类对象的容量操作

1、size() 和 length()

2、capacity()

3、empty()

4、clear()

5、resize()

6、reserve()

(六)string类对象的修改操作

1、push_back()

2、append()

3、operator+=

4、find() 和 npos()

5、rfind()

6、substr()

(七)string类非成员函数

(八)vs和g++下string结构的说明

(九)总结


前言:

string 是C++里面我们最常见的类之一,管理的是字符串。那什么是最常见的呢?

  • 传统的类型,如内置类型只能表示一些基础的信息,当需要表示一些复杂的信息时就不适用了;
  • 假如我们要表示地址,表示身份证号码,此时再用日常的类型则无法较好的表示出来;

(一)为什么学习string类?

1、 C语言中的字符串

基于上述情况,在C语言中引入了字符串来管理

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

(a)string类的引出🔥

 💨  假如此时我们的搬家了,这时需要修改家庭住址,地址信息变长了,这就会导致原先的字符串数组存放不下这么多的信息。

 💨  因此在 C语言 中不能很好的进行管理,在C++ 中就提出了【string】类来管理字符串。


2、两个面试题(暂不做讲解)

把字符串转化为整数

字符串相加

在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本 都使用string类,很少有人去使用C库中的字符串操作函数。


(二)标准库中的string类

1、 string类(了解)

首先,在正式的介绍之前,我先给大家说明一点:

  • 在接下来的学习中,我们要学会去浏览权威的文档,对于那些全是英文的文档,大家不要害怕。作为一个合格的 “程序猿” ,学会看文档是必不可少的一项技能。

【string】文档介绍:string类的文档介绍

接下来,我通过文档简单的介绍一下【string】,看文档里面是怎么“解释说明的”。

此时可能就会有小伙伴有 疑问了。我介绍过说【string】是一个模板。但是根据之前的学习觉得这个跟模板没多大的关系啊似乎?

  • 其实【string】确实是一个模板,只是被 typedef 出来的而已,当我们打开文档时。

我们可以发现如下字眼:

  •  此时,当处于目前页面的文档往回退一页时,大家可以发现如下字眼:

  •  由于一些历史的原因,string 还提供了 宽字符的存在,紧接着由于 C++11 的提出,又新增了两个,分别是【u 16string】和  【u 32string】

 

 那么此时就有很多小伙伴有疑问了,为什么要引入这么多呢?

 💨  其实,大家在这一部分要这么理解。大家在看待这个【string】的时候其实就像我们之前学习的 “顺序表” 一样,底层管理的是一个字符数组,只是支持增删查改;

因此,换句话说,我们可以这样理解:

  1. 【string】   :管理的是 “char”类型的数组;
  2. 【wstring】:管理的是 “wchar” 类型的数组;
  3. 【u 16string】和  【u 32string】:管理的是 “char16_t”和“char32_t” 的数组。

区别在于 :

  1. 【string】:一个字符表示一个字节
  2. 【wstring】和 【u 16string】一个字符表示两个字节
  3.  【u 32string】:一个字符表示四个字节

因此,至于为什么引入这么多。其实目的只有一个就是为了达到管理不同字符数组的需求


2、编码

(a)ascll码

理解上述问题之后,此时又引出了一个问题。

  • 那就是为了达到管理不同的字符的需求,我们需要先理解 “编码” 的基本知识!!!

对于 “编码” 这个东西,我相信大家在之前肯定已经听说过了,对其都有或多或少的了解。那大家知道,我们第一个接触的编码是什么吗?

  • 其实就是在平常学习中听得最多的 ASCLL编码 了。

 此时我们要显示英文是不是很简单啊!因为英文最主要的就是由以下几部分组成:

  1. 26个字母,如果区别大小写,那就是52个;
  2. 在加上数字;
  3. 最后就是标点符号

了解以上之后,此时我问大家计算机能否直接存储以上这些信息呢?

  • 铁铁的是不能的,因为在计算机内存里面一切皆是二进制的 0和1 ;
  • 所以基于上述原因,需要建立一张对应关系的映射表,因此美国基于常见的字符建立了一张ASCll表

  • 假设我要存字符 ‘a’ ,此时我就去查这张表,对于‘a’,在表里面映射的就是 97,因此只需存入一个97 在计算机里面即可。

接下来,我们通过代码演示一下:

解释说明👇

  1.  在上述的代码中,我们写了一段简单的代码;
  2. 此时我们调试起来,我没有使用监视窗口,我直接使用底层的内存去查看;
  3. 大家可以发现,对于我们给出的 “apple” ,在内存中,首字节的 61 即表示 “a”因为在内存下是16进制,转换过来就是 “a”对应的ascll码值 97;
  4. 相同的,后面的字符依次按照这个规则,就可以验证上述编码表的内容。

(b)万国码

但是此时就遇到一个问题,假如我们就照搬这个ascll码,中文是否可以显示呢?

  1. 答案很明显,中文要在计算机上显示是十分困难的。
  2. 对于美国这套相对来说还还是很简单的,就那么些符号,要显示文字就用符号组成;
  3. 但是对于我们中国文字来说,我们走的是象形文字的路线。汉字都有差不多十万个,对于美国那套就像是一个汉字由一个符号组成,但是我们每个汉字都是独立的意思;
  4. 还有数量多了之后,一个字节最多可以表示256个,但是我们的文字是远超于256的;
  5. 同时,世界上不同的国家语言文字表示还各不相同;
  6. 因此,基于上诉这种情况,为了能够更好的显示以及推广,就有 推出了 【Unicode】

 万国码可以表示很多国家的文字,但是同时也会出现一个分歧。

  1. 比如像有些国家它的文字最多上万个甚至更少,而像我们中国那就是多的数不胜数;
  2. 在这个地方假如有一个字节又不好表示,两个字节又太多了;
  3. 因此,基于这种情况万国码又开始划分为三种:UTF-8 、UTF-16、 UTF-32

UTF-8

  • UTF-8以字节为单位对统一码进行编码。同时兼容 ascll码
  • 特点是对不同范围的字符使用不同长度的编码
  • 最重要的一点是支持变长的,意识就是同时支持 1字节,也可以使用2字节,最多支持4字节

UTF-16

  • UTF-16编码以16位无符号整数为单位。

UTF-32

  • UTF-32编码以32位无符号整数为单位。

(c)GBK字库

而对于我们中国来说,由于历史的原因,导致有些文字太复杂或者其他原因,在老外的那一套框架中就没有我们想要的那个字。

  • 因此,就衍生出了我们中国自己的这样一个规范,名为——GBK字库

假如此时我们写了这样的一段代码

int main()
{
	char str[] = "apple";

	char str1[] = "中国";
	cout << sizeof(str1) << endl;

	return 0;
}

 大家知道对于 【str1】的大小是多少吗?

我们在通过调试去观察

解释:

对于 str1 显示的为什么是负的呢?

  • 很简单,因为它要兼容ascll,因此它在这里运行的时候不能用正的值,就是第一个比特位是 0的那个要兼容ascll,它是 char 那个系列的;
  • 此时对于 char的第一个字节如果是 0就去ascll中查,如果不是 0,就需要用两个组合起来去查后面这个编码表。

我们在写出这样的几行代码,再带大家看看最终的结果是什么:

int main()
{
	char str[] = "apple";

	char str1[] = "中国";
	cout << sizeof(str1) << endl;

	str1[3]--;
	cout << str1 << endl;

	str1[3]--;
	cout << str1 << endl;

	str1[3]--;
	cout << str1 << endl;

	str1[3]++;
	cout << str1 << endl;

	str1[3]++;
	cout << str1 << endl;

	str1[3]++;
	cout << str1 << endl;
	return 0;
}
  • 输出结果如下:

  • 从上可见,汉字在经过编码表的时候不是随便编的,他把同音字编到一起去了

对于这种情况在生活中我们可以举个简单的例子来说明,大家知道“净网行动”吧!!

  1. 当我们在峡谷中遇到那些打得又菜,专门来演你的人之后,此时如果你脾气好点就是打字问候对方。但是当我们输入我们想打的字时显示的却是 ‘###’这样的情况;
  2. 所以当我们有编码表时,由于同音词很多,我们可以打同音字。游戏厂家为了营造和谐的上网氛围就会利用这种手段把这些字全部屏蔽掉,对于做的好厂家,此时当我们在输入一些字时,连同音字都打不出来。

小结:

  • 因此,计算机在C++ 里面要与时俱进的去这样发展,为了应对不同的编码,C++就引入了模板:不管你是【char】的数组,还是【wchar】类型的数组都可以支持。所以【string】类才搞得这么的复杂。


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

1、遍历三剑客 🔥

首先,我们要谈的就是关于进行string遍历的三种方法:

(a)迭代器 begin()+end()

begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

  • 代码展示:
int main()
{
    //遍历和读写容器的数据
	string str("hello world");

	string::iterator it = str.begin();
	while (it != str.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	return 0;
}
  • 但是,此时又有一个问题,那就是你这传的是普通对象,那对于 const 对象这样做是否可以呢?
  • 当我们如下这样去赋值时,是否还可以去遍历字符串呢?
void Func(const string& s)
{
	string::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

int main()
{
	string str("hello world");

	// 2.正向迭代器
	string::iterator it = str.begin();
	while (it != str.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	Func(str);

	return 0;
}
  • 当我们去编译代码时,就会自动出现报错的情况

  •  那什么原因呢?我们可以去结合文档查看 begin接口

  •  所以,此时我们需要使用【const】版本的迭代器:
void Func(const string& s)
{
    //遍历和读容器的数据,不能写
    string::const_iterator it = s.begin();
    while (it != s.end())
    {
        //*it += 1;  //此时const迭代器就不允许进行修改操作

        cout << *it << " ";
        ++it;
    }
    cout << endl;
}

int main()
{
    string str("hello world");

    // 2.正向迭代器
    string::iterator it = str.begin();
    while (it != str.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    Func(str);

    return 0;
}

大家不难发现上述的这种方式是从前往后遍历的。

那么当我们想从后往前遍历,string 是否支持呢?

  • 因此为了满足以上这种需求,在 【string】类中还引入了关键字—— rbegin rend

(b)迭代器 rbegin()+rend()

  • 代码展示:

	string::reverse_iterator rit_1 = str.rbegin();
	while (rit_1 != str.rend())
	{
		cout << *rit_1 << " ";
		++rit_1;
	}
	cout << endl;
  • 此处也有 const类型,与上述的同理!!! 

(c)for+[]

返回pos位置的字符,const string类对象调用

  • 代码展示:
int main()
{
	string str("hello world");

	for (size_t i = 0; i < str.size(); ++i)
		cout << str[i] << " ";

	return 0;
}

(d)范围for

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

  • 代码展示:
int main()
{
	string str("hello world");

	for (auto e : str)
		cout << e << " ";

	return 0;
}

  • 整体代码如下:
int main()
{
	string str("hello world");


	// 3种遍历方式:
	// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,
	// 另外以下三种方式对于string而言,第一种使用最多
	
	// 1. for+operator[]
	for (size_t i = 0; i < str.size(); ++i)
		cout << str[i] << " ";

	cout << endl;

	// 2.正向迭代器
	string::iterator it = str.begin();
	while (it != str.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;


	// 3.反向迭代器
	// string::reverse_iterator rit = s.rbegin();
	string::reverse_iterator rit_1 = str.rbegin();
	while (rit_1 != str.rend())
	{
		cout << *rit_1 << " ";
		++rit_1;
	}
	cout << endl;

	// C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
	auto rit_2 = str.rbegin();
	while (rit_2 != str.rend())
	{
		cout << *rit_2 << " ";
		++rit_2;
	}
	cout << endl;

	// 4.范围for
	for (auto e : str)
		cout << e << " ";
	cout << endl;

	return 0;
}

2、单个字符

  • 不仅如此,除了上述的可以遍历整个字符串之外,string还可以遍历一个字符。具体如下:
int main()
{
	string str("hello world");

	cout << str[8] << endl; 
}
  • 我们可以通过输入不同的数字下标来达到遍历每个字符的目的。

  • 同时我们还可以修改字符串中的某个字符:
int main()
{
	string str1("hello world");

	//对str1,可以修改字符串中的某个字符
	cout << str1[8] << endl;
	str1[8] = 'E';
	cout << str1 << endl;

	return 0;
}
  • 本来str[8]的位置的字符是 r,经过我们的手动修改,就把原本的 字符‘r’ ,改为了‘E’,输出结果如下:

  1. 但是以上对字符串进行修改的操作,在const类型下则是不可以的,编译器会提示报错:

int main()
{
	
	const string str2("Hello world");

	//对于str2,则不能修改字符串中的某个字符
	cout << str2[8] << endl;
	str2[8] = 'E';
	cout << str2 << endl; //编译失败,因为const类型对象不能修改

	return 0;
}
  • 当我们运行代码,最后编译器会提示报错:

  • 整体代码如下:
int main()
{
	string str1("hello world");
	const string str2("Hello world");

	//对str1,可以修改字符串中的某个字符
	cout << str1[8] << endl;
	str1[8] = 'E';
	cout << str1 << endl;

	//对于str2,则不能修改字符串中的某个字符
	cout << str2[8] << endl;
	str2[8] = 'E';
	cout << str2 << endl; //编译失败,因为const类型对象不能修改

	return 0;
}

(四)string类对象的常见构造

  • 整体代码如下:
int main()
{
	string str1("hello world");
	string str2="hello world";  //构造和str1一样的效果

	string();//构造空的string类对象,即空字符串

	//复制构造函数,构造 str1 的副本。
	string s1(str1);
	cout << s1 << endl;//hello world

	//子字符串构造函数
	//复制 str1 中从字符位置 8, 开始并跨越 3 字符的部分
	string s2(str1, 8, 3);
	cout << s2 << endl;  //rld

	//复制 s3 指向的以 null 结尾的字符序列
	//表示复制 s3指向的前六个字符
	string s3("have a nice day", 6);
	cout << s3 << endl;  //have a

	//范围构造函数
	//以相同的顺序复制区域【str1.begin(), str1.begin() + 7】 中的字符序列。
	string s4(str1.begin(), str1.begin() + 7);
	cout << s4 << endl; //hello w

    //填充构造函数
	//用字符 x 的 10 个连续副本填充字符串,string类对象中包含10个字符x
	string s5(10, 'x');
	cout << s5 << endl; //xxxxxxxxxx

	
	string s6(10, 42);
	cout << s6 << endl; //**********


	return 0;
}

(五)string类对象的容量操作

接下来,我们将要学习的便是关于 【string】类的容量操作了。主要学习的包括以下几个基本操作的内容:

函数名称功能说明
size (重点)返回字符串有效字符长度
length返回字符串有效字符长度
capacity返回空间总大小
empty(重点)检测字符串释放为空串,是返回true,否则返回false
clear(重点)清空有效字符
reserve(重点)为字符串预留空间**
resize(重点)将有效字符的个数该成n个,多出的空间用字符c填充

1、size() 和 length()

大家看到上述的表之后,可能会觉得奇怪。我第一个写出来的是 size() ,为什么还要有一个 length() 呢?

  • 别着急,我们通过代码具体感受一下二者:
int main()
{
	string str1("hello world");
	cout << str1.size() << endl;
	cout << str1.length() << endl;

	return 0;
}
  • 上述的代码大家觉得最后的结果是一样的吗?我在这就不卖关子了,直接编译代码,最终结果如下图所示:

  • 从上我们不难看出,二者的功能都是相同的;

那这两者到底有什么关系呢?我给大家浅浅的解释一下:

对于这个string 呢,其实还涉及到一个发展历史。具体是什么呢?

  1. 其实string 相对STL的出来还要早一些,严格来说是不属于STL的,它不是在STL下产生的,而是在C++标准库下产生的;
  2. 最开始的出现的时候呢,它的名字就叫做【length 】,最开始设计的时候对于字符串使用 【length】是不是很符合这样的需求;
  3. 之后随着STL的发展,因为在标准库之中已经有了这样的一个雏形了,所以 STL没有加它,但是从功法用途上就是一个数据结构;
  4. 为了跟其他的数据结构保持一致,对于顺序表,链表这样的用【length】还说得过去,但是对于 “树”这样的数据结构却是显得不合适的;
  5. 因此,基于上述这样的原因便引出了 size()。size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。

2、capacity()

很明显,主要的功能就是 :返回已分配存储的大小

  • 代码演示:
int main()
{
	string str1("hello world");

	
	//capacity
	cout << str1.capacity() << endl; //16


	return 0;
}

注意:

  1. 其实呢,对于上述的字符串分配的空间的大小。严格意义上来说是 【16】。
  2. vs下面的这个空间是不包含 \0 的,它不认为 \0 是有效字符,它认为 \0 是标识字符,string的结果使用 \0进行表示的。

  • 接下来,我通过监视窗口带大家瞧一瞧


3、empty()

顾名思义就是判断字符串是否为空的一个接口函数。如果字符串长度为 0,则为 true,否则为 false。


4、clear()

主要功能就是 :清除字符串

  • 代码展示:
int main()
{
	string str1("hello world");

	cout << str1.size() << endl;  //11
	cout << str1 << endl;         //hello world

	str1.clear();
	cout << str1.size() << endl; //0
	cout << str1 << endl;        //被清空了

	return 0;
}
  • 结果展示:

  •  clear()只是将string中有效字符清空,不改变底层空间大小。

5、resize()

主要功能:调整字符串大小。相当于【开空间+初始化】


常见三种用法

  • 1、如果n小于当前的容器大小,那么则保留容器的前n个元素,去除(erasing)超过的部分。
  • 2、如果n大于当前的容器大小,则通过在容器结尾插入(inserting)适合数量的元素使得整个容器大小达到n。且如果给出val,插入的新元素全为val,否则,执行默认构造函数。
  • 3、如果n大于当前容器的容量(capacity)时,则会自动重新分配一个存储空间。

​​​

  • 代码展示:
int main()
{
	string str1("hello world");


	// 将str中有效字符个数增加到12个,多出位置用'X'进行填充
	// “xxxxxxxxx”
	str1.resize(12, 'X');
	cout << str1.size() << endl;     //12
	cout << str1.capacity() << endl; //15
	cout << str1 << endl;            //hello worldX


	//将str中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	// "xxxxxxxxxx\0\0\0\0\0"
	// 注意此时s中有效字符个数已经增加到15个
	str1.resize(15);
	cout << str1.size() << endl;     //15
	cout << str1.capacity() << endl; //15
	cout << str1 << endl;            //hello world    
	cout << endl;


	// 将str中有效字符个数增加到20个,多出位置用缺省值'X'进行填充
	//注意此时容量的变化
	str1.resize(20 ,'X');
	cout << str1.size() << endl;     //20
	cout << str1.capacity() << endl; //31
	cout << str1 << endl;            //hello worldXXXXXXXXX  
	cout << endl;


	// 将str中有效字符个数缩小到5个
	str1.resize(5);
	cout << str1.size() << endl;       //5
	cout << str1.capacity() << endl;   //15
	cout << str1 << endl;              //hello
	cout << endl;

	return 0;
}

注意:

  1. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个;
  2. 不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的 元素空间。
  3. resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
  4. 如果发生了重新分配,则使用容器的分配器分配存储空间,这可能会在失败时抛出异常。

6、reserve()

主要功能请求更改容量,请求使字符串容量适应计划的大小更改为最多 n 个字符。

  • 代码展示:
int main()
{
	string str1("hello world");

	//测试reserve是否会改变string中有效元素个数
	str1.reserve(100);
	cout << str1.size() << endl;     
	cout << str1.capacity() << endl; 
	cout << endl;

	
	// 测试reserve参数小于string的底层空间大小时,是否会将空间缩小
	
	str1.resize(100);
	cout << str1.size() << endl;
	cout << str1.capacity() << endl;

	return 0;
}

  • 因此,有了这样的特性,我们就可以使用 reserve 来提高插入的效率了
int main()
{
	// 利用reserve提高插入数据的效率,避免增容带来的开销
		string str;
		size_t sz = str.capacity();
		cout << "making str grow:\n";
		cout << "capacity changed:" << sz << endl;;

		for (int i = 0; i < 100; ++i)
		{
			str.push_back('X');
			if (sz != str.capacity())
			{
				sz = str.capacity();
				cout << "capacity changed: " << sz << '\n';
			}
		}
}

注意:

  1. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserver不会改变容量大小
  2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。


  • 两者的比较如下:
int main()
{
	string str1("hello world");

	
	str1.reserve(100);
	cout << str1.size() << endl;     
	cout << str1.capacity() << endl; 
	cout << endl;

	
	string str2("hello world");
	str2.resize(100);
	cout << str2.size() << endl;
	cout << str2.capacity() << endl;

	return 0;
}
  • 运行结果如下:

 现象解释:

  1. 从上述我们可以发现,resize 不仅把字符串的 size给改了,而且还把capacity也给改了;
  2. 而reserve只把只把 capacity给改变,而没有改变 size 的大小。

(六)string类对象的修改操作

对于修改操作,我们主要学习的有以下几个接口功能,其余的在这暂不详解,最多带过,如果以后遇到,我们再说。

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

1、push_back()

主要功能将字符 c 追加到字符串的末尾,将其长度增加 1。

  • 当我们想在字符串的末尾追加一个字符时,我们就可以用到 push_back 这个函数接口。

  • 代码演示
int main()
{
	string str1("hello");
	cout << str1 << endl;       //5
	cout << str1.size() << endl;//hello

	str1.push_back('X');       // 在str后插入X
	cout << str1 << endl;      //helloX
	cout << str1.size() << endl;//6
	return 0;
}

2、append()

主要功能追加到字符串,通过在当前值的末尾附加其他字符来扩展basic_string:

  • 那此时我想追加一组字符串,上述的push_back 则不适用了,此时我们需要使用 append() 这个接口函数。
str1.append(" world");  // 在str后插入单词world
cout << str1 << endl;
cout << str1.size() << endl; 

但是在显示中,我们其实很不喜欢这个用法,最常用的就是下面这个接口。


3、operator+=

主要功能:追加到字符串

  • 代码演示
str1 += ' ';            // 在str后插入空格
str1 += "wo";           // 在str1后追加一个字符'wo'   
str1 += "rld";          // 在str1后追加一个字符串"rld"
cout << str1 << endl;
cout << str1.size() << endl;


关于插入功能的函数接口大概就是以上几个,我们常用的就是最后 一种

  • 整体代码展示
int main()
{
	string str1("hello");
	/cout << str1 << endl;
	cout << str1.size() << endl;

	//push_back
	str1.push_back('X');   // 在str后插入X
	cout << str1 << endl;
	cout << str1.size() << endl;

	//append
	str1.append(" world");  // 在str后插入单词world
	cout << str1 << endl;
	cout << str1.size() << endl; 

	//operator+=
	str1 += ' ';            // 在str后插入空格
	str1 += "wo";           // 在str1后追加一个字符'wo'   
	str1 += "rld";          // 在str1后追加一个字符串"rld"
	cout << str1 << endl;
	cout << str1.c_str() << endl;   // 以C语言的方式打印字符串
	cout << str1.size() << endl;


	return 0;
}

4、find() 和 npos()

主要功能查找字符串中的第一个匹配项,在basic_string中搜索由其参数指定的序列的第一个匹配项。

分析:

  1. 函数原理就是从pos位置开始搜索整个字符串,如果没有输入起始位置pos,则默认为0。
  2. 找到能成功匹配的子字符串str,如果可以找到合法存在的子字符串位置,此时需要需要注意一点的是,返回这个位置的索引坐标相对于整个字符串的起始位置而言,而不是相对于起始搜索的位置,否则返回npos.

至于什么是 【npos】,在这里我也简单的提一下:

  • 首先,我们先看看文档是怎么介绍的,具体如下:

 小结:

  1.   npos: 这是一个特殊值,等于size_type可以表达的最大值,通常为无符号整型的最大值。
  2. 确切的含义取决于上下文,通常用来标识字符串结束或者是函数作用错误指示符。
  3. 在本函数当中就用来代表函数作用错误,find函数在找不到指定值得情况下会返回string::npos。

 🔥用法:

  • 1、例如,当我们想查找一个字符时,我们可以使用到 find(),具体如下:
int main()
{
	string str("have a nice day");

	int s1 = str.find("e");       //pos未输入则默认为0
	int s2 = str.find("e", 4);
    
	cout << s1 << ' ' << s2 << endl; //3 10

     if(s.find("i", 10) == s.npos)
        cout << s.npos << endl; 

	return 0;
}

// npos是string里面的一个静态成员变量
// static const size_t npos = -1;
  • 2、例如,我们想在str1 字符串中查找查找和 str2 匹配的字符串,具体如下:

int main()
{
	string str1("have a nice day");
	string str2("ce");

	str1.find(str2);
	if(str1.find(str2) != string::npos)
		cout << str1.find(str2) << endl;

	return 0;
}

5、rfind()

主要功能:是从字符串右侧开始匹配str,并返回在字符串中的下标位置

  • 1、例如,当我们想查找一个字符时,我们可以使用到 rfind(),具体如下:
int main()
{
	string str1("have a nice day");
	string str2("ce");


	cout << str1.find('d') << endl;
	cout << str1.rfind('d') << endl;
	

	return 0;
}
  • 2、例如,我们想在str1 字符串中查找查找和 str2 匹配的字符串,具体如下:
int main()
{
	string str1("have a nice day");
	string str2("ce");

	str1.rfind(str2);
	if (str1.rfind(str2) != string::npos)
		cout << str1.rfind(str2) << endl;


	return 0;
}

小结:

  • 最后的运行结果跟 find 运行的时候结果是一样的。

6、substr()

主要功能生成子字符串,返回一个新构造的对象,其值初始化为此对象的子字符串的副本。

  • 1、例如,当我们想查找一个文件的后缀时,我们可以使用到 substr(),具体如下:
int main()
{

	// 获取file的后缀
	string file("string.cpp");
	size_t pos = file.rfind('.');
	string suffix(file.substr(pos, file.size() - pos));
	cout << suffix << endl;

	return 0;
}

解释说明:

  1. 假设此时我们有一个文件,叫【string.cpp】,当我们想查找文件的后缀时我们使用find查找到文件名的 后缀的起始,即【.】所在的位置后面的即为文件的后缀名;
  2. 在此处,我们通过查找到【.】所在位置,用整个字符串的长度减去pos位置之前的,得到的即为文件后缀名;
  3. 又因为文件后缀也是一个字符串,因此我们还用到了【substr】接口。

  • 2、例如,当我们想取出一个网络的域名时,我们也可以使用到 substr(),具体如下:

int main()
{
	// 取出DNS中的域名
	string DNS("https://legacy.cplusplus.com/reference/string/basic_string/substr/");
	cout << DNS << endl;
	size_t start = DNS.find("://");
	if (start == string::npos)
	{
		cout << "invalid DNS" << endl;
		return -1;
	}

	start += 3;
	size_t finish = DNS.find('/', start);
	string address = DNS.substr(start, finish - start);
	cout << address << endl;

	return 0;
}

(七)string类非成员函数

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

  • 上面的几个接口大家了解一下,在OJ题目中会有一些体现他们的使用。string类中还有一些其他的操作,这里不一一列举,大家在需要用到时不明白了查文档即可。 

(八)vs和g++下string结构的说明

注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

 💨 vs下string的结构

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字 符串的存储空间:

  • 当字符串长度小于16时,使用内部固定的字符数组来存放
  • 当字符串长度大于等于16时,从堆上开辟空间
union _Bxty
{ 
 // storage for small buffer or pointer to larger one
 value_type _Buf[_BUF_SIZE];
 pointer _Ptr;
 char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
  1. 这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内 部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。
  2. 其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量
  3. 最后:还有一个指针做一些其他事情。 故总共占16+4+4+4=28个字节。

 💨  g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指 针将来指向一块堆空间,内部包含了如下字段:

  • 空间总大小
  • 字符串有效长度
  • 引用计数
struct _Rep_base
{
 size_type _M_length;
 size_type _M_capacity;
 _Atomic_word _M_refcount;
};
  • 向堆空间的指针,用来存储字符串


(九)总结

到此,关于本文的内容便全部讲解完毕了。接下来,我们回顾一下本文都学到了什么:

  1. 首先,我们对为什么要学习 string类进行了解释说明。string是表示字符串的字符串类。不管是在以后的工作上还是日常练习都经常使用到 string;
  2. 其次,通过文档我们对标准库中的 string类进行了简单的,string在底层实际是:basic_string模板类的别名【typedef basic_string string】,紧接着还介绍了一系列的补充知识;
  3. 在接下来就是对 string类的常用接口说明以及讲解。该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作;
  4. 最后,说明一点:string 不能操作多字节或者变长字符的序列(🔥)

以上便是全文的基本内容了,非常感谢各位小伙伴的阅读!!

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

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

相关文章

全球SPD市场迎来黄金时代,中国领跑全球增长

近日&#xff0c;专注于前沿领域的国际咨询机构ICV发布了全球单光子探测器市场研究报告&#xff0c;报告分析了单光子探测器&#xff08;SPD&#xff09;市场&#xff0c;包括产品定位、下游应用、主要供应商、市场情况和未来趋势等各个方面&#xff0c;以进行分析和预测。 研究…

微服务 - kong安装,API网关设计(原理篇)

概述 微服务实践的第二个关键组件&#xff0c;微服务API网关设计&#xff0c;API网关是对微服务做统一的鉴权、限流、黑白名单、负载均衡等功能实现,这篇我们先来介绍Api网关的意义和安装kong/konga需要的组件。 网关的作用和意义 网关可以使得服务本身更专注自己的领域&…

Linux Ansible管理变量、管理事实、管理机密

目录 Ansible变量 变量定义范围 变量类型 定义变量并引用 事实变量与魔法变量 事实变量 魔法变量 Ansible加密 ansible-vault参数 ansible-vault举例 Ansible变量 Ansible支持利用变量来存储值&#xff0c;并且可以在Ansible项目的所有文件中重复使用这些值 变量可能…

浏览器缓存原理

使用 HTTP 缓存的好处&#xff1a;通过复用缓存资源&#xff0c;减少了客户端等待服务器响应的时间和网络流量&#xff0c;同时也能缓解服务器端的压力。可以显著的提升网站的应用性能。 HTTP 缓存策略分为两种&#xff1a;强制缓存、协商缓存。 强制缓存 浏览器缓存没有过期…

[pgrx开发postgresql数据库扩展]5.自定义函数与SQL组合应用

老规矩的声明&#xff1a; 并不是所有场景都需要&#xff08;或者适合&#xff09;用rust来写的&#xff0c;绝大部分操作数据库的功能和计算&#xff0c;用SQL就已经足够了&#xff01; 本系列中&#xff0c;所有的案例&#xff0c;仅用于说明pgrx的能力&#xff0c;而并非是…

BPMN2.0 任务-用户任务

“用户任务(user task)”用于对需要人工执行的任务进行建模。当流程执行到达用户任务时,会为指派至该任务的用户或组的任务列表创建一个新任务。 用户任务用左上角有一个小用户图标的标准任务(圆角矩形)表示。 用户任务在XML中如下定义。其中id是必须属性,name是可选属性…

提高网络安全性:探索ADAudit Plus的全功能IT安全审计解决方案

网络安全一直是组织和企业需要关注的重要问题之一&#xff0c;因为随着企业数字化的加速和技术的不断发展&#xff0c;网络攻击的威胁也变得越来越严峻。因此&#xff0c;组织和企业需要采取措施保护其信息资产和网络安全。 ADAudit Plus是一种全功能的IT安全审计解决方案&…

2023年商票研究报告

第一章 行业概况 1.1 定义 商票是指出票人依托商业汇票系统&#xff0c;以数据电文形式制作的&#xff0c;委托付款人在指定日期无条件支付确定的金额给收款人或者持票人的票据。按承兑人的不同&#xff0c;商业汇票分为银行承兑汇票和商业承兑汇票&#xff08;即商票&#x…

flex布局 高度没有自动撑到max-height

在做一个项目时&#xff0c;用到了竖向flex布局&#xff0c;我写了max-height: 820px, 但是到小屏幕时&#xff0c;只能撑到773px&#xff0c;解决方法是height: max-content. 但是不知道为什么只能撑到773px便撑不动了。 https://zhuanlan.zhihu.com/p/130460207 这个文档说的…

workerman开发者必须知道的几个问题

1、windows环境限制 windows系统下workerman单个进程仅支持200个连接。 windows系统下无法使用count参数设置多进程。 windows系统下无法使用status、stop、reload、restart等命令。 windows系统下无法守护进程&#xff0c;cmd窗口关掉后服务即停止。 windows系统下无法在一个…

目标检测之损失函数

损失函数的作用为度量神经网络预测信息与期望信息&#xff08;标签&#xff09;的距离&#xff0c;预测信息越接近期望信息&#xff0c;损失函数值越小。 在目标检测领域&#xff0c;常见的损失分为分类损失和回归损失。 L1损失 L1 Loss也称为平均绝对值误差&#xff08;MAE&…

[HNCTF 2022 WEEK4]ezheap

Index 前言Checksec & IDA 前言 手把手教学&#xff0c;覆盖一切途中会遇到的问题。 [HNCTF 2022 WEEK4]ezheap Checksec & IDA 保护全开&#xff0c;但是四肢健全&#xff08;四项功能 增删改查&#xff09;&#xff0c;因此是ezheap。 主要来观察函数add和show。 d…

注意力机制:基于Yolov5/Yolov7的Triplet注意力模块,即插即用,效果优于cbam、se,涨点明显

论文&#xff1a;https://arxiv.org/pdf/2010.03045.pdf 本文提出了可以有效解决跨维度交互的triplet attention。相较于以往的注意力方法&#xff0c;主要有两个优点&#xff1a; 1.可以忽略的计算开销 2.强调了多维交互而不降低维度的重要性&#xff0c;因此消除了通道和权…

信号完整性分析基础知识之传输线和反射(三):仿真和测试反射波形

使用上面反射系数的定义&#xff0c;可以计算来自任意阻抗的反射信号。当终端阻抗为阻性元件时&#xff0c;阻抗恒定&#xff0c;反射电压容易计算。当终端具有更复杂的阻抗行为&#xff08;例如电容性或电感性终端&#xff0c;或两者的某种组合&#xff09;时&#xff0c;如果…

3.QT布局管理

布局管理系统 布局管理器 QLayout类继承图&#xff1a; 新建Qt Widgets项目mylayout&#xff0c;选择基类QWidget&#xff0c;类名MyWidget .ui拖用Font Combo Box、Text Edit&#xff0c;单击主界面&#xff0c;ctrlL&#xff0c;发现两个部件填满界面&#xff0c;运行后随…

Docker部署MySQL主从复制

文章目录 平台说明一、Docker创建网络二、创建MySQL主从容器1.拉取镜像2.查看镜像3.创建启动容器 三、主从配置1.主机配置文件2.从机配置文件3.注意事项4.重启容器5.连接主从数据库主机配置从机配置启动从机复制 四、测试主从复制 平台说明 操作系统&#xff1a;Windows 11 Do…

接口测试入门必会知识总结(学习笔记)

目录 什么是接口&#xff1f; 内部接口 外部接口 接口的本质 什么是接口测试&#xff1f; 反向测试 为什么说接口测试如此重要&#xff1f; 越接近底层的 Bug&#xff0c;影响用户范围越广 目前流行的测试模型 接口测试的优越性 不同协议形式的测试 接口测试工作场景…

『python爬虫』01. 爬虫入门的基础知识(保姆级图文)

目录 1. 合法性2. 爬虫原理3. 网站源代码查看4. 查看网络请求总结 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 欢迎关注 『python爬虫』 专栏&#xff0c;持续更新中 1. 合法性 查看网站的爬虫协议&#xff0c;简单介绍爬虫协议robots.txt&#xff0c;避免爬虫爬的好…

简单理解内存分页机制

文章目录 1.CPU寻址方式2.段式内存访问的缺点3.80386两级页表4.PAE三级页表5.x64四级页表6.虚拟内存 思考一个问题&#xff1a;如果没有这样的分页机制时应用程序是怎么访问物理内存地址&#xff1f; 1.CPU寻址方式 Effective Address Base (Index * Scale) Displacement …

加载自己的图像数据集

文章目录 1 加载图像数据集2 图像预处理3 再次加载数据集4 这里还有一个问题&#xff0c;我们没有验证集5 构建DataLoader6 检查是否正确导入数据集 原文链接&#xff1a;《加载自己的图像数据集》 ​ 数据集下载链接 1 加载图像数据集 目录结构&#xff1a; 针对这种非常典型…