Una cadena de texto no es más que una secuencia de caracteres Unicode.
En C# se representan mediante objetos del tipo tipo de dato llamado string,
que no es más que un alias del tipo System.String incluido en la BCL.
Las cadenas de texto suelen
crearse a partir literales de cadena o de otras cadenas previamente creadas.
Ejemplos de ambos casos se muestran a continuación:
string cadena1
= “José Antonio”;
string cadena2
= cadena;
En el primer caso se ha creado un
objeto string
que representa a la cadena formada por la secuencia de caracteres José Antonio indicada
literalmente (nótese que las comillas dobles entre las que se encierran los
literales de cadena no forman parte del contenido de la cadena que representan sino que sólo se usan como
delimitadores de la misma) En el segundo caso la variable cadena2 creada se genera a partir de la variable
cadena1 ya existente, por lo que ambas variables apuntarán al mismo objeto en
memoria.
Hay que tener en cuenta que el
tipo string
es un tipo referencia, por lo que en principio la comparación entre
objetos de este tipo debería comparar sus direcciones de memoria como pasa con
cualquier tipo referencia. Sin embargo, si ejecutamos el siguiente código
veremos que esto no ocurre en el caso de las cadenas:
using System;
public class IgualdadCadenas
{
public
static void Main()
{
string
cadena1 = “José Antonio”;
string cadena2 =
String.Copy(cadena1);
Console.WriteLine(cadena1==cadena2);
}
}
El método Copy() de la clase String usado
devuelve una copia del objeto que se le pasa como parámetro. Por tanto, al ser
objetos diferentes se almacenarán en posiciones distintas de memoria y al
compararlos debería devolverse false como pasa con cualquier tipo referencia.
Sin embargo, si ejecuta el programa verá que lo que se obtiene es precisamente
lo contrario: true.
Esto se debe a que para hacer para hacer más intuitivo el trabajo con cadenas,
en C# se ha modificado el operador de igualdad para que cuando se aplique entre
cadenas se considere que sus operandos son iguales sólo si son
lexicográficamente equivalentes y no si referencian al mismo objeto en memoria.
Además, esta comparación se hace teniendo en cuenta la capitalización usada,
por lo que “Hola”==”HOLA” ó “Hola”==”hola” devolverán false ya que contienen
las mismas letras pero con distinta capitalización.
Si se quisiese comparar cadenas
por referencia habría que optar por una de estas dos opciones: compararlas con Object.ReferenceEquals()
o convertirlas en objects y luego compararlas con == Por
ejemplo:
Console.WriteLine(Object.ReferecenceEquals(cadena1,
cadena2));
Console.WriteLine(
(object) cadena1 == (object) cadena2);
Ahora sí que lo que se comparan
son las direcciones de los objetos que representan a las cadenas en memoria,
por lo que la salida que se mostrará por pantalla es:
False
False
Hay que señalar una cosa, y es
que aunque en principio el siguiente código debería mostrar la misma salida por
pantalla que el anterior ya que las cadenas comparadas se deberían corresponder
a objetos que aunque sean lexicográficamente equivalentes se almacenan en
posiciones diferentes en memoria:
using System;
public class IgualdadCadenas2
{
public static void
Main()
{
string
cadena1 = “José Antonio”;
string
cadena2 = “José Antonio”;
Console.WriteLine(Object.ReferenceEquals(cadena1,
cadena2));
Console.WriteLine( ((object)
cadena1) == ((object) cadena2));
}
}
Si lo ejecutamos veremos que la
salida obtenida es justamente la contraria:
True
True
Esto se debe a que el compilador
ha detectado que ambos literales de cadena son
lexicográficamente equivalentes y ha decidido que para ahorra memoria lo
mejor es almacenar en memoria una única copia de la cadena que representan y
hacer que ambas variables apunten a esa copia común. Esto va a afectar a la
forma en que es posible manipular las cadenas como se explicará más adelante.
Al igual que el significado del
operador ==
ha sido especialmente modificado para trabajar con cadenas, lo mismo
ocurre con el operador binario +. En este caso, cuando se aplica entre dos
cadenas o una cadena y un carácter lo que hace es devolver una nueva cadena con
el resultado de concatenar sus operandos. Así por ejemplo, en el siguiente
código las dos variables creadas almacenarán la cadena Hola Mundo:
public class Concatenación
{
public static void
Main()
{
string
cadena = “Hola” + “ Mundo”;
string cadena2 =
“Hola Mund” + ‘o’;
}
}
Por otro lado, el acceso a las
cadenas se hace de manera similar a como si de tablas de caracteres se tratase:
su “campo” Length
almacenará el número de caracteres que la forman y para acceder a
sus elementos se utiliza el operador []. Por ejemplo, el siguiente código muestra por
pantalla cada carácter de la cadena Hola en
una línea diferente:
using System;
public class AccesoCadenas
{
public static void
Main()
{
string
cadena = “Hola”;
Console.WriteLine(cadena[0]);
Console.WriteLine(cadena[1]);
Console.WriteLine(cadena[2]);
Console.WriteLine(cadena[3]);
}
}
Sin embargo, hay que señalar una
diferencia importante respecto a la forma en que se accede a las tablas: las
cadenas son inmutables, lo que significa que no es posible modificar los
caracteres que las forman. Esto se debe a que el compilador comparte en memoria
las referencias a literales de cadena lexicográficamente equivalentes para así
ahorrar memoria, y si se permitiese modificarlos los cambios que se hiciesen a
través de una variable a una cadena compartida afectarían al resto de variables
que la compartan, lo que podría causar errores difíciles de detectar. Por
tanto, hacer esto es incorrecto:
string cadena =
“Hola”;
cadena[0]=”A”;
//Error: No se pueden modificar las cadenas
Sin embargo, el hecho de que no
se puedan modificar las cadenas no significa que no se puedan cambiar los
objetos almacenados en las variables de tipo string.Por ejemplo, el
siguiente código es válido:
String cad = “Hola”;
cad = “Adios”; // Correcto, pues no se modifica la cadena
almacenada en cad
// sino que se hace que cad pase a almacenar otra cadena
distinta..
Si se desea trabajar con cadenas
modificables puede usarse Sytem.Text.StringBuilder, que funciona de manera
similar a string
pero permite la modificación de sus cadenas en tanto que estas no se
comparten en memoria. Para crear objetos de este tipo basta pasar como
parámetro de su constructor el objeto string que contiene la cadena a representar
mediante un StringBuilder,
y para convertir un StringBuilder en String siempre puede
usarse su método ToString()
heredado de System.Object. Por ejemplo:
using System.Text;
using System;
public class ModificaciónCadenas
{
public static void
Main()
{
StringBuilder
cadena = new StringBuilder(“Pelas”);
String
cadenaInmutable;
cadena[0]
= ‘V’;
Console.WriteLine(cadena);
// Muestra Velas
cadenaInmutable
= cadena.ToString();
Console.WriteLine(cadenaInmutable);
// Muestra Velas
}
}
Aparte de los métodos ya vistos,
en la clase System.String
se definen muchos otros métodos aplicables a cualquier cadena y que
permiten manipularla. Los principales son:
·
int IndexOf(string subcadena): Indica cuál es el
índice de la primera aparición de la subcadena indicada
dentro de la cadena sobre la que se aplica. La búsqueda de dicha subcadena se
realiza desde el principio de la cadena, pero es posible indicar en un segundo
parámetro opcional de tipo int cuál es el índice de la misma a partir del
que se desea empezar a buscar. Del mismo modo, la búsqueda acaba al llegar al
final de la cadena sobre la que se busca, pero pasando un tercer parámetro
opcional de tipo int
es posible indicar algún índice anterior donde terminarla.
Nótese que es
un método muy útil para saber si una cadena contiene o no alguna subcadena
determinada, pues sólo si no la encuentra devuelve un –1.
·
int LastIndexOf(string subcadena): Funciona de forma similar a IndexOf() sólo
que devuelve la posición de la última aparición de la subcadena
buscada en lugar de devolver la de la primera.
·
string Insert(int posición, string subcadena):
Devuelve la cadena resultante de insertar la subcadena
indicada en la posición
especificada de la cadena sobre la que se aplica.
·
string Remove(int posición, int número): Devuelve
la cadena resultante de eliminar el número de
caracteres indicado que hubiese en la cadena sobre al que se aplica a partir de
la posición especificada.
·
string Replace(string aSustituir, string sustitua):
Devuelve la cadena resultante de sustituir en la cadena sobre la que se aplica
toda aparación de la cadena aSustituir indicada por la cadena sustituta especificada como segundo parámetro.
·
string Substring(int posición, int número):
Devuelve la subcadena de la cadena sobre la que se aplica que comienza en la posición indicada y tiene el número
de caracteres especificados. Si no se indica dicho número se devuelve la
subcadena que va desde la posición indicada hasta el final de la cadena.
·
string ToUpper() y string ToLower():
Devuelven, respectivamente, la cadena que resulte de convertir a mayúsculas o
minúsculas la cadena sobre la que se aplican.
Es preciso incidir en que aunque
hayan métodos de inserción, reemplazo o eliminación de caracteres que puedan
dar la sensación de que es posible modificar el contenido de una cadena, en
realidad las cadenas son inmutables y dicho métodos lo que hacen es devolver
una nueva cadena con el contenido correspondiente a haber efectuado las
operaciones de modifiación solicitadas sobre la cadena a la que se aplican. Por
ello, las cadenas sobre las que se aplican quedan intactas como muestra el
siguiente ejemplo:
Using System;
public class EjemploInmutabilidad
{
public static void
Main()
{
string cadena1=”Hola”;
string
cadena2=cadena1.Remove(0,1);
Console.WriteLine(cadena1);
Console.WriteLine(cadena2);
}
}
La salida por pantalla de este ejemplo
demuestra lo antes dicho, pues es:
Hola
ola
Como se ve, tras el Remove() la
cadena1 permance intacta y el
contenido de cadena2 es el
que debería tener cadena1 si se
le hubiese eliminado su primer carácter.