总结:

  • string是表示字符串的字符串类
  • 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作
  • string在底层实际是:basic_string模板类的别名(using string = std::basic_string<char>)
  • 不能操作多字节或者变长字符的序列

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

3.1. string类对象的常见构造

代码语言:javascript

AI代码解释

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


3.2. string类的构造函数

接下来我们模拟实现一下string的构造函数。

代码语言:javascript

AI代码解释

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;//
};

这段代码还是比较简单的,就是要注意,strcpy会将字符串末尾“\0”也拷贝过去。


3.3. string的拷贝构造

基本写法:

代码语言:javascript

AI代码解释

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

现代写法:

代码语言:javascript

AI代码解释

        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);
		}

这里的基本写法不能理解,但是这个现代写法似乎有点抽象,我们在后面的赋值拷贝来进行讲解。


3.4. string类的赋值拷贝
3.4.1. 常规构造

我们在写任何一个一个类型的赋值运算符重载类型的时候都要考虑下面这几个问题:

  • 是否将返回值类型设置为该类型的引用,并在函数结束时返回该实例自身的引用。因为只有返回引用,才能支持连续赋值。如果是void,那么该重载的赋值运算符将不支持连续赋值。

  • 是否将传入的参数声明为该类型的引用,因为如果传入的是实例,那么从形参到实参还需要调用拷贝构造函数,传引用就可以避免这样的无谓消耗,提升代码效率。同时我们在赋值运算符内部不会对实例产生修改,所以将在引用参数前面加上const关键字。

代码语言:javascript

AI代码解释

string& operator=(const string& s)
  • 赋值前是否将就内存清理掉了,如果没有释放掉已有的内存,可能就会导致内存泄漏。
  • 判断传入的参数与当前实例是不是同一个实例,如果是的话不进行赋值直接返回。如果现实没有做这个判断,传入参数与与当前实例是同一个实例,那么在释放就内存的时候,传入的参数的内存也被释放掉了,就会找不到要赋值的东西了。

结合上面四点我们写出下面的代码:

代码语言:javascript

AI代码解释

	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就会处于既不是nullptr,也没有保持原来状态,这就违背了异常安全原则。

3.4.2. 考虑异常安全的解法

有两种方法来解决这个安全问题:

  • 第一种简单的方法就是,先分配新内存,然后在delete旧内存,这样的话就算分配内存没有成功,也不会破坏掉原来的string实例
  • 第二种方法就是我创建一个临时实例,这个临时实例是传入实例的副本,因为是调用的拷贝构造函数,所以抛出内存分配异常会在修改实例状态前。然后我们交换临时实例和原来实例,又因为临时实例是一个局部变量,出了函数的作用域会自动调用析构函数

更多推荐