4.3.4. Comparar cadenas

Comparar cadenas es inherentemente diferente a comparar enteros. Los nombres tienen un significado universal y constante. Para evaluar la relación entre las magnitudes de dos cadenas, se necesita hacer una comparación léxica. Una comparación léxica significa que cuando se comprueba un carácter para saber si es "mayor que" o "menor que" otro carácter, está en realidad comparando la representación numérica de aquellos caracteres tal como están especificados en el orden del conjunto de caracteres que está siendo usado. La ordenación más habitual suele ser la secuencia ASCII, que asigna a los caracteres imprimibles para el lenguaje inglés números en un rango del 32 al 127 decimal. En la codificación ASCII, el primer "carácter" en la lista es el espacio, seguido de diversas marcas de puntuación común, y después las letras mayúsculas y minúsculas. Respecto al alfabeto, esto significa que las letras cercanas al principio tienen un valor ASCII menor a aquellos más cercanos al final. Con estos detalles en mente, se vuelve más fácil recordar que cuando una comparació léxica reporta que s1 es "mayor que" s2, simplemente significa que cuando fueron comparados, el primer carácter diferente en s1 estaba atrás en el alfabeto que el carácter en la misma posición en s2.

C++ provee varias maneras de comparar cadenas, y cada una tiene ventajas. La más simple de usar son las funciones no-miembro sobrecargadas de operador: operator==, operator!= operator>, operator<, operator>= y operator<=.

//: C03:CompStr.h
#ifndef COMPSTR_H
#define COMPSTR_H
#include <string>
#include "../TestSuite/Test.h"
using std::string;

class CompStrTest : public TestSuite::Test {
public:
  void run() {
    // Strings to compare
    string s1("This");
    string s2("That");
    test_(s1 == s1);
    test_(s1 != s2);
    test_(s1 > s2);
    test_(s1 >= s2);
    test_(s1 >= s1);
    test_(s2 < s1);
    test_(s2 <= s1);
    test_(s1 <= s1);
  }
};
#endif // COMPSTR_H ///:~

Listado 4.22. C03/CompStr.h


//: C03:CompStr.cpp
//{L} ../TestSuite/Test
#include "CompStr.h"

int main() {
  CompStrTest t;
  t.run();
  return t.report();
} ///:~

Listado 4.23. C03/CompStr.cpp


Los operadores de comaración sobrecargados son útiles para comparar dos cadenas completas y elementos individuales de una cadena de caracteres.

Nótese en el siguiente ejemplo la flexibilidad de los tipos de argumento ambos lados de los operadores de comparación. Por eficiencia, la clase string provee operadores sobrecargados para la comparación directa de objetos string, literales de cadena, y punteros a cadenas estilo C sin tener que crear objetos string temporales.

//: C03:Equivalence.cpp
#include <iostream>
#include <string>
using namespace std;

int main() {
  string s2("That"), s1("This");
  // The lvalue is a quoted literal
  // and the rvalue is a string:
  if("That" == s2)
    cout << "A match" << endl;
  // The left operand is a string and the right is
  // a pointer to a C-style null terminated string:
  if(s1 != s2.c_str())
    cout << "No match" << endl;
} ///:~

Listado 4.24. C03/Equivalence.cpp


La función c_str() retorna un const char* que apunta a una cadena estilo C terminada en nulo, equivalente en contenidos al objeto string. Esto se vuelve muy útil cuando se quiere pasar un strin a una función C, como atoi() o cualquiera de las funciones definidas en la cabecera cstring. Es un error usar el valor retornado por c_str() como un argumento constante en cualquier función.

No encontrará el operador not (!) o los operadores de comparación lógicos (&& y ||) entre los operadore para string. (No encontrará ninguna versión sobrecargada de los operadores de bits de C: &, |, ^, o ~.) Los operadores de conversión no miembros sobrecargados para la clases string están limitados a un subconjunto que tiene una aplicación clara y no ambigua para caracteres individuales o grupos de caracteres.

La función miembro compare() le ofrece un gran modo de comparación más sofisticado y preciso que el conjunto de operadores nomiembro. Provee versiones sobrecargadas para comparar:

//: C03:Compare.cpp
// Demonstrates compare() and swap().
#include <cassert>
#include <string>
using namespace std;

int main() {
  string first("This");
  string second("That");
  assert(first.compare(first) == 0);
  assert(second.compare(second) == 0);
  // Which is lexically greater?
  assert(first.compare(second) > 0);
  assert(second.compare(first) < 0);
  first.swap(second);
  assert(first.compare(second) < 0);
  assert(second.compare(first) > 0);
} ///:~

Listado 4.25. C03/Compare.cpp


La función swap() en este ejemplo hace lo que su nombre implica: cambia el contenido del objeto por el del parámetro. Para comparar un subconjunto de caracteres en un o ambos string, añada argumentos que definen donde empezar y cuantos caracteres considerar. Por ejemplo, puede usar las siguientes versiones sobrecargadas de compare():

s1.compare(s1StartPos, s1NumberChars, s2, s2StartPos, s2NumberChars);

Aqui un ejemplo:

//: C03:Compare2.cpp
// Illustrate overloaded compare().
#include <cassert>
#include <string>
using namespace std;

int main() {
  string first("This is a day that will live in infamy");
  string second("I don't believe that this is what "
                "I signed up for");
  // Compare "his is" in both strings:
  assert(first.compare(1, 7, second, 22, 7) == 0);
  // Compare "his is a" to "his is w":
  assert(first.compare(1, 9, second, 22, 9) < 0);
} ///:~

Listado 4.26. C03/Compare2.cpp


Hasta ahora, en los ejemplos, hemos usado la sintaxis de indexación de arrays estilo C para referirnos a un carácter individual en un string. C++ provee de una alternativa a la notación s[n]: el miembro at(). Estos dos mecanismos de indexación producen los mismos resultados si todo va bien:

//: C03:StringIndexing.cpp
#include <cassert>
#include <string>
using namespace std;

int main() {
  string s("1234");
  assert(s[1] == '2');
  assert(s.at(1) == '2');
} ///:~

Listado 4.27. C03/StringIndexing.cpp


Sin embargo, existe una importante diferencia entre [ ] y at() . Cuando usted intenta referenciar el elemento de un arreglo que esta fuera de sus límites, at() tiene la delicadeza de lanzar una excepción, mientras que ordinariamente [ ] le dejará a su suerte.

//: C03:BadStringIndexing.cpp
#include <exception>
#include <iostream>
#include <string>
using namespace std;

int main() {
  string s("1234");
  // at() saves you by throwing an exception:
  try {
    s.at(5);
  } catch(exception& e) {
    cerr << e.what() << endl;
  }
} ///:~

Listado 4.28. C03/BadStringIndexing.cpp


Los programadores responsables no usarán índices erráticos, pero puede que quiera beneficiarse de la comprobación automática de indices, usandoat() en el lugar de [ ] le da la oportunidad de recuperar diligentemente de las referencias a elementos de un arreglo que no existen. La ejecución de sobre uno de nuestros compiladores le da la siguiente salida: "invalid string position"

La función miembro at() lanza un objeto de clase out_of_class, que deriva finalmente de std::exception. Capturando este objeto en un manejador de excepciones, puede tomar las medidas adecuadas como recalcular el índice incorrecto o hacer crecer el arreglo. Usar string::operator[]( ) no proporciona ningún tipo de protección y es tan peligroso como el procesado de arreglos de caracteres en C.[37] [7]



[7] Por las razones de seguridad mencionadas, el C++ Standards Committee está considerando una propuesta de redefinición del string::operator[] para comportarse de manera idéntica al string::at() para C++0x.