C++string类的模拟实现

string类的模拟实现

一、string类的成员变量 二、四大默认成员函数的实现 (1)构造函数 (2)拷贝构造函数 (3)析构函数 (4)赋值运算符重载 三、string类中辅助功能实现 (1)reserve (2)resize (3)size (4)clear (5)empty 四、string的增删查改 (1)push_back (2)append (3)operator+= (4)insert (5)erase (6)pop_back 五、string的遍历 (1)重载operator[ ] 的遍历方式 (2)使用迭代器的遍历方式4 (3)使用范围for的遍历方式 六、查找 find 七、大小比较 各种符号

一、string类的成员变量

string类成员变量类似于顺序表
class string
{
private:
	char* _str;			//存储字符串
	size_t _capacity;	//表征string类的存储有效字符个数的容量大小
	size_t _size;		//表征已存储的有效字符个数
};

二、四大默认成员函数的实现

(1)构造函数

构造函数最好写成带有缺省参数的
string(const char* s = "")		//默认值应该给 “” 如果给空会在strlen时访问空指针报错,所以要默认给一个空串
	:_size(strlen(s))
	, _capacity(_size)						//_size和_capacity在参数列表初始化
	, _str(new char[_capacity + 1])			//开的空间比capacity多1
{
	strcpy(_str, s);						//把字符串内容拷贝过去
}

(2)拷贝构造函数

string类再拷贝构造的时候,指针不能进行浅拷贝,而是需要深拷贝
string(const string& s)
	:_str(new char[strlen(s._str)+1])		//多开出一个空间,用来存储‘\0’(字符串的结束标志)
{
	strcpy(_str, s._str);
	_size = s._size;
	_capcacity = s._capacity;
}

(3)析构函数

string类的析构函数还是很有必要的,因为有动态开辟空间需要清理
~string()
{
	delete[] _str;
	_str = nullptr;
	_size = 0;
	_capacity = 0;
}

(4)赋值运算符重载

与拷贝构造同理,也是要进行深拷贝
string& operator&=(string s)		//返回值是strng&便于连续赋值
{
	if(this != &s)					//不相等,说明不是自己给自己赋值
	{
		delete[] _str;				//为了进行深拷贝,先将原先空间释放,再按照s开辟新空间
		_str = new char[strlen(s._str)+1];
		strcpy(_str, s._str);			//将s中字符串拷贝给_str
		_size = s._size;
		_capacity = s._capacity;
	}
}

三、string类中辅助功能实现

在这里插入图片描述

(1)reserve

开辟空间,扩展_capacity,但是_str、_size不变
void reverse(size_t n)
{
	if(n>_capacity)
	{
		char* tmp = new char[n+1];		//利用tmp开辟一块容量为n+1的空间(多的一个还是用来存'\0')
		strncpy(tmp, _str, _size+1);	//将原空间的内容拷贝到tmp中
		delete[] _str;					//释放掉原来空间
		_str = tmp;						//_str指向tmp
		_capacity = n;					//改变_capacity
	}
}

(2)resize

不是开辟空间,而是扩展_size,并且将_str中存储相应字符(默认’\0’)
void resize(size_t n, char ch = '\0')
{
	if(n>_size)
	{
		if(n>_capacity)
		{
			reverse(n);
		}
		memset(_str+_size, ch, n-_size);
	}
	_size = n;
	_str[_size] = '\0';
}

(3)size

返回串中有效字符个数
const size_t size()
{
	return _size;
}

(4)clear

直接将字符串完全清空
void clear()
{
	_size = 0;
	_str[0] = '\0';
}

(5)empty

判断字符串是否为空
bool empty()
{
	return _size == 0;
}

四、string的增删查改

在这里插入图片描述

(1)push_back

在串后面增加字符
void push_back(char ch)
{
	if(_size == _capacity)		//空间不够,徐娅扩容,增加_capacity,一般增加两倍
	{
		reverse(_capacity == 0? 4:2*_capacity);
	}
	_str[_size++] = ch;			//加上字符
	_str[_size] = '\0';			//补充上'\0'
}

(2)append

在串后面增加字符串
void append(const char* s)
{
	size_t len = _size + strlen(s);		//计算增加后字符总长度
	if(len > _capacity)					//如果总长度大于原_capacity,说明需要扩容
	{
		reverse(len);
	}
	strcpy(_str+_size, s);				//将字符串拷贝到原字符串末尾
	_size = len;						//更新_size
}

(3)operator+=

在串后面增加字符 或者字符串,有两个函数重载
string& operator+=(char ch)				//为了支持连续++,就要返回string&
{
	push_back(ch);						//在最后增加一个字符,可以直接复用push_back()
	return *this;
}

string& operator+=(const char* s)
{
	append(s);							//在字符串未增加字符串,可直接复用append
	return *this;
}

(4)insert

可以在任意位置增加字符或者字符串,有两个函数重载
string& insert(szie_t pos = 0, char ch)
{
	assert(pos <= _size)							//确保pos位置的合法性
	if(_size == _capacity)						//考虑容量
	{
		reverse(_capacity == 0?4:2*_capacity);
	}
	for(size_t i = _size; i>pos; i--)			//将pos及以后的字符往后挪动一位
	{
		_str[i] = _str[i-1];
	}
	_str[pos] = ch;
	_size += 1;
	_str[_size] = '\0';
	return *this;
}

string& insert(size_t pos = 0, const char* s)
{
	assert(pos <= _size);				//判断pos位置的合法性
	size_t len = _size + strlen(s);		//计算增加后的总长度
	if(len > _capacity)					//判断是否需要扩容
	{
		reverse(len);
	}
	for(size_t i = len; i > pos; i--)	//往后挪动数据
	{
		_str[i] = _str[i - strlen(s)];
	}
	for(size_t i = pos; i < pos+strlen(s); i++)//将字符串拷贝到原字符串中
	{
		_str[i] = s[i-pos];
	}
	_size += strlen(s);					//更新_size
	_str[_size] = '\0';					//增添'\0'
	return *this;
}

(5)erase

与insert相反,在指定位置删除字符或者字符串
string& erase(size_t pos = 0, size_t len = npos)
{
	assert(pos < _size);				//判断位置合法性
	size_t leftLen = _size - pos;		//计算pos及以后总字符长度
	if(len > leftLen)					//如果要求删除的多余剩余的,
	{
		_size = pos;
		_str[pos] = '\0';
	}		
	else								//要求删除的少于剩余的
	{
		strcpy(_str+pos, _str+pos+len);	//剩余字符往前挪动
		_size -= len;					//更新_size
	}
	return *this;
}

(6)pop_back

在串后面j减少一个字符
void pop_back()
{
	erase(_size-1, 1);			//直接复用erase即可
}

五、string的遍历

(1)重载operator[ ] 的遍历方式

使对象能够像数组一样遍历
char& operator[](size_t i)		//因为要想数组一样,可读可写,所以要返回引用,否则是一个临时变量,只可读不可写。
{
	assert(i<_size);
	return _str[i];
}
int main()
{
	string s("hello world");
	for(size_t i = 0;i < s.size(); i++)
		s[i] += 1;
	return 0;
}

(2)使用迭代器的遍历方式4

typedef char* iterator;				//在string类中,迭代器就是指针,归根结底就是在操作指针,但是不是所有的迭代器都是指针
iterator begin()
{
	return _str;
}
iterator end()
{
	return _str+_size;
}
int main()
{
	string s("hello world");
	iterator it = s.begin();
	while(it != s1.end())
	{
		cout<< *it << " ";
		it++;
	}
}

(3)使用范围for的遍历方式

int main()
{
	string s("hello world");
	for(auto ch : s)				//auto可以自动识别类型
	{								//其实范围for底层就是迭代器
		cout << ch <<" ";
	}
	return 0;
}

六、查找

find

支持从某一位置开始,寻找子串或者字符,返回起始位置下标。因此有两个函数重载
size_t find(char ch, size_t pos = 0) const	//由于不改变内容,所以可const修饰
{
	assert(pos < _size);
	for(size_t i = 0; i<_size; i++)
	{
		if(_str[i] == ch)
			return i;
	}
	return npos;
}

size_t find(const char* s, size_t pos)
{
	assert(pos < _size);
	char* ret = strstr(_str+pos, s);
	if(ret == NULL)
		return npos;
	else
		return ret-_str;			//指针-指针就是距离 = 下标
}

七、大小比较

各种符号

bool operator>(const string& s) const
{
	return strcmp(_str, s._str) > 0;
}
bool operator<(const string& s) const
{
	return strcmp(_str, s._str) < 0;
}
bool operator==(const string& s) const
{
	return strcmp(_str, s._str) == 0;
}
bool operator>=(const string& s) const
{
	return !(*this < s);
}
bool operator<=(const string s) const
{
	return !(*this>s);
}
bool operator!=(const string& s) const
{
	return !(*this == s);
}
来源url
栏目