string的模拟实现(知根知底)

目录

1 string的自己实现源码

2 string实现的一些细节

2.1reserve和resize的区别:

2.2深浅拷贝:

​2.3 拷贝构造和赋值重载区分:

2.4传统写法与现代写法:

3 自己在实现过程中的一些见解

3.1:接口函数中的形参是否冗余了呢?

3.2拷贝构造这跟我没有初始化_char导致的错误:


1 string的自己实现源码

下面是string的主要部分的代码实现:
 

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;

namespace kl
{
	class String
	{
		const int InitCapacity = 16;

	public:
			
		typedef char* iterator;
		typedef const char* const_iterator;

		//对象的访问及其遍历
		const_iterator begin()const
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator end()const
		{
			return _str + _size;
		}
		char& operator[](size_t pos)
		{
			return *(_str + pos);
		}

		const char& operator[](size_t pos)const
		{
			return *(_str + pos);
		}


		//构造函数
		String(const char* str = "")
			:_str(nullptr)
			, _size(0)
			,_capacity(InitCapacity)
		{
			int len = strlen(str);
			if (len + 1 > _capacity)
			{
				_capacity = len;
			}
			_str = new char[_capacity];
			memcpy(_str, str, _capacity);
			_size = len;
		}
        
//这跟是验证当时为什么char类型不可以直接隐式类型转化成string类型,原来是string里面没有其构造函数
		//char*可以是因为其有拷贝构造
		/*String(const char str)
			:_str(nullptr)
			, _size(1)
			, _capacity(InitCapacity)
		{
			_str = new char[1];
			_str[0] = str;
		}*/
		//迭代器构造
		template <class InputIterator>
		String(InputIterator first, InputIterator last)
		{
			_str = new char[_capacity];
			while (first != last)
			{
				push_back(*first);
				first++;
			}
			//_size = strlen(_str);
		}

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

		//拷贝构造函数  传统写法
		//String(const String& s)
		//{

		//	_str = new char[strlen(s._str) + 1];
		//	//拷贝
		//	memcpy(_str, s._str, sizeof(char) * strlen(s._str) + 1);
		//	_size = s._size;
		//	_capacity = s._capacity;
		//}

		String(const String& s)
			:_str(nullptr)//这个东西一定要写上,如果不写上,后面一定会出现内存越界的问题
		{                 //因为在拷贝之前的时候,要拷贝的新对象还没有进行初始化,没有准备好
			String tmp(s._str);
			this->swap(tmp);
		}
		//拷贝构造函数  现代写法
		
		//赋值重载 传统写法
		String& operator=(const String& s)
		{
			if (*this != s)
			{
				_str = new char[strlen(s._str) + 1];
				//拷贝
				memcpy(_str, s._str, sizeof(char) * strlen(s._str) + 1);
				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}

		//赋值重载 现代写法
		/*String& operator=(String s)
		{
			swap(s);

			return *this;
		}*/
		//对象的容量操作
		int size()
		{
			return _size;
		}

		int capacity()
		{
			return _capacity;
		}

		bool empty()
		{
			return size() == 0 ? true : false;
		}

		void clear()
		{//clear不改变容量
			_str[0] = '\0';
			_size = 0;
		}

		void reserve(size_t n = 0)
		{
			if (n > _capacity)
			{
				//扩容
				char *tmp = new char[_capacity * 2];
				memcpy(tmp, _str, sizeof(char) * _capacity * 2);
				delete _str;
				_str = tmp;
				//reserve中的_size不需要改变
				_capacity = _capacity * 2;
			}
		}
		void resize(size_t n = 0, char ch = '\0')
		{
			if (n < _size)
			{
				_str[n] = '\0';
				//改变_size的大小
				_size = n;
			}
			else
			{//如果_size < 参数 在元素的后面补上参数
				reserve(n);
				for (int i = _size; i < n; i++)
				{
					_str[i] = ch;
				}

				//加上终止符号,防止打印的时候出现乱码
				_str[n] = '\0';
				_size = n;
			}
		}

		//对象的修改操作
		const char* c_str()const
		{
			return _str;
		}

		iterator begin()
		{
			return _str;
		}

		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				// 2倍扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			_str[_size] = ch;

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

		//在字符串尾部插入字符串
		String& append(const char* s)
		{
			int len = strlen(s);

			if (_size + len > _capacity)
			{//扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			for (int i = 0; i < len; i++)
			{
				_str[_size++] = s[i];
			}

			return *this;
		}

		//插入的时候一定要从后往前移
		String& insert(size_t pos, size_t n, char c)
		{
			assert(pos < _size);
			if (pos + n > _size)
			{//扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			
			int end = pos + n;
			int size = _size + n - 1;
			while (end >= pos)
			{
				_str[size--] = _str[end--];
			}

			for (int i = pos; i < pos + n; i++)
			{
				_str[i] = c;
			}
			//更新_size
			_size = _size + n;
			_str[_size] = '\0';

			return *this;
		}

		String& erase(size_t pos = 0, size_t len = npos)
		{
			if (pos + len > _size)
			{
				_size = pos;
				_str[_size] = '\0';
			}
			else
			{
				int end = pos + len;
				while (end <= _size)
				{
					_str[pos++] = _str[end++];
				}
			}
			_size = _size - len;
			_str[_size] = '\0';

			return *this;
		}

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

			return -1;
		}

		size_t find(const char* s, size_t pos = 0) const
		{
			char* tmp = strstr(_str + pos, s);
			if (tmp)
			{
				return tmp - _str;
			}
			else
			{
				return npos;
			}
		}

		size_t find(const String& str, size_t pos = 0) const
		{
			char* tmp = strstr(_str + pos, str.c_str());
			if (tmp)
			{
				return tmp - _str;
			}
			else
			{
				return npos;
			}
		}
        
        String substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);

			size_t n = len;
			if (len == npos || pos + len > _size)
			{
				n = _size - pos;
			}

			kl::String tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < pos + n; i++)
			{
				//tmp += _str[i];
				tmp.operator+=(_str[i]);
			}

			return tmp;
		}


		String& operator+=(const String& s)
		{
			append(s.c_str());

			return *this;
		}

		bool operator==(const String& s)
		{
			if (s._size != _size)
			{
				return false;
			}
			for (int i = 0; i < _size; i++)
			{
				if (s[i] != (*this)[i])
				{
					return false;
				}
			}
			return true;
		}

		bool operator!=(const String& s)
		{
			return !operator==(s);
		}

		bool operator>(const String& s)
		{

		}

		bool operator>=(const String& s)
		{

		}

		bool operator<=(const String& s)
		{

		}

		//析构函数
		~String()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
		}
	private:
		char* _str;
		int _size;
		int _capacity;

		const static size_t npos;
	};

	//静态成员变量类外初始化
	const size_t String:: npos = -1;
}

下面是与库中string的比对测试:

#include "String.h"

void test01()
{
	kl::String str("hello world");

	kl::String str1 = "linux is not linux";
	kl::String str2(str);
	//str1 = str2;
	cout << str.c_str() << endl;
	cout << str1.c_str() << endl;
	cout << str2.c_str() << endl;
}
void test02()
{
	kl::String s = "12345678999";
	//string s = "12345678999";
	//auto it = s.begin();
	kl::String::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	for (auto e : s)
	{
		cout << e << " ";
		e++;
	}
	cout << s.c_str() << endl;
}
void test03()
{
	string s = "1234567";
	s.erase(2,3);
	cout << s.c_str() << endl;
	kl::String s1 = "1234567";
	s1.erase(2, 3);
	cout << s1.c_str() << endl;
	//string s1(s.begin(), s.end());
}

void test04()
{
	/*string s = "abcdefg";
	string s1 = "cg";
	int pos = s.find(s1);
	cout << pos << endl;
	kl::String s2 = "abcdefg";
	kl::String s3 = "cg";
	int pos2 = s2.find(s3);
	cout << pos2 << endl;*/
	string str = "12345";
	str.insert(2, 2, 'x');
	/*str.append("9999");
	str += "8888";*/
	cout << str.c_str() << endl;

	kl::String str2 = "12345";
	/*str2.append("9999");
	str2 += "8888";*/
	str2.insert(2, 2, 'x');
	cout << str2.c_str() << endl;
}

int main()
{
	test01();
	//test02();
	//test03();
	//test04();
	return 0;
}

2 string实现的一些细节

2.1reserve和resize的区别:

用我自己的话来说就是reserve不会改变字符串的_size长度,只会改变capacity容量,而resize两者都会改变,resize函数接口中的参数,如果大于开始的size,这时候会在原始空余的后面加上字符填充(默认是'\0'),这里具体的话可以点击以下链接查看。

链接:STL 中 resize() 和 reserve() 的区别__牛客网

2.2深浅拷贝:

这里出错的原因是string类型中有char*这样一个自定义类型的数据,他会重新开辟一段新的空间,而普通的浅拷贝(也就是值拷贝),这时候str1对象只会简单的把他的那个空间的地址赋给str中的char*,因此两个string对象中的char*指向同一块空间,在他们销毁(调用析构函数的时候)会重复析构,导致后面哪一个析构的指针成了野指针,故而报错。不懂的可以看下我下面的这张图。

2.3 拷贝构造和赋值重载区分:

这里可以具体的看这个string对象是否为新创建的,如果是新创建的调用的就是拷贝构造,反之就是赋值重载。

如图:上面的倒数第二行调用的是构造,最后一行调用的是赋值重载。

2.4传统写法与现代写法:

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

        //拷贝构造函数  传统写法
		//String(const String& s)
		//{

		//	_str = new char[strlen(s._str) + 1];
		//	//拷贝
		//	memcpy(_str, s._str, sizeof(char) * strlen(s._str) + 1);
		//	_size = s._size;
		//	_capacity = s._capacity;
		//}

		String(const String& s)
			:_str(nullptr)//这个东西一定要写上,如果不写上,后面一定会出现内存越界的问题
		{                 //因为在拷贝之前的时候,要拷贝的新对象还没有进行初始化,没有准备好
			String tmp(s._str);
			this->swap(tmp);
		}
		//拷贝构造函数  现代写法
		
		//赋值重载 传统写法
		String& operator=(const String& s)
		{
			if (*this != s)
			{
				_str = new char[strlen(s._str) + 1];
				//拷贝
				memcpy(_str, s._str, sizeof(char) * strlen(s._str) + 1);
				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}

		//赋值重载 现代写法
		/*String& operator=(String s)
		{
			swap(s);

			return *this;
		}*/

现代写法与传统写法之间的区别无法就是更加简洁了(个人理解),巧妙的运用了swap这个函数,这里我的swap实现可能很多人会有疑问为什么不之间把两个string对象之间交换呢?

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

这是因为,string是自定义类型,自定义类型如果在T tmp = a, a = b, b = tmp。就会导致死循环了

,自定义类型在赋值的时候,他都是要调用operator=这个重载函数的,然重载函数中,在之间调用swap这个函数,试问,这样是不是构成死循环了? 

3 自己在实现过程中的一些见解

3.1:接口函数中的形参是否冗余了呢?

如这个函数如一个string s = “1234567”; string s2 = "yyy"; s.append("xxx"); s.append(s2);

当时我没有实现上面一个函数的时候,发现后面的s.append(s2);跑不过,(这里string对象类型不可能转化成char*)后面我把上面的函数给实现了,然后把下面的函数给删除了,两个都可以跑,s.append("xxx");也可以跑的原因是(调用的时候,会发生隐式类型转化“xxx”char*类型会转化为string类型)然后再调用上面的那个函数,我心里想为啥只要一个都可以实现了还有两个函数都实现呢?原来是因为效率的原因。

3.2拷贝构造这跟我没有初始化_char导致的错误:

String(const String& s)
			:_str(nullptr)//这个东西一定要写上,如果不写上,后面一定会出现内存越界的问题
		{                 //因为在拷贝之前的时候,要拷贝的新对象还没有进行初始化,没有准备好
			String tmp(s._str);
			this->swap(tmp);
		}

我遇到的问题是我把那个:_str(nullptr)给屏蔽掉就会报错。这是为什么呢?

首先想想我们如果不置空,会出现什么问题呢?调用拷贝构造函数,肯定是没有构造好的新对象,那么swap的时候就是新对象和tmp进行交换,那_str指向哪里是不确定的,有野指针的风险。

有些人说那为什么调用上面的这个 kl::String str2 = str;函数时没有报错呢,这里可能就是编译器的原因,万一其他编译器不是这样处理的呢,所以我们还要严谨一点,让我们的代码更有移植性。

🆗就说到这样了,这是一个正在努力变强的小菜鸡的一些理解(我也是查看很多资料,问了一些老师才总结滴)!最后,如果有错误理解,也希望大佬可以指正。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值