【C++】string模拟实现

模拟实现string类

模拟string类,采用声明和定义分离的形式,声明在.h文件中,定义在.cpp文件中

1、成员变量

c 复制代码
private:
		char* _str;
		size_t _size;
		size_t _capacity;
public:
		static const size_t npos;  //需要在类外面定义
  • _str用来存储字符串,_size用来表示有效字符个数,_capacity用来表示可以存储有效字符的容量
  • npos是一个固定的特殊数字 ,默认值为-1,用来表示两种意思:1.查找时没找到 2.一直到最后
  • 因为string本质是一个动态顺序表,所以字符串通过在堆上申请空间进行存储

2、成员函数

2.1 默认构造函数

c 复制代码
namespace MyString
{
	string::string(const char* str)
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_size + 1];
		//memcpy(_str, str, _size);
		//strcpy(_str, str);  //直接用strcpy也可以
		memcpy(_str, str, _size + 1);
	}

注意:形参必须加const修饰,这样才能用C语言中的常量字符串初始化string对象,形参的缺省值直接给一个空字符即可,用""表示

2.2 拷贝构造函数

c 复制代码
//传统写法
tring::string(const string& s)
{
	cout << "string::string(const string& s)" << endl;
	_str = new char[s._capacity + 1];
	memcpy(_str, s._str, s._size + 1);
	_size = s._size;
	_capacity = s._capacity;
}

//优化后写法
void string::swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

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

注意:优化后写法不需要手动申请空间才初始化,而是调用构造函数去完成,重载swap函数,利用swap函数进行互换,如果string对线中有'\0',只会把'\0'前面的字符拷贝过去

2.3 析构函数

c 复制代码
string::~string()
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}

2.4 operator=

传统写法:

c 复制代码
string& string::operator=(const string& s)
{
	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;
}

注意:这种写法需要手动开辟空间tmp,并且释放空间_str,改进后通过重载函数完成这些操作

优化后:

c 复制代码
//优化后写法
string& string::operator=(const string& s)
{
	if (this != &s)
	{
		string tmp(s);
		std::swap(_str, tmp._str);
		std::swap(_size, tmp._size);
		std::swap(_capacity, tmp._capacity);
	}
	return *this;
}

//再次改进
void string::swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}
string& string::operator=(string tmp)
{
	swap(tmp);

	return *this;
}

这种写法通过拷贝构造申请空间,利用局部对象出了作用域就会被销毁的特点,通过swap将需要释放的资源交换给这个局部变量,同时可以不用库里面的swap,重载一个swap交换两个string对象

2.5 c_str()

c 复制代码
const char* string::c_str() const
{
	return _str;
}

注意:要加上const,使普通对象和const类型的对象都可以使用

2.6 size()

c 复制代码
size_t string::size() const
{
	return _size;
}

2.7 operator[]

c 复制代码
//可读可写
char& string::operator[](size_t i)
{
	assert(i < _size);

	return _str[i];
}
c 复制代码
//只读
const char& string::operator[](size_t i) const
{
	assert(i < _size);

	return _str[i];
}

两个函数构成函数重载,普通对象调用读写版本,const对象会调用只读版本

2.8 iterator

iterator是string类里面定义的类型,string类中的iterator是通过typedef来实现的

c 复制代码
typedef char* iterator;
typedef const char* const_iterator;
string::iterator string::begin()
{
	return _str;
}

string::iterator string::end()
{
	return _str + _size;
}

string::const_iterator string::begin() const
{
	return _str;
}

string::const_iterator string::end() const
{
	return _str + _size;
}

迭代器设置为char*指针类型,所以begin()和end()是获取指针位置

2.9 reserve

c 复制代码
void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		cout << "reserve:" << n << endl;
		char* tmp = new char[n + 1];
		//strcpy(tmp, _str);
		memcpy(tmp, _str, _size + 1);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

2.10 push_back

c 复制代码
void string::push_back(char ch)
{
	//先检查容量,进行扩容
	if (_size >= _capacity)
	{
		size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
		reserve(newcapacity);
	}

	_str[_size] = ch;
	++_size;
	_str[_size] = '\0';
}

注意:调用reserve函数进行扩容时,需要先进行判断,capacity==0时给一个初始大小

2.11 pop_back

c 复制代码
void string::pop_back()
{
	assert(_size > 0);

	--_size;
	_str[_size] = '\0';
}

2.12 append

c 复制代码
oid string::append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
		reserve(newcapacity);
	}

	//strcpy(_str+_size, str);
	memcpy(_str + _size, str, len + 1);
	_size += len;
}

2.13 operator+=

c 复制代码
//追加一个字符
string& string::operator+=(char ch)
{
	push_back(ch);
	return *this;
}

//追加字符串
string& string::operator+=(const char* str)
{
	append(str);
	return *this;
}

2.14 insert

c 复制代码
//插入一个字符
string& string::insert(size_t pos, char ch)
{
	assert(pos <= _size);

	if (_size >= _capacity)
	{
		size_t newcapacity = _capacity == 0 ? 4 : 2 * _capacity;
		reserve(newcapacity);
	}
	//挪动数据
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}

	_str[pos] = ch;
	++_size;

	return *this;
}

注意:注意挪动时的判断条件,end不能初始化为int类型,如果end初始化类型为int(有符号),pos和_size是size_t(无符号),例如:

c 复制代码
int end = _size;
while (end >= (int)pos)
{
	_str[end + 1] = _str[end];
	--end;
}

end最后会减到-1,而-1在无符号数里是4294967295(极大值),永远为真,程序崩溃

整形的静态成员变量在加上const修饰后就可以在声明的地方给默认值,仅限整形

c 复制代码
static const size_t npos = -1;  //正确
static const double a = 1.2;   //错误
c 复制代码
string& string::insert(size_t pos, const char* str)
{
	assert(pos <= _size);

	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		size_t newcapacity = 2 * _capacity > _size + len ? 2 * _capacity : _size + len;
		reserve(newcapacity);
	}

	// 挪动数据
	size_t end = _size + len;
	while (end > pos + len - 1)
	{
		_str[end] = _str[end - len];
		--end;
	}

	for (size_t i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}

	_size += len;

	return *this;
}

2.15 erase

c 复制代码
string& string::erase(size_t pos, size_t len)
{
	assert(pos < _size);

	// 要删除的数据,大于pos后面的字符个数
	// pos后面全删
	if (len == npos || len >= (_size - pos))
	{
		_size = pos;
		_str[_size] = '\0';
	}
	else
	{
		size_t i = pos + len;
		memmove(_str + pos, _str + i, _size + 1 - i);
		_size -= len;
	}

	return *this;
}

注意 :pos是需要删除数据的起始位置,数组分为两部分,[0,pos-1][pos,_size-1] ,[0,pos-1]区域不需要删除 ,[pos,_size-1]是待删除区域 ,需要删除len个元素,当不需要删除的字符数加需要删除的字符数len大于等于全部的有效字符数时,说明待删除区域的所有字符都要删除,即pos+len>=_size 时,需要从pos位置开始删除后面的所有字符,最后需要把pos位置的字符设置成\0

2.16 clear

c 复制代码
void string::clear()
{
	_str[0] = '\0';
	_size = 0;
}

清空字符串,将第一个位置改为'\0',把字符串长度改为0

2.17 find

c 复制代码
//查找一个字符
size_t string::find(char ch, size_t pos)  const
{
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}

	return npos;
}

//查找一个字符串
size_t string::find(const char* str, size_t pos)  const
{
	const char* p1 = strstr(_str + pos, str);
	if (p1 == nullptr)
	{
		return npos;
	}
	else
	{
		return p1 - _str;
	}
}

注意 :查找字符串的位置时,最后应该返回的位置是p1 - _str

2.18 substr

c 复制代码
string string::substr(size_t pos, size_t len) const
{
	if (len == npos || len >= _size - pos)
	{
		len = _size - pos;
	}

	string ret;
	ret.reserve(len);
	for (size_t i = 0; i < len; i++)
	{
		ret += _str[pos + i];
	}
	return ret;
}

利用reserve函数给新的字符串分配所需空间,再拷贝字符串

2.19 operator<

c 复制代码
bool string::operator<(const string& s) const
{
	size_t i1 = 0, i2 = 0;
	while (i1 < _size && i2 < s._size)
	{
		if (_str[i1] < s[i2])
		{
			return true;
		}
		else if (_str[i1] > s[i2])
		{
			return false;
		}
		else
		{
			++i1;
			++i2;
		}
	}

	return i2 < s._size;
}

注意:string类对象是根据ASCII码值进行比较的,不能用strcmp,strcmp遇到'\0'会终止,不支持带空字符的字符串,也不能用memcmp,memcmp 只管内存内容,只比较长度相等的部分

2.20 operator==

c 复制代码
bool string::operator==(const string& s) const
{
	size_t i1 = 0, i2 = 0;
	while (i1 < _size && i2 < s._size)
	{
		if (_str[i1] != s[i2])
		{
			return false;
		}
		else
		{
			++i1;
			++i2;
		}
	}

	return i1 == _size && i2 == s._size;
}

利用<和==,解决剩下的部分

2.21 <=、>、>=、!=

c 复制代码
bool string::operator<=(const string& s) const
{
	return *this < s || *this == s;
}

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

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

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

2.22 operator<<

c 复制代码
ostream& operator<<(ostream& out, const string& s)
{
	//out << s.c_str();
	for (size_t i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;
}

注意 :流插入和流提取运算符重载要写在类外面,不能用str._str或str.c_str()打印,因为字符串的有效字符中可能有\0存在,这两种方法遇到\0会终止打印,无法将一个string对象完整的打印出来,所以要逐个打印

2.23 operator>>

c 复制代码
istream& operator>>(istream& in, string& s)
{
	s.clear();

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

注意 :空格' '和\n作为输入时分割多个string对象的标志,cin >> ch 不能读取到空格和换行,需要借助get()函数才能读到空格和换行符,其次string进行二次流提取时会进行覆盖,插入前要先进行判断

优化版本:

c 复制代码
istream& operator>>(istream& in, string& s)
{
	s.clear();

	char buff[128];
	int i = 0;

	char ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}

		ch = in.get();
	}

	if (i > 0)
	{
		buff[i] = '\0';
		s += buff;
	}

	return in;
}

开辟一个数组,将输入的字符存储在数组中,然后把数组的数据拷贝到string中

注意:istream和ostream必须用引用,因为这两个类中的对象不允许被拷贝或赋值

如果文章中有错误或不足,欢迎大家指正交流。

相关推荐
特种加菲猫2 小时前
C++进阶:模板深度解析与继承机制初探
开发语言·c++
旖-旎2 小时前
递归(快速幂)(5)
c++·算法·力扣·递归
大江东去浪淘尽千古风流人物5 小时前
【cuVSLAM】GPU 加速、多相机、实时视觉/视觉惯性 SLAM设计优势
c++·人工智能·数码相机·ubuntu·计算机视觉·augmented reality
自信1504130575911 小时前
重生之从0开始学习c++之模板初级
c++·学习
历程里程碑11 小时前
2. Git版本回退全攻略:轻松掌握代码时光机
大数据·c++·git·elasticsearch·搜索引擎·github·全文检索
极客智造11 小时前
深度解析 C++ 类继承与多态:面向对象编程的核心
c++
零号全栈寒江独钓14 小时前
基于c/c++实现linux/windows跨平台获取ntp网络时间戳
linux·c语言·c++·windows
CSCN新手听安14 小时前
【linux】高级IO,以ET模式运行的epoll版本的TCP服务器实现reactor反应堆
linux·运维·服务器·c++·高级io·epoll·reactor反应堆
松☆16 小时前
C++ 算法竞赛题解:P13569 [CCPC 2024 重庆站] osu!mania —— 浮点数精度陷阱与 `eps` 的深度解析
开发语言·c++·算法