【C++】string类--常见接口及其模拟实现

目录

1. 遍历

1.1. 下标+operator[ ]

1.2. c_str

1.3. 迭代器

1.4. 范围for

2. 增

2.1. push_back

2.2. 重载+=(char ch)

2.3. appand

2.4. 重载+=(char* ch)

2.5. insert(任意位置插入)

2.5.1. 任意位置插入一个字符

2.5.2. 任意位置插入一个字符串

3. 删

3.1. earse

情况一:len==npos说明要全部删除完

情况二:并不全部删除完

4. 查

4.1. 查找一个字符

4.2. 查找一个字符串

4.3. 字符串比较

5. 改

5.1. reserve

5.2. resize

情况一:resize的n大于当前字符串空间长度(注意是空间长度)

情况二:resize的n小于_capacity,但是大于_size

情况三:resize的n小于当前字符串长度


上一篇文章我们讲解了string类的构造、拷贝构造、赋值及其模拟实现
下面是我们本篇文章的主要内容:

1. 遍历

1.1. 下标+operator[ ]

char& operator[](size_t pos)
{
	assert(pos < strlen(_str));
	return _str[pos];
}
 
const char& operator[](size_t pos) const 
{
	assert(pos < strlen(_str));
	return _str[pos];
}
 
int main()
{
	string s1("hello world");
	for (int i = 0; i < s1.size(); ++i)
	{
 
		cout << s1[i];
	}
	return 0;
}


1.2. c_str

也就是说c_str返回的值指向该字符串并且包含“\0”的字符序列,使用c_str( )来打印字符串,当碰到“\0”时就停止(这是因为C/C++中字符串处理函数(如printfcoutstrcpy等)都遵循一个约定:将以null字符('\0')作为字符串的结束标志)。

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


1.3. 迭代器

迭代器:string的迭代器的底层其实就是一个char*的原生指针,所以使用string迭代器只需要像使用普通指针一样即可。但是其他容器的底层不应该否是原生指针。

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

iterator begin() const
{
	return _str;
}
iterator end()
{
	return _str + _size;
}

iterator end() const
{
	return _str + _size;
}

然后我们使用迭代器来进行字符串的遍历:

int main()
{
    s::string s1("Hello World");
    s::string::iterator it = s1.begin();
    while (it != s1.end())
    {
	    cout << *it << "";
	    it++;
    }
}

PS:这个地方要注意的是要注意使用的是C++string类中的迭代器还是我们自定义类string中的迭代器。


1.4. 范围for

for的使用:

for (auto e : s1)
{
	cout << e << " ";
}

其实范围for的底层机制同样是一个迭代器,我们可以通过下面的方式进行验证,我们将迭代器给注释掉,我们来看一下发生什么:


2. 增

2.1. push_back

可以看到,push_back的作用是将一个字符添加到原有字符串后面,具体步骤:

  • 我们先要判断时候需要扩容:如果size==capacity,说明满了。扩容:开辟一个两倍内存的新空间,然后原有字符串拷贝至新空间,释放原空间
  • 添加的新字符应该放在size的位置
  • 然后处理“\0”即可

2.2. 重载+=(char ch)

但是我们在日常写代码中并不会经常使用push_back,而是使用 += ,所以我们来重载一下 += ,它的实现中也可以服用push_back:

string& operator+=( char ch)
{
	push_back(ch);
	return *this;
}

2.3. appand

这里可以注意到,前面两个接口都是将单个字符添加到已有字符串结尾,而这个接口是将一个字符串添加到原来的字符串,步骤如下:

  • 首先需要考虑要不要扩容,而且这里的扩容不能只是简单地将空间变为两倍,因为这样并不能保证空间足够。应该是将原字符串长度和新添加字符串长度相加,这样得到的空间就一定能满足要求,这里我们使用reserve函数(见下面的5.1)
  • 然后更新 _size
		void append(const char* str)
		{
			size_t len = _size + strlen(str);
			if (len > _capacity)
			{
				reserve(len);
			}
			strcpy(_str + _size, str);
			_size = len;
			//insert(_size, str);
		}

2.4. 重载+=(char* ch)

与单个字符的重载复用push_back一样,复用appand接口:

	string& operator+=(const char* str)
	{
		append(str);
		return *this;
	}

2.5. insert(任意位置插入)

2.5.1. 任意位置插入一个字符

  • 首先判断pos位置是否合法
  • 然后判断是否需要扩容
  • 然后从结尾的"\0"开始,从后往前一次向后移动一位,到pos为止
  • 插入新字符,然后更新_size

string& insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_capacity == _size)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end - 1] = _str[end];
		--end;
	}
	_str[pos] = ch;
	++_size;
	return *this;
}

2.5.2. 任意位置插入一个字符串

  • 插入位置是否合法
  • 判断是否需要扩容,这里需要扩大到原字符串长度+插入字符串长度
  • 将原字符串包括pos在内的后面所有字符(包括"\0")往后移动len=strlen(ch)位
  • 然后将新字符串插入,然后更新_size

		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (len == 0)
			{
				return *this;
			}
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			//挪动数据
			size_t end = _size + len;
			//while(end >= end+len)
			while (end > pos+len-1)
			{
				_str[end] = _str[end-len];
				--end;
			}
			//插入数据  
			size_t i = 0;
			while (i < len)
			{
				_str[pos+i] = str[i];
				++i;
			}
			_size += len;
			return *this;
		}

3. 删

3.1. earse

我们看这里提到了一个参数npos:

所以可以知道,earse的作用就是从pos开始,往后len个长度,把这些字符删去。

情况一:len==npos说明要全部删除完

那其实我们逻辑上删除它只需要将pos位置赋值"\0"即可:

情况二:并不全部删除完

首先找到需要删除的子字符串的后一位,将其定为begin,然后我们需要做的就是将begin后面的所有字符全部往前移动,覆盖掉需要删除的字符:

string& earse(size_t pos, size_t len = std::string::npos)
{
	assert(pos < _size);
	if (len == std::string::npos || len + pos >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
		return *this;
	}
	else
	{
		size_t begin = pos + len;
		while (begin <= _size)
		{
			_str[begin - len] = _str[begin];
			++begin;
		}
		_size -= len;
		return *this;
	}
}

4. 查

4.1. 查找一个字符

find查找字符串,返回查到的第一个满足的字符:

size_t find(char ch, size_t pos = 0)
{
	for (; pos < _size; ++pos)
	{
		if (_str[pos] == ch)
		{
			return pos;
		}
	}
	return std::string::npos;
}

4.2. 查找一个字符串

size_t find(const char* ch, size_t pos)
{
	const char* p = strstr(_str + pos, ch);
	if (nullptr == p)
	{
		return std::string::npos;
	}
	else
	{
		return p - _str;
	}
}

4.3. 字符串比较

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

对于这段代码有疑问的可以看下【C++】类和对象--类中6个默认成员函数(2) --运算符重载,这里涉及到成员函数隐藏this指针的问题。


5. 改

5.1. reserve

reserve的作用就是是个string的容量变成n:

  • 如果n小于等于_capacity,则不进行扩容
  • 否则开辟一个容量为n的char*,然后进行拷贝,注意释放掉原有的内存
void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;

		_capacity = n;
	}
}

5.2. resize

resize是将字符串大小修改为n。

resize和reserve的区别是:reserve只对空间进行处理,但是resize不仅对空间进行影响,而且会改变_size的值。

情况一:resize的n大于当前字符串空间长度(注意是空间长度)

看下面这个例子,假设空间长度为15,字符串长度为11:

也就是扩容了之后会使用字符串参数填充满:

情况二:resize的n小于_capacity,但是大于_size

这种情况下不需要扩容,所以_capacity不会变:

情况三:resize的n小于当前字符串长度

这个时候只会保存原字符串的前n个字符:

代码如下:

//扩空间+初始化
//删除部分数据,保留前n个
void resize(size_t n, char ch = '\0')
{
	if (n < _size)
	{
		_size = n;
		_str[_size] = '\0';
	}
	else
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		for (size_t i = _size; i < n; ++i)
		{
			_str[i] = ch;
		}
		_size = n;
		_str[_size] = '\0';
	}

}

(本篇完)

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值