C++: implementación simulada de la clase de cadena

  Queremos imitar la cadena en la biblioteca STL y básicamente implementar algunas operaciones de uso común.

contenido

interfaz de función miembro

función miembro predeterminada

Constructor

copiar constructor

incinerador de basuras

operator=sobrecarga del operador de asignación

Funciones relacionadas con la capacidad 

función de tamaño ( )

función de capacidad ()

función vacía ()

función de reserva ()

función de cambio de tamaño () 

función borrar ()

Acceder a funciones relacionadas con subíndices

función operador[ ]

Buscar funciones relacionadas

buscar () carácter

encontrar() cadena

Funciones relacionadas con el iterador

Funciones relacionadas para insertar caracteres

insert() un caracter

insertar() cadena

Insertar cola push_back()

Agregue la cadena append()

operador+= más es igual a un carácter

operador+= más es igual a una cadena

borrar borrar función

Funciones sobrecargadas del operador relacional

operador < menor que 

operador== es igual a 

operador<= menor o igual que 

operador > mayor que

operador>= mayor o igual que 

operador!= no es igual a 

Sobrecarga de la función de extracción de flujo de inserción de flujo

extracción de corriente 

inserción de flujo 

otras funciones

Intercambiar ( ) función de intercambio

cadena en forma de c


interfaz de función miembro

#pragma once
#include<iostream>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
using namespace std;
namespace sjj//自定义一个命名空间
{
	class string
	{
	public:
		typedef char* iterator;//string的迭代器就是原生指针
		typedef const char* const_iterator;
		//构造函数
		//string("hello");
		string(const char* str = "");

		//拷贝构造函数
		//s2(s1)
		string(const string& s);

		//赋值重载
		//s2=s1
		string& operator=(string s);//传值传参也是拷贝构造

		//析构
		~string();

		//容量大小相关的函数//
		size_t size()const;

		size_t capacity()const;

		bool empty()const;

		void clear();

		void reserve(size_t n);//对容量进行改变,让容量到n

		void resize(size_t n, char ch = '\0');//改变size的大小

		//访问下标相关的函数//
		char& operator[](size_t pos);

		const char& operator[](size_t pos)const;

		//查找相关的函数//
		//正向查找一个字符
		size_t find(char c, size_t pos = 0);

		//正向查找一个字符串
		size_t find(char* s, size_t pos = 0);

		///迭代器相关的函数/
		iterator begin();

		iterator end();

		const_iterator begin()const;

		const_iterator end()const;

		///插入字符串相关的函数/

		//尾插
		void push_back(char c);

		//追加字符串
		void append(const char* str);

		//加等于
		string& operator+=(char c);

		const string& operator+=(char* str);

		//插入一个字符
		string& insert(size_t pos, char c);

		//插入字符串
		string& insert(size_t pos, const char* s);

		//删除相关的函数
		//删除pos位置开始的n个字符
		string& erase(size_t pos, size_t len = npos);

		//其他函数//
		//自己写的交换函数
		void Swap(string& s);
		//C形式的字符串
		const char* c_str()const;
	private:
		char* _str;//字符串数组
		size_t _size;//有效字符的个数 不含\0
		size_t _capacity;//能存储有效字符的空间,不包含\0
		static const size_t npos;//静态成员变量,在类外部初始化
	};
	const size_t string::npos = -1; //初始化

	///关系运算符重载//
	bool operator<(const string& s1, const string& s2);

	bool operator==(const string& s1, const string& s2);

	bool operator<=(const string& s1, const string& s2);

	bool operator>(const string& s1, const string& s2);

	bool operator>=(const string& s1, const string& s2);

	bool operator!=(const string& s1, const string& s2);


	//  operator<<   operator>>
	//  cout<<s1 --> operator<<(out,s1);
	///流插入 流提取 重载///
	ostream& operator<<(ostream& out, const string& s);
	//ostream&是系统的类型,它能够实现输出内置类型的数据

	istream& operator>>(istream& in, string& s);
}

función miembro predeterminada

Constructor

Se proporciona un carácter nulo como valor predeterminado. Cuando no se pasa ningún parámetro, se construye una cadena vacía. Cuando se pasa un parámetro, se utiliza el parámetro pasado.

string(const char* str = "")//给缺省值""空字符串
	:_size(strlen(str))
	, _capacity(_size)
{
	_str = new char[_capacity + 1];//  +\0
	strcpy(_str, str);//char *strcpy( char *strDestination, const char *strSource );
}

copiar constructor

En primer lugar, debe comprender los conocimientos relacionados con la copia profunda y superficial, ¡haga clic aquí!

La primera forma de escribir: la escritura tradicional

//传统写法     //要用自己写的
string(const string& s)
	:_size(0)
	, _capacity(0)
{
	_str = new char[s._capacity + 1];//_str申请一块和s._str一样大的空间
	strcpy(_str, s._str);//将两个指针指向交换
	_size = s._size;
	_capacity = s._capacity;
}

Honestamente, hacemos una copia profunda, primero un espacio nuevo tan grande como el original y luego una copia profunda de los datos, para que los dos espacios no se afecten entre sí. 

La segunda: escritura moderna (escritura recomendada)

//现代写法————投机取巧
string(const string& s)
	:_str(nullptr)
	, _size(0)
	, _capacity(0)
{
	string tmp(s._str);//复用构造函数,构造一个tmp对象出来
	this->Swap(tmp);//相当于Swap(this,tmp)
}

Resumen :

  El método de escritura moderno es oportunista, reutiliza el constructor, déjalo abrir un objeto temporal, fuera del alcance, el objeto temporal local se destruye automáticamente, no necesitamos preocuparnos por eso, solo necesitamos intercambiar sus punteros en el final, el código es muy conciso.

incinerador de basuras

Limpiar algunos recursos dinámicos

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

operator=sobrecarga del operador de asignación

Si no lo escribimos, el compilador también lo generará de forma predeterminada, pero también es una copia superficial, que no cumple con nuestros requisitos de uso.

Sabemos que para copiar en profundidad, primero debemos considerar la capacidad de los dos espacios :

① La cantidad de datos en s3 es mayor que la cantidad de datos en s1, debemos considerar la expansión de s1

②El espacio de s1 es mucho mayor que la cantidad de datos de s3, debemos considerar el problema de la contracción

De esta manera, también podríamos simplificar y simplificar el problema, liberar directamente el espacio de s1, volver a abrir un espacio tan grande como s3 y finalmente copiar los datos allí.

 La primera: la escritura tradicional

string& operator=(const string& s)
{
	/*if (this != &s)//防止自己给自己赋值s3=s3
	{
		delete[] _str;//释放掉s1 默认有个隐藏的this指针,实际上是这样: this->_str
		_str = new char[strlen(s._str) + 1];
		strcpy(_str, s._str);
	}*/
	//优化版本
	//new可能会失败,但是却先释放s1了,我们可以先new
	if (this != &s)//防止自己给自己赋值s3=s3
	{
		char* tmp = new char[strlen(s._str) + 1];
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
	}
	return *this;//返回s1
}

 Podemos ver a través de la ventana de depuración:

La segunda: escritura moderna (muy ingeniosa)

//更NB的写法
//s1=s3 这里传值传参也是一种拷贝,s充当的就是tmp
string& operator=(string s)
{
	this->Swap(s);
	return *this; //返回左值,支持连续赋值
}

Resumen : la escritura moderna utilizada aquí es muy inteligente, ya que utiliza copia por valor, lo que equivale a una llamada al constructor de copia.

Funciones relacionadas con la capacidad 

función de tamaño ( )

Podemos devolver directamente _size, excluyendo \0

const size_t size()const
{
	return _size;
}

función de capacidad ()

size_t capacity()const
{
	return _capacity;
}

función vacía ()

Puede usar la función strcmp para comparar dos punteros de cadena.

//判断是否为空
bool empty()
{
	return strcmp(_str, "") == 0;
}

función de reserva ()

Para ajustar la capacidad a n, primero creamos un nuevo espacio de n+1, luego copiamos los datos originales en el nuevo espacio, liberamos el espacio original, intercambiamos los punteros de los dos punteros y establecemos _capacidad en n Eso es todo.

Echemos un vistazo a la demostración de animación:

void reserve(size_t n)//对容量进行改变,让容量到n
{
	if (n > _capacity)
	{
		char* tmpstr = new char[n + 1];
		strcpy(_str, tmpstr);
		delete[] _str;
		_str = tmpstr;
		_capacity = n;
	}
}

función de cambio de tamaño () 

void resize(size_t n, char ch = '\0')//改变size的大小
{
	if (n <= _size)//缩小的情况
	{
		_size = n;
		_str[n] = '\0';
	}
	else//要插入数据的情况
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		//void *memset( void *dest, int c, size_t count );
		memset(_str + _size, ch, n - _size);
		_size = n;
		_str[n] = '\0';
	}
}

Hay dos situaciones en la función de cambio de tamaño:

El primero: el valor n del tamaño dado es menor que _size, en este caso los datos se truncarán y solo se conservará el primer n

El segundo tipo: el valor n del tamaño dado es mayor que la capacidad, y se debe considerar el problema de expansión

función borrar ()

Establecer la posición _size en una barra inclinada 0 es equivalente a vaciar la cadena.

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

Acceder a funciones relacionadas con subíndices

función operador[ ]

El operador[] es la forma más familiar   de acceder a los elementos mediante subíndices , y es muy simple y conveniente. Se implementa para que podamos acceder a cadenas como matrices . Podemos implementar la versión normal y la versión del objeto const.

char& operator[](size_t pos)
{
	assert(pos < _size);//防止越界
	return _str[pos];//返回pos位置的引用,相当于就是把那个字符给返回了
}
//const版本
const char& operator[](size_t pos)const
{
	assert(pos < _size);//防止越界
	return _str[pos];//返回pos位置的引用,相当于就是把那个字符给返回了
}

Buscar funciones relacionadas

buscar () carácter

  npos es una variable miembro estática de la clase de cadena y su valor es el valor máximo del entero.

  Al atravesar, buscamos la cadena coincidente desde el principio hasta el final, encontramos el subíndice de retorno y devolvemos npos si no se encuentra.

//正向查找一个字符
size_t find(char c, size_t pos = 0)
{
	assert(pos < _size);
	for (int i = 0; i < _size; ++i)
	{
		if (_str[i] == c)
		{
			return i;
		}
	}
	return npos;//没有找到目标字符,返回npos
}

encontrar() cadena

  Se utiliza la función de búsqueda de cadena strstr, se encuentra el puntero al primer carácter de la cadena devuelta y no se encuentra NULL.

Habiendo encontrado la cadena, podemos determinar la posición de la cadena objetivo por la diferencia de punteros.

//正向查找一个字符串
size_t find(char* s, size_t pos = 0)
{
	assert(pos < _size);

	//const char * strstr ( const char * str1, const char * str2 );
	const char* ptr = strstr(_str + pos, s);//调用strstr字符串查找函数
	if (s)
	{
		return ptr - _str;//返回找到第一个字符的下标
	}
	else
	{
		return npos;
	}
}

Funciones relacionadas con el iterador

  El iterador en la clase de cadena es equivalente a un puntero nativo, pero aquí es un typedef, así que aquí solo lo tratamos como un puntero.

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

const_iterator begin()const
{
	return _str;
}

const_iterator end()const
{
	return _str + _size;
}

Funciones relacionadas para insertar caracteres

Primero echemos un vistazo al principio de inserción de la demostración de animación:

insert() un caracter

  La función de inserción necesita insertar un carácter en cualquier posición. Primero, verifique la validez del subíndice y luego determine si necesita expandirse. Para la expansión, puede usar la función de reserva y luego mover la posición pos y los caracteres detrás de él por un lugar, dejando los caracteres que se insertarán fuera de la posición, luego inserte el carácter en la cadena

//插入一个字符
string& insert(size_t pos, char c)
{
	assert(pos <= _size);
	if (_size == _capacity)//判断是否需要增容
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	//从后往前挪动数据,以免被覆盖
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = c;
	++_size;
	return *this;
}

insertar() cadena

El principio es similar a insertar un carácter

//插入字符串
string& insert(size_t pos, const char* s)
{
	assert(pos <= _size);//检查有效位置
	size_t len = strlen(s);//记录要插入的字符串长度
	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, s, len);//拷贝数据到pos位置
	_size += len;
	return *this;
}

Insertar cola push_back()

  push_back es para empalmar caracteres al final de una cadena. Primero debemos considerar si hay suficiente espacio. Si no es suficiente, podemos usar la función de reserva para expandir 2 veces (no necesariamente 2 veces), luego inserte los datos , y finalmente rellene al final \0 es suficiente.

void push_back(char c)
{
	if (_size == _capacity)//扩容
	{
        //这里要注意,刚开始初始化给定的_capacity=0时
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	//再插入
	_str[_size] = c;
	++_size;
	_str[_size] = '\0';
}

También puede reutilizar la función de inserción directamente.Inserción de cola significa insertar en la posición donde la posición pos es _size.

//尾插字符
void push_back(char c)
{
	insert(_size, c); //在字符串末尾插入字符ch
}

Agregue la cadena append()

  Append es en realidad similar a push_back, excepto que append está empalmando una cadena.¿El problema aquí es que la cadena se duplica? ¿Expandir 1,5 veces? No estoy muy seguro. Lo que necesitamos aquí es el número de cadenas entrantes. Nuestro espacio debe ser al menos _size+len, y el espacio es suficiente para satisfacer todos los caracteres. Después de abrir el espacio, use strcpy para copiar los datos en la cadena.

//追加字符串
void append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		//扩容
		reserve(_size + len);
	}
	strcpy(_str + _size, str);
	_size += len;
}

  También es posible reutilizar la función de inserción, que se puede insertar al final de la cadena original.

//尾插字符串
void append(const char* str)
{
	insert(_size, str); //在字符串末尾插入字符串str
}

operador+= más es igual a un carácter

  Add equals también es una función que usamos más, porque es conveniente y concisa, y es fácil de entender de un vistazo. Podemos reutilizar push_back, prestar atención a devolver un nuevo objeto.

//加等于一个字符
string& operator+=(char c)
{
	//复用push_back
	push_back(c);
	return *this;
}

operador+= más es igual a una cadena

reutilizar agregar

//加等于一个字符串
const string& operator+=(char* str)
{
	//复用append
	append(str);
	return *this;
}

borrar borrar función

Queremos eliminar los caracteres len que comienzan en la posición pos.

Tenemos que considerar dos cuestiones:

El primero: elimine todas las posiciones después de la posición pos, coloque \0 en la posición pos, _size-=pos

  El segundo: solo se elimina una parte de los caracteres después de la posición pos, y la parte restante se debe completar en el espacio vacío anterior.

//删除pos位置开始的n个字符
string& erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
	if (len == npos || pos + len > _size)//删除pos位置之后的所有字符
	{
		_size = pos;
		_str[_size] = '\0';
	}
	else//删除pos位置之后的部分字符
	{
		strcpy(_str + pos, _str + pos + len);//用后面的剩余字符填充空位
		_size -= len;
	}
	return *this;//返回新的对象
}

Funciones sobrecargadas del operador relacional

operador < menor que 

  La función strcmp() no compara la longitud de la cadena, sino el tamaño de los caracteres en la posición correspondiente de la cadena (es decir, se compara el valor del código ASCII y también distingue entre mayúsculas y minúsculas, el c_str entrante es un puntero no vacío

bool operator<(const string& s1, const string& s2)
{
	//	size_t i1 = 0;
	//	size_t i2 = 0;
	//	while (i1 < s1.size() && i2 < s2.size())
	//	{
	//		if (s1[i1]<s2[i2])
	//		{
	//			return true;
	//		}
	//		else if (s1[i1]>s2[i2])
	//		{
	//			return false;
	//		}
	//		else
	//		{
	//			++i1;
	//			++i2;
	//		}
	//	}
	//	//while的条件是 且
	//	//一个一个的字符按照ASCII码值去比较
	//	//现在是有一个字符串已经走完了
	//	// "abcd"   "abcd"    false
	//	// "abcd"   "abcde"   true   
	//	// "abcde"  "abcd"    false
	//	return i2 < s2.size() ? true : false;

	//c_str是一个内容为字符串指向字符数组的临时指针
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}

operador== es igual a 

bool operator==(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) == 0;

}

operador<= menor o igual que 

  Implementando los anteriores menor que e igual a, se pueden reutilizar los siguientes operadores ya implementados.

reutilizar menos o igual que

bool operator<=(const string& s1, const string& s2)
{
	return s1 < s2 || s1 == s2;
}

operador > mayor que

Reutilizar el inverso de menor o igual que


bool operator>(const string& s1, const string& s2)
{
	return !(s1 < s2&& s1 == s2);
}

operador>= mayor o igual que 

reutilizar menos de

bool operator>=(const string& s1, const string& s2)
{
	return !(s1 < s2);
}

operador!= no es igual a 

la multiplexación es igual a

bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}

Sobrecarga de la función de extracción de flujo de inserción de flujo

extracción de corriente 

  Cabe señalar que las clases ostream e istream son las clases base en el flujo de salida estándar de C++. Puede generar tipos incorporados. Podemos escribirlo como una función global o sobrecargarlo como un amigo, pero el amigo destruye la encapsulación. del programa. ,No recomendado.

ostream& operator<<(ostream& out, const string& s)
//ostream&是系统的类型,它能够实现输出内置类型的数据
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}

inserción de flujo 

Primero debemos prestar atención a limpiar el objeto s, para no dar un valor inicial al inicializar el objeto.

istream& operator>>(istream& in, string& s)
{
    s.clear();//防止对象初始化已经给了值
	char ch = in.get();
	while (ch != ' ' && ch != '\n')//遇到空格或者换行就停止
	{
		s += ch;
		ch = in.get();//再获取下一个
	}
	return in;
}

otras funciones

Intercambiar ( ) función de intercambio

  Debido a que la función de intercambio se usa en muchos lugares y la implementación es similar, se puede encapsular como una función miembro.


//模拟string类里面的交换函数
void Swap(string& s)
{
	std::swap(_str, s._str);//用C++库里面自带的交换函数
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

  Podemos consultar, hay una función de intercambio en la biblioteca global de C++, y también hay una función de intercambio en la clase de cadena.Estas dos funciones se pueden intercambiar

La función de intercambio en la biblioteca estándar:

La función de intercambio en la clase de cadena:

int main()
{
	std::string s1("hello");
	std::string s2("C++");
	s1.swap(s2);//调用string类里面的swap函数
	swap(s1,s2);//调用全局的swap函数
	return 0;
}

¿Cuál de los dos es más eficiente?

  Respuesta : La función de intercambio especial en la clase de cadena es más eficiente, porque solo intercambia variables miembro, mientras que la biblioteca estándar se implementa con plantillas, que crearán objetos temporales y realizarán tres copias profundas, ¡lo cual es más costoso! 

cadena en forma de c

devuelve un puntero no nulo

//C语言形式的字符串
const char* c_str()const
{
	return _str;
}

¡Gracias por ver!

Supongo que te gusta

Origin blog.csdn.net/weixin_57675461/article/details/123800470
Recomendado
Clasificación