[C++] STL | Simulation to achieve simple string

Table of contents

1. Framework construction

 2. Implementation of iterators

3. String copy construction and assignment (deep copy)

copy construction

assignment construct

4. Add, delete, check and modify string

reserve interface

resize interface

push_back interface

append interface

operator+=() implementation

 insert interface

 erase interface

find interface

substr interface

clear interface

Stream Insertion and Stream Extraction 

5. Operator Overloaded Functions for Comparison

operator<

operator==

 Other multiplexing

6. Source code sharing

Write at the end:


1. Framework construction

First of all, we need to build a framework,

Then fill it with content.

The framework mainly includes the following points:

1. Basic default member functions

2. Required member variables

3. Commonly used simple interfaces (let the code run first)

Look at the code:

#pragma once

#include <iostream>
#include <string>
#include <assert.h>

#include <string.h>

using namespace std;

namespace xl {
	class string {
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

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

	public:
		char& operator[](size_t pos) {
			assert(pos < _size);
			return _str[pos];
		}

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

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

		size_t size() const {
			return _size;
		}
	};
} 

Implemented functions include:

1. Constructor and destructor

2. Basic [ ] access

3. c_str available for conversion

4. And the size related to the capacity 

We can run a traversal first:

#include "string.h"

int main()
{
	xl::string s1("hello");
	cout << s1.c_str() << endl;

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

	return 0;
}

output:

 2. Implementation of iterators

Iterators may or may not be pointers,

But in string, iterators are pointers.

We implement the iterator into the class, because the iterator in the standard library exists in the class,

We can access it directly through the class field.

Look at the code:

public:
	typedef char* iterator;

	iterator begin() {
		return _str;
	}

	iterator end() {
		return _str + _size;
	}

This way we can run directly:

#include "string.h"

int main()
{
	xl::string s1("hello");
	cout << s1.c_str() << endl;

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

	xl::string::iterator it = s1.begin();
	while (it != s1.end()) {
		cout << *it << " ";
		it++;
	}
	cout << endl;
 
	return 0;
}

output:

 But ah, this only supports iterators of ordinary objects,

There are also const objects, so we have to implement another one:

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

We can observe,

Using const iterators is indeed forbidden:

 However, using ordinary iterators can modify the pointed value:

void test2() {
	xl::string s1("hello");

	xl::string::const_iterator cit = s1.begin();
	while (cit != s1.end()) {
		//*cit += 1;
		cout << *cit << " ";
		cit++;
	}
	cout << endl;

	xl::string::iterator it = s1.begin();
	while (it != s1.end()) {
		*it += 1;
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

output:

3. String copy construction and assignment (deep copy)

copy construction

Need to open a new space:

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

assignment construct

We directly adopt the strategy of deleting old space, opening up new space, and copying data:

string& operator=(const string& s) {
	if (this != &s) {
		char* tmp = new char[s._capacity + 1];
		memcpy(tmp, s._str, s._size + 1);
		delete[] _str;
		_str = tmp;
	}
	return *this;
}

 The above-mentioned well-regulated method, we call it the traditional writing method,

Then there are traditional ways of writing, and of course modern ways of writing, let’s look at this way of writing:

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

// 现代写法
string& operator=(string tmp) {
	swap(tmp);
	return *this;
}

We have implemented a swap in a string class, and the tmp formed by copy construction will help us work,

Then we can use the swap to prostitute the content of tmp.

I personally think that this method is essentially the reuse of copy construction.

In fact, copy construction can also be written in the modern way:

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

Found no, the essence of the modern writing method of copy construction is also a reuse,

What he reuses is the constructor we implemented. 

4. Add, delete, check and modify string

Before implementing those fancy interfaces,

Let's solve the problem of expansion first:

reserve interface

According to the size of the given n, it can be expanded directly:

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

resize interface

There is another way to expand capacity is resize, but string is rarely used resize,

Let's see the implementation:

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

push_back interface

 The push_back of string is to insert an element at the end.

The mechanism of double expansion is adopted (this depends on personal preference, there is also 1.5 times expansion)

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

append interface

append is to insert a string at the end,

The expansion mechanism I use is on-demand expansion.

void append(const char* str) {
	size_t len = strlen(str);
	if (_size + len > _capacity) {
		// 至少扩容到 _size + len
		reserve(_size + len);
	}
	memcpy(_str + _size, str, len + 1);
	_size += len;
}

Of course, we actually prefer to use +=:

operator+=() implementation

Of course, for this function, we can directly reuse the push_back and append implemented earlier:

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

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

This is of course much easier to use:

void test4() {
	xl::string s1("hello");
	cout << s1.c_str() << endl;

	s1 += " ";
	s1 += "a";
	s1 += "a";
	s1 += "a";
	s1 += "a";
	s1 += "a";
	s1 += "a";
	s1 += " ";
	s1 += "string";
	cout << s1.c_str() << endl;
}

output:

 insert interface

In fact, STL's string implements many redundant overloads,

As a learning, we only implement the core calling method.

Insert we implement two overloads:

void insert(size_t pos, size_t n, char ch) {

}

void insert(size_t pos, const char* str) {

}

Let’s implement the first one first, inserting a character:

void insert(size_t pos, size_t n, char ch) {
	assert(pos <= _size);
	if (_size + n > _capacity) {
		// 至少扩容到_size + n
		reserve(_size + n);
	}
	// 挪动数据
	size_t end = _size;
	while (end >= pos && end != npos) {
		_str[end + n] = _str[end];
		end--;
	}
	// 填值
	for (size_t i = 0; i < n; i++) _str[pos + i] = ch;

	_size += n;
}

Second, insert a string:

The implementation methods are similar:

void insert(size_t pos, const char* str) {
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity) {
		// 至少扩容到_size + len
		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;
}

Let's test it out:

void test5() {
	xl::string s1("hello");
	cout << s1.c_str() << endl;

	s1.insert(5, 3, 'x');
	s1.insert(0, "string ");
	cout << s1.c_str() << endl;
}

output:

 erase interface

If the deleted characters exceed some characters, or if the number of deleted characters is not specified, all will be deleted:

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

We can test it out:

void test5() {
	xl::string s1("hello");
	cout << s1.c_str() << endl;

	s1.insert(5, 3, 'x');
	s1.insert(0, "string ");
	cout << s1.c_str() << endl;

	s1.erase(10, 3);
	cout << s1.c_str() << endl;

	s1.erase(2, 100);
	cout << s1.c_str() << endl;
}

output:

find interface

If it is a single character, just find it directly:

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

For strings, we can use strstr to violently match:

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

substr interface

The operation of intercepting the string:

string substr(size_t pos = 0, size_t len = npos) {
	assert(pos <= _size);
	size_t n = len + pos;
	if (len == npos || pos + len > _size) {
		n = _size;
	}
	string tmp;
	tmp.reserve(n);
	for (size_t i = pos; i < n; i++) {
		tmp += _str[i];
	}
	return tmp;
}

Let's test it out:

void test6() {
	xl::string s1("hello string");
	cout << s1.c_str() << endl;
	
	size_t pos = s1.find('s');
	cout << s1.substr(pos, 3).c_str() << endl;

	pos = s1.find('s');
	cout << s1.substr(pos, 100).c_str() << endl;
}

output:

clear interface

By the way, realize it:

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

Stream Insertion and Stream Extraction 

We need to implement this outside the class, because the order of operators requires,

Let's look at stream insertion first:

ostream& operator<<(ostream& out, const string& s) {
	for (auto e : s) cout << e;
	return out;
}

Let's look at stream extraction again:

istream& operator>>(istream& in, string& s) {
	s.clear();
	char ch = in.get();
	while (ch != ' ' && ch != '\n') {
		s += ch;
		ch = in.get();
	}
	return in;
}

Test time~

void test7() {
	string s1;
	cin >> s1;
	cout << s1 << endl;
}

output:

5. Operator Overloaded Functions for Comparison

We still do the same operation, implement two first, and reuse them all:

operator<

We use the library function memcmp to achieve:

bool operator<(const string& s) {
	int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
	return ret == 0 ? _size < s._size : ret < 0;
}

operator==

bool operator==(const string& s) {
	return memcmp(_str, s._str, _size < s._size ? _size : s._size) == 0;
}

 Other multiplexing

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

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

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

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

6. Source code sharing

#pragma once

#include <iostream>
#include <string>

#include <assert.h>
#include <string.h>

using namespace std;

namespace xl {
	class string {
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		const static size_t npos = -1; // 可以这样用,但不建议,违背了C++的语法准则(建议声明和定义分离)

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

		 传统写法
		//string(const string& s) {
		//	_str = new char[s._capacity + 1];
		//	memcpy(_str, s._str, s._size + 1);
		//	_size = s._size;
		//	_capacity = s._capacity; 
		//}

		// 现代写法
		string(const string& s) 
			: _str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}

		 传统写法
		//string& operator=(const string& s) {
		//	if (this != &s) {
		//		char* tmp = new char[s._capacity + 1];
		//		memcpy(tmp, s._str, s._size + 1);
		//		delete[] _str;
		//		_str = tmp;
		//	}
		//	return *this;
		//}

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

		// 现代写法
		string& operator=(string tmp) {
			swap(tmp);
			return *this;
		}

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

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

	public:
		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) {
				_size = n;
				_str[_size] = '\0';
			}
			else {
				reserve(n);
				for (size_t i = _size; i < n; i++) {
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

		void push_back(char ch) {
			if (_size == _capacity) {
				// 2倍扩容
				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) {
				// 至少扩容到 _size + len
				reserve(_size + len);
			}
			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;
		}

		void insert(size_t pos, size_t n, char ch) {
			assert(pos <= _size);
			if (_size + n > _capacity) {
				// 至少扩容到_size + n
				reserve(_size + n);
			}
			// 挪动数据
			size_t end = _size;
			while (end >= pos && end != npos) {
				_str[end + n] = _str[end];
				end--; 
			}
			// 填值
			for (size_t i = 0; i < n; i++) _str[pos + i] = ch;

			_size += n;
		}

		void insert(size_t pos, const char* str) {
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity) {
				// 至少扩容到_size + len
				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, size_t len = npos) {
			assert(pos <= _size);
			if (len == npos || pos + len >= _size) {
				_size = pos;
				_str[pos] = '\0';
			}	
			else {
				size_t end = pos + len;
				while (end <= _size) {
					_str[pos++] = _str[end++];
				}
				_size -= len;
			}
		}

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

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

		string substr(size_t pos = 0, size_t len = npos) {
			assert(pos <= _size);
			size_t n = len + pos;
			if (len == npos || pos + len > _size) {
				n = _size;
			}
			string tmp;
			tmp.reserve(n);
			for (size_t i = pos; i < n; i++) {
				tmp += _str[i];
			}
			return tmp;
		}

	public:
		char& operator[](size_t pos) {
			assert(pos < _size);
			return _str[pos];
		}

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

		bool operator<(const string& s) {
			int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
			return ret == 0 ? _size < s._size : ret < 0;
		}

		bool operator==(const string& s) {
			return _size == s._size && memcmp(_str, s._str, _size) == 0;
		}

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

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

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

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

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

		size_t size() const {
			return _size;
		}

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

	ostream& operator<<(ostream& out, const string& s) {
		for (auto e : s) cout << e;
		return out;
	}

	istream& operator>>(istream& in, string& s) {
		s.clear();
		char ch = in.get();
		while (ch != ' ' && ch != '\n') {
			s += ch;
			ch = in.get();
		}
		return in;
	}
}

Write at the end:

The above is the content of this article, thank you for reading.

If you feel that you have gained something, you can give the blogger a like .

If there are omissions or mistakes in the content of the article, please private message the blogger or point it out in the comment area~

Guess you like

Origin blog.csdn.net/Locky136/article/details/131640133