【C++】string的底层原理及实现

2024-07-06 1536阅读

文章目录

  • string类的存储结构
  • 默认成员函数
    • 构造函数
    • 析构函数
    • 拷贝构造函数
    • 赋值重载
    • 容量操作
      • size()
      • capacity()
      • reserve()
      • resize()
      • clear()
      • 遍历与访问
        • operator[ ]
        • 迭代器范围与for
        • 增删查改
          • push_back()
          • pop_back()
          • append()
          • operator+=
          • insert()
          • erase()
          • c_str()
          • find()
          • substr()
          • 非成员函数
            • operator+
            • 关系运算符
            • 流插入
            • getline()

              本篇参考C++string类参考手册,实现一些string的常用接口,接口原型在该网站查阅。

              string类的存储结构

              string类的底层实际上是char类型的顺序表,所以结构上也比较相似。

              namespace lw
              {
              	class string
              	{
              		public:
              			static const size_t npos;
              		private:
              			size_t _size;//有效数据个数
              			size_t _capacity;//可存储的容量
              			char* _str;//指向字符串起始位置的地址
              	};
              	const size_t string::npos = -1;
              };
              

              STL源码中,许多整型变量类型都是size_t无符号整型(没有负值);npos是string类常用到的一个值,有些函数的参数或者返回值是npos,并且这个值设为-1(表示232-1),参考标准库中的定义。

              【C++】string的底层原理及实现

              为什么string定义在一个命名空间中?

              我们如果展开了标准库using namespace std; 那string默认使用的就是标准库中的。如果不展开标准库,那么所有的cin和cout都需要加上std:: 比较繁琐。

              用一个命名空间对我们自己实现的string类进行封装,可以方便我们随时测试代码,与库里的string类进行测试对比,只需要改::前面的作用域即可。

              #include 
              using namespace std;
              int main()
              {
              	string s("hello world");//默认标准库
              	std::string s("hello world");//标准库
              	lw::string s("hello world");//自己定义的类
              	cout 
              	//strcpy(_str, str);//strcpy也可以,只是为了与后面统一,换成memcpy
              	memcpy(_str, str, _size + 1);//'\0'也要拷贝
              }
              
              	delete[] _str;
              	_str = nullptr;
              	_size = _capacity = 0;
              }
              
              	_str = new char[s.capacity() + 1];//多一个空间给'\0'
              	//strcpy(_str, s._str);//遇到'\0'终止,后面内容无法拷贝
              	memcpy(_str, s._str, s._size + 1);//末尾'\0'也拷贝过来
              	_size = s._size;
              	_capacity = s._capacity;
              }
              
              	if (this != &s)
              	{
              		char* tmp = new char[s._capacity + 1];
              		memcpy(tmp, s._str, s._size + 1);
              		delete[] _str;//释放原始空间
              		_str = tmp;
              		_size = s._size;
              		_capacity = s._capacity;
              	}
              	return *this;//支持连续赋值
              }
              
              	std::swap(_str, s._str);
              	std::swap(_size, s._size);
              	std::swap(_capacity, s._capacity);
              }
              //现代写法
              string& operator=(string s)//不能传引用也不能加const
              {
              	swap(s);
              	return *this;
              }
              
              	return _size;
              }
              
              	return _capacity;
              }
              
              	if (n  _capacity)
              	{
              		char* tmp = new char[n + 1];
              		//strcpy(tmp, _str);//错误,无法拷贝'\0'后面的内容
              		memcpy(tmp, _str, _size + 1);
              		delete[] _str;
              		_str = tmp;
              		_capacity = n;
              	}
              }
              
              hr / h3resize()/h3 presize()只改变有效数据个数,不会改变容量。/ppn > _size:缩小长度为n,多余内容删掉。

              n

              void resize (size_t n);
              void resize (size_t n, char c);
              

              库里的resize()函数有两个版本,同样我们可以合成一个版本,第二个参数给缺省值\0即可。

              void resize(size_t n, char ch = '\0')
              {
              	assert(n >= 0);
              	if (n  
              

              clear()

              void clear()
              {
              	_size = 0;
              	_str[0] = '\0';
              }
              

              遍历与访问

              operator[ ]

              需要实现两个版本:普通对象调用[ ]可读可写,const对象调用[ ]只可读不可写。

              char& operator[](size_t pos)//可读可写
              {
              	assert(pos  
              

              迭代器范围与for

              迭代器不一定是指针,而string的迭代器我们可以用指针来模拟实现。将指针重命名为迭代器。

              typedef char* iterator;//普通迭代器 可读可写
              iterator begin()
              {
              	return _str;
              }
              iterator end()
              {
              	return _str + _size;
              }
              typedef const char* const_iterator;//const迭代器 只可读
              const_iterator begin() const
              {
              	return _str;
              }
              const_iterator end() const
              {
              	return _str + _size;
              }
              

              左闭右开区间,所以begin()返回字符串起始位置,end()返回末尾字符的下一个位置\0。

              范围for

              实现迭代器后,我们就可以使用范围for了。范围for是C++11的新语法,它的底层是傻瓜式地替换成迭代器,支持迭代器就支持范围for。

              int main()
              {
              	lw::string s1("hello world");
              	lw::string::iterator it = s1.begin();
              	while (it != s1.end())
              	{
              		*it += 1;
              		cout 
              		cout 
              	if (_size == _capacity)
              	{
              		reserve(_capacity == 0 ? 10 : _capacity * 2);
              	}
              	_str[_size++] = ch;
              	_str[_size] = '\0';
              }
              
              	assert(_size  0);
              	_size--;
              	_str[_size] = '\0';
              }
              
              	size_t len = strlen(str);
              	if (_size + len  _capacity)
              	{
              		reserve(_size + len);
              	}
              	//strcpy(_str + _size, str);//strcat效率O(n+m) strcpy效率O(m)
              	memcpy(_str + _size, str, len + 1);//末尾的'\0'也要拷贝
              	_size += len;
              	return *this;
              }
              string& append(const string& s)
              {
              	if (_size + s._size _capacity)
              	{
              		reserve(_size + s._size);
              	}
              	memcpy(_str + _size, s._str, s._size + 1);//末尾的'\0'也要拷贝
              	_size += s._size;
              	return *this;
              }
              
              	push_back(ch);
              	return *this;
              }
              string& operator+=(const char* str)
              {
              	append(str);
              	return *this;
              }
              string& operator+=(const string& s)
              {
              	append(s);
              	return *this;
              }
              
              	assert(pos 
              		reserve(_size + n);
              	}
              	for (size_t end = _size; end = pos; end--)
              	{
              		//pos==0时end会减到-1
              		//无符号整型 end=-1时表示42亿多 不加判断会无限循环
              		if (end == npos)
              		{
              			break;
              		}
              		_str[end + n] = _str[end];
              	}
              	for (size_t i = 0; i = _size)
              	{
              		//pos及pos后面所有字符都删掉
              		_size = pos;
              		_str[_size] = '\0';
              	}
              	else
              	{
              		memcpy(_str + pos, _str + pos + len, len);
              		_size -= len;
              	}
              	return *this;
              }
              

              c_str()

              返回字符串首地址,以\0结束。这个接口主要是用来兼容C语言的。

              const char* c_str() const
              {
              	return _str;
              }
              

              find()

              find查找失败会返回npos,找到则返回下标

              //查找字符
              size_t find(char ch, size_t pos = 0)
              {
              	assert(pos  
              

              substr()

              返回子串

              string substr(size_t pos = 0, size_t len = npos) const
              {
              	assert(pos  _size)
              	{
              		n = _size - pos;
              	}
              	string tmp;
              	for (size_t i = 0; i  
              

              非成员函数

              operator+

              实际上+操作符很少用,直接用+=更方便省事;这里只实现了一个接口,重载为友元函数。

              string operator+(const string& s1, const string& s2)
              {
              	string tmp(s1);
              	tmp += s2;
              	return tmp;
              }
              

              关系运算符

              C++官方手册中,每种运算符都有3种重载版本,这里每个只实现了一种,重要的是理解本质,加深对string的理解。

              为什么用C语言的memcmp函数进行字节上的比较,不用strcmp?

              因为strcmp遇到\0终止,而string不看\0,以_size为终止。

              当然也可以不用memcmp,遍历每个字符进行比较。

              bool operator==(const string& s1, const string& s2)
              {
              	return s1._size == s2._size 
              		&& memcmp(s1._str, s2._str, min(s1._size, s2._size)) == 0;
              }
              bool operator
              	int ret = memcmp(s1._str, s2._str, min(s1._size, s2._size));
              	return ret == 0 ? s1._size 永远无法读取空格和换行,程序会一直运行一直可以输入。

              3.如果字符串前面有空格或者换行,标准库的>>会默认清理空格和换行。所以要预先处理前面的空格和换行。

              istream& operator>>(istream& in, string& s)
              {
              	s.clear();//清空之前的内容
              	char ch = in.get();//可以读取空格和换行
               	//处理掉缓冲区前面的空格或者换行
              	while (ch == ' ' || ch == '\n')
              	{
              		ch = in.get();
              	}
              	while (ch != ' ' && ch != '\n')
              	{
              		s += ch;
              		ch = in.get();
              	}
              	return in;
              }
              

              getline()

              getline可以自定义读取的分隔符,遇到分隔符就不再读取,字符参数默认为\n,换行截断。

              istream& getline(istream& in, string& s, char delim = '\n')
              {
              	s.clear();//清空之前的内容
              	char ch = in.get();
              	while (ch != delim)
              	{
              		s += ch;
              		ch = in.get();
              	}
              	return in;
              }
              

              整体代码->:string模拟实现代码

VPS购买请点击我

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

目录[+]