C++中string的简单实现

string的简单实现中一些函数的实现可以复用一些其他的函数来实现;比较重要的是在实现是深浅拷贝问题

目录

string的结构

实现构造和析构

reserve扩容

容量

push_back和append

insert和erase的实现

swap的实现(不是成员函数但是string类的友元)

赋值运算符的重载和+=运算符的重载

c_str和substr

关系运算符的重载

流插入和流提取运算符的重载(跟swap一样是string的友元)


string的结构

    class string
	{	
    private:
        typedef char* iterator;//普通迭代器
        typedef const char* const_iterator;//const迭代器
		char* _str;
		size_t _size;//长度,大小
		size_t _capacity;//容量
		static const size_t npos = -1;//npos相当于一个很大的数
	};

为啥用char* 指针就可以充当迭代器?

因为可以直接通过char*来访问string中的字符数组。在库中的实现可能不是用的char*。



实现构造和析构

		string(const char* s = "");//构造 默认参数即使不传参也可以构造一个空字符串
		string(const string& s);//拷贝构造
		~string();//析构

为什么需要显示实现析构函数?

因为_str指向的空间是new出来的,是资源,所以需要用delete手动释放该资源。

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

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

	string::~string()
	{
		delete[] _str;
		_size = _capacity = 0;
	}

实现没有什么就是简单的对字符串的操作。


reserve扩容

	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n];
			strcpy(tmp, _str);
			delete[] _str;//释放原来的空间
			_str = tmp;
			_capacity = n;
		}
	}

注意一定要释放原来的空间否则会内存泄漏

操作简图


容量

	size_t string::size()
	{
		return _size;
	}
	size_t string::capacity()
	{
		return _capacity;
	}
	size_t string::size()const//const修饰的string会调用该函数
	{
		return _size;
	}
	size_t string::capacity()const
	{
		return _capacity;
	}

push_back和append

	void string::push_back(char ch)
	{
        //扩容
		if (_size == _capacity)
		{
			int newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
			reserve(newcapacity);
		}
		_str[_size++] = ch;
        //别忘了在最后加上'\0'
		_str[_size] = '\0';

	}

	string& string::append(const char* s)
	{
		int len = strlen(s);
        //扩容
		if (_size + len >= _capacity)
		{
			reserve(_size + len+1);//需要多扩一个字节来存放'\0'
		}
		strcpy(_str + _size, s);
		_size = _size + len;
		return *this;//返回调用append这个函数的string对象本身
	}

注意:向string中插入新的数据,就有可能需要扩容,扩容正好可以复用之前实现的reserve函数。


insert和erase的实现

	string& string::insert(size_t pos, const char* s)
	{
		assert(pos >= 0 && pos < _size);//pos这个要插入的位置要合法
		int len = strlen(s);
        //扩容
		if (_size + len >= _capacity)
		{
			reserve(_size + len +1);
		}
		int end = _size + len;
        //将pos位置之后的字符整体向后移动len(插入字符串的长度)个字节
		while (end - len >= pos)
		{
			_str[end] = _str[end - len];
			end--;
		}
        //插入字符串
		for (int i = 0; i < len; i++)
		{
			_str[pos + i] = s[i];
		}
        //strcpy(_str+pos,s);也可以直接这样写
		_size = _size + len;
		return *this;
	}

	string& string::insert(size_t pos,char ch)
	{
		assert(pos >= 0 && pos < _size);
		if (_size + 1 >= _capacity)
			reserve(_size + 1 +1);
		int end = _size;
        //将pos位置之后的字符整体向后移动1个字节
		while (end >= pos)
		{
			_str[end + 1] = _str[end];
			end--;
		}
		_str[pos] = ch;
		_size++;
		return *this;
	}

	string& string::erase(size_t pos, size_t len)
	{
        //如果长度很长就直接将pos位置设置为'\0'
		if (pos + len > _size)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
            //如果长度不是很长,那么就pos+len位置之后的字符整体前移
			for (size_t i = pos; i+len <= _size; i++)
			{
				_str[i] = _str[i + len];
			}
            _size-=len;
		}
		return *this;
	}

swap的实现(不是成员函数但是string类的友元)

	void swap(string& x, string& y)
	{
		std::swap(x._str, y._str);
		std::swap(x._capacity, y._capacity);
		std::swap(x._size, y._size);
	}

在标准库中有实现的一个swap的函数模板

//标准库中的swap的实现
template<class T>
void swap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;
}

如果使用std::swap来交换两个字符串效率不高:tmp需要调用拷贝构造,重新拷贝一份a;将b赋值给a和将tmp赋值给b都会调用赋值运算符,重新开辟空间,释放掉原来的空间。总结,需要完成三次深拷贝,性能不高。

而为string专门实现的swap就只需要交换资源就可以。效率很高。


赋值运算符的重载和+=运算符的重载

	string& string::operator=(const string& s)
	{
		if (this != &s)//如果自己赋值给自己那么就什么也不需要做
		{
			char* tmp = new char[s._capacity + 1];
			strcpy(tmp, s._str);
			delete[] _str;
			_str = tmp;
			_size = s._size;
			_capacity = s._capacity;
		}
		return *this;
	}

赋值重载的另一个版本

	string& string::operator=(string s)
	{
        
		swap(*this,s);//直接调用swap交换两个*this和s两个资源就可以了
		return *this;
	}

直接将临时变量的资源与*this对象的资源互换,并且临时变量在销毁时也会delete释放资源。

跟上面的代码效率差不多,但如果是自己赋值自己,那么下面就会进行一次深拷贝,而上面啥也不做,效率不如上面。


c_str和substr

	//返回字符串首元素地址
    const char* string::c_str()
	{
		return _str;
	}
    //返回字符串中的一段字符构造的string
    string string::substr(size_t pos, size_t len)
	{
		string tmp;
        //len的长度很长直接将pos后的所有字符用来构造string
		if (pos + len >= _size)
		{
			for (int i = pos; i <= _size; i++)
			{
				tmp += _str[i];
			}
			return tmp;
		}
		int i;
		for (i = 0; i < len; i++)
		{
			tmp += _str[pos+i];
		}
		tmp += '\0';
		return tmp;
	}

这里的substr的实现直接复用了+=运算符


关系运算符的重载

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

	bool string::operator>(string& s)
	{
		return strcmp(_str, s._str) > 0;
	}

	bool string::operator>=(string& s)
	{
		return (*this == s) || (*this > s);
	}

	bool string::operator<(string& s)
	{
		return !(*this >= s);
	}

	bool string::operator<=(string& s)
	{
		return (*this < s) || (*this == s);
	}

有的运算符的重载可以复用已经实现的运算符

流插入和流提取运算符的重载(跟swap一样是string的友元)

	istream& operator>>(istream& in,string& s)
	{
		s.clear();
		char ch = in.get();//get从流(string)中读入一个char
		while (ch != ' ' && ch != '\n')
		{
			s += ch;
			ch = in.get();
		}
		return in;
	}

	ostream& operator<<(ostream& out,const string& s)
	{
		for (int i = 0; i <s._size; i++)
		{
			out << s[i];
		}
		return out;
	}

完整的string实现在gitee中:

刷题记录: 用来调试刷题是写的代码 (gitee.com)

相关推荐
爱编程— 的小李9 分钟前
结构体(c语言)
c语言·开发语言
星光樱梦20 分钟前
24. 正则表达式
c++
fathing21 分钟前
c# 调用c++ 的dll 出现找不到函数入口点
开发语言·c++·c#
前端青山43 分钟前
webpack指南
开发语言·前端·javascript·webpack·前端框架
nukix1 小时前
Mac Java 使用 tesseract 进行 ORC 识别
java·开发语言·macos·orc
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】内存可见性问题 & volatile
java·开发语言·java-ee
hope_wisdom1 小时前
C++网络编程之SSL/TLS加密通信
网络·c++·ssl·tls·加密通信
erxij1 小时前
【游戏引擎之路】登神长阶(十四)——OpenGL教程:士别三日,当刮目相看
c++·经验分享·游戏·3d·游戏引擎
Lizhihao_2 小时前
JAVA-队列
java·开发语言
林开落L2 小时前
前缀和算法习题篇(上)
c++·算法·leetcode