C++模拟实现string类

1.string类

1.1 在http://www.cplusplus.com/reference/string/string/ 中是这样解释的:

String class

Strings are objects that represent sequences of characters.
The standard string class provides support for such objects with an interface similar to that of a standard container of bytes, but adding features specifically designed to operate with strings of single-byte characters.
The string class is an instantiation of the basic_string class template that uses char (i.e., bytes) as its character type, with its default char_traits and allocator types (see basic_string for more info on the template).
Note that this class handles bytes independently of the encoding used: If used to handle sequences of multi-byte or variable-length characters (such as UTF-8), all members of this class (such as length or size), as well as its iterators, will still operate in terms of bytes (not actual encoded characters).

总结一下呢,就四点:

  1. string是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作的string的常规操作
  3. string在底层实际是:basic_string模板类的别名, typedef basic_string<char, char_traits, allocator> string;
  4. 不能操作多字节或者变长字符的序列

1.2 string类的常用接口说明

在这里我只介绍我实现string类的过程和一些方法,具体用法请参照上面的网站进行查阅。

  1. 四大成员函数

    ![四大成员函数](F:\C++\CPPStudyJourney\知识点总结\8. string类\四大成员函数.png)

    这里只有三个为什么说是四大成员函数呢?因为拷贝构造函数在构造函数选项里。

    在这里我自己模拟实现了一下string类,下面是我模拟的四大成员函数:

    //Member functions//////////////////////////
    //成员函数写在类里,写在类里的函数默认是内联(inline)函数
    		String(const char *str = "")
    		{
    			_size = strlen(str);
    			_capacity = _size > 15 ? _size : 15;
    			_str = new char[_capacity + 1];
    			strcpy(_str, str);
    		}
    
    
    		~String()
    		{
    			if (_str)
    			{
    				delete[] _str;
    				_str = nullptr;
    				_size = 0;
    				_capacity = 0;
    			}
    		}
    
    
    		String(const String& s)
    			:_str(nullptr),
    			_size(0),
    			_capacity(0)
    		{
    			String tmp(s._str);
    			Swap(tmp);
    		}
    
    
    		String& operator=(String s)
    		{
    			Swap(s);
    			return *this;
    		}
    

    在这里重点说一下 拷贝构造函数和赋值运算符重载函数

    这两个函数都用了现代写法;

    **拷贝构造函数:**先初始化一个空类型。然后再用默认构造函数构造一个跟 s一样的一个临时的string对象。再将原本初始化的空类型和这个临时的string对象交换,返回即可。系统会自动调用析构函数清理被交换后的临时string对象。

    **赋值运算符重载函数:**这里直接用了传值的做法,在调用函数的时候会发生值拷贝,拷贝了一份临时的string对象,直接交换this对象和这个临时的对象,返回即可,也是一样,系统会自动调用析构函数清理资源。

  2. string类对象的容量操作

    ![string类对象的容量操作](F:\C++\CPPStudyJourney\知识点总结\8. string类\string类对象的容量操作.png)

    上代码!!!

    //这思个因为比较短小,所以写在类里了
    		void clear()
    		{
    			_size = 0;
    			_str[0] = '\0';
    		}
    		
    		int empty() const
    		{
    			if (_size == 0)
    				return 1;
    
    			return 0;
    		}
    
    		size_t size() const
    		{
    			return _size;
    		}
    
    
    		size_t capacity() const
    		{
    			return _capacity;
    		}
    //其他的写在类外,所以有命名空间
    void Gerald::String::reserve(size_t n)
    {
    	if (n > _capacity)
    	{
    		char *new_str = new char[n + 1];
    		strcpy(new_str, _str);
    		delete[] _str;
    
    		_str = new_str;
    		_capacity = n;
    	}
    }
    
    void Gerald::String::resize(size_t n, char ch)
    {
    	if (n < _size)
    	{
    		_size = n;
    		_str[_size] = '\0';
    	}
    	else if (n > _size)
    	{
    		if (n > _capacity)
    		{
    			reserve(n);
    		}
    		/*while (_size < n)
    		{
    			push_back(ch);
    			_size++;
    		}*/
    			
    		memset(_str + _size, ch, n - _size);//这里直接可以用memset来拷贝字节
            								  //不用上面的循环很麻烦
    		_size = n;
    		_str[_size] = '\0';
    	}
    
    	
    }
    
    
    

    这六个是比较常用的,其他的要么不实用,要么就会有些冗余,这也是很多人吐槽string类的地方,这么一个类有一百多个接口,冗余太多了。

    说明:

    1. size()和length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
    2. clear()只是将string中的有效字符清空,不改变底层空间大小。
    3. reserve()只能增容,如果参数比现在的容量小,则不做任何操作。
    4. resize()如果参数比size小,那么就改size大小,然后给size位置放’\0’;如果参数大于size小于容量,并且如果用户给了填入的字符,则会把size扩大到参数大小,并将扩大的地方填入用户给的字符,若没给,默认’\0’;如果参数大于容量,就扩容,然后做跟上一步一样的操作。
  3. 类的对象访问操作![类对象访问操作](F:\C++\CPPStudyJourney\知识点总结\8. string类\类对象访问操作.png)

    在这里基本上只能用的上[]运算符重载

    		char& operator[](size_t pos)
    		{
    			assert(pos < _size);
    			return _str[pos];
    		}
    
    
    		const char& operator[](size_t pos) const
    		{
    			assert(pos < _size);
    			return _str[pos];
    		}
    
  4. 对类对象的修改操作

    ![对类对象的修改操作](F:\C++\CPPStudyJourney\知识点总结\8. string类\对类对象的修改操作.png)

    void Gerald::String::push_back(char ch)
    {
    	if (_size == _capacity)
    	{
    		reserve(_capacity * 2);
    	}
    
    	_str[_size] = ch;
    	_size++;
    
    	//这一步比较重要,若忘记加了,则会出现读取字符串停不下来
    	_str[_size] = '\0';
    }
    
    
    void Gerald::String::append(const char *str)
    {
    	int len = strlen(str);
    	if (_size + len > _capacity)
    	{
    		reserve(_size + len);
    	}
    
    	strcpy(_str + _size, str);
    	_size += len;
    }
    
    
    Gerald::String& Gerald::String::operator+= (char ch)
    {
    	push_back(ch);
    	return *this;
    }
    
    
    Gerald::String& Gerald::String::operator+= (const char *str)
    {
    	append(str);
    	return *this;
    }
    
    
    size_t Gerald::String::find(char ch, size_t pos)
    {
    	assert(pos < _size);
    
    	for (pos; pos < _size; pos++)
    	{
    		if (_str[pos] == ch)
    			return pos;
    	}
    
    	return npos;
    }
    size_t Gerald::String::find(const char *str, size_t pos)
    {
    	assert(pos < _size);
    
    	char *pstr = strstr(_str, str);
    	if (pstr)
    	{
    		return pstr - str;
    	}
    
    	return npos;
    }
    
    
    void Gerald::String::Insert(size_t pos, const char ch)
    {
    
    	if (_size == _capacity)//_size位置放的是'\0'要统一
    	{
    		size_t new_size = _capacity == 0 ? 15 : 2 * _capacity;
    		reserve(new_size);
    	}
    
    	size_t i = _size + 1; //这里+1可以在第一步直接将'\0'挪到最后面,所以在最后就不需要赋值'\0'了
    	for (i; i > pos; i--)
    	{
    		_str[i] = _str[i - 1];
    	}
    	_str[pos] = ch;
    
    	_size++;
    }
    
    void Gerald::String::Insert(size_t pos, const char *str)
    {
    	size_t len = strlen(str);
    	if (_size + len > _capacity)
    	{
    		reserve(_size + len);
    	}
    
    	size_t i = _size + len;
    	for (i; i > pos + len - 1; i--)  //i > pos + len - 1   极端位置 i = pos + len  可以把pos位置的数据挪过来
    	{								//就算在字符串尾部插入,循环还是会把'\0'挪到最后面,不用担心最后没有加'\0'的问题
    		_str[i] = _str[i - len];
    	}
    
    	strncpy(_str + pos, str, len);
    	_size += len;
    	
    }
    
    
    void Gerald::String::Erase(size_t pos, size_t len)
    {
    	if (pos + len > _size)//直接将后面的字符串屏蔽
    	{
    		_str[pos] = '\0';
    		_size = pos;
    		return;
    	}
    
    	for (size_t i = pos; i < _size - len + 1; i++)//i < _size - len + 1可以直接将'\0'挪到缩减后的最后一个位置
    	{
    		_str[i] = _str[i + len];
    	}
    
    	_size -= len;
    }
    
    
    Gerald::String Gerald::String::substr(size_t pos, size_t len)
    {
    	if (_size - pos < len)
    		len = _size - pos;
    
    	String sub;
    	sub.reserve(len);
    
    	for (size_t i = pos; i < pos + len; i++)
    	{
    		sub += _str[i];
    	}
    
    	return sub;
    }
    
    
    

猜你喜欢

转载自blog.csdn.net/weixin_42678507/article/details/88822032