c++ | string类模拟实现

1.成员变量

private:  
    char* _str;
    size_t _size;//有效字符长度
    size_t _capacity;//容量
    const static size_t npos;//静态成员 npos

注:npos 是静态常量,类型是size_t, 具有最大可能值,此值在字符串的成员函数中用作 len(或 sublen)参数的值时,表示“直到字符串结束”。

2.构造函数

缺省构造函数

string(const char* str="")
	:_size(strlen(str))
{
	_capacity = _size == 0 ? 3 : _size;
	_str = new char[_capacity+1];//“+1”给‘\0’准备
	strcpy(_str,str);
}

1)我们在对成员变量进行初始化时,初始化的顺序是按照声明顺序进行的,因此我们在初始化列表中只对_size进行了初始化是为了防止在用_size对_capacity进行赋值时_size为随机值;

2)在为_str开空间时_str的实际容量大小为_capacity+1,因为capacity为有效字符的最大存储容量(不包括'\0');

3)复用strcpy函数将数据添加到_str中;

4)最后我们需要给他添加一个缺省值,有些同学会直接写上(const char* str=nullptr);这种写法是错误的,因为我们在初始化_size时会使用strlen()会对空指针进行解引用而造成程序崩溃,我们可以使用""或"\0"作为缺省值。

拷贝构造函数

1.传统写法

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

1)若不写拷贝构造,系统会默认生成一个拷贝构造,但他并不能满足我们的需求,因为系统默认生成的拷贝构造为浅拷贝/值拷贝,因此我们需要自己实现拷贝构造进行深拷贝;

2)在拷贝构造时不可以直接写_str=s._str;这会造成浅拷贝问题,会导致s和*this中的_str指向同一块内存,当其中的某一字符串对象修改这块空间时另一个字符串对象的数据也会发生改变,最后调用析构函数时也会将同一块空间释放两次而造成程序崩溃。

浅拷贝:

因此,我们需要进行深拷贝,给*this中的_str分配内存,再将s中_str的数据拷贝进去 

深拷贝:

 2.现代写法

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

 

 3.析构函数

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

1)若不写析构函数,系统默认生成的析构函数会对内置数据类型不做处理,自定义类型调用他自己的拷贝构造,我们有为_str动态开辟内存,若不手动释放资源,则会造成内存泄漏问题;

2)将_str的内存释放掉,然后再将成员变量置0置空,

 4.扩容reserve

void reserve(size_t n) {
	if (n < _capacity) {
		return;
	}
	char* temp = new char[n + 1];//‘/0’
	strcpy(temp, _str);
	delete[]_str;
	_str = temp;
	_capacity = n;
	return;
}

1)进行扩容时首先判断n是否大于_capacity若n小则直接退出避免进行缩容操作;

2)因不支持原地扩容,我们先将空间开给一临时变量;

3)将数据拷贝给临时变量指向的空间后,释放_str的空间再将_str指向新空间。

5.resize

void resize(size_t n, char c = '\0') {
	if (n<_size) {
		_size = n;
		_str[_size] = '\0';
		return;
	}
	if (n > _capacity) {
		reserve(n);
	}
	while (_size < n) {
		_str[_size++] = c;
	}
	_str[_size] = '\0';
	return;
}

1)在resize中n的不同取值范围会有不同的效果,我们需要分情况讨论;

2)当n<_size时会将字符串有效长度保留为n个,其他元素直接删除;

3)当n>_size时会将字符串有效长度增加为n个,增加的元素由字符c填充,c缺省为'\0';

4)当n>capacity会进行扩容,但n<capacity时不会缩容;

5)最后一定要将_size位置的元素置为'\0'。

6. insert

由于库里insert函数重载太多了,我们在这里只实现两个

插入单个字符

void insert(size_t pos, char ch) {
	assert(pos <= _size );
	if (_size + 1 > _capacity) {
		reserve(_capacity * 2);
	}
    //移位
	size_t end = _size+1;
	while (end > pos) {
		_str[end] = _str[end-1];
		end--;
	}
    //插入
	_str[pos] = ch;
	++_size;
}

1)首先判断pos位置的合理性;

2)其次判断是否需要进行扩容操作当_size+1>_capacity时就要进行扩容;

3)确保由足够的空间后,将pos位置为新元素预留出来,进行移位操作;

4)最后将新元素插入。

注:有些同学会在移位写 

size_t end=_size;

while(end>=pos){

        _str[end+1]=_str[end--];

}

这个写法可能会出现问题,当pos=0,end=-1时,理应跳出循环但由于pos和end都为size_t类型,size_t类型中的-1为最大值,就会再次进入循环并越界。

 插入字符串

void insert(size_t pos, const char* str) {
	assert(pos >= 0 && pos <= _size);
	size_t len = strlen(str);
	//扩容
	if (_size + len > _capacity) {
		reserve(_size + len);
	}
	//移位
	size_t end1 = _size;//原字符串'\0'位置
	size_t end2 = _size + len;//新字符串'\0'的位置
	//pos+len是给插入字符预留位置的后一个位置
	while (end2 >= pos + len) {
		_str[end2--] = _str[end1--];
	}
	//插入
	const char* cur = str;
	while (*cur != '\0') {
		_str[pos++] = *cur;
	}
	_size += len;
}

 在插入时也可使用strncpy(_str+pos,str);

7.erase

void erase(size_t pos, size_t len=npos) {
	if (pos + len >= _size-1) {
		_size = pos;
		_str[_size] = '\0';
	}
	else if (len == 0) {
		return;
	}
	else {
		int start1 = pos;//起始删除位置
		int start2 = pos + len;//删除结束位置(最后一个被删字符的下一位置)
		while (start2 <= _size) {
			_str[start1++] = _str[start2++];
		}
		_size -= len;
	}

}

当pos+len>_size-1时将pos位置后所有元素删除。

8.push_back

void push_back(char ch) {
	if (_size +1 > _capacity) {
		reserve(_capacity * 2);
	}
	_str[_size] = ch;
	_size++;
	_str[_size] = '\0';
}

 也可以复用 insert(_size,ch);

9.append

string& append(const char* str) {//多个
	size_t len = strlen(str);
	if (_size + len > _capacity) {
		reserve(_size + len);
	}
	const char* cur = str;
	while (*cur != '\0') {
		_str[_size++] = *(cur++);
	}
	_str[_size] = '\0';
	return *this;
}

复用insert(_size, str);

相对于insert来说,省略了移位步骤。

10.swap

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

 与库中swap函数的不同点:

1)string中的swap是直接更改了两个_str的指向并没有创建出一个新对象来;

2)库中的swap是通过拷贝构造创建出一个对象再通过=(赋值)来进行数据的交换的,很大降低了效率。

库中swap实现:

11.find

找字符

size_t find(char c, size_t pos = 0) const {
	assert(pos >= 0 && pos < _size);
	for (int i = 0; i < _size; i++) {
		if (_str[i] == c) {
			return i;
		}
	}
	return npos;
}

找字符串

size_t find(const char* s, size_t pos = 0) {
	assert(pos >= 0 && pos < _size);
	int i = 0, j = 0;
	while (i < _size && j < strlen(s)) {
		if (_str[i] == s[j]) {
			i++;
			j++;
		}
		else {
			i = i - j + 1;
			j = 0;
		}
	}
	if (j == strlen(s)) {
		return i - j;
	}
	return npos;

}

while循环可以用char* p=strstr(str+pos,str);替代,若p!=nullptr返回p-str,若p==nullptr返回npos。

12.clear

void clear() {
	erase(0, _size);
	_size = 0;
}

13.运算符重载

[ ]

char& operator [](size_t i) {
	assert(i >= 0 && i < _size);
	return _str[i];
}
char& operator [](size_t i) const{
	assert(i >= 0 && i < _size);
	return _str[i];
}

 引用返回可以直接对返回值进行修改。

+=

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

=

传统写法:

string& operator =(const string& s) {
	if (this == &s) {
		return *this;
	}
	char* tmp = new char[s._size + 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) {
		return *this;
	}
	string tmp(s);//拷贝构造
	swap(tmp);
}

比较运算符

//重载 ==
bool operator ==(string& s) const {
	return strcmp(_str, s._str)==0;
}
//重载 >
bool operator >(string& s) const {
	return strcmp(_str, s._str) > 0;
}
//重载>=
bool operator >=(string& s) const {
	return *this == s || *this > s;
}
//重载<
bool operator <(string& s) const {
	return !(*this >= s);
}
//重载<=
bool operator <=(string& s)const {
	return !(*this > s);
}
//重载!=
bool operator !=(string& s)const {
	return !(*this == s);
}
		

13.迭代器

typedef char* iterator;
typedef const char* const_iterator;
iterator begin() {
	return _str;
}
iterator end() {
	return _str + _size;
}
const_iterator cbegin() const {
	return _str;
}
const_iterator cend() const {
	return _str + _size;
}
		

 迭代器是一个类指针的东西,我这里的迭代器就是由指针实现的,而vs的迭代器是用一个类单独封装起来的。

14.流插入,流提取

std::ostream& operator <<(std::ostream& out, string& s) {
	size_t i = 0;
	while (i < s.size()) {
		out << s[i++];
	}
	return out;
}
std::istream& operator >>(std::istream& in, string& s) {
	s.clear();
	char ch = in.get();//get()可以拿到缓冲区的所有值
	char buff[128];
	size_t i = 0;
	while (ch != ' ' && ch != '\n') {//getline()只用'\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;
}

 在流提取中为了减少+=时扩容的次数先将缓冲区的值保存在数组buff中;等缓冲区中数据读取完毕或buff数组满了后在进行插入字符串;可以提高效率。

15.整体代码

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<assert.h>
using std::cout;
using std::endl;
using std::cin;
namespace kyokam {
	class string {
	public:
		//构造函数
		string(const char* str="")
			:_size(strlen(str))
		{
			_capacity = _size == 0 ? 3 : _size;
			_str = new char[_capacity+1];//“+1”给‘\0’准备
			strcpy(_str,str);
		}
		//拷贝构造
		string(const string& s)
			:_size(s._size)
			, _capacity(s._capacity)
		{
			_str = new char[_capacity + 1];
			strcpy(_str, s._str);
		}
		string(const string& s)
			:_size(s._size)
			, _capacity(s._capacity)
		{
			string tmp(s._str);
			std::swap(_str, tmp._str);
		}
		~string() {
			delete[]_str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}
		//迭代器
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin() {
			return _str;
		}
		iterator end() {
			return _str + _size;
		}
		const_iterator cbegin() const {
			return _str;
		}
		const_iterator cend() const {
			return _str + _size;
		}
		
		//扩容+初始化
		void resize(size_t n, char c = '\0') {
			if (n<_size) {
				_size = n;
				_str[_size] = '\0';
				return;
			}
			if (n > _capacity) {
				reserve(n);
			}
			while (_size < n) {
				_str[_size++] = c;
			}
			_str[_size] = '\0';
			return;
		}
		//扩容
		void reserve(size_t n) {
			if (n < _capacity) {
				return;
			}
			char* temp = new char[n + 1];//‘/0’
			strcpy(temp, _str);
			delete[]_str;
			_str = temp;
			_capacity = n;
			return;
		}
		//在某位进行插入删除
		string& insert(size_t pos, char c) {
			assert(pos <= _size);
			if (_size + 1 > _capacity) {
				reserve(_capacity * 2);
			}
			//移位
			size_t end = _size + 1;
			while (end >= pos + 1) {
				_str[end--] = _str[end];
			}
			//修改
			_str[pos] = c;
			
			_size += 1;
			return *this;
		}
		string& insert(size_t pos, const char* str) {
			assert(pos >= 0 && pos <= _size);
			size_t len = strlen(str);
			//扩容
			if (_size + len > _capacity) {
				reserve(_size + len);
			}
			//移位
			size_t end = _size;
			for (int n = 0; n <= _size - pos + 1; n++) {//_size-pos+1是被移动的个数
				_str[end + len] = _str[end];
				end--;
			}

			//size_t end1 = _size;//原字符串'\0'位置
			//size_t end2 = _size + len;//新字符串'\0'的位置
			pos+len是给插入字符预留位置的后一个位置
			//while (end2 > pos + len -1) {//避免极端情况pos==0, len==0(>=pos+len)
			//	_str[end2--] = _str[end1--];
			//}
			//插入
			strncpy(_str + pos, str, len);

			/*const char* cur = str;
			while (*cur != '\0') {
				_str[pos++] = *cur;
			}*/
			_size += len;
			//assert(pos <= _size);
			//size_t len = strlen(str);
			//if (len == 0) {
			//	return;
			//}
			扩容
			//if (_size + len > _capacity) {
			//	reserve(_size + len);
			//}
			移位
			//size_t end1 = _size + len;
			//size_t end2 = _size;
			//while (end1 >= pos + len) {
			//	_str[end1--] = _str[end2--];
			//}
			修改
			//const char* cur = str;
			//while (*cur != '\0') {
			//	_str[pos++] = *(cur++);
			//}
			//_size += len;
			return *this;
		}
		string& insert(size_t pos, const string& s) {
			assert(pos <= _size);
			if (s._size == 0) {
				return *this;
			}
			if (s._size + _size > _capacity) {
				reserve(_size + s._size);
			}
			//移位
			size_t end1 = _size + s._size;
			size_t end2 = _size;
			while (end1 >= pos + s._size) {
				_str[end1--] = _str[end2--];
			}
			//修改
			size_t start1 = pos;
			size_t start2 = 0;
			while (start2 < s._size) {
				_str[start1++] = s._str[start2++];
			}
			_size += s._size;
			return *this;
		}
		string& erase(size_t pos, size_t n = npos) {
			assert(pos < _size);
			if (n == 0) {
				return *this;
			}
			if (pos + n >= _size - 1 || n == npos) {//pos+n是下标
				_size = pos;
				_str[_size] = '\0';
				return *this;
			}
			size_t start = pos + n;
			while (start < _size + 1) {
				_str[pos++] = _str[start++];
			}
			_size -= n;
			return *this;
		}
		//后插
		string& push_back(char c) {//插一个
			//insert(_size, c);
			if (_size + 1 > _capacity) {
				reserve(_capacity * 2);
			}
			_str[_size++] = c;
			_str[_size] = '\0';
			return *this;
		}
		string& append(const char* str) {//多个
			//insert(_size, str);
			size_t len = strlen(str);
			if (_size + len > _capacity) {
				reserve(_size + len);
			}
			const char* cur = str;
			while (*cur != '\0') {
				_str[_size++] = *(cur++);
			}
			_str[_size] = '\0';
			return *this;
		}
		string& append(const string& s) {
			insert(_size, s);
			return *this;
		}
		//转常量字符串
		const char* c_str() const {
			return _str;
		}
		//交换
		void swap(string& s) {
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		//find
		size_t find(char c, size_t pos = 0) const {
			assert(pos >= 0 && pos < _size);
			for (int i = 0; i < _size; i++) {
				if (_str[i] == c) {
					return i;
				}
			}
			return npos;
		}
		size_t find(const char* s, size_t pos = 0) {
			assert(pos >= 0 && pos < _size);
			int i = 0, j = 0;
			while (i < _size && j < strlen(s)) {
				if (_str[i] == s[j]) {
					i++;
					j++;
				}
				else {
					i = i - j + 1;
					j = 0;
				}
			}
			if (j == strlen(s)) {
				return i - j;
			}
			return npos;

		}
		//清除
		void clear() {
			erase(0, _size);
			_size = 0;
		}
		//返回_size
		size_t size() const{
			return _size;
		}
		//重载 []
		char& operator [](size_t i) {
			assert(i >= 0 && i < _size);
			return _str[i];
		}
		char& operator [](size_t i) const{
			assert(i >= 0 && i < _size);
			return _str[i];
		}
		//重载+=
		string& operator +=(char c) {
			push_back(c);
			return *this;
		}
		string& operator +=(string& str) {
			append(str);
			return *this;
		}
		string& operator +=(const char* c) {
			append(c);
			return *this;
		}
		//重载=
		string& operator =(const string& s) {
			if (this == &s) {
				return *this;
			}
			char* tmp = new char[s._size + 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) {
				return *this;
			}
			string tmp(s);//拷贝构造
			swap(tmp);
		}
		//重载 +
		string operator +(string& s) {
			if (s._size == 0) {
				return *this;
			}
			string tmp;
			tmp += *this;
			tmp += s;
			return tmp;
		}
		//重载 ==
		bool operator ==(string& s) const {
			return strcmp(_str, s._str)==0;
		}
		//重载 >
		bool operator >(string& s) const {
			return strcmp(_str, s._str) > 0;
		}
		//重载>=
		bool operator >=(string& s) const {
			return *this == s || *this > s;
		}
		//重载<
		bool operator <(string& s) const {
			return !(*this >= s);
		}
		//重载<=
		bool operator <=(string& s)const {
			return !(*this > s);
		}
		//重载!=
		bool operator !=(string& s)const {
			return !(*this == s);
		}
		
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		const static size_t npos;
	};
	const  size_t string:: npos = -1;
	std::ostream& operator <<(std::ostream& out, string& s) {
		size_t i = 0;
		while (i < s.size()) {
			out << s[i++];
		}
		return out;
	}
	std::istream& operator >>(std::istream& in, string& s) {
		s.clear();
		char ch = in.get();//get()可以拿到缓冲区的所有值
		char buff[128];
		size_t i = 0;
		while (ch != ' ' && ch != '\n') {//getline()只用'\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;
	}
	
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值