El programa Find.cpp anterior en este
capítulo nos lleva a hacernos la pregunta obvia: ¿por que la
comparación sensible a mayúsculas/minúsculas no es parte de la
clase estándar string
? La respuesta nos
brinda un interesante transfondo sobre la verdadera naturaleza
de los objetos string
en C++.
Considere qué significa para un carácter tener "mayúscula/minúscula". El Hebreo escrito, el Farsi, y el Kanji no usan el concepto de "mayúscula/minúscula", con lo que para esas lenguas esta idea carece de significado. Esto daria a entender que si existiera una manera de designar algunos lenguages como "todo mayúsculas" o "todo minúsculas", podriamos diseñar una solución generalizada. Sin embargo, algunos leguajes que emplean el concepto de "mayúscula/minúscula", tambien cambian el significado de caracteres particulares con acentos diacríticos, por ejemplo la cedilla del Español, el circumflexo en Francés y la diéresis en Alemán. Por esta razón, cualquier codificación sensible a mayúsculas que intenta ser comprensiva acaba siendo una pesadilla en su uso.
Aunque tratamos habitualmente el string
de C++ como una clase, esto no es del todo cierto. El tipo
string
es una especialización de algo
más general, la plantilla basic_string<
>
. Observe como está declarada
string
en el fichero de cabecera de C++
estándar.
typedef basic_string<char> string;
Para comprender la naturaleza de la clase
string
, mire la plantilla
basic_string< >
template<class charT, class traits = char_traits<charT>,
class allocator = allocator<charT> > class basic_string;
En el Capítulo 5, examinamos las plantillas con gran detalle
(mucho más que en el Capítulo 16 del volúmen 1). Por ahora
nótese que el tipo string
es creada
cuando instanciamos la plantilla
basic_string
con
char
. Dentro de la declaración plantilla
basic_string< >
la línea:
class traits = char_traits<charT<
,
nos dice que el comportamiento de la clase hecha a partir de
basic_string< >
es defineida por
una clase basada en la plantilla char_traits<
>
. Así, la plantilla
basic_string< >
produce clases
orientadas a string
que manipulan otros
tipos que char
(caracteres anchos, por
ejemplo). Para hacer esto, la plantilla
char_traits< >
controla el
contenido y el comportamiento de la ordenación de una variedad
de conjuntos de caracteres usando las funciones de comparación
eq()
(equal),
ne()
(not
equal), y lt()
(less than). Las funciones de
comparación de basic_string< >
confian en esto.
Es por esto por lo que la clase string no incluye funciones
miembro sensibles a mayúsculas/minúsculas: eso no esta en la
descripción de su trabajo. Para cambiar la forma en que la
clase string trata la comparación de caracteres, tiene que
suministrar una plantilla char_traits<
>
diferente ya que define el comportamiento
individual de las funciones miembro de comparación carácteres.
Puede usar esta información para hacer un nuevo tipo de
string
que ignora las
mayúsculas/minúsculas. Primero, definiremos una nueva
plantilla no sensible a mayúsculas/minúsculas de
char_traits< >
que hereda de una
plantilla existente. Luego, sobrescribiremos sólo los miembros
que necesitamos cambiar para hacer la comparación carácter por
carácter. (Además de los tres miembros de comparación léxica
mencionados antes, daremos una nueva implementación para
laspara las funciones de char_traits
find()
y
compare()
). Finalmente, haremos un
typedef
de una nueva clase basada en
basic_string
, pero usando nuestra
plantilla insensible a mayúsculas/minúsculas,
ichar_traits
, como segundo argumento:
//: C03:ichar_traits.h // Creating your own character traits. #ifndef ICHAR_TRAITS_H #define ICHAR_TRAITS_H #include <cassert> #include <cctype> #include <cmath> #include <cstddef> #include <ostream> #include <string> using std::allocator; using std::basic_string; using std::char_traits; using std::ostream; using std::size_t; using std::string; using std::toupper; using std::tolower; struct ichar_traits : char_traits<char> { // We'll only change character-by- // character comparison functions static bool eq(char c1st, char c2nd) { return toupper(c1st) == toupper(c2nd); } static bool ne(char c1st, char c2nd) { return !eq(c1st, c2nd); } static bool lt(char c1st, char c2nd) { return toupper(c1st) < toupper(c2nd); } static int compare(const char* str1, const char* str2, size_t n) { for(size_t i = 0; i < n; ++i) { if(str1 == 0) return -1; else if(str2 == 0) return 1; else if(tolower(*str1) < tolower(*str2)) return -1; else if(tolower(*str1) > tolower(*str2)) return 1; assert(tolower(*str1) == tolower(*str2)); ++str1; ++str2; // Compare the other chars } return 0; } static const char* find(const char* s1, size_t n, char c) { while(n-- > 0) if(toupper(*s1) == toupper(c)) return s1; else ++s1; return 0; } }; typedef basic_string<char, ichar_traits> istring; inline ostream& operator<<(ostream& os, const istring& s) { return os << string(s.c_str(), s.length()); } #endif // ICHAR_TRAITS_H ///:~
Listado 4.29. C03/ichar_traits.h
Proporcionamos un typedef
llamado
istring
ya que nuestra clase actuará
como un string
ordinario en todas sus
formas, excepto que realizará todas las comparaciones sin
respetar las mayúsculas/minúsculas. Por conveniencia, damos un
operador sobrecargado operator<<()
para que pueda imprimir los
istring
. Aque hay un ejemplo:
//: C03:ICompare.cpp #include <cassert> #include <iostream> #include "ichar_traits.h" using namespace std; int main() { // The same letters except for case: istring first = "tHis"; istring second = "ThIS"; cout << first << endl; cout << second << endl; assert(first.compare(second) == 0); assert(first.find('h') == 1); assert(first.find('I') == 2); assert(first.find('x') == string::npos); } ///:~
Listado 4.30. C03/ICompare.cpp
Este es solo un ejemplo de prueba. Para hacer
istring
completamente equivalente a un
string
, deberiamos haber creado las
otras funciones necesarias para soportar el nuevo tipo
istring
.
La cabecera <string> provee de un
string
ancho [8] gracias
al siguiente typedef
:
typedef basic_string<wchar_t> wstring;
El soporte para string
ancho se revela
tambien en los streams
anchos
(wostream
en lugar de
ostream
, tambien definido en
<iostream>
) y en la especialización de
wchar_t
de los char_traits
en la libreria estándar le da la posibilidad de hacer una
version de carácter ancho de
ichar_traits
//: C03:iwchar_traits.h {-g++} // Creating your own wide-character traits. #ifndef IWCHAR_TRAITS_H #define IWCHAR_TRAITS_H #include <cassert> #include <cmath> #include <cstddef> #include <cwctype> #include <ostream> #include <string> using std::allocator; using std::basic_string; using std::char_traits; using std::size_t; using std::towlower; using std::towupper; using std::wostream; using std::wstring; struct iwchar_traits : char_traits<wchar_t> { // We'll only change character-by- // character comparison functions static bool eq(wchar_t c1st, wchar_t c2nd) { return towupper(c1st) == towupper(c2nd); } static bool ne(wchar_t c1st, wchar_t c2nd) { return towupper(c1st) != towupper(c2nd); } static bool lt(wchar_t c1st, wchar_t c2nd) { return towupper(c1st) < towupper(c2nd); } static int compare( const wchar_t* str1, const wchar_t* str2, size_t n) { for(size_t i = 0; i < n; i++) { if(str1 == 0) return -1; else if(str2 == 0) return 1; else if(towlower(*str1) < towlower(*str2)) return -1; else if(towlower(*str1) > towlower(*str2)) return 1; assert(towlower(*str1) == towlower(*str2)); ++str1; ++str2; // Compare the other wchar_ts } return 0; } static const wchar_t* find(const wchar_t* s1, size_t n, wchar_t c) { while(n-- > 0) if(towupper(*s1) == towupper(c)) return s1; else ++s1; return 0; } }; typedef basic_string<wchar_t, iwchar_traits> iwstring; inline wostream& operator<<(wostream& os, const iwstring& s) { return os << wstring(s.c_str(), s.length()); } #endif // IWCHAR_TRAITS_H ///:~
Listado 4.31. C03/iwchar_traits.h
Como puede ver, esto es principalmente un ejercicio de poner 'w' en el lugar adecuado del código fuente. El programa de prueba podria ser asi:
//: C03:IWCompare.cpp {-g++} #include <cassert> #include <iostream> #include "iwchar_traits.h" using namespace std; int main() { // The same letters except for case: iwstring wfirst = L"tHis"; iwstring wsecond = L"ThIS"; wcout << wfirst << endl; wcout << wsecond << endl; assert(wfirst.compare(wsecond) == 0); assert(wfirst.find('h') == 1); assert(wfirst.find('I') == 2); assert(wfirst.find('x') == wstring::npos); } ///:~
Listado 4.32. C03/IWCompare.cpp
Desgraciadamente, todavia algunos compiladores siguen sin ofrecer un soporte robusto para caracteres anchos.
[8] (N.del
T.) Se refiere a string
amplio puesto
que esta formado por caracteres anchos wchar_t
que deben soportar la codificación mas grande que soporte el
compilador. Casi siempre esta codificación es
Unicode, por lo que casi siempre el ancho
de wchar_t
es 2 bytes