La Página de DriverOp

Cadenas de caracteres en Turbo Pascal

Contenido.

1. Introducción.

Las cadenas de caracteres (strings) son arreglos (vectores) de un largo máximo específico. Los strings son tipos estructurado, esto es, pueden ser divididos en sus componentes individuales, cada componente es un caracter y su tipo es char. Strings en Turbo Pascal 7.0 tienen 255 de tamaño por omisión y se declaran con la palabra reservada String; de otra manera, su tamaño máximo se declara cuando se definen. Los strings pueden crecer dinámicamente desde el tamaño 0 (= "cadena vacía" o "cadena nula") hasta un máximo absoluto de 255. Ejemplos:

Type
  Nombre_Archivo = String[14];          { Tam. Máximo = 14 }
  Nombre_Empleado = String[30];

Var
  Nombre_Archivo_Entrada: Nombre_Archivo;
  Nombre_Emp: Nombre_Empleado;
  Linea: String;                    { Tam. Máximo = 255 }

Se pueden leer y escribir strings con los procedimientos Read y Write, de modo que (usando los ejemplos anteriores), Read(Nombre_emp) y Writeln(Nombre_Archivo_Entrada) son sentencias válidas.

Hay muchos métodos para modificar y comparar strings.

2. Asignación de strings.

Los strings se pueden asignar usando el operador de asignación :=.

Por ejemplo: Nombre_Archivo_Entrada := 'A:\PROG006.OUT';

Notar que si se trata de hacer esto: Nombre_Archivo_Entrada :='A:\OUTPUT\PROG006.OUT', el valor asignado a Nombre_Archivo_Entrada será 'A:\OUTPUT\PROG' lo cual no sería lo esperado, porque lo que está a la derecha del operador de asignación es más grande que el tamaño declarado del string a la izquierda.

3. Concatenación ("pegar" o "sumar") strings.

Se usa el símbolo +

Por ejemplo:

Nombre_Archivo_Entrada := 'A:\' + 'PROG006' + '.OUT';

También se pueden concatenar varios tipos string:

Unidad := 'A:\';
Nombre := 'PROG006';
Extension := '.OUT';
Nombre_Archivo_Entrada := Unidad + Nombre + Extension;

4. Elementos individuales del string.

Sintaxis: Nombre_Archivo_Entrada[n] donde n es de tipo byte.

Ejemplo:

Writeln(Nombre_Archivo_Entrada[3]);

Aquí se imprime el tercer caracter del string.

5. Comparación de strings.

Símbolos: =, <, <=, >, >=, y <>

Estos operadores de comparación devuelven true o false (verdadero o falso).

Sean A y B variables de tipo string.

A = B es true si y solo si los tamaños son iguales y cada elemento individual de A es igual a cada elemento individual de B (A[1] = B[1], A[2] = B[2], etc...).

Cuando se comparan A y B, se lo hace en secuencia de acuerdo al valor ordinal en la tabla ASCII. Si los strings son de diferente tamaño pero iguales caracter a caracter hasta e incluyendo el último caracter del string más corto, entonces el string más corto se considera menor al más largo. Ejemplos:

     'A' < 'B'           TRUE
     'Z' < 'a'           TRUE
     'a' < 'B'           FALSE
     'TURBO' = 'TURBO'   TRUE
     'TURBO' = 'Turbo'   FALSE
     'turbo ' = 'turbo'  FALSE
     'turbo ' > 'turbo'  TRUE

6. Funciones de manipulación de Strings.

6.1. Tamaño o longitud.

Sintaxis: Length(Cad)

Devuelve el tamaño actual del string Cad en un tipo byte (en el rango 0..255).

Ejemplo:

          Var
            Cad: String[20];  Tam: Integer;
            ...
            Cad := '';              { asingar null a Cad }
            Tam := Length(Cad);     { Tam vale 0 }
            Cad := Cad + 'Begin';
            Tam := Length(Cad);     { Tam vale 5 }

La posición cero de un string (Cad[0]) guarda el tamaño dinámico del string que es lo mismo que devuelve la función Length(). Sin embargo siempre es recomendable usar esta función para saber el tamaño del string por razones de compatibilidad. También es posible asingar un valor a Cad[0] de tipo byte, lo que causará que se modifique el tamaño dinámico, pero esto es altamente no recomendable pues podría causar, bajo ciertas circunstancias, errores en tiempo de ejecución u obtener resultados impredecibles.

6.2. Posición de una subcadena en un string.

Sintaxis: Pos(Patron, Objetivo)

La función Pos() busca el contenido de Patron en el string Objetivo y devuelve la posición de la primera ocurrencia de Patron en Objetivo en un tipo byte (rango 0..255). Si Patron no está contenido en Objetivo, esta función devuelve el valor cero.

Ejemplo:

     Var Patron, Objetivo: String[20]; 
     Posicion: Byte;
     ...
     Objetivo := 'ABRA KADABRA';
     Patron := 'BR';

     Posicion := Pos(Patron, Objetivo);  { Posicion = 2 }

Si Patron = 'ADA', entonces Posicion = 7.

Si Patron = 'br', entonces Posicion = 0. ('br' <> 'BR')

Si Patron = '  ', entonces Posicion = 0. (no hay doble espacio en Objetivo)

 

6.3. Copiar una subcadena de un string.

Sintaxis: Copy(Fuente, Inicio, Tamano)

La funcion copy() devuelve una subcadena copiada desde el string Fuente comenzando desde la posición Inicio y conteniendo los siguientes Tamano caracteres. Fuente debe ser un tipo string. Inicio y Tamano deben ser de tipo integer siempre que sean positivos. Si Inicio > Length(Fuente) (es decir, Inicio está fuera del string), copy() devuelve un string vacío. Si Inicio + Tamano > Length(Fuente), entonces se devuelven todos los caracteres comenzando desde Inicio y hasta el final de Fuente. Si Inicio está fuera del rango 1..255, ocurre un error en tiempo de ejecución.

Ejemplo:

     Var  Fuente, Parte: String[20];
          Inicio, Tamano: Integer;
     ...
     Fuente := 'ABRA KADABRA';
     Inicio := 6;
     Tamano := 3;
     Parte := Copy(Fuente, Inicio, Tamano);  { Parte = 'KAD' }

Si Inicio = 13, entonces Parte = ''. (Inicio señala una posición que está fuera del tamaño dinámico actual de Fuente)

Si Inicio = 4 y Tamano = 20, entonces Parte = 'A KADABRA'.

7. Procedimientos de manipulación de Strings.

7.1. Eliminar subcadena.

Sintaxis: Delete(Objetivo, Inicio, Tamano)

Este procedimiento modificará Objetivo eliminando Tamano caracteres a partir de la posición Inicio. Si Inicio > Length(Objetivo), no se elimina ningún caracter de Objetivo. Si Inicio + Tamano > Length(Objetivo) se eliminan todos los caracteres a partir de la posición Inicio y hasta el final de Objetivo.

Objetivo debe ser un tipo string, Inicio y Tamano deben ser de tipo integer siempre que sean positivos y estén en el rango 1..255.

     Var  Objetivo: String[20];
          Inicio, Tamano: Integer;
     ...
     Objetivo := 'ABRA KADABRA';
     Inicio := 6;
     Tamano := 3;
     Delete(Objetivo, Inicio, Tamano); { Objetivo = 'ABRA ABRA' }

Si Inicio = 15, entonces Objetivo no cambia. (Inicio está fuera el tamaño dinámico actual de Objetivo)

Si Inicio = 2 y Tamano = 20, entonces Objetivo = 'A'.

7.2. Cortar una subcadena.

No existe una función o procedimiento que recorte una porción de una cadena. Con recortar se quiere decir tomar una parte de la cadena eliminándola del string y devolviendo la parte recortada en otra. Pero combinando las funciones y procedimientos anteriores es posible obtener ese comportamiento. Ejemplo:

Var
   Objetivo, Parte: String;


function Cortar(var S: string; Inicio, Tamano: integer): string;
begin
   if Inicio <= Length(S) then
     begin
       Cortar:=Copy(S, Inicio, Tamano);
       Delete(S, Inicio, Tamano);
     end
   else
     Cortar:='';
end;

begin
   Objetivo := 'ABRA KADABRA';
   Parte := Cortar(Objetivo, 6, 3); { Parte = 'KAD', Objetivo = 'ABRA ABRA' }
end.
7.3. Insertar una subcadena en un string.

Sintaxis: Insert(Parte, Objetivo, Posicion)

El procedimiento Insert() inserta una copia del contenido del string Parte en el string Objetivo a la izquierda del caracter apuntado por Posicion del string Objetivo desplazando el resto de la cadena hacia la derecha. Por lo tanto Objetivo queda alterado. Si Posicion > Length(Objetivo) entonces Parte se concatenará al final de Objetivo (sería análogo a hacer Objetivo := Objetivo + Parte;). Si Length(Objetivo) + Length(Parte) antes de hacerse la inserción es mayor al tamaño máximo declarado para Objetivo, entonces se pierden caracteres por la derecha de Objetivo al hacer la inserción.

Ejemplo:

     Var
       Parte, Objetivo: String[20];
     ...
     Objetivo := 'ABRA KADABRA';
     Parte := 'xyz';
     Insert(Parte, Objetivo, 5); { Objetivo = 'ABRAxyz KADABRA' }

Si Parte = 'abcdefghijkl' y Posicion = 5, entonces Objetivo = 'ABRAabcdefghijkl KAD' ya que el tamaño declarado para Objetivo es menor que el tamaño de la cadena resultante luego de la inserción.

Si Posicion = 30, entonces Objetivo = 'ABRA KADABRAabcdefgh'.

7.4. Convertir cadena numérica a valor numérico entero.

Sintaxis: Val(Cadena, Numero, Codigo_Error)

El propósito de este procedimiento es convertir la cadena de caracteres que contiene un número en forma de string al valor que representa. Si la conversión puede hacerse sin errores, entonces el valor es almacenado en Numero siempre que el valor convertido sea válido para el tipo de dato que fue declarado Numero. En caso de éxito, Codigo_Error valdrá cero.

En caso de que Cadena no contenga un valor que pueda ser convertido, Numero no será modificado y Codigo_Error contendrá un valor mayor a cero que será la posición en Cadena del caracter que impide la conversión.

     Var
       CadNum: String[20];
       RealNum: Real;
       IntNum, Error: Integer;
     ...
     CadNum := '23.45';
     Val(CadNum, RealNum, Error); {RealNum = 23.45, Error = 0}
     Val(CadNum, IntNum, Error); {Error = 3; RealNum permanece en 23.45, el punto causa el error }
     CadNum := '256,78';
     Val(CadNum, RealNum, Error); {RealNum = 23.45, Error = 4, la coma causa el error }
     CadNum := '23456';
     Val(CadNum, IntNum, Error); {IntNum = 23456, Error = 0}
     Val(CadNum, RealNum, Error); {RealNum = 23456.0, Error = 0}

Tener en cuenta que Turbo Pascal usa la notación anglosajona para los números reales por lo tanto el punto, y no la coma, es el separador de decimales. Los espacios en blanco también invalidan la conversión. Son admisibles los caracteres '+' y '-' e indicarían el signo del número resultante siempre que estén al inicio de la cadena.

    CadNum:='-12';
Val(CadNum, IntNum, Error); { IntNum = -12, Error = 0 } CadNum:='1-2';
Val(CadNum, IntNum, Error); { IntNum = -12, Error = 2 }

Por lo tanto, los caracteres que puede contener CadNum serían, para poder convertir a un tipo entero: '0'..'9' + '+' + '-'.

Para poder convertir a un tipo real además de los caracteres válidos indicados para enteros se agrega la letra 'E' (mayúscula o minúscula) para poder formar la notación científica y el caracter punto '.' como separador de decimales: '0'..'9' + '.' + '+' + '-' + 'E' + 'e'.

    CadNum:='1.2e+1'; { notación científica de '12.0' }
Val(CadNum, RealNum, Error); { RealNum = 12.0, Error = 0 }
7.5. Convertir un valor numérico a una cadena de caracteres.

Sintaxis: Str(Numero, Cadena)

Este procedimiento hace lo inverso de val(), es decir, recibe un valor numérico (sea una constante numérica o una variable numerica como integer, byte, word, real, etc...) y lo convierte a su representación en un string. El valor numérico puede formatearse como se haría al momento de imprimirse por pantalla.

Ejemplos:

     Var
        IntNum: Integer;
        RealNum: Real;
        Cadena: String;
     ...
     IntNum := -345;
     Str(IntNum, Cadena);           { Cadena = '-345' }
     RealNnum := 2345.678;
     Str(RealNum :0:6, Cadena);      { Cadena = '2345.678000' }
     Str(RealNum :0:1, Cadena);      { Cadena = '2345.7' }
     Str(RealNum :10:2, Cadena);     { Cadena = '   2345.68' }
     Str(RealNum, Cadena);           { Cadena = ' 2.3456780000E+03' (notación científica) }

8. Problemas con cadenas de caracteres resueltos.

Para las soluciones que se muestran a continuación se usa la siguiente declaración de variables:

Const
  Espacio = ' ';

Var
  Cad, Primero, Ultimo, Nuevo: String[80];
  Posicion, Tam: Integer;
  Quitar: Char;
8.1. Quitar espacios en blanco al inicio de una cadena.

Quita los espacios en blanco al inicio de una cadena.

    Cad := '     ahora es el momento';
    while Pos(Espacio, Cad) = 1 do
      Delete(Cad, 1, 1);
8.2. Invertir el orden de nombre y apellido.

Dado un nombre y un apellido separados por un espacio, se invierte el órden de los mismos logrando primero el apellido y luego el nombre.

Cad := 'Diego Romero';
Posicion := Pos(Espacio, Cad);        { Posicion = 6 }
Tam := Length(Cad);                   { Tam = 12 }
Ultimo := Copy(Cad, 1, Posicion - 1); { Ultimo = 'Diego'}
Primero := Copy(Cad, Posicion + 1, Tam - Posicion); { Primero = 'Romero' }
Cad := Primero + Espacio + Ultimo;    { Cad = 'Romero Diego' }
8.3. Quitar todas las ocurrencias de un caracter dado de un string.
Cad := 'Esxxxta xxxorxxacixon xxtixxene xxxdexxmaxsxiadxas exxquxis.';
Quitar := 'x';
Nuevo := '';
for Posicion := 1 to Length(Cad) do
  if Cad[Posicion] <> Quitar then
    Nuevo := Nuevo + Cad[Posicion];

Aunque la intuición al ver el enunciado del problema podría hacernos pensar que se debe utilizar el procedimiento Delete() y resolver el problema así:

for Posicion := 1 to Length(Cad) do
  if Cad[Posicion] = Quitar then
    Delete(Cad, Posicion, 1);

Esto solo quitará ocurrencias aisladas de 'x'. En los casos en que aparezca 'xx' solo la primera se elimina pues Delete() 'acorta' el string desplazando la cadena a la izquierda y Posicion 'se saltea' un caracter a medida que avanza.

8.4. Insertar un caracter nuevo cada 5 caracteres de la cadena.

Se pretende insertar un caracter cualquiera cada 5 caracteres de otra cadena.

  Cad := 'Abra Kadabra';
  Nuevo := '';  { asigna vacio }
  for Posicion := 1 to Length(Cad) Do
  if Posicion MOD 5 = 0 then { es Posicion múltiplo de 5? }
       Nuevo := Nuevo + Cad[Posicion] + '?' { inserta el caracter '?' }
  else Nuevo := Nuevo + Cad[Posicion];
8.5. Invertir una cadena.

Dada una cadena se pretende obtener la misma cadena pero en orden inverso (de atrás para adelante).

Cad := 'Diego Romero';
Nuevo := '';
for Posicion := Length(Cad) downto 1 do
  Nuevo := Nuevo + Cad[Posicion];
{ Nuevo = 'oremoR ogeiD' }
8.6. Contar cuántas vocales hay en una oración.

Dada una oración almacenada en un string, decir cuántas vocales hay en esa oración. Se hace uso del tipo de dato set para resolverlo.

Var
  Cad, Nuevo: String[80];
  Posicion: byte;
  Cont: byte;
  CarValidos: Set of Char;
...
Cont := 0; { inicializa el contador }
CarValidos := ['A', 'E', 'I', 'O', 'U', 'a', 'e', 'i', 'o', 'u']; { Conjuto de vocales }
Cad := 'Diego Romero dijo Abra Kadabra';
for Posicion := 1 to Length(Cad) do
  if Cad[Posicion] IN CarValidos then
      inc(Cont);  { es lo mismo que Cont := Cont + 1; }
Writeln('Hay ', Cont, ' vocales en ', Cad);
8.7. Convertir todas las letras minúsculas a mayúsculas y a la inversa.

Dada una cadena de caracteres, encontrar todas las letras minúsculas y convertirlas a mayúsculas. También el proceso inverso, es decir, de mayúsculas a minúsculas.

{ Convertir minúsculas a mayúsculas }
For Posicion := 1 to Length(Cad) Do
  Cad[Posicion] := Upcase(Cad[Posicion]);

La función Upcase() es estandar en Turbo Pascal, recibe un caracter, y si es una letra minúscula, devuelve su mayúscula. No funciona con vocales acentuadas.

{ Convertir mayúsculas a minúsculas }
CarValidos := ['A'..'Z']; { ver punto 8.6. más arriba }
for Posicion := 1 to Length(Cad) do
  if Cad[Posicion] IN CarValidos then
    Cad[Posicion] := Chr(Ord(Cad[Posicion]) + 32);

La función Chr() acepta un valor de tipo byte (rango 0..255) y devuelve el caracter de ese valor según la tabla ASCII. La función Ord() acepta un caracter y devuelve su posición en la tabla ASCII (estrictamente hablando hace más que eso, en realidad Ord() devuelve la posición ordinar de cualquier tipo ordinal, como el tipo Char que es un tipo ordinal). El truco, al combinar estas dos funciones, consiste en aprovecharse de la diferencia que hay entre las posiciones de las letras mayúsculas y minúsculas en la tabla ASCII que es 32, es decir, la letra 'a' está 32 posiciones más arriba que la letra 'A' y así sucesivamente. El código no funciona con vocales acentuadas.

Como se ha mencionado, ninguno de los códigos funciona con letras acentuadas. El problema no es de Pascal, sino de la tabla ASCII. La tabla ASCII original no tiene letras acentuadas, es más, ésta solo abarca hasta la posición 127. El resto de los caracteres es variable dependiendo de la tabla de códigos de caracter cargada en el DOS (sistema operativo). Si el código de caracteres en el DOS es el 850 entonces la tabla ASCII sí tiene vocales acentuadas.

8.8 Quitar algunos caracteres de una cadena dada.

Teniendo una cadena de caracteres se quiere quitar de ella las letras 'a', 'b' y 'c'.

Type
  TLetras = Set of 'A'..'z';

Var
  Quitar: TLetras;
  Nuevo: String[80];
  Posicion: Integer;
...
Quitar := ['a', 'b', 'c']; { las letras a quitar }
Cad := 'Pablito clavó un clavito.';
Nuevo := '';
For Posicion := 1 to Len(Cad) Do
  If not(Cad[Posicion] IN Quitar) then { si el caracter actual no está en el conjunto... }
     Nuevo := Nuevo + Cad[Posicion];
WriteLn(Nuevo);
8.9 Tomar un valor real, intercambiar su parte entera y decimal.

Dado un valor real, obtener otro valor real donde la parte entera será la parte decimal del original y la parte decimal la parte entera del original.

Const
  DecSep = '.'; { separador de decimales }
  Cero = '0';

Var
  Posicion: byte;
  Tam, Error: Integer;
  PrimerReal, SegundoReal: Real;
  CadReal, CadInt, CadDec: String[30];

begin
  ReadLn(PrimerReal);
  Str(PrimerReal :0:8, CadReal);                         { convertir real a string }
  Posicion := Pos(DecSep, CadReal);                      { tomar la posición del separador de decimales }
  Tam := Length(CadReal);                                { medir la cadena }
  CadInt := Copy(CadReal, 1, Posicion - 1);              { copiar la parte entera }
  CadDec := Copy(CadReal, Posicion + 1, Tam - Posicion); { copiar la parte fraccionaria }

  Posicion := Length(CadDec);                            { quitar ceros a la izquierda }
  While CadDec[Posicion] = Cero Do
    dec(Posicion); { es lo mismo que Posicion := Posicion - 1; }

{ si la parte decimal son todos ceros, ajustar la posicion }
  if Posicion = 0 then Posicion := 1;

  CadDec := Copy(CadDec, 1, Posicion);
  CadReal := CadDec + DecSep + CadInt;
  Val(CadReal, SegundoReal, Error);

  WriteLn(SegundoReal:0:8);
end.

9. Analizar sintácticamente ("parsear") una cadena de caracteres.

Este programa sirve para demostrar cómo analizar sintácticamente una cadena de caracteres ("parsear" un string).

La cadena de caracteres consiste en una lista de valores enteros separados por el caracter '|' (ASCII 124).

Por ejemplo:

'34|45|789'

Pero también podría contener esto:

'||3 | 48| 79|'

El objetivo del programa es extraer esos valores para procesamiento futuro. En este ejemplo, se acumulan en un sumador.

Como se puede ver en el ejemplo, la línea de texto puede no estar formateada apropiadamente, pudiendo contener "campos" vacíos o con espacios en blanco por delante o por detrás del número.

La idea básica para procesar esto es tener un bucle While controlado por el tamaño (longitud) del string. El string se acorta a medida que el bucle progresa porque partes del string serán removidos de él.

Mientras el tamaño del string es > 0

  1. Borrar todos los espacios en blanco en el string.
  2. Encontrar la posición del primer '|' en el string.
  3. Copiar del string todos los caracteres desde el comienzo del string hasta e incluyendo el '|', almacenar la parte copiada en Parte.
  4. Borrar los caracteres que están en Parte del string original.
  5. Contar hacia atrás desde el final de Parte, encontrar el último dígito en Parte (si es que hay alguno).
  6. Copiar de Parte la porción que sí contiene dígitos (o parece tenerlos).
  7. Convertir el string resultante a un valor entero usando Val().
  8. Si la conversión es exitosa, imprimir el valor numérico y sumarlo a Sum.

Cuando termina el proceso, mostrar el resultado de Sum.

Program Parse;

Uses
  crt;

Const
  Linea = '     |    23    |      478   |8|  8z7  |  2.3 |xvt| 987 |  5996   ';
  Sep = '|'; { separador de campo }
  Espacio = ' ';
  Digitos: Set of Char = ['0'..'9'];

Var
  X: String; { la cadena de trabajo }
  N, Error, Sum: Integer;
  Parte: String;
  Loc: Integer;

begin
  clrscr;

  X := Linea;
  X := X + Sep;  { Asegura que X termina con '|' }
  Sum := 0;

  while Length(X) > 0 do
    begin


      { Quitar todos los Espacios al inicio de X }

      while Pos(Espacio, X) = 1 do
        Delete(X, 1, 1);


      { Borrar cualquier caracter que no sea un dígito. }

      while not(X[1] in Digitos) and (Length(X) >= 1) do
        Delete(X, 1, 1);


      { Encontrar la posición del primer '|' en X, copiar todos los caracteres en X desde el
	    principio hasta e incluyendo el '|' en Parte; borrar esos caracteres del principio de X }

      Loc := Pos(Sep, X);
      Parte := Copy(X, 1, Loc);
      Delete(X, 1, Loc);


      { Contar hacia atrás desde el final de Parte hasta que se encuentre un dígito } 

      While not(Parte[Loc] in Digitos) and (Loc > 0) Do
        dec(Loc); { es lo mismo que Loc := Loc - 1; }


      { Hacer una nueva copia de Parte que consiste en los caracteres del principio
        de Parte hasta donde los dígitos fueron encontrados. Es posible que Loc = 0
        si no se encontró ningún dígito, por ejemplo si el campo contiene solo espacios
        o caracteres que no son dígitos }

      Parte := Copy(Parte, 1, Loc);


      { Solo para controlar que todo está bien, mostrar Parte }

      if Length(Parte) > 0 then
	       Writeln('Parte: ', Parte)
      else Writeln('Parte: (vacío)');


      { Convertir Parte un valor entero N }

      Val(Parte, N, Error);

      { Si Parte está vacío o contiene "basura", ignorar  N (o sea que Error <> 0);
        sino, sumar N a Sum e imprimir N }

      if Error = 0 then
          begin
            Writeln(N :4);
            Sum := Sum + N;
          end;

    end;  { While }

    Writeln('Suma total: ');
    Writeln(Sum :4);
    Readln;
end.

Por Diego Romero,