example - string c++ funciones
¿Cuál es el punto de los Rasgos de Carácter STL? (1)
Noté que en mi copia de la referencia de SGI STL, hay una página sobre Rasgos de caracteres pero no puedo ver cómo se usan. ¿Reemplazan las funciones de string.h? No parecen ser utilizados por std::string
, por ejemplo, el método length()
en std::string
no utiliza el método de length()
Character Traits length()
. ¿Por qué existen los Rasgos de carácter y se usan alguna vez en la práctica?
Los rasgos de caracteres son un componente extremadamente importante de las bibliotecas de secuencias y cadenas porque permiten que las clases de secuencia / cadena separen la lógica de los caracteres que se almacenan de la lógica de las manipulaciones que se deben realizar en esos caracteres.
Para empezar, la clase de rasgos de carácter predeterminada, char_traits<T>
, se usa ampliamente en el estándar de C ++. Por ejemplo, no hay una clase llamada std::string
. Por el contrario, hay una plantilla de clase std::basic_string
que se ve así:
template <typename charT, typename traits = char_traits<charT> >
class basic_string;
Entonces, std::string
se define como
typedef basic_string<char> string;
Del mismo modo, las corrientes estándar se definen como
template <typename charT, typename traits = char_traits<charT> >
class basic_istream;
typedef basic_istream<char> istream;
Entonces, ¿por qué estas clases están estructuradas como son? ¿Por qué deberíamos estar usando una clase de rasgos raros como argumento de plantilla?
La razón es que, en algunos casos, es posible que deseemos tener una cadena como std::string
, pero con algunas propiedades ligeramente diferentes. Un ejemplo clásico de esto es si desea almacenar cadenas de una manera que ignore el caso. Por ejemplo, podría querer hacer una cadena llamada CaseInsensitiveString
para que pueda tener
CaseInsensitiveString c1 = "HI!", c2 = "hi!";
if (c1 == c2) { // Always true
cout << "Strings are equal." << endl;
}
Es decir, puedo tener una secuencia donde dos cadenas que difieren solo en su sensibilidad de caso se comparan por igual.
Ahora, supongamos que los autores de la biblioteca estándar diseñaron cadenas sin usar rasgos. Esto significaría que tendría en la biblioteca estándar una clase de cuerdas inmensamente poderosa que era completamente inútil en mi situación. No pude reutilizar gran parte del código para esta clase de cadena, ya que las comparaciones siempre funcionarían en contra de cómo quería que funcionaran. Pero al usar rasgos, en realidad es posible reutilizar el código que maneja std::string
para obtener una cadena que no distinga entre mayúsculas y minúsculas.
Si saca una copia del estándar ISO de C ++ y mira la definición de cómo funcionan los operadores de comparación de la cadena, verá que están todos definidos en términos de la función de compare
. Esta función a su vez se define llamando
traits::compare(this->data(), str.data(), rlen)
donde str
es la cuerda con la que estás comparando y rlen
es la menor de las dos longitudes de cuerda. Esto es realmente bastante interesante, porque significa que la definición de compare
utiliza directamente la función de compare
exportada por el tipo de rasgos especificado como parámetro de plantilla. En consecuencia, si definimos una nueva clase de rasgos, entonces definimos compare
para que compare los caracteres sin distinguir entre mayúsculas y minúsculas, podemos construir una clase de cadena que se comporte como std::string
, ¡pero trata las cosas sin distinción de mayúsculas y minúsculas!
Aquí hay un ejemplo. Heredamos de std::char_traits<char>
para obtener el comportamiento predeterminado para todas las funciones que no escribimos:
class CaseInsensitiveTraits: public std::char_traits<char> {
public:
static bool lt (char one, char two) {
return std::tolower(one) < std::tolower(two);
}
static bool eq (char one, char two) {
return std::tolower(one) == std::tolower(two);
}
static int compare (const char* one, const char* two, size_t length) {
for (size_t i = 0; i < length; ++i) {
if (lt(one[i], two[i])) return -1;
if (lt(two[i], one[i])) return +1;
}
return 0;
}
};
(Tenga en cuenta que también he definido eq
y lt
aquí, que comparan caracteres para igualdad y menor que, respectivamente, y luego se definen compare
en términos de esta función).
Ahora que tenemos esta clase de rasgos, podemos definir CaseInsensitiveString
trivialmente como
typedef std::basic_string<char, CaseInsensitiveTraits> CaseInsensitiveString;
¡Y voilá! ¡Ahora tenemos una cadena que trata todo sin distinción de mayúsculas y minúsculas!
Por supuesto, hay otras razones además de esto para usar los rasgos. Por ejemplo, si desea definir una cadena que utiliza algún tipo de carácter subyacente de tamaño fijo, puede especializar char_traits
en ese tipo y luego crear cadenas de ese tipo. En la API de Windows, por ejemplo, hay un tipo TCHAR
que es un carácter angosto o ancho dependiendo de qué macros se establezca durante el preprocesamiento. A continuación, puede hacer cadenas de TCHAR
s escribiendo
typedef basic_string<TCHAR> tstring;
Y ahora tienes una cadena de TCHAR
s.
En todos estos ejemplos, observe que definimos algunos rasgos de clase (o usamos uno que ya existía) como un parámetro para algún tipo de plantilla con el fin de obtener una cadena para ese tipo. El objetivo de todo esto es que el autor de basic_string
solo necesita especificar cómo usar los rasgos y mágicamente podemos hacer que usen nuestros rasgos en lugar del valor predeterminado para obtener cadenas que tengan algún matiz o peculiaridad que no forme parte del tipo de cadena por defecto.
¡Espero que esto ayude!
EDITAR : como @phooji señaló, esta noción de rasgos no solo es utilizada por el STL, ni es específica para C ++. Como autopromoción completamente desvergonzada, hace un tiempo escribí una implementación de un árbol de búsqueda ternario (un tipo de árbol raíz aquí descrito ) que usa rasgos para almacenar cadenas de cualquier tipo y usando cualquier tipo de comparación que el cliente quiera que almacenen. Puede ser una lectura interesante si quiere ver un ejemplo de dónde se usa esto en la práctica.
EDITAR : En respuesta a su afirmación de que std::string
no utiliza traits::length
, resulta que sí lo hace en algunos lugares. En particular, cuando construyes una std::string
partir de una cadena de estilo char*
C, la longitud nueva de la cadena se deriva llamando a traits::length
en esa cadena. Parece que traits::length
se usa principalmente para tratar secuencias de caracteres de estilo C, que son el "mínimo denominador común" de cadenas en C ++, mientras que std::string
se usa para trabajar con cadenas de contenido arbitrario.