【C++】STL- > string类(超详解!!!)


前言

提示:这里可以添加本文要记录的大概内容:

字符串是编程中非常重要的数据类型,用于存储和处理文本信息。在C++中,可以使用两种主要方式来表示和操作字符串:

  • C风格字符串:以空字符(‘\0’)结尾的字符数组,例如"Hello, world!"。这种方式比较传统,但存在一些缺点,例如不易于使用和不安全。
  • string类:C++标准模板库(STL)中提供的类,专门用于表示和操作字符串。它具有易于使用、安全等优点。
    本文将介绍C++ STL中的string类,包括其基本概念、使用方法、模拟实现以及扩展阅读等内容。

提示:以下是本篇文章正文内容,下面案例可供参考

1、string类的出现

1.1 C语言中的字符串

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

1.2 平时使用

在平时使用中,不管是学习还是工作都需要大量用到string,很少有人会用C库中的库函数去操作字符串,string类的出现不仅仅是为了弥补C语言的缺陷,也是为了让我们快速操作字符串

2. 标准库中的string类

2.1 string类的常用文档(重要)!!!!

在库中,甚至是string中的接口都是数量很大的,如果要求我们每一个接口都十分熟悉那显示是不可能的,那么就需要我们学习利用文档学习,下面给出一个常用文档

如果觉得页面卡顿或者是用着不习惯,也可能使用以前的旧版本,更加的简洁(标红处)
在这里插入图片描述

注意:在使用string类时,必须包含#include头文件以及using namespace std;

2.2 string类的常用接口说明(接口原型我这里就不展示了,文档中都有可以去文档中去查找)

1. string类对象的常见构造

在这里插入图片描述
主要考虑以下几个接口
在这里插入图片描述

#include <iostream>
#include <string>
using namespace std;
int main()
{
	string s1;//构造空的string类对象,即空字符串
	string s2("hello cplusplus!!!");//用C-string来构造string类对象
	string s3(s2);//拷贝构造函数
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	return 0;
}

2. string类对象的容量操作

在这里插入图片描述

void testString2()
{
	string s1("hello cplusplus!!");
	size_t n = s1.size();
	cout << n << endl;
	cout << s1.capacity() << endl;
	cout << s1.empty() << endl;
	s1.clear();//清空字符串中的内容,使其成为空字符串,但是不会改变size的值和容量的值
	n = s1.size();
	cout << n << endl;
	cout << s1.capacity() << endl;
	cout << s1.empty() << endl;
	s1.reserve(100);//reserve在英文中的意思是预定的意思,相当于预定了你指定空间的容量
	n = s1.size();
	cout << n << endl;
	cout << s1.capacity() << endl;
	cout << s1.empty() << endl;
	s1.resize(20,'a');
	n = s1.size();
	cout << n << endl;
	cout << s1.capacity() << endl;
	cout << s1.empty() << endl;
}

注意:

  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_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
  4. reserve(size_t n = 0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserve不会改变容量大小。

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

这里的迭代器大家目前可以暂时理解为指针
在这里插入图片描述
上述图片的表述有一些错误,rbegin和rend和begin、end相反,他们是反着打印,获取的也是相反的(具体可以去之前提到的文档链接去查到内容即可)

void testString3()
{
	string s1("hello cplusplus!!");
	const string s2("hello cplusplus!!");
	cout << s1 << " " << s2 << endl;
	cout << s1[0] << " " << s2[0] << endl;
	s1[0] = 'a';
	//s2[0] = 'a';//这里会发生编译错误,因为const类型对象不能修改
	cout << s1<<endl;



	string s("hello,c++");
	//三种遍历方式:
	//需要注意的是,以下三种方式除了遍历打印string对象,还可以适用于遍历修改string中的字符
	//另外,三种方式中,对于string而言,第一种方式使用的最多

	// 1. for+operator[]
		for(size_t i = 0; i < s.size(); i++)
		{
			cout << s[i]<<" ";
		}
		cout << endl;
	// 2.迭代器(这种方式底层大家暂时先不用明白,先了解大概怎么用,了解用法)
		//string::iterator it = s.begin();
		auto it = s.begin();

		while (it != s.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	// 3.范围for
		for (auto& ch : s)
		{
			cout << ch << " ";
		}

}

4. string类对象的修改操作

在这里插入图片描述

void testString4()
{
	string s1("hello_c++");
	string s2("-hhh");
	s1.push_back('a');
	cout << s1 << endl;
	s1.append(s2);
	cout << s1 << endl;
	s1.append("xxx");
	cout << s1 << endl;
	s1 += '\0';
	s1 += "111";
	cout << s1 << endl;
	const char* s = s1.c_str();
	cout << s << endl;
	size_t n = s1.find("111",1);
	cout << n << endl;
	 n = s1.rfind("hhh",18 );
	cout << n << endl;
	string s3 = s1.substr(0, 3);
	cout << s3 << endl;
}

注意:

  1. 在string尾部追加字符时,s.push_back© / s.append(1, c) / s += 'c’三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
  2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

5. string类非成员函数

在这里插入图片描述

**void testString5()
{
	string s1("hello_cpp");
	string s3 = s1.substr(0, 3);
	cout << s3 << endl;
	s1 = s1 + s3;
	cout << s1 << endl;
	string s2;
	//operator>>(cin,s2);
	//cout << s2 <<endl;
	getline(cin,s2);
	cout << s2 << endl;
	cout << ("abc" > "bbb") << endl;
}
**

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

2.3 关于string的练习题

  1. 题目一
  2. 题目二
  3. 题目三
  4. 题目四
  5. 题目五
  6. 题目六
  7. 题目七
  8. 题目八
  9. 题目九
  10. 题目十
  11. 题目十一

3. string类的模拟实现

3.1 经典的string类问题

上面已经对string类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己
来模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。大家看下以
下string类的实现是否有问题?

class string
{
public:
 /*string()
 :_str(new char[1])
 {*_str = '\0';}
 */
 //string(const char* str = "\0") 错误示范
 //string(const char* str = nullptr) 错误示范
 string(const char* str = "")
 {
 // 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
 if(nullptr == str)
 {
	 assert(false);
	 return;
 }
 
	 _str = new char[strlen(str) + 1];
	 strcpy(_str, str);
 }
 
 ~string()
 {
 if(_str)
 {
	 delete[] _str;
	 _str = nullptr;
 }
 }
 
private:
	 char* _str;
};
// 测试
void Teststring()
{
 	string s1("hello bit!!!");
 	string s2(s1);
}

在这里插入图片描述
说明:上述string类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构
造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块
空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

3.2 浅拷贝

浅拷贝:也称值拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共
享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为
还有效,所以 当继续对资源进项操作时,就会发生发生了访问违规。要解决浅拷贝问题,C++中引入了深拷
贝。

3.3 深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情
况都是按照深拷贝方式提供。

在这里插入图片描述

3.3.1 传统版写法的string类

class string
{
public:
	string(const char* str = "")
	{
		// 构造string类对象时,如果传递nullptr指针,认为程序非法,此处断言下
		if (nullptr == str)
		{
			assert(false);
			return;
		}

		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	string(const string& s)
		: _str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}

	string& operator=(const string& s)
	{
		if (this != &s)
		{
			char* pStr = new char[strlen(s._str) + 1];
			strcpy(pStr, s._str);
			delete[] _str;
			_str = pStr;
		}

		return *this;
	}

	~string()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}

private:
	char* _str;
};

3.3.2 现代版写法的string类

class string
{
public:
	string(const char* str = "")
	{
		if (nullptr == str)
			str = "";
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}

	string(const string& s)
		: _str(nullptr)
	{
		string strTmp(s._str);
		swap(_str, strTmp._str);
	}

	// 对比下和上面的赋值那个实现比较好?
	string& operator=(string s)
	{
		swap(_str, s._str);
		return *this;
	}

	/*
	string& operator=(const string& s)
	{
	if(this != &s)
	{
	string strTmp(s);
	swap(_str, strTmp._str);
	}

	return *this;
	}
	*/

	~string()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}

private:
	char* _str;
};

3.3 写时拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。

引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给
计数增加1,当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源,如果计数为1,说明该
对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

3.4 string类的模拟实现

查看完整代码

总结

本文介绍了C++ STL中的string类的基本概念和使用方法,包括:

  • string类的创建和初始化
  • 访问和修改字符串内容
  • 常用字符串操作函数
  • 模拟实现string类
  • 扩展阅读
    通过本文的学习,读者应该能够掌握string类的基本用法,并能够在实际编程中使用它来完成各种字符串操作。

参考资料(都是一些很好的文章和文档搜索网站)

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值