(c++)string的模拟实现

发布于:2025-05-31 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

1.构造函数

2.析构函数

3.扩容

   1.reserve(扩容不初始化)

   2.resize(扩容加初始化)

4.push_back

5.append

6. += 运算符重载

  1.+=一个字符

  2.+=一个字符串

7 []运算符重载

8.find

1.找一个字符 

2.找一个字符串

9.insert

 1.插入一个字符

 2.插入一个字符串

9.erase

10.substr

11.运算符重载比较大小

         1.<

        2.==

        3.<=

        4.>

        5.>=

        6.!=

12.拷贝构造

13.赋值 =

14.<<

15.>>

16.迭代器

完整代码实现


      首先我们先定义头文件String.h,和命名空间_string防止和库中的string发生名字冲突

#include <iostream>
#include <cassert>
#include <string.h>
using namespace std;

namespace _string {
	
    class string {
    public:
    
    static size_t npos;
    protected:
		char*  _str;
		size_t _size;
		size_t _capacity;
    }

    size_t string::npos = -1;
}

        其是我们的string类里面就是一个顺序表, 所以我们会定义一个数组_str,_size为实际字符所占的大小,_capacity为容量,如果达到了最大的容量我们就是_size == _capacity 我们就要进行扩容处理.

1.构造函数

string(const char* str = "")
		:_str(new char[strlen(str) + 1])
		,_size(strlen(str))
		,_capacity(strlen(str))
	{
		memcpy(_str, str, _size + 1);
	}

        这里我们为什么要多开一个空间strlen(str) + 1呢?因为我们这里字符串后面会有'\0',我们要多开一个位置给'\0',但是为什么_size和_capacity为什么不用+1呢?因为我们的'\0' 不算大小,所以我们_size和_capacity不需要给多一个空间.

        这里需要注意的是,我们开好空间的时候不要忘记去把str的内容拷贝的_str中,这里拷贝_size + 1是因为有'\0'也是需要拷贝的.

2.析构函数

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

        这里析构函数我认为,如果_str为空才需要释放空间,不为空不需要判断,所以我们这里加上一个判断,不为空的时候才可以去释放空间。

3.扩容

          1.reserve(扩容不初始化)

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

		_str = tmp;
		_capacity = n;
	}
}

   扩容的时候需要进行判断,如果n大于容量扩容才有意义,如果小于就没有意义了

        2.resize(扩容加初始化)

void resize(size_t n , char ch = '\0') {
		if (n < _size) {
			_size = n;
			_str[_size] = '\0';
		}
		else {

			reserve(n);
			while (_size != n) {
				_str[_size++] = '\0';
			}
			_str[_size] = '\0';
        }
}

        resize如果给的n容量会继续缩小,所以当n < _size的时候我们直接把n位置变为'\0'就可以了

如果这里的n > _ size,就会出现两种情况,一种是  _capacity > n > _size 还有一种是 n > _capacity

因为我们写了扩容reserve,里面有判断,所以我们之间继续复用就可以了,最后再完成数据的拷贝就可以了.

4.push_back

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

5.append

void append(const char* str) {
		size_t len = strlen(str);
		if (_size + len > _capacity) {
			reserve(_size + len);
		}

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

        push_back和append都一样,使用时应该要检查容量是否足够,不够的时候再扩容,最后完成数据的拷贝就可以了.

6. += 运算符重载

        1.+=一个字符

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

        2.+=一个字符串

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

      我们上面实现了push_back()和append()所以这里我们实现复用就可以了.

7 []运算符重载

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

//读写
char operator[](size_t pos) {
		assert(pos < _size);
		return _str[pos];
}

        我们在进行重载的时候应该注意,pos的范围不能超过_size的大小.

8.find

1.找一个字符 

            找一个字符返回当前位置

//默认从0位置开始

size_t find(char ch, size_t pos = 0) {
		assert(pos < _size);

		for (size_t i = pos; i < _size; i++) {
			if (_str[i] == ch) {
				return i;
			}
		}
		return npos;
}

2.找一个字符串

        找一个字符串返回第一个字符出现的位置.

size_t find(const char* str, size_t pos) {
		assert(pos < _size);
		char* ptr = strstr(_str, str);
		if (ptr) {
			return ptr - _str;
		}
		else {
			return npos;
		}
}

        什么两个如果都没有找到的话,会返回npos,npos是string中静态变量,为无符号数的最大值.

9.insert

        1.插入一个字符

void insert(size_t pos , char ch) {
	assert(pos < _size);
	if (_size == _capacity) {
		reserve(_capacity == 0 ? 4 : _capacity*2);
	}

	//'\0'也要一起移动
	size_t end = _size;
	while (end >= pos &&  end != npos) {
		_str[end + 1] = _str[end];
		end--;
    }
	_str[pos] = ch;
	_size++;
}

        2.插入一个字符串

void insert(size_t pos, const char* str) {
	size_t len = strlen(str);
	if (_size + len > _capacity) {
		reserve(_size + len);
	}	
	//移动数据
	size_t end = _size;
	while (end >= pos && end != npos) {
		_str[end + len] = _str[end];
		end--;
	}
	//拷贝数据
	for (size_t i = 0; i < len; i++) {
		_str[pos + i] = str[i];
	}
	_size += len;
}

        我们两个函数都需要注意的是移动数据的时候这里是end--,如果没有npos != end,pos的位置是0,会造成死循环的问题,这是为什么?

        没有npos != end,如果pos的位置是0,那么循环的结束条件应该是end < 0,但是这里的end是无符号数,当end到达-1时相当于正数的最大值,不可能小于0. 所以我们这里应该要加上npos != end;

9.erase


void erase(size_t pos = 0 , size_t len = npos) {
	// '\0'也要移动所以可以等于 _size;
	assert(pos <= _size);
	if (pos + len >= _size || len == npos) {
		_str[_size] = '\0';
		_str[pos] = '\0';
		_size = pos;
	}
	else {
		size_t end = pos + len;
		while (end <= _size) {
			_str[pos++] = _str[end++];
		}
		_size -= len;
	}
}

        从pos位置开始删除,长度为len的字符. 如果pos + len >= _size 或者说 len说民删除完后面的字符, 直接把pos换成'\0'就可以了.

        如果pos + len < _size 就移动数据,为什么end <= _size ? 因为'\0'也要被移动.

10.substr

string substr(size_t pos = 0,size_t len = npos) {
	assert(pos < _size);

	//调整len的大小
	if (pos + len >= _size || len == npos) {
		len = _size - pos;
	}

	//预留好空间
	string tmp;
	tmp.reserve(len);

	for (size_t i = 0; i < len; i++)
	{
		tmp += _str[pos + i];
	}
			
	tmp += '\0';
	return tmp;
}

        不要忘记最后一个位置要加上'\0'

11.运算符重载比较大小

         1.<

        我们要注意有三种特殊的情况

hello helloo true
hello hell false
hello hello false

        我们给两个长度len1,len2从0开始遍历碰到第一个小于就返回true,大于返回false,要是等于就继续len1和len2都要加加.

        如果出了循环,我们看上面三种情况,只有len2 < str._size && len1 == _size 才能返回true

其他都是false,所以我们直接返回len2 < str._size && len1 == _size就可以了

bool operator<(const string& str) const {
	size_t len1 = 0, len2 = 0;
	while (len1 < _size && len2 < str._size) {
		if (_str[len1] < str._str[len2]) {
			return true;
		}
		else if (_str[len1] > str._str[len2]) {
			return false;
		}
		else {
			len1++;
			len2++;
		}
	}

	return len1 == _size && len2 < str._size;
}

        2.==

bool operator==(const string& str) const {
	if (_size != str._size) {
		return false;
	}
	else {

		size_t len1 = 0, len2 = 0;
		while (len1 < _size && len2 < str._size) {
			if (_str[len1] < str._str[len2]) {
				return false;
			}
			else if (_str[len1] > str._str[len2]) {
				return false;
			}
			else {
				len1++;
				len2++;
			}
		}
		return true;
	}
}

       如果两个字符串的长度不相等直接返回false就可以了,长度如果相等有不相等的两个字符也是直接返回false,如果能出循环直接返回true;

        我们上面实现了< 和 = 我们直接复用就可以了

        3.<=

bool operator<=(const string& str) const {
		return *this < str || *this == str;
}

        4.>

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

        5.>=

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

        6.!=

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

12.拷贝构造

        类里面默认生成的拷贝构造为浅拷贝,如果我们直接用默认生成的赋值,会造成析构函数释放同一块空间两次的问题编译器会就行报错,所以我们需要实现深拷贝.

string(const string& str) {
	//要多给一个位置给'\0'
	char* _str = new char[str._capacity + 1];
	_size = str._size;
	_capacity = str._capacity;
	//'\0' 也要进行拷贝所以拷贝_size + 1
	memcpy(_str,str._str,str._size + 1);
}

13.赋值 =

        和上面的拷贝构造一样,必须要实现深拷贝否则编译器会报错.

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

//str为临时变量出了作用域就会销毁
//交换后不用就行处理
string& operator=(string str) {
	swap(str);
	return *this;
}

        这里有很多人不明白赋值这里参数为什么不传引用?这里的string没有引用是一个临时变量,处理作用域就会销毁.所以我们把*this 的值和str的值进行交换不会有任何的问题

        这里的<< 和 >> 我们一般不喜欢写在类里面,如果写在类里面,第一个参数默认是this指针,那么我们调用的时候就需要反过来写,不符合我们的习惯.

str >> cin   

str << cout 

14.<<

//按照_size的大小来进行打印
//不是碰到'\0'就停止
ostream& operator<<(ostream& _cout, const string& str) {
	for (size_t i = 0; i < str.size(); i++)
	{
		_cout << str[i];
	}
	return _cout;
}

        这里需要注意的是打印的是按照_size去打印,不是碰到'\0'就停下.

15.>>

//每次输入之前都要清空
istream& operator>>(istream& _cin, string& str) {	
	//清空
	str.clear();
	char ch;

	//给一buff数组来减小拷贝来提高效率
	int i = 0;
	char buff[128] = { 0 };
		
	//读取前置空格
	ch = _cin.get();
	while (ch == '\n' || ch == ' ')
		ch = _cin.get();

	while (ch != '\n' && ch != ' ') {
			
		buff[i++] = ch;
		if (i == 127) {
            //最后一个位置给'\0'
			buff[i] = '\0';
			str += buff;
			i = 0;
		}
		ch = _cin.get();
	}

	//如果i不等于0说明里面还有字符
	if (i != 0) {
		buff[i] = '\0';
		str += buff;
	}

	return _cin;
}

16.迭代器

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

完整代码实现

namespace _String {

	class string {
		friend ostream& operator<<(ostream& _cout, const string& str);
		friend istream& operator>>(istream& _cin, string& str);
	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 = "")
			:_str(new char[strlen(str) + 1])
			, _size(strlen(str))
			, _capacity(strlen(str))
		{
			memcpy(_str, str, _size + 1);
		}

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

		string(const string& str) {
			//要多给一个位置给'\0'
			char* _str = new char[str._capacity + 1];
			_size = str._size;
			_capacity = str._capacity;
			//'\0' 也要进行拷贝所以拷贝_size + 1
			memcpy(_str,str._str,str._size + 1);
		}

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

		//str为临时变量出了作用域就会销毁
		//交换后不用就行处理
		string& operator=(string str) {
			swap(str);
			return *this;
		}

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

		//读写
		char operator[](size_t pos) {
			assert(pos < _size);
			return _str[pos];
		}

		// hello helloo true
		// hello hell false;
		// hello hello false

		bool operator<(const string& str) const {
			size_t len1 = 0, len2 = 0;
			while (len1 < _size && len2 < str._size) {
				if (_str[len1] < str._str[len2]) {
					return true;
				}
				else if (_str[len1] > str._str[len2]) {
					return false;
				}
				else {
					len1++;
					len2++;
				}
			}

			return len1 == _size && len2 < str._size;
		}

		bool operator==(const string& str) const {
			if (_size != str._size) {
				return false;
			}
			else {

				size_t len1 = 0, len2 = 0;
				while (len1 < _size && len2 < str._size) {
					if (_str[len1] < str._str[len2]) {
						return false;
					}
					else if (_str[len1] > str._str[len2]) {
						return false;
					}
					else {
						len1++;
						len2++;
					}
				}
				return true;
			}
		}

		bool operator<=(const string& str) const {
			return *this < str || *this == str;
		}

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

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

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

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

				_str = tmp;
				_capacity = n;
			}
		}

		void resize(size_t n, char ch = '\0') {
			if (n < _size) {
				_str[_size] = '\0';
				_size = n;
				_str[_size] = '\0';
			}
			else {
				//会自己判断不用加
				reserve(n);
				while (_size != n) {
					_str[_size++] = '\0';
				}
				_str[_size] = '\0';
			}
		}

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


		void insert(size_t pos , char ch) {
			assert(pos <= _size);
			if (_size == _capacity) {
				reserve(_capacity == 0 ? 4 : _capacity*2);
			}

			//'\0'也要一起移动
			size_t end = _size;
			while (end >= pos &&  end != npos) {
				_str[end + 1] = _str[end];
				end--;
			}
			_str[pos] = ch;
			_size++;
		}

		void insert(size_t pos, const char* str) {
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity) {
				reserve(_size + len);
			}
			
			//移动数据
			size_t end = _size;
			while (end >= pos && end != npos) {
				_str[end + len] = _str[end];
				end--;
			}

			//拷贝数据
			for (size_t i = 0; i < len; i++) {
				_str[pos + i] = str[i];
			}

			_size += len;
		}

		void erase(size_t pos = 0 , size_t len = npos) {
			
			// '\0'也要移动所以可以等于 _size;
			assert(pos <= _size);
			if (pos + len >= _size || len == npos) {
				_str[_size] = '\0';
				_str[pos] = '\0';
				_size = pos;
			}
			else {
			
				size_t end = pos + len;
				while (end <= _size) {
					_str[pos++] = _str[end++];
				}
				_size -= len;
			
			}
		}

		//如果len == npos 说明从pos位置开始截完全部
		string substr(size_t pos = 0,size_t len = npos) {
			assert(pos < _size);

			//调整len的大小
			if (pos + len >= _size || len == npos) {
				len = _size - pos;
			}

			//预留好空间
			string tmp;
			tmp.reserve(len);

			for (size_t i = 0; i < len; i++)
			{
				tmp += _str[pos + i];
			}
			
			tmp += '\0';
			return tmp;
		}


		//默认从0位置开始
		size_t find(char ch, size_t pos = 0) {
			assert(pos < _size);

			for (size_t i = pos; i < _size; i++) {
				if (_str[i] == ch) {
					return i;
				}
			}

			return npos;
		}

		//默认0位置开始
		size_t find(const char* str, size_t pos = 0) {
			assert(pos < _size);
			char* ptr = strstr(_str, str);
			if (ptr) {
				return ptr - _str;
			}
			else {
				return npos;
			}
		}
		
		void append(const char* str) {
			size_t len = strlen(str);
			if (_size + len > _capacity) {
				reserve(_size + len);
			}

			//'\0'也要就行拷贝
			memcpy(_str + _size, str, len + 1);
			_size += len;
		}

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

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

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

		size_t size() const {
			return _size;
		}

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

		static size_t npos;
	protected:
		char* _str;
		size_t _size;
		size_t _capacity;
	};

	size_t string::npos = -1;
	
	//按照_size的大小来进行打印
	//不是碰到'\0'就停止
	ostream& operator<<(ostream& _cout, const string& str) {
		for (size_t i = 0; i < str.size(); i++)
		{
			_cout << str[i];
		}
		return _cout;
	}

	//每次输入之前都要清空
	istream& operator>>(istream& _cin, string& str) {
		
		//清空
		str.clear();
		char ch;

		//给一buff数组来减小拷贝来提高效率
		int i = 0;
		char buff[128] = { 0 };
		
		//读取前置空格
		ch = _cin.get();
		while (ch == '\n' || ch == ' ')
			ch = _cin.get();

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

		//如果i不等于0说明里面还有字符
		if (i != 0) {
			buff[i] = '\0';
			str += buff;
		}

		return _cin;
	}
}

        如果有错误,欢迎各位大佬指正.