C++ elementary-string class (3) simulation implementation

Table of contents

0. Preface

1. Constructor - destructor - [] overload implementation

2. Deep copy problem

2.1 Shallow copy

2.2 Deep copy

2.3 copy-on-write

3. Copy function - assignment overloading traditional and modern writing

4. Iterator implementation

5.Reserve, push_back, append, += operator overloading

6.insert, erase implementation

7. Implementation of find, relational operators, stream insertion, stream extraction, etc.

8. string class interview reference


0. Preface

Simulate the implementation of the string class, the most important thing is to realize the construction, copy construction, assignment operator overloading and destructor of the string class.

1. Constructor - destructor - [] overload implementation

class string
	{
	public:
	/*	string()
			:_str(new char[1])
			, _size(0)
			, _capacity(0)
		{
			_str[0] = '\0';
		}*/

		//string(const char* str = "\0")//相当于两个\0

	/*	string(const char* str = "")
			:_str(new char[strlen(str)+1])
			,_size(strlen(str))
			,_capacity(strlen(str)) //strlen时间复杂度 O(N)
		{
			strcpy(_str, str);
		}*/
		
		//不推荐下面写法,因为想当于将成员绑定了,不利于维护
	/*	string(const char* str = "")
			:_size(strlen(str))
			,_capacity(_size)
			,_str(new char[_capacity + 1])
		{
			strcpy(_str, str);
		}*/
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
        ~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

        const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
        
        //测试调用
        const char* c_str() const
		{
			return _str;
		}
		size_t size()const
		{
			return _size;
		}
		size_t capacity()const
		{
			return _capacity;
		}

    private:
		//vs下,sizeof(),给一个小于16的字符串,默认算出结果是28个字节
		// 因为其成员变量中有一个_Buf[16]的数组
		// 以空间换时间,避免较小空间的频繁扩容
		// < 16 字符串存在_Buf数组中,否则,存在堆空间
		// char _Buf[16];
		char* _str;
		size_t _size;
		size_t _capacity;
	public:
		//C++特列,const static特殊处理
		//直接可以定义初始化
		const static size_t npos = -1;
	};
}

test:

void test_string1()
	{
		string s1("hello world");
		string s2;
		std::cout << s1.c_str() << std::endl;
		std::cout << s2.c_str() << std::endl;

		for (size_t i = 0; i < s1.size(); ++i)
		{
			std::cout << s1[i] << " ";
		}
		std::cout << std::endl;
		
		for (size_t i = 0; i < s1.size(); ++i)
		{
			s1[i] ++;
			std::cout << s1[i] << " ";
		}
		std::cout << std::endl;
	}

2. Deep copy problem

 Explanation: The above String class does not explicitly define its copy constructor and assignment operator overload. At this time, the compiler will synthesize the default one. When s1 is used to construct s2, the compiler will call the default copy constructor. The final problem is that s1 and s2 share the same memory space, and the same space is released multiple times during release, causing the program to crash . This copy method is called shallow copy .

2.1 Shallow copy

Shallow copy: Also known as bit copy, the compiler just copies the value in the object. If resources are managed in the object, multiple objects will eventually share the same resource. When an object is destroyed, the resource will be released. At this time, other objects do not know that the resource has been released and think it is still valid, so An access violation occurs while continuing to perform operations on the resource.

Just like there are two children in a family, but the parents only bought one toy, and if the two children are willing to play together, everything will be fine.

 Deep copy can be used to solve the shallow copy problem, that is, each object has an independent resource and should not be shared with other objects. Parents buy a toy for each child, and there will be no problem with playing with their own .

 

2.2 Deep copy

If resource management is involved in a class, its copy constructor, assignment operator overload, and destructor must be explicitly given. In general, it is provided in the form of deep copy.

 

2.3 copy-on-write

Copy-on-write is a kind of procrastination, which is realized by adding reference counting on the basis of shallow copy.

Reference count: used to record the number of resource users. When constructing, the count of the resource is given as 1. Every time an object is added to use the resource, the count is increased by 1. When an object is destroyed, the count is first decremented by 1, and then check whether the resource needs to be released. If The count is 1, indicating that the object is the last user of the resource, and the resource is released; otherwise, it cannot be released because there are other objects using the resource.

Copy-On-Write technology of C++ STL string | Cool Shell - CoolShell

C++'s std::string's "copy while reading" technology! | Cool Shell - CoolShell

3. Copy function - assignment overloading traditional and modern writing

		/*string(const string& s)
			:_str(new char[s._capacity + 1])
			,_size(s._size)
			,_capacity(s._capacity)
		{
			strcpy(_str, s._str);
		}*/
		//现代写法 - - 拷贝构造
		void swap(string& tmp)
		{
			std::swap(_str, tmp._str);
			std::swap(_size, tmp._size);
			std::swap(_capacity, tmp._capacity);
		}
		string(const string& s)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}
		//现代写法 - - 赋值重载
		//s顶替tmp做大工人
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}

        //传统写法
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s._str);
				swap(tmp);
			}
			return *this;
		}*/
		/*string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				//先开空间,拷贝数据,再去释放原空间
				//就算开辟失败,对元数据没有破坏
				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}*/

Test case:

	void test_string3()
	{
		string s1("hello world");
		string s2(s1);
		std::cout << s1.c_str() << std::endl;
		std::cout << s2.c_str() << std::endl;

		string s3 = "1111111111111111111";
		s1 = s3;
		std::cout << s3.c_str() << std::endl;
		std::cout << s1.c_str() << std::endl;
		s1 = s1;
		std::cout << s1.c_str() << std::endl;
	}

4. Iterator implementation

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

Test case:

	void test_string2()
	{
		string s1("hello world");
		string::iterator it = s1.begin();
		while (it != s1.end())
		{
			std::cout << *it <<" ";
			++it;
		}
		std::cout << std::endl;

		for (auto ch : s1)
		{
			std::cout << ch << " ";
		}
		std::cout << std::endl;
	}

5.Reserve, push_back, append, += operator overloading

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str+_size,str);
			_size += len;
		}
		void append(const string& s)
		{
			append(s._str);
		}
		void append(size_t n, char ch)
		{
			reserve(_size + n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(ch);
			}
		}

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

Test case:

	void test_string4()
	{
		string s1;
		char ch = 'l';
		s1.push_back('h');
		s1.push_back('e');
		s1.push_back(ch);
		s1.push_back(ch);
		s1.push_back('o');
		std::cout << s1.c_str() << std::endl;
		std::cout << s1.capacity() << std::endl;
		s1 += ' ';
		s1 += 'w';
		s1 += 'o';
		s1 += 'r';
		s1 += 'l';
		s1 += 'd';
		std::cout << s1.c_str() << std::endl;
		std::cout << s1.capacity() << std::endl;

		s1.append(" 算你厉害");
		std::cout << s1.c_str() << std::endl;

		const char* str = " 六六六";
		s1 += (" 你六");
		s1 += str;
		s1.append(str);
		std::cout << s1.c_str() << std::endl;
		string s2 = "你好 ";
		string s3("世界");
		s2.append(s3);
		std::cout << s2.c_str() << std::endl;
	}

6.insert, erase implementation

		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			//满了就扩容
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			挪动数据——当pos为0就会越界访问
			//end为 -1 时,由于是无符号类型,会被转为很大得数
			//把end类型改为int,仍然运行错误
			//因为pos为无符号,和end比较,发生隐士类型转换
			/*size_t end = _size;
			while (end >= pos)
			{
				_str[end + 1] = _str[end];
				--end;
			}*/

			//不推荐,修改一
			/*int end = _size;
			while (end >= (int)pos)
			{
				_str[end + 1] = _str[end];
				--end;
			}*/

			//推荐,修改二
			size_t end = _size+1;
			while (end > pos)
			{
				_str[end] = _str[end-1];
				--end;
			}
			_str[pos] = ch;
			_size++;
			return *this;
		}
		string& insert(size_t pos, const char* ch)
		{
			assert(pos <= _size);
			size_t len = strlen(ch);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			//挪动数据
			size_t end = _size + len;
			while (end >= pos + len)
			{
				_str[end] = _str[end - len];
				--end;
			}
			strncpy(_str + pos, ch, len);
			_size += len;
			return *this;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || len >= _size - pos)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
		}

Test case:

	void test_string5()
	{
		string s1 = "hello 你好";
		std::cout << s1.c_str() << std::endl;
		s1.insert(5, '#');
		std::cout << s1.c_str() << std::endl;
		s1.insert(0, '#');
		std::cout << s1.c_str() << std::endl;
		s1.insert(0, "hello");
		std::cout << s1.c_str() << std::endl;
		s1.insert(2, "hello");
		std::cout << s1.c_str() << std::endl;
	}

	void test_string6()
	{
		string s1 = "hello 你好";
		s1.erase(3, 3);
		std::cout << s1.c_str() << std::endl;
		s1.erase(3);
		std::cout << s1.c_str() << std::endl;
	}

7. Implementation of find, relational operators, stream insertion, stream extraction, etc.

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
        void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

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

			for (size_t i = pos; i < _size; i++)
			{
				if (ch == _str[i])
				{
					return i;
				}
			}
			return npos;
		}
		size_t find(const char* sub, size_t pos = 0) const
		{
			assert(pos < _size);
			assert(sub);
			//子串匹配算法:strstr kmp bm
			const char* ptr = strstr(_str + pos, sub);
			if (ptr == nullptr)
			{
				return npos;
			}
			else
			{
				return ptr - _str;
			}
		}
		string substr(size_t pos, size_t len = npos) const
		{
			assert(pos < _size);
			size_t realLen = len;
			if (len == npos || len+pos > _size)
			{
				realLen = _size - pos;
			}
			string sub;
			for (size_t i = 0; i < realLen; ++i)
			{
				sub += _str[pos+i];
			}
			return sub;
		}
		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 || *this == s;
		}
		bool operator<(const string& s) const
		{
			return !(*this >= s);
		}
		bool operator<=(const string& s) const
		{
			return !(*this > s);
		}

Stream insertion, stream extraction:

	std::istream& operator>>(std::istream& in, string& s)
	{
		s.clear();
		char ch;
		ch = in.get();
		const size_t N = 32;
		char buff[N];
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff; // 一批一批的进行扩容,buff出作用域就会销毁
				// 因此流插入多少个字符,就会扩容多大的空间,不会进行频繁扩容
				i = 0;
			}
			ch = in.get();
		}

		buff[i] = '\0';
		s += buff;
		输入字符串很长,不断+=,频繁扩容,效率很低,需要优化
		//char ch;
		in >> ch;// 使用流插入遇到换行和空格不会自己结束
		//ch = in.get();
		s.reserve(128);// 一上来就reserve,缺陷空间可能会浪费
		//
		//while (ch != ' ' && ch != '\n')
		//{
		//	size_t old = s.capacity();
		//	s += ch;
		//	/*in >> ch;*/

		//	/*if (s.capacity() != old)
		//	{
		//		std::cout << old << "扩容" << s.capacity() << std::endl;
		//	}*/
		//	ch = in.get();
		//}
		return in;
	}
	std::ostream& operator<<(std::ostream& out, const string& s)
	{
		for (size_t i = 0; i < s.size(); ++i)
		{
			out << s[i];
		}
		return out;
	}

8. string class interview reference

A correct way of writing string class in C++ interview | Cool Shell - CoolShell

(7 messages) What happened to the string class of STL? _haoel's Blog-CSDN Blog

Guess you like

Origin blog.csdn.net/IfYouHave/article/details/130052150