当前位置: 首页 > article >正文

【C++】string类

1 标准库中的string类

在C语言中,字符串都是以 ‘\0’ 结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,例如:strlen,strcpy,strcat… 但是这些库函数与字符串是分离开的,不太符合C++中面向对象的思想,而且底层空间仍需要用户自己管理,并且很有可能会越界访问。

因此在C++中,为了更简单,方便,快捷的使用字符串类型,C++提供了string类

1.1string类

在使用string类时,必须包含string的头文件 #include

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、不能操作多字节或者变长字符的序列。

2 string类的常见构造及模拟实现

2.1string类对象的常见构造

在这里插入图片描述
在这里插入图片描述

int main()
{
	string s1;   //构造空的s1  等价于string s1("");
	string s2("hello");//用C格式字符串构造
	string s3(s2);
 
	return 0;
}

我们来自己模拟实现一下string类的构造函数

2.2string类的构造函数

class string
{
public:
	//全缺省的构造函数
	string(const char* str = "")
		:_size(strlen(str))
		,_capacity(_size)
	{
		//实际上要多开一个 留给'\0'
		_str = new char[_capacity + 1];
		strcpy(_str, str);//会把'\0也拷过去'
	}
private:
	char* _str;
	size_t _size;//有效字符个数 不算'/0';
	size_t _capacity;//实际存储有效字符的空间
};

在这里插入图片描述
想必这段代码大家肯定是可以看的懂得。

2.3string类的拷贝构造

基本写法

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

现代写法

void swap(string& s)
	{
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}
	//现代写法
	string(const string& s)
		:_str(nullptr)
		,_size(0)
		,_capacity(0)
	{
		string tmp(s._str);
		swap(tmp);
	}

基本写法想必大家肯定很好理解,但是现代写法可能会有点疑惑,我们在下面的赋值拷贝就会分析。

2.4 string类的赋值构造

2.4.1常规解法

我们在写任何一个类型的赋值运算符重载函数时一定要考虑到下面这几个方面:

1》是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例自身的引用(*this)。只有返回一个引用,才能支持连续赋值。否则,如果函数的返回值是void,在使用该赋值运算符将不能支持连续赋值。这与我们日常使用是违背的。

在这里插入图片描述

2》是否把传入的参数的类型声明为常量引用。如果传入的参数不是引用而是实例,那么从形参到实参会调用一次复制拷贝函数。把参数声明为引用可以避免这样无谓消耗。能提高代码效率。同时,我们在赋值运算符函数内不会改变传入的实例的状态,因此应该为传入的引用参数加上const关键字。

string& operator=(const string& s)

3》是否释放实例自己已有的内存。如果我们忘记在分配新内存之前释放自身已有的空间,则程序会出现内存泄漏

4》判断传入的参数和当前的实例(this)是不是同一个实例。如是同一个,则不进行赋值操作,直接返回即可。如果实现不做判断就进行赋值,那么在释放实例自身空间内存的时候就会导致严重的问题:当this和传入的参数是同一个实例时,一旦释放了自身的内存,传入的参数的内存也同时被释放了,因此再也找不到需要赋值的内容了。

结合以上4点,我们可以写出:

string& operator=(const string& s)
{
if (this == &s)
return *this;//自己赋值自己 直接返回
delete[] _str;
_str = nullptr;
_str = new char[strlen(s._str) + 1];
strcpy(_str, s._str);
return *this;
}

这种经典解法已经能够解决问题了,但是我们如果在极端情况下考虑如果delete释放_str后,内存不足导致new char抛出异常,则_str将是一个空指针,这样很容易导致程序崩溃。也就是说,一旦在赋值运算符函数内部抛出一个异常,string的实例不再保持有效的状态,这就违背了异常安全原则。

2.4.2考虑异常安全法的解法:

我们有两种解法:

1》一种简单的办法是我们先用new分配新内容,再用delete释放已有的内容。这样只在分配内容成功之后才会释放原来的内容,也就是当分配内存失败时我们能确保string的实例不会被修改。

2》第二种解法是我们先创建一个临时实例,再交换临时实例和原来的实例。由于我们创建的临时实例时一个局部变量,一旦程序除了该局部变量的作用域,就会自动调用析构函数,把这个临时空间所指向的内存释放掉。由于这个临时空间指向的内存就是我们原来的实例的内存。这就相当于自动调用析构函数释放实例的内存。

并且我们在string的构造函数里面用new分配内存,如果由于内存不足抛出异常,但是我们还没有修改原来实例的状态,因此实例的状态还是有效的,这也就保证了异常安全性。

下面是考虑异常安全的代码:

string& operator=(const string& s)
	{
		if (this != &s)
		{
			string tmp(s._str);
 
			std::swap(_str, tmp._str);
			std::swap(_size, tmp._size);
			std::swap(_capacity, tmp._capacity);
		}
 
		return *this;
	}

我们使用我们自己模拟实现的看看效果:

int main()
{
	//std库
	//std::string s1;   //构造空的s1  等价于string s1("");
	//std::string s2("hello");//用C格式字符串构造
	//std::string s3;
 
	//自己模拟实现
	s::string s1("hello world");
	s::string s2;
	s::string s3;
 
	s3 = s2 = s1;
 
	return 0;
}

在这里插入图片描述

3 全部代码

namespace s
{
	class string
	{
	public:
		//全缺省的构造函数
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			//实际上要多开一个 留给'\0'
			_str = new char[_capacity + 1];
			strcpy(_str, str);//会把'\0也拷过去'
		}
 
		string(const string& s)
			:_size(strlen(s._str))
			, _capacity(_size)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}
 
		//string& operator=(const string& s)
		//{
		//	if (this == &s)
		//		return *this;//自己赋值自己 直接返回
		//	delete[] _str;
		//	_str = nullptr;
		//	_str = new char[strlen(s._str) + 1];
		//	strcpy(_str, s._str);
		//	return *this;
		//}
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s._str);
 
				std::swap(_str, tmp._str);
				std::swap(_size, tmp._size);
				std::swap(_capacity, tmp._capacity);
			}
 
			return *this;
		}
 
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
				_size = 0;
				_capacity = 0;
			}
		}
	private:
		char* _str;
		size_t _size;//有效字符个数 不算'/0';
		size_t _capacity;//实际存储有效字符的空间
	};
}

http://www.kler.cn/a/587598.html

相关文章:

  • <rust><tauri><GUI>基于tauri和rust,编写一个二维码生成器
  • 【鸿蒙开发】Hi3861学习笔记-Visual Studio Code安装(New)
  • 在线招聘小程序:AI简历筛选与精准职位推荐服务
  • 【生日蛋糕——DFS剪枝优化】
  • 网络安全系统集成
  • 微信小程序项目 tabBar 配置问题:“pages/mine/mine“ need in [“pages“]
  • 缓存之美:Guava Cache 相比于 Caffeine 差在哪里?
  • 游戏引擎学习第157天
  • neo4j中常用cql命令汇总(基础版)
  • Spark eventlog
  • [文献阅读] 可变形卷积DCN - Deformable Convolutional Networks
  • 服务性能防腐体系:基于自动化压测的熔断机制
  • 【软考-架构】3.4、数据库新技术-SQL语言
  • 基于牛优化( OX Optimizer,OX)算法的多个无人机协同路径规划(可以自定义无人机数量及起始点),MATLAB代码
  • 【eNSP实战】将路由器配置为DHCP服务器
  • 数据库原理10
  • 锂电池保护板测试仪:守护能源安全的科技卫士
  • 【STM32】USART串口收发HEX数据包收发文本数据包
  • 力扣 11.盛水最多的容器(双指针)
  • QT核心类:基础类、GUI类、多媒体与图表、网络与数据库