目录
  • 一、string类
  • 二、string的常用见用法
    • 2、1 string对象的构造
      • 2、1、1 string对象的构造的使用方法
      • 2、1、2 string()的底层实现
      • 2、1、3 string(const char* s)的底层实现
    • 2、2 string对象的修改操作
      • 2、3 string对象的容量操作
        • 2、4 string对象的访问和遍历操作
        • 三、string常用结构的底层实现
          • 3、1 初建结构
            • 3、2 返回大小和容量
              • 3、3 拷贝构造和赋值重载
                • 3、4 扩容(reserve)
                  • 3、5 插入(push_back、append、operator+=、insert)
                    • 3、6 删除(erase)
                      • 3、7 查找(find)
                        • 3、8 返回子串(substr)
                          • 3、9 迭代器(iterator)
                            • 3、10 比较(>、<、>=、<=、==、!=)
                            • 四、总结

                              一、string类

                              在学习 string 前,我们不妨先来了解一下 string 类到底是什么,有什么用呢?下图是C++标准库中的对 string 内容:

                              C++中String的语法及常用接口的底层实现详解

                              what???没错,C++标准库都是英语解释。我们也应该试着去适应,不懂的可以查阅。当然,在这里我就直接给出翻译,主要是以下内容:

                              字符串是表示字符序列的类;

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

                              了解到上面的内容后,我们要开始真正的学习 string 的用法了。

                              二、string的常用见用法

                              2、1 string对象的构造

                              2、1、1 string对象的构造的使用方法

                              最为常用的无非就是我们用串string来构造一个对象,也就是存储一个字符,常用的方法有如下几点:

                              • string()——构造空的 string 类对象,即空字符串;
                              • string(const char* s)——用 char* 来构造 string 类对象;
                              • string(size_t n, char c)——string类对象中包含n个字符c
                              • string(const string&s)——拷贝构造函数

                              下面是使用方法所对应的实例,帮助更好的理解其用法。

                              C++中String的语法及常用接口的底层实现详解

                              根据上面的实例和对应的输出结果,我们可以更好的理解。 

                              2、1、2 string()的底层实现

                              构造空串,其有效字符长度为0,但是实际上是开辟了一个字节的空间存储 ‘\0’ 的。具体如下:

                              string()
                              		:_str(new char[1])
                              		, _size(0)
                              		,_capacity(0)
                              	{
                              	}

                              2、1、3 string(const char* s)的底层实现

                              我们这里就给出以上两个底层的构造实现,其余两个类似,就不再给出。具体实现如下:

                              string(const char* str = "") //默认空串。注意:空串是以 \0 结尾
                              		{
                              			_size = strlen(str);
                              			_capacity = _size;
                              			_str = new char[_size + 1];
                              			strcpy(_str, str);
                              		}

                              2、2 string对象的修改操作

                              当我们构建好对象后,我们接下来就要往对应的字符串对象进行修改操作了。常用的修改操作无非就是插入和查找,具体有如下几种常见用法:

                              • push_back——在字符串后尾插字符c
                              • insert——在pos位置插入n个字符或者插入一个字符串;
                              • append ——在字符串后追加一个字符串
                              • operator+=——在字符串后追加字符或者字符串str; c_str——返回C格式字符串;
                              • find——从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置。

                              我们再看其具体的使用方法实例,如下图:

                              C++中String的语法及常用接口的底层实现详解

                              上图为常用的修改用法。当然,其他的用法还有很多。如果想了解的过可去C++官网(cppreference)或者 cplusplus 的标准库中查询。具体也可看下图:

                              insert

                              C++中String的语法及常用接口的底层实现详解

                              operator+= 

                              C++中String的语法及常用接口的底层实现详解

                              find 

                              C++中String的语法及常用接口的底层实现详解

                              2、3 string对象的容量操作

                              在平常对字符串的操作中,我们也经常需要去了解到字符串的实际长度为多少,或者改数组到底能够存下多长的字符串,又或是修改字符串的长度和空间大小。C++的string类中,这些操作都提供了相应的接口,具体如下:

                              • size——返回字符串有效字符长度;
                              • length——返回字符串有效字符长度
                              • empty——检测字符串释放为空串,是返回true,否则返回false;
                              • reserve——为字符串预留空间;
                              • resize——将有效字符的个数该成n个,多出的空间用字符c填充;
                              • capacity——返回空间总大小;
                              • clear——清空有效字符;

                              我们发现。size和length的功能一样的。确实都是求字符串的有效长度。那我们接着看其具体的实例:

                              C++中String的语法及常用接口的底层实现详解

                              注意:capacity返回的是空间的总大小size和length返回的是字符串的实际有效长度。 两者是有所区别的。一个字符串的capacity是有底层的具体实现决定的,不同的编译器可能实现的是不同的。

                              2、4 string对象的访问和遍历操作

                              string对象的访问,支持像数组一样使用 [] 进行访问,也可通过迭代器进行访问,具体有如下用法:

                              • operator[]——返回pos位置的字符,const string类对象调用;
                              • begin+ end——begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭 代器;
                              • 范围for——C++11支持更简洁的范围for的新遍历方式。

                              其具体的用法实例如下:

                              C++中String的语法及常用接口的底层实现详解

                              范围for的底层实现就是用迭代器来实现的。写起来更加的便捷和方便。 

                              以上为string中较为常用的接口。以上完全足够我们平常的使用了,如果想要了解的更多,可参考C++的标准库。不过string容器一共实现了106个接口!!!其中大部分都是冗余的。这也是很多人吐槽string类实现的过于复杂和冗余的一个重要原因。所以在查看时,我们只需要看自己想要了解的接口即可。 

                              三、string常用结构的底层实现

                              3、1 初建结构

                              我们通过上述的构造,不难发现也不难理解string的底层其实就是一个字符指针,该指针指向一个数组。当然,我们还需要两个变量来维护其有效长度(_size)数组容量(_capacity)

                              其次,我们自己实现的string类为了区分std命名空间,我们可自己设置一个命名空间。处型的模拟实现如下:

                              namespace gtm
                              {
                              	class string
                              	{
                                   public:
                                      //string()
                              		//	:_str(new char[1])
                              		//	, _size(0)
                              		//	,_capacity(0)
                              		//{
                              		//}
                              		//string(const char* str)
                              		//	:_str(new char[strlen(str) + 1])  //三次strlen函数,效率低。
                              		//	,_size(strlen(str))
                              		//	,_capacity(strlen(str))
                              		//{
                              		//	strcpy(_str, str);
                              		//}
                              		// 不再使用strlen函数,初始化列表与变量声明顺序固定
                              		string(const char* str = "") //默认空串。注意:空串是以 \0 结尾
                              		{
                              			_size = strlen(str);
                              			_capacity = _size;
                              			_str = new char[_size + 1];
                              			strcpy(_str, str);
                              		}
                                      ~string()
                              		{
                              			delete[] _str;
                              			_str = nullptr;
                              			_size = _capacity = 0;
                              		}
                                   private:
                              		char* _str;
                              		size_t _size;
                              		size_t _capacity;
                              	};

                              注意,我们上述省略了无参的构造。原因是我们在字符串的构造中有缺省参数,即为空串。

                              3、2 返回大小和容量

                              这两个部分,是比较容易实现的两部分。同时也是较为常用的两部分。具体如下:

                              size_t size() const
                              		{
                              			return _size;
                              		}
                              		size_t capacity() const
                              		{
                              			return _capacity;
                              		}

                              3、3 拷贝构造和赋值重载

                              这两部分较为复杂的两部分。其中均需要深拷贝去实现完成,而浅拷贝是不可以的。注意:拷贝构造使用一个已定义变量去初始化另一个变量,赋值重载是两个已定义变量进行赋值

                              具体实现如下:

                              //深拷贝
                              		//string(const string& s)
                              		//	:_str(new char[s._capacity+1])
                              		//	,_size(s._size)
                              		//	,_capacity(s._capacity)
                              		//{
                              		//	strcpy(_str, s._str);
                              		//}
                              		void swap(string& tmp)
                              		{
                              			//调用全局的swap
                              			::swap(_str, tmp._str);
                              			::swap(_size, tmp._size);
                              			::swap(_capacity, tmp._capacity);
                              		}
                              		//借助变量tmp
                              		string(const string& s)
                              			:_str(nullptr) 
                              			, _size(0)
                              			, _capacity(0)
                              		{
                              			string tmp(s._str);
                              			swap(tmp);
                              		}
                              		//赋值
                              		//string& operator=(const string& s)
                              		//{
                              		//	if(this == &s)
                              		//	{
                              		//		return *this;
                              		//	}
                              		//	//先开空间拷贝数据,以防new失败销毁原来的空间
                              		//	char* tmp = new char[s._capacity + 1];
                              		//	strcpy(tmp, s._str);
                              		//	delete[] _str;
                              		//	_str = tmp;
                              		//	_size = s._size;
                              		//	_capacity = s._capacity;
                              		//	return *this;
                              		//	//delete[] _str;
                              		//	//_str = new char[s._capacity + 1];
                              		//	//strcpy(_str, s._str);
                              		//	//_size = s._size;
                              		//	//_capacity = s._capacity;
                              		//	return *this;
                              		//}
                              		//string& operator=(const string& s)
                              		//{
                              		//	if(this == &s)
                              		//	{
                              		//		return *this;
                              		//	}
                              		//	string tmp(s._str);
                              		//	swap(tmp);
                              		//  return *this;
                              		//}
                              		string& operator=(string s)
                              		{
                              			if (this == &s)
                              			{
                              				return *this;
                              			}
                              			swap(s);
                              			return *this;
                              		}

                              上述的辅助重载我们巧妙地借助了临时变量s。当赋值完成后,出了作用域s会自动调用戏后进行销毁,这里是需要反复理解的。

                              3、4 扩容(reserve)

                              我们可简单的理解reserve为扩容(扩容的前提为要求的容量比原来的大),但是我们要记得把字符数组中原有的内容拷贝过来,并且释放之前所动态开辟的空间。 具体实现如下:

                              void reserve(size_t capacity)
                              		{
                              			if (capacity > _capacity)
                              			{
                              				char* tmp = new char[capacity + 1];
                              				strcpy(tmp, _str);
                              				delete[] _str;
                              				_str = tmp;
                              				_capacity = capacity;
                              			}
                              		}

                              3、5 插入(push_back、append、operator+=、insert)

                              插入的实现,主要的点就是是否要进行扩容。其次,当我们实现push_back和append后,其他的均可复用这两个结构进行实现。具体实现如下:

                                      void push_back(char ch)
                              		{
                              			if (_size == _capacity)
                              			{
                              				reserve(_capacity == 0 ? 4 : _capacity * 2);
                              			}
                              			_str[_size] = ch;
                              			_size++;
                              			_str[_size] = '\0';
                              		}
                              		void append(const char* str)
                              		{
                              			size_t len = strlen(str);
                              			if (len + _size > _capacity)
                              			{
                              				reserve(len + _size >= _capacity * 2 ? len + _size : _capacity * 2);
                              			}
                              			strcpy(_str + _size, str);
                              			_size += len;
                              		}
                              		void append(const string& s)
                              		{
                              			append(s._str);
                              		}
                              		void append(int n, char ch)
                              		{
                              			reserve(_size + n);
                              			for (int i = 0; i < n; i++)
                              			{
                              				push_back(ch);
                              			}
                              		}
                              		string& operator+= (char ch)
                              		{
                              			push_back(ch);
                              			return *this;
                              		}
                              		string& operator+= (const char* str)
                              		{
                              			append(str);
                              			return *this;
                              		}
                              		string& insert(size_t pos, char ch)
                              		{
                              			assert(pos <= _size);
                              			if (_size == _capacity)
                              			{
                              				reserve(_capacity == 0 ? 4 : _capacity * 2);
                              			}
                              			//注意,当运算数一个是有符号,另一个是无符号时,有符号的运算数会强制类型转换为无符号数。pos等于0的位置插入,end--后为超大数据,会出错。
                              			//int end = _size;
                              			//while (end >= (int)pos)
                              			//{
                              			//	_str[end + 1] = _str[end];
                              			//	end--;
                              			//}
                              			size_t end = _size+1;
                              			while (end > pos)
                              			{
                              				_str[end] = _str[end - 1];
                              				end--;
                              			}
                              			_str[pos] = ch;
                              			_size++;
                              			return *this;
                              		}
                              		string& insert(size_t pos, const char* str)
                              		{
                              			assert(pos <= _size);
                              			size_t len = strlen(str);
                              			if (len + _size > _capacity)
                              			{
                              				reserve(len + _size >= _capacity * 2 ? len + _size : _capacity * 2);
                              			}
                              			size_t end = _size + len;
                              			while (end >= pos+len)
                              			{
                              				_str[end] = _str[end - len];
                              				end--;
                              			}
                              			for (int i = pos,j=0; j < len;j++, i++)
                              			{
                              				_str[i] = str[j];
                              			}
                              			_size += len;
                              			return *this;
                              		}

                              3、6 删除(erase)

                              我们这里实现的从某个位置开始删除,删除长度为 len 的字符。len有一个缺省参数,为npos(npos是一个很大的数,也就是不传参给 len 的话,默认删除到最后)。如果 len 本就很大,删除的长度超过从pos开始所剩余的长度,那么默认也是pos后的删除完。那么我们看其具体的实现。

                              void erase(size_t pos, size_t len = npos)
                              		{
                              			assert(pos < _size);
                              			if (len == npos || _size - pos <= len)
                              			{
                              				_str[pos] = '\0';
                              				_size = pos;
                              			}
                              			else
                              			{
                              				strcpy(_str + pos, _str + pos + len);
                              				_size -= len;
                              			}
                              		}

                              3、7 查找(find)

                              查找的话,主要常用的就两个:从pos位置开始查找,查找的内容可能是一个字符,也可能是一个子串。如果找到,则返回其下标。没找到就返回npos。具体实现如下:

                              size_t find(char ch, size_t pos = 0)const
                              		{
                              			for (size_t i = pos; i < _size; i++)
                              			{
                              				if (_str[i] == ch)
                              					return i;
                              			}
                              			return npos;
                              		}
                              		size_t find(const char* sub, size_t pos = 0)const
                              		{
                              			const char* ret=strstr(_str + pos, sub);
                              			if (ret == nullptr)
                              			{
                              				return npos;
                              			}
                              			else
                              			{
                              				return ret - _str;
                              			}
                              		}

                              3、8 返回子串(substr)

                              返子串也是我们经常需要的一个接口。返回子串就一个接口,从某个位置开始查找,查找长度为 len 的字符。len有一个缺省参数,为npos(npos是一个很大的数,也就是不传参给 len 的话,默认返回到最后)。如果 len 本就很大,返回子串的长度超过从pos开始所剩余的长度,那么默认也是pos后的子串全部返回。我们看其具体实现:

                                      string  substr(size_t pos, size_t len = npos)const
                              		{
                              			assert(pos < _size);
                              			size_t realLen = len;
                              			if (len == npos || pos + len > _size)
                              			{
                              				realLen = _size - pos;
                              			}
                              			string s;
                              			for (size_t i = 0; i < realLen; i++)
                              			{
                              				s += _str[pos + i];
                              			}
                              			return s;
                              		}

                              3、9 迭代器(iterator)

                              string中的迭代器底层就是指针,但是并不是所有的迭代器底层实现都是指针!我们直接看起底层实现:

                              typedef char* iterator;
                              		typedef const char* const_iterator;
                              		iterator begin()
                              		{
                              			return _str;
                              		}
                              		iterator end()
                              		{
                              			return _str + _size;
                              		}
                              		const_iterator begin() const
                              		{
                              			return _str;
                              		}
                              		const_iterator end() const 
                              		{
                              			return _str + _size;
                              		}

                              这里的begin()就是返回的字符串的首元素地址end()返回的是字符串最后一个元素的后一个地址

                              3、10 比较(>、<、>=、<=、==、!=)

                              字符串的比较并非比较其长度,而是与其相同位置字符的大小有关,也就是我们所说的字典序。我们这里只需要实现其中的两个,其他均可复用。具体如下:

                              bool operator> (const string& s) const
                              		{
                              			return strcmp(_str, s._str) > 0;
                              		}
                              		bool operator== (const string& s) const
                              		{
                              			return strcmp(_str, s._str) == 0;
                              		}
                              		bool operator>= (const string& s) const
                              		{
                              			return *this > s || *this == s;
                              		}
                              		bool operator< (const string& s) const
                              		{
                              			return !(*this >= s);
                              		}
                              		bool operator<= (const string& s) const
                              		{
                              			return !(*this > s);
                              		}
                              		bool operator!= (const string& s) const
                              		{
                              			return !(*this == s);
                              		}

                              四、总结

                              string 在C++中算是比较重要的了,也是入门时必须所学的容器。在平常中使用的频率较高,所以我们不仅要掌握其简单的用法,更应该去了解其底层的实现。这有助于我们后续的使用和理解。本篇文章列举出了string中常用的语法和接口底层的底层实现,这些都是我们应该熟练掌握的内容。

                              本篇文章讲解就到这里,感谢观看ovo~

                              以上就是C++中String的语法及常用接口的底层实现详解的详细内容,更多关于C++ String的语法及接口底层实现的资料请关注其它相关文章!

                              声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。