[C++] STL | Simulación para lograr una cadena simple

Tabla de contenido

1. Construcción del marco

 2. Implementación de iteradores

3. Construcción y asignación de copias de cadenas (copia profunda)

copiar construcción

construcción de asignación

4. Agregar, eliminar, verificar y modificar cadenas

interfaz de reserva

cambiar el tamaño de la interfaz

interfaz push_back

añadir interfaz

implementación del operador+=()

 insertar interfaz

 borrar interfaz

encontrar interfaz

interfaz substr

interfaz clara

Inserción de flujo y extracción de flujo 

5. Funciones sobrecargadas del operador para comparación

operador<

operador ==

 Otra multiplexación

6. Compartir código fuente

Escribe al final:


1. Construcción del marco

En primer lugar, necesitamos construir un marco,

Luego llénalo con contenido.

El marco incluye principalmente los siguientes puntos:

1. Funciones de miembro predeterminadas básicas

2. Variables miembro requeridas

3. Interfaces simples de uso común (deje que el código se ejecute primero)

Mira el código:

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

Las funciones implementadas incluyen:

1. Constructor y destructor

2. Acceso básico [ ]

3. c_str disponible para conversión

4. Y el tamaño relacionado con la capacidad. 

Podemos ejecutar un recorrido primero:

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

producción:

 2. Implementación de iteradores

Los iteradores pueden o no ser punteros,

Pero en una cadena, los iteradores son punteros.

Implementamos el iterador en la clase, porque el iterador en la biblioteca estándar existe en la clase,

Podemos acceder a ella directamente a través del campo de clase.

Mira el código:

public:
	typedef char* iterator;

	iterator begin() {
		return _str;
	}

	iterator end() {
		return _str + _size;
	}

De esta manera podemos ejecutar directamente:

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

producción:

 Pero ah, esto solo admite iteradores de objetos ordinarios,

También hay objetos const, por lo que tenemos que implementar otro:

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

podemos observar,

De hecho, está prohibido usar iteradores const:

 Sin embargo, el uso de iteradores ordinarios puede modificar el valor señalado:

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

producción:

3. Construcción y asignación de copias de cadenas (copia profunda)

copiar construcción

Necesidad de abrir un nuevo espacio:

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

construcción de asignación

Adoptamos directamente la estrategia de eliminar el espacio antiguo, abrir un nuevo espacio y copiar datos:

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

 El método bien regulado mencionado anteriormente, lo llamamos el método de escritura tradicional,

Luego están las formas tradicionales de escribir y, por supuesto, las formas modernas de escribir, veamos esta forma de escribir:

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

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

Hemos implementado un intercambio en una clase de cadena, y el tmp formado por la construcción de copias nos ayudará a trabajar,

Entonces podemos usar el intercambio para prostituir el contenido de tmp.

Personalmente, creo que este método es esencialmente la reutilización de la construcción de copias.

De hecho, la construcción de copias también se puede escribir de forma moderna:

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

Encontrado no, la esencia del método de escritura moderno de construcción de copias es también una reutilización,

Lo que reutiliza es el constructor que implementamos. 

4. Agregar, eliminar, verificar y modificar cadenas

Antes de implementar esas interfaces sofisticadas,

Resolvamos primero el problema de la expansión:

interfaz de reserva

Según el tamaño de la n dada, se puede expandir directamente:

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

cambiar el tamaño de la interfaz

Hay otra forma de expandir la capacidad es cambiar el tamaño, pero la cadena generalmente rara vez se usa para cambiar el tamaño,

Veamos la implementació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';
	}
}

interfaz push_back

 El push_back de string es insertar un elemento al final.

Se adopta el mecanismo de doble expansión (esto depende de la preferencia personal, también hay una expansión de 1,5 veces)

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

añadir interfaz

append es insertar una cadena al final,

El mecanismo de expansión que uso es la expansión bajo demanda.

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

Por supuesto, en realidad preferimos usar +=:

implementación del operador+=()

Por supuesto, para esta función, podemos reutilizar directamente push_back y append implementado anteriormente:

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

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

Por supuesto, esto es mucho más fácil de usar:

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

producción:

 insertar interfaz

De hecho, la cadena de STL implementa muchas sobrecargas redundantes,

Como aprendizaje, solo implementamos el método de llamada central.

Insertar implementamos dos sobrecargas:

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

}

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

}

Implementemos primero el primero, insertando un carácter:

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

En segundo lugar, inserte una cadena:

Los métodos de implementación son similares:

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

Vamos a probarlo:

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

producción:

 borrar interfaz

Si los caracteres eliminados superan algunos caracteres, o si no se especifica el número de caracteres eliminados, se eliminarán todos:

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

Podemos probarlo:

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

producción:

encontrar interfaz

Si es un solo carácter, simplemente encuéntrelo directamente:

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

Para cadenas, podemos usar strstr para hacer coincidir violentamente:

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

interfaz substr

La operación de interceptar la cadena:

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

Vamos a probarlo:

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

producción:

interfaz clara

Por cierto, date cuenta:

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

Inserción de flujo y extracción de flujo 

Necesitamos implementar esto fuera de la clase, porque el orden de los operadores requiere,

Veamos primero la inserción de flujo:

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

Veamos de nuevo la extracción de secuencias:

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

Tiempo de prueba ~

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

producción:

5. Funciones sobrecargadas del operador para comparación

Todavía hacemos la misma operación, implementamos dos primero y los reutilizamos todos:

operador<

Usamos la función de biblioteca memcmp para lograr:

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

operador ==

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

 Otra multiplexación

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. Compartir código fuente

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

Escribe al final:

Lo anterior es el contenido de este artículo, gracias por leer.

Si sientes que has ganado algo, puedes darle un me gusta al blogger .

Si hay omisiones o errores en el contenido del artículo, envíe un mensaje privado al blogger o indíquelo en el área de comentarios ~

Supongo que te gusta

Origin blog.csdn.net/Locky136/article/details/131640133
Recomendado
Clasificación