【C++】简述STL——string类的使用

举报
平凡的人1 发表于 2022/11/16 00:22:22 2022/11/16
【摘要】 @[toc] 一、STL的简述STL(standard template libaray-标准模板库):是C++标准库的重要组成部分**,不仅是一个可复用的组件库,而且一个包罗数据结构与算法的软件框架**。 1.STL的框架 2.STL版本原始版本Alexander Stepanov、Meng Lee 在惠普实验室完成的原始版本,本着开源精神,他们声明允许任何人任意运用、拷贝、修改、传播、商...

简述STL
@[toc]

一、STL的简述

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

1.STL的框架

image-20221020103721147

2.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的学习我们可以前去官网看文档,多了解了解cplusplus.com


二、编码铺垫

string类本身就是一个模板,为什么要把string写成模板?是因为字符串的数组涉及编码问题,字符数组编码不同。所以需要模板

u16string:表示两个字节

u32string:表示四个字节

这里简单了解一下编码

  • ASCII码

美国信息交换标准码。

ASCII码表是计算机存值和文字符号的对应关系

只有256个字符

  • Unicode

万国码

Unicode是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码

包括了utf-8,utf-16,utf-32

utf-8兼容了ASCII,utf-8使用比较普遍,也比较节省空间

  • gbk

gbk即国标,针对中文而设计的编码。采用双字节编码。


三、string类

根据不同的编码选用不同的string(接口是差不多的),这里我们只需重点学习string(utf-8):

image-20221020204928009

string类模板的大概框架:

template <class T>
//动态增长字符数组
class basic_string
{
private:
	T* _str;
	size_t _size;
	size_t _capacity;
};

使用string类的时候,我们要包含头文件#include <string>

下面我们开始说一说string类常用的接口,对于常用接口我们需要熟练使用,其他的即可查阅学习。


四、常见构造

废话不多说,我们直接来使用一下这些构造函数,形成初步了解:

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

void test_test1()
{
	string s1;//默认构造
	string s2("hello string");//带参构造
	s2 += "world";
	string s3 = "hehe";//构造+拷贝构造----直接构造
	string s4(s2);//拷贝构造
	string s5 = s2;
	string s6(10, 'X');//个数初始化
	string s7("hello world", 5);//取前n个
	string s8(s7, 2, 3);
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	cout << s6 << endl;
	cout << s7 << endl;
	cout << s8 << endl;
}
int main()
{
	test_test1();
	return 0;
}

image-20221104103212636

==注意:string类对象可支持直接用cin和cout进行输入和输出,这是因为重载了流插入>>和流提取<<操作符==

对于s8,如果取的长度过大,会发生什么?查看文档:

直到结束,并不会报错:

image-20221104103916086

对于默认值npos:

image-20221104104016869

这里的npos是-1,但是这里是无符号,实际并不是-1,是4294967295。

通过这里的构造函数,我们需要知道,对于一些细节东西我们是要学会去看文档的,对于学习文档是非常重要的。


五、operator[]

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

实际上重载了[],让string类可以像数组一样访问。数组的[]的本质是解引用,而这里是调用函数:

image-20221104110145560

char& operator[](size_t pos)
{
	assert(pos<_size);//检查越界
	return _str[i];
}

传引用返回,可读可改。支持修改返回值:我们通过代码来实现这一功能

#include <iostream>
#include <string>
using  namespace std;
int main()
{
	string s("123456");
	//读
	for (size_t i = 0; i < s.size(); i++)
	{
		cout << s[i] << ' ';
	}
	cout << endl;

	//改
	for (size_t i = 0; i < s.size(); i++)
	{
		s[i] += 1;
	}
	cout << s << endl;
	return 0;
}

image-20221104110758346

当然,对于const修饰的字符串肯定不能修改了。


六、访问及遍历

对于遍历操作,第一个方式就是我们上面所说的operator[],第二个方式就是范围for

image-20221021174258380


七、iterator迭代器

对于遍历,除了上面2种方式,我们也可以采取迭代器。迭代器行为上像指针,但是却不一定是指针。对于string的遍历,最简单的肯定是第一种,为什么还要有迭代器的存在呢

  • 为什么要有迭代器

迭代器的意义在于通用,所有容器都可以使用迭代器这种方式去进行遍历和修改。而对于string类,[]足矣

1.正向迭代器

迭代器提供了begin和end的函数:

image-20221104114512310

image-20221104114544999

image-20221104114604002

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s("123456");
	//读
	string::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	//写
	it = s.begin();
	while (it != s.end())
	{
		*it += 1;
		++it;
	}
	cout << s << endl;
	return 0;
}

image-20221104113404530

image-20221104113830663

2.反向迭代器

反向迭代器提供了rbegin和rend:

image-20221104114840313

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s("hello world");
	string::reverse_iterator rit = s.rbegin();
	while (rit != s.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
	return 0;
}

image-20221104121643204

3.const迭代器

就是加上const进行修饰。普通的迭代器可读可写,而const迭代器可读不可写,因为const修饰this指向的内容,不支持写

image-20221104124630215

image-20221104124837546

image-20221104124858958

image-20221104124923962

const迭代器也可分为正向迭代器和方向迭代器,提供给const对象使用:

void Print(const string& s)
{
	string::const_iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}

	cout << endl;
	string::const_reverse_iterator rit = s.rbegin();
	while (rit != s.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
	cout << endl;
}

int main()
{
	string s("hello world");
	Print(s);
	return 0;
}

image-20221104125342381

c++11还特意区别了普通迭代器和const迭代器提供了下面的接口,作为区分:

image-20221104125608962


八、Capacity容量操作

image-20221104143215255

1.常用接口

函数名称 功能说明
size 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
empty 检测字符串释放为空串,是返回true,否则返回false
clear 清空有效字符
reserve 为字符串预留空间(影响capacity)
resize 将有效字符的个数该成n个,多出的空间用字符c填充(影响size和capacity)
//string类对象支持直接用cin和cout进行输入和输出
void Teststring1()
{
	string s("hello world!");
	cout << s.size() << endl;//12
	cout << s.length() << endl;//12
	cout << s.capacity() << endl;//15
	cout << s << endl;
	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	cout << s.size() << endl;//0
	cout << s.capacity() << endl;//15

	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
	// “aaaaaaaaaa”
	s.resize(10, 'a');
	cout << s.size() << endl;//10
	cout << s.capacity() << endl;//15
	// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	// "aaaaaaaaaa\0\0\0\0\0"
	// 注意此时s中有效字符个数已经增加到15个
	s.resize(16,'x');
	cout << s.size() << endl;//16
	cout << s.capacity() << endl;//31,超过15,扩容
	cout << s << endl;
	// 将s中有效字符个数缩小到5个
	s.resize(5);
	cout << s.size() << endl;//5
	cout << s.capacity() << endl;//15
	cout << s << endl;
}

int main()
{
	Test_string1();
	return 0;
}

image-20221027083307928

值得说一下的是,size()和length()两个接口底层是完全一样的,length是由于发展历史缘故导致出现的,无法避免。功能自然也是一样,这里推荐使用size(),方便后期的接口一致。

2.扩容问题

此外,我们也可以来看看是怎么扩容的(在vs2019下):

image-20221027085332833

在g++下:

image-20221027090226398

image-20221027090254044

但是,扩容也会有开销,如果我们提前知道要开多少,就可以使用reserve():

void TestPushBackReserve()
{
	string s;
	s.reserve(1000);
	size_t sz = s.capacity();
	cout << "capacity changed : " << sz << '\n';
	cout << "making s grow:\n";
	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
}
int main()
{
	TestPushBackReserve();
	return 0;
}

注意:

size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()

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


九、Modifiers修改操作

对于这些接口可以查看文档:

image-20221104144217248

函数名称 功能说明
push_back 在字符串后尾插字符c
append 在字符串后追加一个字符串
operator+= 在字符串后追加字符串str
c_str 返回C格式字符串
find + npos 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr 在str中从pos位置开始,截取n个字符,然后将其返回
void Teststring()
{
	string str("hello");
	str.push_back('X'); // 在str后插入X
	str.append("world"); // 在str后追加一个字符"world"
	str += 'C'; // 在str后追加一个字符'C'
	str += "PP"; // 在str后追加一个字符串"PP"
	cout << str << endl;
	cout << str.c_str() << endl; // 以C语言的方式打印字符串
}

int main()
{
	Teststring();
	return 0;
}

image-20221027120853622

void test1()
{
	string s1("hello world");
	//insert
	s1.insert(0, "hwc ");
	cout << s1 << endl;
	s1.insert(9, "hwc");
	cout << s1 << endl;
	//erasse
	s1.erase(9, 3);
	cout << s1 << endl;
	s1.erase(0, 4);
	cout << s1 << endl;
	s1.erase(5,30);
	cout << s1 << endl;
}
void test2()
{
	string s1("hello world hello world");
	string s2("hello world hello world");
	string s3("we are family");
	string s4(s3);
	//assign
	s1.assign("hehehe",5);
	cout << s1 << endl;
	//replace
	s2.replace(6,5,"hwc");
	cout << s2 << endl;

	//find与replace的结合使用
	size_t pos = s3.find(' ');
	while (pos != string::npos)
	{
		s3.replace(pos, 1, "%20");
		pos = s3.find(' ', pos + 3);
	}
	cout << s3 << endl;
	//+=的使用
	string ret;
	for (auto ch : s4)
	{
		if (ch != ' ')
		{
			ret += ch;
		}
		else
		{
			ret += "%20";
		}
	}
	cout << ret << endl;
}
//c_str
void test3()
{
	string file("test.cpp");
	FILE* fout = fopen(file.c_str(), "r");
	assert(fout);
	char ch = fgetc(fout);
	while (ch != EOF)
	{
		cout << ch;
		ch = fgetc(fout);
	}
	fclose(fout);
}

image-20221101145859990

image-20221101145951558

image-20221101150043780

//取文件后缀
//rfind()和substr
void test4()
{
	string file;
	cin >> file;
	size_t pos = file.rfind('.');
	if (pos != string::npos)
	{
		string sub = file.substr(pos);
		cout << sub << endl;
	}
}

image-20221101152654689


十、非成员函数重载

对于流插入和流提取都是以空格、换行作为结束标志的(scanf也是这个样子的)。

image-20221104155040206

为了解决这个问题,我们可以采用getline,

image-20221104155436411

image-20221104155449916

遇到\n才会结束。

这个有什么用呢。比如计算 字符串最后一个单词的长度

#include <iostream>
using namespace std;
#include <string>
int main() {
    string str;
    getline(cin,str);
    size_t pos = str.rfind(' ');
    cout<<str.size()-pos-1<<endl;
}

十一、总结

对于string类的常用接口使用我们就先说到这里,另外,值得在这里强调对于一些接口的使用我们可以去查文档进行理解使用。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。