[C++](10)C++的string类如何实现?

构造析构及一些简单函数

string 类包含3个成员变量,_str 指针指向对应的字符串,_size 表示有效字符的个数(不包括\0),_capacity 表示有效容量的大小(不包括存\0的空间)。

class String
{
public:

private:
	char* _str;
	size_t _size;
	size_t _capacity;
};

构造函数和析构函数:

String(const char* str = "") //构造函数提供全缺省的更好,默认传入空字符串。
	:_size(strlen(str))
	, _capacity(_size)
{
	_str = new char[_capacity + 1]; //+1是为了给\0开空间
	strcpy(_str, str);
}

~String()
{
	if (_str)
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
}

c_str operator[] size capacity

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

char& operator[](size_t pos) //记得传引用返回,支持修改
{
	assert(pos < _size);
	return _str[pos];
}
const char& operator[](size_t pos) const //也要提供const版本
{
	assert(pos < _size);
	return _str[pos];
}

size_t size() const
{
	return _size;
}

size_t capacity() const
{
	return _capacity;
}

深浅拷贝(拷贝构造、赋值重载)

拷贝构造我们可以不写吗,直接用编译器自动生成的?

答案是不可以,因为默认生成的完成的是浅拷贝,仅仅是把指针拷贝过去,导致两个对象的 _str 指向的是同一块空间,程序运行时会出现:1.析构两次 2.一个对象的修改影响另外一个

所以我们要写一个深拷贝,为新的对象在堆上开辟空间:

传统写法

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

赋值重载也面临同样的问题,同样要我们自己写深拷贝:

赋值是在两个对象都已经存在的情况下进行的,所以有些人容易想当然地以为直接 strcpy 就可以了,其实不然,如果被赋值的对象 _str 所指向的空间较小,要拷贝的内容就存不进去,如果空间较大,会造成浪费。

考虑到上述问题,这里可以简单粗暴地直接把原空间释放,重新开辟空间进行拷贝:

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;
}

👆:记得考虑同一个对象自己给自己赋值的问题。

👆:先开空间后释放比先释放后开空间好。

现代写法、swap

拷贝构造

先完成成员函数swap

然后直接拿原对象里的 _str 去构造一个 tmp 对象,然后对 tmp 对象“夺舍”。

void swap(String& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

String(const String& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	String tmp(s._str);
	swap(tmp);
}

👆注意:交换前应该先初始化,否则 tmp 拿到的 _str 指针是随机值,析构时会出错

赋值重载

同样的思路:

String& operator=(const String& s)
{
	if (this != &s)
	{
		String tmp(s._str);
		swap(tmp);
	}
	return *this;
}

其实传值传参就是现成的 tmp,代码还可以写得更简洁:

String& operator=(String s)
{
    swap(s);
    return *this;
}

reserve、insert、push_back、append、+=

为了方便扩容,我们先写reserve

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

insert 插入一个字符,要注意检查是否需要扩容,这里采用2倍扩容:

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

👆:传返回值是为了模仿标准库,实际没什么用。

👆:这里的插入算法如果写成下列形式可能会出现死循环:

for (size_t end = _size; end >= pos; --end)
{
    _str[end + 1] = _str[end];
}

👆:因为我们用的是 size_t (无符号整型),当 pos 为0(头插)时,end < 0 的终止条件无法达成,形成死循环

insert 插入一个字符串:

插入字符串和插入字符不同,插入字符串的扩容要特别计算一下,因为对于插入字符串来说,无脑2倍扩不一定够:

String& insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
		reserve(_size + len);

	for (size_t end = _size + len; end > pos + len - 1; --end)
	{
		_str[end] = _str[end - len];
	}
	strncpy(_str + pos, str, len);
	_size += len;
	return *this;
}

剩下的函数直接复用 insert 即可:

void push_back(char ch)
{
    insert(_size, ch);
}
void append(const char* str)
{
    insert(_size, str);
}
String& operator+=(char ch)
{
    insert(_size, ch);
    return *this;
}
String& operator+=(const char* str)
{
    insert(_size, str);
    return *this;
}

erase

首先定义一个静态成员变量npos,类内声明,类外定义。

class String
{
    //...
private:
	//...
	const static size_t npos;
};
const size_t String::npos = -1;

erase的实现,分两种情况:

  1. pos 位置开始,删到最后,这种情况直接把 pos 位置的值设为 \0 即可
  2. pos 位置开始,没有删到最后,那就要把后面的往前移
void erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	if (len == npos || pos + len >= _size)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		for (size_t begin = pos + len; begin <= _size; ++begin)
		{
			_str[begin - len] = _str[begin];
		}
		_size -= len;
	}
}

resize

void resize(size_t n, char ch = '\0')

分三种情况:

  1. n > capacity:扩容并将字符追加至n个
  2. size < n < capacity:不扩容,直接将字符追加至n个
  3. n < size:不扩容,缩减字符至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';
	}
}

比较运算符重载(非成员函数)

字符串比较直接用 strcmp,先写 <== ,其他的复用即可。

bool operator<(const String& s1, const String& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator==(const String& s1, const String& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator<=(const String& s1, const String& s2)
{
	return s1 < s2 || s1 == s2;
}
bool operator>(const String& s1, const String& s2)
{
	return s2 < s1;
}
bool operator>=(const String& s1, const String& s2)
{
	return !(s1 < s2);
}
bool operator!=(const String& s1, const String& s2)
{
	return !(s1 == s2);
}

迭代器

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;
}

使用迭代器遍历:

void test_string5()
{
	String s1("hello world");
	String::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << ' ';
		++it;
	}
	cout << endl;
}
//结果:h e l l o   w o r l d

有了迭代器,范围for也就可以用了,因为范围for的底层实现其实就是迭代器:

void test_string5()
{
	String s1("hello world");
	for (auto& ch : s1)
	{
		cout << ch << ' ';
	}
	cout << endl;
}
//结果:h e l l o   w o r l d

find

查找字符:

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

查找字符串,用 strstr

size_t find(const char* str, size_t pos = 0) const
{
    const char* p = strstr(_str + pos, str);
    if (p == nullptr) return npos;
    return p - _str;
}

流插入、流提取运算符重载、clear

为了保证 cout 是第一操作数,流插入运算符重载要实现在类外面。

通过 c_str 函数,我们也不需要访问私有成员变量,也就不需要设置友元函数。

ostream& operator<<(ostream& out, const String& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

注意:不能把范围for写成 out << s.c_str() 。因为如果字符串中间有\0,这种写法打印到\0就结束了

如下,两种方式的结果是不同的:

void test_string7()
{
	String s("hello");
	s += '\0';
	s += " world";
    
	cout << s.c_str() << endl;
	//结果:hello
	for (auto& ch : s)
	{
		cout << ch;
	}
	cout << endl;
    //结果:hello world
}

流提取之前需要把原有数据清空,所以先实现 clear

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

流提取:

一个字符一个字符地读取,遇到空格或换行符停止。

in.get() 用来读取空格和换行符。

istream& operator>>(istream& in, String& s)
{
    s.clear();
	char ch;
	ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		ch = in.get();
	}
	return in;
}

如果输入的字符串很长,频繁+=的扩容有一点影响效率,优化版本:

给一个大小为128的缓冲数组,满了再+=

istream& operator>>(istream& in, String& s)
{
    s.clear();
	char ch;
	ch = in.get();
	char buff[128] = { 0 };
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			s += buff;
			memset(buff, '\0', 128);
			i = 0;
		}
		ch = in.get();
	}
	s += buff;
	return in;
}

完整代码

#pragma once
#include <iostream>
#include <cassert>
using namespace std;

class String
{
public:
	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;
	}
	String(const char* str = "")
		:_size(strlen(str))
		, _capacity(_size)
	{
		_str = new char[_capacity + 1];
		strcpy(_str, str);
	}

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

	void swap(String& s)
	{
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}

	String(const String& s)
		:_str(nullptr)
		, _size(0)
		, _capacity(0)
	{
		String tmp(s._str);
		swap(tmp);
	}

	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
	}

	//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& operator=(const String& s)
	//{
	//	if (this != &s)
	//	{
	//		String tmp(s._str);
	//		swap(tmp);
	//	}
	//	return *this;
	//}

	String& operator=(String s)
	{
		swap(s);
		return *this;
	}

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

	char& operator[](size_t pos)
	{
		assert(pos < _size);
		return _str[pos];
	}
	const char& operator[](size_t pos) const
	{
		assert(pos < _size);
		return _str[pos];
	}

	size_t size() const
	{
		return _size;
	}

	size_t capacity() const
	{
		return _capacity;
	}

	String& operator+=(const char* str)
	{
		insert(_size, str);
		return *this;
	}

	String& operator+=(char ch)
	{
		insert(_size, ch);
		return *this;
	}

	void reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = 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';
		}
	}

	void push_back(char ch)
	{
		insert(_size, ch);
	}
    
	void append(const char* str)
	{
		insert(_size, str);
	}

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

	String& insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t len = strlen(str);
		if (_size + len > _capacity)
			reserve(_size + len);

		for (size_t end = _size + len; end > pos + len - 1; --end)
		{
			_str[end] = _str[end - len];
		}
		strncpy(_str + pos, str, len);
		_size += len;
		return *this;
	}

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

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

	size_t find(const char* str, size_t pos = 0) const
	{
		const char* p = strstr(_str + pos, str);
		if (p == nullptr) return npos;
		return p - _str;
	}

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

private:
	char* _str;
	size_t _size;
	size_t _capacity;
	const static size_t npos;
};
const size_t String::npos = -1;

ostream& operator<<(ostream& out, const String& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

//istream& operator>>(istream& in, String& s)
//{
//	char ch;
//	ch = in.get();
//	while (ch != ' ' && ch != '\n')
//	{
//		s += ch;
//		ch = in.get();
//	}
//	return in;
//}

istream& operator>>(istream& in, String& s)
{
	s.clear();
	char ch;
	ch = in.get();
	char buff[128] = { 0 };
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			s += buff;
			memset(buff, '\0', 128);
			i = 0;
		}
		ch = in.get();
	}
	s += buff;
	return in;
}

bool operator<(const String& s1, const String& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator==(const String& s1, const String& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator<=(const String& s1, const String& s2)
{
	return s1 < s2 || s1 == s2;
}
bool operator>(const String& s1, const String& s2)
{
	return s2 < s1;
}
bool operator>=(const String& s1, const String& s2)
{
	return !(s1 < s2);
}
bool operator!=(const String& s1, const String& s2)
{
	return !(s1 == s2);
}
  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

世真

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值