Explicación e implementación de simulación de la clase de cadena (c ++)

contenido

1. ¿Qué es STL?

1. ¿Qué es STL?

2. Versión STL

3. Seis componentes de STL

4. Defectos de STL

2. ¿Qué es una cuerda?

 1. ¿Por qué aprender cuerdas?

2. La introducción de cadenas en el documento

3. Resumen

 3. Funciones miembro de la clase de cadena

1. Construcción común de objetos de clase de cadena

 2. Operación de capacidad de objetos de clase de cadena

3. Operaciones de acceso y recorrido de objetos de clase de cadena

 4. La operación de modificación del objeto de clase de cadena.

5. Función no miembro de clase de cadena

 4. La implementación de simulación de la clase de cadena.

1. Variable miembro, declaración de iterador y declaración de amigo de sobrecarga de funciones de entrada y salida

2. Construcción común de objetos de clase de cadena

3. Iterador (piense en ello como un puntero)

4. Operación de capacidad de objetos de clase de cadena

5. Operación de modificación del objeto de clase de cadena

6. Acceso a objetos de clase de cadena

7. Función de no miembro de clase de cadena

8. Operaciones especiales relacionadas con cadenas en la clase de cadena

9. Sobrecarga de funciones de entrada y salida de objetos de cadena

5. Asuntos que requieren atención al usar la clase de cadena

1. Variables miembro internas de clase de cadena real

2. El método de expansión de clase de cadena real (diferentes compiladores pueden ser diferentes)

3. El proceso de uso de la reserva para la expansión de la capacidad.

4. La diferencia entre [ ] y at

5. Detalles sobre la sobrecarga del constructor de copia y del operador de asignación

6.string和vector <char>


1. ¿Qué es STL?

Para simular e implementar cadenas, primero debemos comprender qué son las cadenas y, para comprender las cadenas, primero debemos conocer STL.

1. ¿Qué es STL?

El nombre completo de STL es la biblioteca de plantillas estándar libaray de plantilla estándar, que es una parte importante de la biblioteca estándar de C++. No es solo una biblioteca de componentes reutilizables, sino también un marco de software que incluye estructuras de datos y algoritmos. Las estructuras de datos están encapsuladas. en plantillas y proporcionar algunos algoritmos generales y flexibles.

2. Versión STL

①.Versión original La versión original completada por Alexander Stepanov y Meng Lee en HP Labs, en el espíritu del código abierto, declaran que permiten que cualquier persona use, copie, modifique, difunda y use comercialmente estos códigos sin pago. La única condición es que también debe usarse como código abierto como la versión original, la versión de HP, el antepasado de todas las implementaciones de STL.

La versión ②.PJ es desarrollada por PJ Plauger, heredada de la versión HP, adoptada por Windows Visual C++, no puede divulgarse ni modificarse, defectos: baja legibilidad, nombres de símbolos extraños.

La versión ③.RW es desarrollada por Rouge Wage Company, heredada de la versión HP, adoptada por C++ Builder, no puede publicarse ni modificarse y tiene legibilidad general.

④ La versión SGI fue desarrollada por Silicon Graphics Computer Systems, Inc y se heredó de la versión HP. Adoptado por GCC (Linux), tiene buena portabilidad, se puede publicar, modificar e incluso vender.Desde el punto de vista del estilo de nomenclatura y el estilo de programación, es muy legible. Leeremos parte del código fuente para aprender STL más adelante, y esta versión es la referencia principal.

3. Seis componentes de STL

La clase de cadena que vamos a simular es parte del contenedor. 

4. Defectos de STL

①. La actualización de la biblioteca STL es demasiado lenta. Esta es una queja grave. La última versión fue C++98, y el C++03 en el medio fue básicamente revisado. Han pasado 13 años desde el lanzamiento de C ++ 11. renovar.

② STL no es compatible con la seguridad de subprocesos ahora, necesitamos bloquearnos en un entorno concurrente y la granularidad del bloqueo es relativamente grande.

③ La búsqueda extrema de eficiencia de STL conduce a componentes internos complejos, como la extracción de tipos y la extracción de iteradores.

4. El uso de STL tendrá el problema de la expansión del código. Por ejemplo, el uso de vector<int>/vector<double>/vector<char> generará varios códigos. Por supuesto, esto se debe a la sintaxis de la plantilla en sí. .

2. ¿Qué es una cuerda?

 1. ¿Por qué aprender cuerdas?

Cadena en lenguaje C: en lenguaje C, una cadena es una colección de caracteres que terminan en '\ 0'. Para facilitar la operación, se proporcionan algunas funciones de biblioteca de la serie str en la biblioteca estándar de C, pero estas funciones de biblioteca no están relacionadas con cadenas Está separado, lo que no está en línea con la idea de OOP (programación orientada a objetos), y el espacio subyacente debe ser administrado por el usuario, y se puede acceder fuera de los límites si no tiene cuidado.

Por lo tanto: en el trabajo de rutina, la clase de cadena se usa básicamente por simplicidad, conveniencia y velocidad, y pocas personas usan las funciones de manipulación de cadenas en la biblioteca C.

2. La introducción de cadenas en el documento

①.String es una clase que representa una secuencia de caracteres.

② La clase de cadena estándar brinda soporte para dichos objetos y su interfaz es similar a la del contenedor de caracteres estándar, pero agrega características de diseño específicas para manipular cadenas de caracteres de un solo byte.

③ La clase de cadena debe usar char (es decir, como su tipo de carácter, use sus char_traits predeterminados y el tipo de asignador (para obtener más información sobre las plantillas, consulte basic_string)).

4. La clase de cadena es una instancia de la clase de plantilla basic_string, que utiliza char para instanciar la clase de plantilla basic_string y utiliza char_traits y allocator como parámetros predeterminados de basic_string (para obtener más información sobre la plantilla, consulte basic_string).

⑤ Tenga en cuenta que esta clase maneja bytes independientemente de la codificación utilizada: si se usa para manejar secuencias de caracteres multibyte o de longitud variable (como UTF-8), todos los miembros de esta clase (como longitud o tamaño) y su todavía operan en bytes (en lugar de los caracteres codificados reales).

Nota: Al usar la clase de cadena, debe incluir #include<cadena> y usar el espacio de nombres estándar;

3. Resumen

 3. Funciones miembro de la clase de cadena

Nota: aquí solo explicamos algunas funciones miembro de uso común, no todas.

1. Construcción común de objetos de clase de cadena

 2. Operación de capacidad de objetos de clase de cadena

Darse cuenta:

1. El principio de implementación subyacente de los métodos size() y length() es exactamente el mismo. La razón para introducir size() es ser coherente con las interfaces de otros contenedores. En general, se utiliza básicamente size().

2. clear() simplemente borra los caracteres válidos en la cadena sin cambiar el tamaño del espacio subyacente.

3. resize(size_t n) y resize(size_t n, char c) cambian el número de caracteres válidos en la cadena a n, la diferencia es que cuando aumenta el número de caracteres: resize(n) llena el número de caracteres con 0. El espacio de elemento extra, resize(size_t n, char c) llena el espacio de elemento extra con el carácter c. Nota: Cuando resize cambia el número de elementos, si se aumenta el número de elementos, puede cambiar el tamaño del Capacidad subyacente Si se reduce el número de elementos, el tamaño total del espacio subyacente permanece sin cambios.

4. reserve(size_t res_arg=0): Reserve espacio para la cadena sin cambiar el número de elementos válidos. Cuando el parámetro de reserva es menor que el tamaño total del espacio subyacente de la cadena, la reserva no cambiará la capacidad.

3. Operaciones de acceso y recorrido de objetos de clase de cadena

 4. La operación de modificación del objeto de clase de cadena.

Darse cuenta:

1. Al agregar caracteres al final de la cadena, los tres métodos de s.push_back(c) / s.append(1, c) / s += 'c' son similares. En general, la operación += del La clase de cadena se usa para la comparación Múltiple, las operaciones += no solo pueden concatenar caracteres individuales, sino también cadenas de caracteres.

2. Cuando opere en cadenas, si puede estimar aproximadamente cuántos caracteres poner, puede reservar el espacio a través de la reserva primero.

5. Función no miembro de clase de cadena

 4. La implementación de simulación de la clase de cadena.

1. Variable miembro, declaración de iterador y declaración de amigo de sobrecarga de funciones de entrada y salida

//输入输出函数重载
        friend ostream& operator<<(ostream& _cout, const lzStr::string& s);
        friend istream& operator>>(istream& _cin, lzStr::string& s);
//迭代器声明
        typedef char* iterator;
//成员变量      
        char* _str;
        size_t _capacity;
        size_t _size;

2. Construcción común de objetos de clase de cadena

		string(const char* str = "")//构造函数
			:_str(nullptr)
			, _size(0)
			, _capacity(0) 
		{
			if (str != nullptr) {
				_str = new char[strlen(str) + 1];
				strcpy(_str, str);
				_size = strlen(str);
				_capacity = strlen(str);
			}
			else {
				perror("string:");
			}
		}

		string(const string& s)//拷贝构造函数
			:_str(nullptr)
		{
			lzStr::string tempStr(s._str);
			swap(tempStr);
		}

		string& operator=(const string& s) {//赋值运算符重载
			if (this != &s) {
				lzStr::string tempStr(s._str);
				this->swap(tempStr);
			}
			return *this;
		}

		~string() {//析构函数
			if (_str != nullptr) {
				delete[] _str;
				_str = nullptr;
			}
		}

3. Iterador (piense en ello como un puntero)

		iterator begin() {
			return _str;
		}

		iterator end() {
			return _str + _size;
		}

4. Operación de capacidad de objetos de clase de cadena

		size_t size()const {//有效元素个数
			return _size;
		}

		size_t capacity()const {//容量
			return _capacity;
		}

		bool empty()const {//判断有效元素个数是否为0
			if (size() == 0) {
				return true;
			}
			return false;
		}

		void resize(size_t n, char c = '\0') {//改变有效元素个数,以c填充
			int new_size = n;
			if (new_size > size()) {
				if (new_size > capacity()) {
					reserve(new_size);
				}
				int i;
				for (i = size(); i < new_size; i++) {
					_str[i] = c;
				}
			}
			_size = new_size;
			_str[n] = '\0';
		}

		void reserve(size_t n) {//扩容
			if (n > capacity()) {
				char* temp = new char[n + 1];
				strcpy(temp, _str);
				delete[] _str;
				_str = temp;
				_capacity = n;
			}
		}

5. Operación de modificación del objeto de clase de cadena

		void push_back(char c) {//尾插字符
			if (_size == _capacity) {
				reserve(_capacity*2);
			}
			*end() = c;
			_size++;
			*end() = '\0';
		}

		string& operator+=(char c) {//+=运算符重载,类似于尾插字符
			push_back(c);
			return *this;
		}

		void append(const char* str) {//在尾部追加字符串
			size_t new_size = _size + strlen(str);
			if (new_size > _capacity) {
				reserve(_capacity * 2);
			}
			int i = _size,j=0;
			while (str[j] != '\0') {
				_str[i] = str[j];
				++i, ++j;
			}
			_str[i] = '\0';
			_size = i;
		}

		string& operator+=(const char* str) {//+=运算符重载,类似于尾插字符串
			append(str);
			return *this;
		}

		void clear() {//将有效元素个数置为0
			*begin() = '\0';
			_size = 0;
		}

		void swap(string& s) {//交换
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		const char* c_str()const {//将string对象转为字符串
			return _str;
		}

6. Acceso a objetos de clase de cadena

		char& operator[](size_t index) {//[]运算符重载(普通类型)
			if (index<0 || index>=size()) {
				perror("operator[]:");
			}
			return *(_str + index);
		}

		const char& operator[](size_t index)const {//[]运算符重载(const类型)
			if (index < 0 || index >= size()) {
				perror("const operator[]:");
			}
			return *(_str + index);
		}

7. Función de no miembro de clase de cadena

		bool operator<(const string& s) {//<运算符重载
			if (strcmp(_str, s._str) < 0) {
				return true;
			}
			return false;
		}

		bool operator<=(const string& s) {//<=运算符重载
			if (strcmp(_str, s._str) > 0) {
				return false;
			}
			return true;
		}

		bool operator>(const string& s) {//>运算符重载
			if (strcmp(_str, s._str) > 0) {
				return true;
			}
			return false;
		}

		bool operator>=(const string& s) {//>=运算符重载
			if (strcmp(_str, s._str) < 0) {
				return false;
			}
			return true;
		}

		bool operator==(const string& s) {//==运算符重载
			if (strcmp(_str, s._str) == 0) {
				return true;
			}
			return false;
		}

		bool operator!=(const string& s) {//!=运算符重载
			if (strcmp(_str, s._str) == 0) {
				return false;
			}
			return true;
		}

8. Operaciones especiales relacionadas con cadenas en la clase de cadena

// 返回c在string中第一次出现的位置

		size_t find(char c, size_t pos = 0) const {
			if (pos < 0 || pos >= size()) {
				perror("find c:");
			}
			int i;
			for (i = pos; i < size(); i++) {
				if (_str[i] == c) {
					return i;
				}
			}
			return -1;
		}

// 返回子串s在string中第一次出现的位置

		size_t find(const char* s, size_t pos = 0) const {
			if (pos < 0 || pos >= size()) {
				perror("find s:");
			}
			int i;
			for (i = pos; i < size(); i++) {
				int j = 0;
				int i_1 = i;
				while (i_1 < size() && j < strlen(s) && _str[i_1] == s[j]) {
					++i_1, ++j;
				}
				if (j == strlen(s)) {
					return i;
				}
			}
			return -1;
		}

// 在pos位置上插入字符c/字符串str,并返回该字符的位置

		string& insert(size_t pos, char c) {
			if (pos < 0 || pos >= size()) {
				perror("insert c:");
			}
			int new_size = size() + 1;
			if (new_size > capacity()) {
				reserve(capacity() * 2);
			}
			auto i = begin() + pos;
			for (auto j = end() - 1; j >= i; --j) {
				*(j + 1) = *j;
			}
			*i = c;
			_size = new_size;
			return *this;
		}

		string& insert(size_t pos, const char* str) {
			if (pos < 0 || pos >= size()) {
				perror("insert s:");
			}
			int new_size = size() + strlen(str);
			if (new_size > capacity()) {
				reserve(capacity() * 2);
			}
			auto i = begin() + pos;
			for (auto j = end() - 1; j >= i; --j) {
				*(j + strlen(str)) = *j;
			}
			for (int j = 0; str[j] != '\0'; ++j) {
				*i = str[j];
				++i;
			}
			_size = new_size;
			return *this;
		}
		
// 删除pos位置上的元素,并返回该元素的下一个位置

		string& erase(size_t pos, size_t len) {
			if (pos < 0 || pos >= size()) {
				perror("erase:");
			}
			auto i = begin() + pos;
			int new_size = size() - len;
			for (auto j = i + len; j < end(); ++j) {
				*i = *j;
				++i;
			}
			_size = new_size;
			return *this;
		}

9. Sobrecarga de funciones de entrada y salida de objetos de cadena

//输出函数重载
ostream& lzStr::operator<<(ostream& _cout, const lzStr::string& s) {
	int i;
	for (i = 0; i < s.size(); i++) {
		_cout << s[i];
	}
	return _cout;
}

//输入函数重载
istream& lzStr::operator>>(istream& _cin, lzStr::string& s) {
	char* str = (char*)malloc(sizeof(char) * 100);
	char* buf = str;
	int i = 1;
	while ((*buf = getchar()) == ' ' || (*buf == '\n'));
	for (; ; ++i) {
		if (*buf == '\n') {
			*buf = '\0';
			break;
		}
		else if (*buf == ' ') { 
			*buf = '\0';
			break;
		}
		else if (i % 100 == 0) { 
			i += 100; 
			str = (char*)realloc(str, i);
		}
		else {  
			buf = (str + i);
			*buf = getchar();
		}
	}
	s._str = str;
	s._capacity = s._size = i;
	return _cin;
}

5. Asuntos que requieren atención al usar la clase de cadena

1. Variables miembro internas de clase de cadena real

Cuando creamos un objeto de tipo cadena, usamos directamente sizeof() para imprimir su tamaño. Encontraremos que tiene 28 bytes en un sistema operativo de 32 bits, pero las variables en la clase de cadena que especulamos son solo: char* str, size_t size, size_t capacity; El tamaño de la cadena calculada a partir de estas variables debe ser 12, entonces, ¿por qué hay 16 bytes adicionales? Cuando C++ creó el contenedor de cadenas, proporcionó un char str[16] , cuando la longitud de la cadena cadena que escribimos tiene menos de 16 bytes, no usaremos char* str para una nueva matriz dinámica de caracteres, esta configuración mejora en gran medida la eficiencia cuando usamos cadena, por supuesto, usamos inversa a cadena Estos problemas también se pueden encontrar cuando el objeto se expande. Cuando sigamos aumentando la capacidad del objeto de cadena, no habrá problemas, pero si usamos el reverso para reducir la capacidad del objeto de cadena, la capacidad del objeto de cadena no cambiará, porque solicitar espacio es No es fácil pasar por muchos procesos, por lo que no necesitamos liberar el espacio antes del final de su ciclo de vida, pero habrá excepciones, cuando reduzcamos su capacidad a menos de 16 palabras. En el momento de la sección, el str[16] que nos ha proporcionado C++ es completamente suficiente, por lo que se libera el espacio para la aplicación dinámica, y str[16] se puede usar directamente. En este momento, el tamaño de la capacidad es 15, porque se requiere una palabra sección para almacenar '\0', reducirla de nuevo no la cambiará.

2. El método de expansión de clase de cadena real (diferentes compiladores pueden ser diferentes)

Al usar reserve in string para expandir, el compilador no se expandirá de acuerdo a nuestros requerimientos, sino que se expandirá de acuerdo a capacidad*1.5 (vs), hasta que la capacidad sea mayor o igual a nuestras necesidades y se detenga.El vector no es el mismo , al usar reserve en vector para expandir, el compilador se expandirá según nuestras necesidades, lo expandiremos tanto como necesitemos, y expandir a un tamaño pequeño no tendrá efecto.

3. El proceso de uso de la reserva para la expansión de la capacidad.

1. Abre un nuevo espacio.   

2. Copie el elemento.   

3. Libere espacio antiguo.   

4. Usa el nuevo espacio.

4. La diferencia entre [ ] y at

Cuando usamos [ ] y at, debemos prestar atención a la diferencia entre ellos. Ambos pueden usarse para acceder a elementos en la cadena con el mismo efecto. La diferencia es que cuando ocurre un error, si se usa [ ], el programa se bloqueará directamente y lanzará una excepción cuando ocurra un error.

5. Detalles sobre la sobrecarga del constructor de copia y del operador de asignación

una pregunta:

Cuando implementamos una clase de cadena nosotros mismos, debemos prestar atención al problema de usar una copia superficial en el constructor de copia de clase y la sobrecarga del operador de asignación.

B. Varios métodos para resolver la copia superficial:

① Copia profunda (Antiguo)

(La programación de recursos de espacio existe en la clase de premisa) Usamos la versión anterior de la copia profunda en las dos funciones miembro del constructor de copia y la sobrecarga del operador de asignación para implementar, es decir, cuando se trata de la copia de recursos de espacio, lo llamamos cada vez Crea un nuevo espacio para que lo use el objeto que llama.

② Copia profunda (Nuevo)

(La programación de recursos de espacio existe en la clase de premisa) Usamos la nueva versión de la copia profunda en las dos funciones miembro del constructor de copias y la sobrecarga del operador de asignación para implementar, es decir, en estas dos funciones, el _str de la parámetro objeto se llama como un parámetro El constructor de la clase reconstruye un objeto, e intercambia los miembros del objeto temporal con los miembros del objeto que llama a estas dos funciones uno por uno.Finalmente, hay nuevos miembros en el objeto que llama estas dos funciones Los miembros del objeto temporal serán liberados.

③ Copia en escritura (no recomendado para defectos)

¿Qué es la copia sobre escritura?

Cuando definimos la sobrecarga del constructor de copia y el operador de asignación, todos usamos el método de copia superficial para definir, por supuesto, ¿puede haber dudas? Si usamos la copia superficial para definir, entonces cuando se llame a estas dos funciones miembro, definitivamente Aparece el problema de varios objetos usando el mismo espacio, entonces, ¿cómo resolverlo?Cuando usamos el mismo espacio para varios objetos, mientras no modifiquemos el valor en el espacio, no habrá problema, entonces agregamos otro block to the space El espacio se usa para registrar cuántos objetos se comparten en este espacio. En la sobrecarga del constructor de copias y del operador de asignación, el contador en este espacio se incrementa en 1 cada vez que se llama, y ​​luego se realiza una copia superficial. .En la función miembro, se detecta el valor en este espacio de contador.Si el número de acciones es mayor que 1, entonces se aplica un nuevo espacio y se copia el contenido en el espacio original, y el valor del contador en el nuevo el espacio está asignado a 1, y el valor del contador en el espacio anterior es -1, deje que el objeto apunte a este nuevo espacio y opere en él. Esto es copia en escritura. Además, debemos prestar atención a el destructor. Debemos juzgar primero cuando se define el destructor. Si el contador en el espacio utilizado por el objeto es 1, si no es 1, el valor del contador será -1, y si es 1, el espacio será realizado.

¿Copiar también al leer? (defecto)

Cuando usamos [ ] o iterator, podemos modificar el contenido en el espacio o acceder al contenido en el espacio, por lo que el compilador no puede saber qué queremos hacer, por lo que se agrega un juicio dentro de estas dos funciones miembro, cuando Cuando solo hay un objeto en el espacio, puede devolver directamente el valor correspondiente, pero si hay más de un objeto en uso, la tecnología de copia en escritura aún se usará para el procesamiento. Por lo tanto, cuando usamos [ ] para recorrer el contenido del espacio, el sistema encontrará que este espacio no está en uso por un objeto, incluso si estamos en modo de solo lectura, la función [ ] continuará --> volver a solicitar espacio nuevo - -> copiar elementos --> usar espacio nuevo y esperar a que se lleve a cabo una serie de técnicas de copia en escritura.

6.string和vector<char>

pregunta:

La clase de cadena es una tabla de secuencia dinámica que administra caracteres, y el vector es una tabla de secuencia dinámica que administra elementos de cualquier tipo. Dado que vector<char> es una matriz de caracteres, ¿por qué STL proporciona una clase de cadena separada?

la diferencia:

1. La matriz de caracteres no es necesariamente una cadena, porque está estipulado en c/c++ que el último elemento válido de la cadena debe ser \0.

2. La clase de cadena tiene algunas operaciones especiales relacionadas con las cadenas.

Supongo que te gusta

Origin blog.csdn.net/weixin_49312527/article/details/124106668
Recomendado
Clasificación