string类函数的手动实现

发布于:2024-12-06 ⋅ 阅读:(30) ⋅ 点赞:(0)

在上一篇文章中,我们讲解了一些string类的函数,但是对于我们要熟练掌握c++是远远不够的,今天,我将手动实现一下这些函数~

注意:本篇文章中会大量应用复用,这是一种很巧妙的方法

和以往一样,还是分为string.h string.cpp test.cpp三个文件

为了保证完整性,string.h我统一放在这

1.string.h文件

#pragma once
#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;
namespace my_string
{
	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 = "");
		string(size_t n, char ch);
		string(const string& s);
		string& operator=(const string& s);
		~string();
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		const char* c_str() const
		{
			return _str;
		}
		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		void insert(size_t pos, size_t n, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos = 0, size_t len = npos);
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);
		size_t size()const
		{
			return _size;
		}
		size_t capacity()const
		{
			return _size;
		}
		char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		string substr(size_t pos, size_t len = npos);
		bool operator==(const string& s)const;
		bool operator!=(const string& s)const;
		bool operator<(const string& s)const;
		bool operator<= (const string & s)const;
		bool operator>(const string& s)const;
		bool operator>=(const string& s)const;
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		const static size_t npos;
	};
	//cout<<s1
	ostream& operator<<(ostream& out, const string& s);
	//cin>>s1
	istream& operator<<(istream& in, string& s);
	istream& getline(istream& is, string& s, char delim = '#');

}

2. 增加类函数(append\insert\push_back\+=)

这是string.cpp文件

	void string::push_back(char ch)
	{
		if (_size + 1 > _capacity)
		{
			//意味着此时已经满了,需要扩容才能插入
			//扩容,建议使用函数复用
			//还要讨论原来容量是不是0
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		//此时已经完成扩容,容量足够用
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';            //别忘了把\0也考过来
	}
	void string::append(const char* str)
	{
		//注:我们这里是直接按照库里的思路去实现的  
		// 在这里扩容_size+len也是可以的   只不过思路不一样
		// 也可能官方认为追加直接扩二倍  后面人继续使用的时候可以少调几次开空间吧
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			//意味着此时已经满了,需要扩容才能插入
			//扩容,与push_back不同的是,
			// 默认使用append是认为你这个字符串原先就是有内容才追加的
			// 如果害怕有人确实会直接使用这个接口 可以加上_capacity=0的情况
			size_t newcapacity = 2 * _capacity;
			//考虑到可能插入的字符串过长,2倍扩容都可能不够
			//为防止越界的产生,我们再严谨的讨论一下
			if (_size + len > 2 * _capacity)
			{
				newcapacity = _size + len;
			}
			reserve(newcapacity);
		}
		//strcpy在拷贝时会从第一个字符出发找\0,
		// 为了节约编译器运行时间,我们直接手动让他从\0出发
		strcpy(_str + _size, str);   
		_size += len; 
	}
	void string::insert(size_t pos, size_t n, char ch)
	{
		assert(pos <= _size);
		assert(n > 0);
		//还是要考虑扩容问题
		if (_size + n > _capacity)
		{
			size_t newcapacity = 2 * _capacity;
			if (_size + n > 2 * _capacity)
			{
				newcapacity = _size + n;
			}
			reserve(newcapacity);
		}
		size_t end = _size + n;       //一切以\0为准
		while (end > pos + n - 1)     //准备挪动数据,这是需要挪动的数据范围
		{
			_str[end] = _str[end - n];//最后一个数据先动
			end--;
		}
		//挪完了,有地方了,但是要插入的数还没进来呢!
		for (size_t i = 0; i < n; i++)
		{
			_str[pos + i] = ch;
		}
		_size += n;
	}
	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t n = strlen(str);
		if (_size + n > _capacity)
		{
			size_t newCapacity = 2 * _capacity;
			if (_size + n > 2 * _capacity)
			{
			    newCapacity = _size + n;
			}
			reserve(newCapacity);
		}
		size_t end = _size + n;
        while (end > pos + n - 1)
        {
	        _str[end] = _str[end - n];
	        --end;
        }
		for (size_t i = 0; i < n; i++)
		{
			_str[pos + i] = str[i];
		}
	}
string& string::operator+=(char ch)
{
	push_back(ch);
	return *this;
}
string& string::operator+=(const char* str)
{
	append(str);
	return *this;
}

这是test.cpp文件

#include"string.h"
void test_string1()
{
	string s1("hello world");
	cout << s1.c_str() << endl;
	s1 += ' ';
	cout << s1.c_str() << endl;
	s1 += '+';
	cout << s1.c_str() << endl;
	s1 += "hello everybody";
	cout << s1.c_str() << endl;
	s1.push_back(',');
	cout << s1.c_str() << endl;
	s1.append("welcome!");
	cout << s1.c_str() << endl;
	s1.insert(6,1, 't');
	cout << s1.c_str() << endl;
	s1.insert(7, "he ");
	cout << s1.c_str() << endl;
	s1.insert(41, "nice to meet you");
	cout << s1.c_str() << endl;
	s1.insert(0, "good morning!");
	cout << s1.c_str() << endl;
}
int main()
{
	test_string1();
	return 0;
}

 结果如下:

2.find和erase

这是string.cpp文件

	size_t string::find(char ch, size_t pos)
	{
		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 char* p = strstr(_str + pos, str);   
		//strstr() 函数的作用是在一个字符串(str1)中查找另一个字符串(str2)的出现位置。
		//如果找到,它返回一个指向 str1 中第一次出现的 str2 的指针;
		// 如果找不到,则返回空指针(NULL)。
		if (p == nullptr)
		{
			return npos;
		}
		else
		{
			return p - _str;      //两个指针相减,结果得到这个元素的下标
		}
	}
	void string::erase(size_t pos, size_t len)
	{
		if (len > _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			size_t end = pos + len;
			{
				while (end <= _size)
				{
					_str[end - len] = _str[end];
					++end;
				}
				_size -= len;
			}
		}
	}

这是test.cpp文件 

void test_string_find_erase()
{
	string s1("hello world");
	cout << s1.c_str() << endl;
	s1.erase(6,2);
	cout << s1.c_str() << endl;
	s1.erase(6, 20);
	cout << s1.c_str() << endl;
	s1.erase(3);
	cout << s1.c_str() << endl;
	string s2("welcome to guangzhou!");
	cout << s2.find('o') << endl;
	cout << s2.find("guangzh") << endl;

}
int main()
{
	test_string_find_erase();
	return 0;
}

结果如下:

3.迭代器

这是test.cpp文件

void test_string_iterator()
{
	string s1("hello world");
	for (size_t i = 0; i < s1.size(); i++)
	{
		s1[i]++;
		cout << s1[i] << " ";
	}
	cout << endl;
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto e : s1)
	{
		cout << e;
	}
	cout << endl;

}
int main()
{
	test_string_iterator();
	return 0;
}

运行结果:

4.substr()

string string::substr(size_t pos, size_t len)
{
	size_t leftlen = _size - pos;     //求出要截取部分长度
	if (len > leftlen)
	{
		len = leftlen;
	}
	string tmp;
	tmp.reserve(len);
	for (size_t i = 0; i < len; i++)
	{
		tmp += _str[pos + i];
	}
	return tmp;
}
void test_string5()
{
	string s1("hello world");
	string sub1 = s1.substr(6, 3);
	cout << sub1.c_str() << endl;
	string sub2 = s1.substr(6, 300);
	cout << sub2.c_str() << endl;
	string sub3 = s1.substr(6);
	cout << sub3.c_str() << endl;
	string s2("hello bitxxxxxxxxxxxxxxxxxx");
	s1 = s2;
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;
	s1 = s1;
	cout << s1.c_str() << endl;
}
int main()
{
	//test_string_add();
	//test_string_find_erase();
	//test_string_iterator();
	test_string5();
	return 0;
}

5.流插入和提取

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}
	istream& operator>>(istream& in, string& s)
	{
		s.clear();        //此举是为了防止s原有内容对输入的影响
		//类比我们要接满一个大水桶,但是我们不知道需要到底具体有多少水
		// 正好手里有一个可以装N升水的小盆,我们可以用这个小盆装水,满了后导入大桶里
		// 这样可以使得:
		// 输入短串,不会浪费空间
		// 输入长串,避免不断扩容
		const size_t N = 1024;
		char buff[N];
		int i = 0;
		char ch = in.get();       //获取首个单个字符
		while (ch != ' ' && ch != '\n');
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();    //获取其余诸多单个字符
		}
		//此时有两种情况:1是输入字符串的字符个数正好为N的整数倍,此时i==0;(盆里面没有水了)
		//2是输入字符串的字符个数不为N的整数倍,此时i>0;(盆里面还有水)
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	istream& getline(istream& in, string& s, char delim)
	{
		s.clear();
		const size_t N = 1024;
		char buff[N];
		int i = 0;
		char ch = in.get();
		while (ch != delim)
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
void test_string6()
{
	string s1, s2;
	cin >> s1 >> s2;
	cout << s1 << endl;
	cout << s2 << endl;

	string s3;
	//getline(cin, s3);
	getline(cin, s3, '!');
	cout << s3 << endl;
}
int main()
{
	//test_string_add();
	//test_string_find_erase();
	//test_string_iterator();
	//test_string5();
	test_string6();
	return 0;
}

6.汇总:

这是string.h文件

#pragma once
#include<iostream>
#include<assert.h>
#include<string.h>
using namespace std;
namespace my_string
{
	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 = "");
		string(size_t n, char ch);
		string(const string& s);
		string& operator=(const string& s);
		~string();
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		const char* c_str() const
		{
			return _str;
		}
		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		string& operator+=(char ch);
		string& operator+=(const char* str);
		void insert(size_t pos, size_t n, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos = 0, size_t len = npos);
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);
		size_t size()const
		{
			return _size;
		}
		size_t capacity()const
		{
			return _size;
		}
		char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		string substr(size_t pos, size_t len = npos);
		bool operator==(const string& s)const;
		bool operator!=(const string& s)const;
		bool operator<(const string& s)const;
		bool operator<= (const string & s)const;
		bool operator>(const string& s)const;
		bool operator>=(const string& s)const;
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
		const static size_t npos;
	};
	//cout<<s1
	ostream& operator<<(ostream& out, const string& s);
	//cin>>s1
	istream& operator<<(istream& in, string& s);
	istream& getline(istream& is, string& s, char delim = '#');

}

这是string.cpp文件

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"
namespace my_string
{
	const size_t string::npos = -1;
	string::string(size_t n, char ch)
		:_str(new char[n + 1])
		, _size(n)
		, _capacity(n)
	{
		for (size_t i = 0; i < n; i++)
		{
			_str[i] = ch;
		}
		_str[_size] = '\0';
	}
	string::string(const char* str)
		:_size(strlen(str))
	{
		_capacity = _size;
		_str = new char[_size + 1];   //多开一个空间放\0
		strcpy(_str, str);
	}
	//s2(s1)
	string::string(const string& s)
	{
		_str = new char[s._capacity + 1];    //永远都记得多开一个
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
	}
	//s1=s2
	//s1=s1(不建议这样做)
	string& string::operator=(const string& s)
	{
		//this :s1     s:s2
		if (this != &s)     //避免s1=s1这种事件发生
		{
			//这里由于我们之前在构造_str的时候使用new[]了,但为了我们之后将s2拷贝给s1,
			//我们要开一个能装下s2的空间,所以这里我们先delete[],再new[]一个,用于拷贝s2,
			//注意,strcpy不能变插边扩容,这才是我们这么做的根本原因
			delete[] _str;
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}
		return *this;          //我们要通过s2构造s1,故返回*this
	}
	string::~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

	void string::push_back(char ch)
	{
		if (_size + 1 > _capacity)
		{
			//意味着此时已经满了,需要扩容才能插入
			//扩容,建议使用函数复用
			//还要讨论原来容量是不是0
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		//此时已经完成扩容,容量足够用
		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';            //别忘了把\0也考过来
	}
	void string::append(const char* str)
	{
		//注:我们这里是直接按照库里的思路去实现的  
		// 在这里扩容_size+len也是可以的   只不过思路不一样
		// 也可能官方认为追加直接扩二倍  后面人继续使用的时候可以少调几次开空间吧
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			//意味着此时已经满了,需要扩容才能插入
			//扩容,与push_back不同的是,
			// 默认使用append是认为你这个字符串原先就是有内容才追加的
			// 如果害怕有人确实会直接使用这个接口 可以加上_capacity=0的情况
			size_t newcapacity = 2 * _capacity;
			//考虑到可能插入的字符串过长,2倍扩容都可能不够
			//为防止越界的产生,我们再严谨的讨论一下
			if (_size + len > 2 * _capacity)
			{
				newcapacity = _size + len;
			}
			reserve(newcapacity);
		}
		//strcpy在拷贝时会从第一个字符出发找\0,
		// 为了节约编译器运行时间,我们直接手动让他从\0出发
		strcpy(_str + _size, str);   
		_size += len; 
	}
	string& string::operator+=(char ch)
	{
		push_back(ch);
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		append(str);
		return *this;
	}
	void string::insert(size_t pos, size_t n, char ch)
	{
		assert(pos <= _size);
		assert(n > 0);
		//还是要考虑扩容问题
		if (_size + n > _capacity)
		{
			size_t newcapacity = 2 * _capacity;
			if (_size + n > 2 * _capacity)
			{
				newcapacity = _size + n;
			}
			reserve(newcapacity);
		}
		size_t end = _size + n;       //一切以\0为准
		while (end > pos + n - 1)     //准备挪动数据,这是需要挪动的数据范围
		{
			_str[end] = _str[end - n];//最后一个数据先动
			end--;
		}
		//挪完了,有地方了,但是要插入的数还没进来呢!
		for (size_t i = 0; i < n; i++)
		{
			_str[pos + i] = ch;
		}
		_size += n;
	}
	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		size_t n = strlen(str);
		if (_size + n > _capacity)
		{
			size_t newCapacity = 2 * _capacity;
			if (_size + n > 2 * _capacity)
			{
			    newCapacity = _size + n;
			}
			reserve(newCapacity);
		}
		size_t end = _size + n;
        while (end > pos + n - 1)
        {
	        _str[end] = _str[end - n];
	        --end;
        }
		for (size_t i = 0; i < n; i++)
		{
			_str[pos + i] = str[i];
		}
	}
	void string::erase(size_t pos, size_t len)
	{
		if (len > _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			size_t end = pos + len;
			{
				while (end <= _size)
				{
					_str[end - len] = _str[end];
					++end;
				}
				_size -= len;
			}
		}
	}
	size_t string::find(char ch, size_t pos)
	{
		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 char* p = strstr(_str + pos, str);   
		//strstr() 函数的作用是在一个字符串(str1)中查找另一个字符串(str2)的出现位置。
		//如果找到,它返回一个指向 str1 中第一次出现的 str2 的指针;
		// 如果找不到,则返回空指针(NULL)。
		if (p == nullptr)
		{
			return npos;
		}
		else
		{
			return p - _str;      //两个指针相减,结果得到这个元素的下标
		}
	}
	string string::substr(size_t pos, size_t len)
	{
		size_t leftlen = _size - pos;     //求出要截取部分长度
		if (len > leftlen)
		{
			len = leftlen;
		}
		string tmp;
		tmp.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			tmp += _str[pos + i];
		}
		return tmp;
	}
	bool string::operator==(const string& s)const
	{
		return strcmp(_str, s._str) == 0;
	}
	bool string::operator!=(const string& s)const
	{
		return !(*this == s);
	}
	bool string::operator<(const string& s)const
	{
		return strcmp(_str, s._str) < 0;
	}
	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 || *this > s;
	}
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}
	istream& operator>>(istream& in, string& s)
	{
		s.clear();        //此举是为了防止s原有内容对输入的影响
		//类比我们要接满一个大水桶,但是我们不知道需要到底具体有多少水
		// 正好手里有一个可以装N升水的小盆,我们可以用这个小盆装水,满了后导入大桶里
		// 这样可以使得:
		// 输入短串,不会浪费空间
		// 输入长串,避免不断扩容
		const size_t N = 1024;
		char buff[N];
		int i = 0;
		char ch = in.get();       //获取首个单个字符
		while (ch != ' ' && ch != '\n');
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();    //获取其余诸多单个字符
		}
		//此时有两种情况:1是输入字符串的字符个数正好为N的整数倍,此时i==0;(盆里面没有水了)
		//2是输入字符串的字符个数不为N的整数倍,此时i>0;(盆里面还有水)
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	istream& getline(istream& in, string& s, char delim)
	{
		s.clear();
		const size_t N = 1024;
		char buff[N];
		int i = 0;
		char ch = in.get();
		while (ch != delim)
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

这是test.cpp文件

#include"string.h"
#include<string>
void test_string_add()
{
	string s1("hello world");
	cout << s1.c_str() << endl;
	s1 += ' ';
	cout << s1.c_str() << endl;
	s1 += '+';
	cout << s1.c_str() << endl;
	s1 += "hello everybody";
	cout << s1.c_str() << endl;
	s1.push_back(',');
	cout << s1.c_str() << endl;
	s1.append("welcome!");
	cout << s1.c_str() << endl;
	s1.insert(6,1, 't');
	cout << s1.c_str() << endl;
	s1.insert(7, "he ");
	cout << s1.c_str() << endl;
	s1.insert(41, "nice to meet you");
	cout << s1.c_str() << endl;
	s1.insert(0, "good morning!");
	cout << s1.c_str() << endl;
}
void test_string_find_erase()
{
	string s1("hello world");
	cout << s1.c_str() << endl;
	s1.erase(6,2);
	cout << s1.c_str() << endl;
	s1.erase(6, 20);
	cout << s1.c_str() << endl;
	s1.erase(3);
	cout << s1.c_str() << endl;
	string s2("welcome to guangzhou!");
	cout << s2.find('o') << endl;
	cout << s2.find("guangzh") << endl;

}
void test_string_iterator()
{
	string s1("hello world");
	for (size_t i = 0; i < s1.size(); i++)
	{
		s1[i]++;
		cout << s1[i] << " ";
	}
	cout << endl;
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	for (auto e : s1)
	{
		cout << e;
	}
	cout << endl;

}
void test_string5()
{
	string s1("hello world");
	string sub1 = s1.substr(6, 3);
	cout << sub1.c_str() << endl;
	string sub2 = s1.substr(6, 300);
	cout << sub2.c_str() << endl;
	string sub3 = s1.substr(6);
	cout << sub3.c_str() << endl;
	string s2("hello bitxxxxxxxxxxxxxxxxxx");
	s1 = s2;
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;
	s1 = s1;
	cout << s1.c_str() << endl;
}
void test_string6()
{
	string s1, s2;
	cin >> s1 >> s2;
	cout << s1 << endl;
	cout << s2 << endl;

	string s3;
	//getline(cin, s3);
	getline(cin, s3, '!');
	cout << s3 << endl;
}
int main()
{
	//test_string_add();
	//test_string_find_erase();
	//test_string_iterator();
	//test_string5();
	test_string6();
	return 0;
}