C++容器---string类

1. 什么是STL

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

1.1 STL的六大组件

在这里插入图片描述

1.2 学好STL的三种境界

第一境界:熟练使用STL
第二境界:了解泛型技术的内涵与STL的学理乃至实作
第三境界:扩充STL

简单总结一下:学习STL的三个境界:能用,明理,能扩展 。

2. string类

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

总结:

  1. string是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator> string;
  4. 不能操作多字节或者变长字符的序列。
  5. 在使用string类时,必须包含#include头文件以及using namespace std;这种方式在平时练习的时候使用,但是当你真正的在一个工程中的时候,最好使用std::string 这种方式单独的对string进行展开,如果这个string在这个工程中特别的常用,那么也可以单独的使用using std::string进行展开

2.1 string类的常用接口说明

2.1.1.string类对象的常见拷贝构造

在这里插入图片描述

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

int main()
{
	//string();
	string s1;

	//string (const char* s);
	string s2("hello world");

	//string(const string& str);拷贝构造
	string s3(s2);

	//string(const string& str, size_t pos, size_t len = npos); 和上面的拷贝构造构成函数重载
	string s4(s2,6,10);

	//这里你不给npos这个传参,他就会给默认的最大值也就是-1的补码,也会默认到最后结束
	string s5(s2, 6);
	string s6(s2, 6, 3);
	
	//string (size_t n, char c);
	string s7(10, 'x');

	//string是重载了operator<< 和operator>>的
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	cout << s6 << endl;
	cout << s7 << endl;
	return 0;
}

在这里插入图片描述

2.1.2 string类对象的容量操作

在这里插入图片描述
sizelength都是获取字符串的长度(是不算‘\0’的),那么为什么都有了size还会有一个length呢?那是由于历史性的原因,其实对于string类来说是比STL出现的早的,在早期都是使用length来表示字符串的长度,也好理解,但是后面为了和STL的风格保持一致,所以引入了一个size也是获取字符串的长度。

capacity是开空间的大小,显示的是能存放的数据个数,但实际开空间的大小要+1,因为他没有把‘\0’算上。

int main()
{
	string s1("hello world");
	cout << s1.size() << endl;
	cout << s1.length() << endl;
	cout << s1.capacity() << endl;

	// 将s1中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小 
	s1.clear(); //返回的是bool值   
	cout << s1.size() << endl;   
	cout << s1.capacity() << endl;

	return 0;
}

在这里插入图片描述

int main()
{

	int n = 100;
	string numstr;
	int capacity = numstr.capacity();
	cout << "capacity:" << numstr.capacity() << endl;
	for (int i = 0; i < n; i++)
	{
		numstr.push_back('0' + i);
		if (capacity != numstr.capacity())
		{
			cout << "capacity:" << numstr.capacity() << endl;
			capacity = numstr.capacity();
		}
	}

	cout << numstr << endl;

	return 0;
}

在这里插入图片描述
但是你会发现你的很多增容的过程都是无效的,因为你不断的在增容,一旦原先所开辟的空间不够,就需要在重新开辟一个空间,然后把上面的数据拷贝下来,在对原先开辟的空间进行释放,就会很繁琐,并且这个过程中会产生内存碎片的问题,所以当你知道你所要开辟的空间大小的时候就直接可以使用reserve来进行一个空间的直接开辟。

	int n = 100;
	string numstr;
	numstr.reserve(n);
	int capacity = numstr.capacity();
	cout << "capacity:" << numstr.capacity() << endl;
	for (int i = 0; i < n; i++)
	{
		numstr.push_back('0' + i);
		if (capacity != numstr.capacity())
		{
			cout << "capacity:" << numstr.capacity() << endl;
			capacity = numstr.capacity();
		}
	}

	cout << numstr << endl;

	return 0;

在这里插入图片描述
reserve只会改变capacity,resize不但会改变capacity的大小,而且也会改变size的大小,当你没有给他指定size传什么的时候,他就会默认传‘\0’,所以resize的使用地方是:你不但想要对空间增容,而且想要对增容的空间初始化。

	int n = 100;
	string numstr;
	//numstr.resize(n);
	numstr.resize(n,‘x’);
	int capacity = numstr.capacity();
	cout << "capacity:" << numstr.capacity() << endl;
	for (int i = 0; i < n; i++)
	{
		numstr.push_back('0' + i);
		if (capacity != numstr.capacity())
		{
			cout << "capacity:" << numstr.capacity() << endl;
			capacity = numstr.capacity();
		}
	}

	cout << numstr << endl;

	return 0;

numstr.resize(n);
在这里插入图片描述
numstr.resize(n,‘x’);
在这里插入图片描述

注意

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

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

  3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用’\0’来填充多出的元素空间resize(size_tn, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。

  4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

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

在这里插入图片描述

char& operator[](size_t pos);

出了函数作用域,返回对象还存在,可以使用引用返回
1.使用引入可以减少拷贝
2.使用引入的好处就是不止可以读到字符串的pos位置,而且还可以修改

const char& operator[] (size_t pos) const
//将字符串转换为整数
//把字符串逆置
class Solution {
public:
	int StrToInt(string str) {
		int num = 0;
		for (size_t i = 0; i < str.size(); ++i) //为了和C++中的size区分开来,这里定义变量使用的类型都是size_t(unsigned int)
		{
			num *= 10;
			num += str[i] - '0';
		}
		return num;
	}

	string Reverse(string s)
	{
		int begin = 0, end = s.size() - 1;
		while (begin < end)
		{
			swap(s[begin], s[end]);//在C++的库里面就有交换函数,所以不需要在自己去实现了
			//但是平时是不咋使用的
			//swap(s.at(begin), s.at(end));
			begin++;
			end--;
		}
		return s;
	}

};


int main()
{
	cout << Solution().StrToInt("1234") << endl;
	cout << Solution().Reverse("1234") << endl;
	return 0;
}

这里还可以使用at 和operator[]是一样的swap(s.at(begin), s.at(end));,他们的区别就是[ ]是断言,而at是采用抛异常,但是at平时是不咋使用的

迭代器
现阶段就把迭代器理解为指针,当然后面会对其进行纠正。

int StrToInt(string str) {
		int num = 0;
		//一般给迭代器的名称就起为it
		string::iterator it = str.begin();
		while (it != str.end()) //这里str.end()指向的是‘\0’的位置
		{
			num *= 10;
			num += *it - '0';
			it++;
		}
		return num;
	}

rbegin 和 rend反向迭代器
其中rbegin指向的就是字符串的最后一个有效的字符位置,然后这里++他是会往前遍历的,rend()指向的就是空的位置

	int StrToInt(string str) {
		int num = 0;
		//反向迭代器
		string::reverse_iterator rit = str.rbegin();
		//这里的变量名字很长,很麻烦,所以这里就可以使用到auto了
		//auto rit = str.rbegin();
		while (rit != str.rend()) // 这里的str.rend()代表的是空
		{
			num *= 10;
			num += *rit - '0';
			rit++; //这里的++就是从最后一个位置往前走
		}
		return num;
	}

总结:string的遍历建议使用下标+[ ]或者范围for,但是也要知道迭代器的用法,因为迭代器是容易通用的遍历方式

范围for(C++11)
范围for的底层又是迭代器,所以所有的容器都支持范围for

int main()
{

	string s("hello");
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
}

在这里插入图片描述

2.1.4 string类对象的修改操作

在这里插入图片描述

int main()
{
	string s("hello");
	for (auto e : s)
	{
		cout << e <<" ";
	}
	cout << endl;

	s.push_back('\0');
	s.push_back('x');
	s.push_back('x');
	s.push_back('x');
	
	s.append("world");
	
	cout << s.c_str() << endl;
	cout << s << endl;

}

在这里插入图片描述

所以对于s这个string所创造的对象来说,相当于迭代器遍历,不遇见最后的‘\0’是不会停止的,但是对于c_str来说,遇见‘\0’就相当于到头了。

对于在string所定义出来的对象的尾部增加字符使用的是push_back('x'),但是当在尾部增加一个字符串的时候又要使用append('xxx'),说实话很麻烦,所以一般的做法就是重载一个+=,那么这两个都可以同时实现 s += 'x'; s+= 'xxxxx';但是这个+=运算符的实现又是依靠push_back 和 append。

int main()
{
	string s1("hello");
	//basic_string& insert (size_type pos, size_type n, charT c);
	cout << s1.insert(0, 1, 'x') << endl;


	string file1("string.cpp");
	string file2("string.cpp.tar.zip");
	string file3("string.txt");

    //size_type find (charT c, size_type pos = 0) const;
	size_t pos1 = file1.find('.');
	if (pos1 != string::npos)
	{
		//basic_string substr (size_type pos = 0, size_type len = npos) const;
		cout << file1.substr(pos1, file1.size() - pos1)<<endl;
		//cout << file1.substr(pos1)<<endl;
	}

    //size_type rfind (charT c, size_type pos = npos) const;
	size_t pos2 = file2.rfind('.');
	if (pos2 != string::npos)
	{
		cout << file2.substr(pos2)<<endl; //不给参数也是可以的,因为有默认的缺省值
	}
}

在这里插入图片描述

int main()
{

	string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t pos1 = url.find(':');
	if (pos1 != string::npos)
	{
		cout << url.substr(0, pos1)<<endl;
	}

	size_t pos2 = url.find('/', pos1 + 3);
	if (pos2 != string::npos)
	{
		cout << url.substr(pos1 + 3, pos2-(pos1 + 3))<<endl;
	}

	cout << url.substr(pos2 + 1,string::npos)<<endl; 
	
	//隐藏前面的域名
	string copy = url; //拷贝构造一个,不改变原始的url
	//basic_string& erase(size_type pos = 0, size_type len = npos);
	cout << copy.erase(0, pos1+3);

	return 0;
}

在这里插入图片描述

2.1.5 string类非成员函数

在这里插入图片描述

3. Leetcode好的string类题

Leetcode链接: link.反转字符串

牛客网链接: link.找字符串中第一个只出现一次的字符

牛客网链接: link.字符串里面最后一个单词的长度

Leetcode链接: link.验证一个字符串是否是回文

Leetcode链接: link.字符串相加

Leetcode链接: link.翻转字符串III:翻转字符串中的单词

Leetcode链接: link.字符串相乘

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值