CADENA DE CARACTERES


Cuando se estudió los tipos de datos definidos en C, se pudo observar que el lenguaje C sólo define datos de tipo entero y de punto flotante. Se puede apreciar que existe un tipo de dato denominado char, sin embargo una variable definida de este tipo alberga números enteros entre -128 y 127 y no caracteres como lo hacen otros lenguajes como el Pascal.

También se pudo apreciar que un valor constante de tipo entero se puede expresar de diferentes formas, por jemplo como un número entre tradicional (123, 5, 796, 114567, etc), como un número en base 8, anteponiendo un cero al número (0173, 05, 01434, 337607, etc.) o como números hexadecimales, anteponiéndoles 0x (0x7B, 0x5, 0x31C, 0x1BF87, etc.), pero también se puede expresar un número entero empleando una notación de caracteres, es decir escribiendo un caracter entre apóstrofos simples ('A', '?', 'h' '%', etc.). Esto se da porque un pograma en C/C++ (o en cualquier otro lnguaje de programación) se redacta en un editor de palabras, por lo tanto todo lo que se escribe en el editor son secuencias de caracteres. Es el compilador el que interpreta estos caracteres y los almacena en la memoria como representaciones binarias. Es lo mismo para el compilador haber escrito int a = 65; que escribir int a = 'A'; o escribir int a = 0x41; en la dirección de memoria dada para la variable a se almacena la misma representación binaria (0100 0001). Es por esta razón que una expreción puede darse de esta manera a = 'A' + 32;, no es que se pretenda sumar el valor de 32 a la letra A, si no qe se esta sumando 32 al valor numérico que este caracter represanta.

En este sentido podemos definir una cadena de caracteres en C como un arreglo en el que en cada elemento es de tipo char. Los caracteres de una cadena se colocarán uno a continuación del otro en los elementos del arreglo, a partir del primero. Para poder saber donde terminan los caraceres válidos en el arreglo, se debe colocar un valor de terminación al final de la secuencia de caracteres, este valor es cero, que representa al caractere cuyo código ASCII es cero. Este valor se le puede proporcionar al arreglo simplemente como 0 ó mediante su representación como caractere: '\0'.

Esta forma de manipular las cadenas de caracteres trae muchas ventajas en comparación de la forma en que lo hacen otros lenguajes de programación. Por ejemplo en Pascal, la longitud de las cadenas de caracteres se almacena en la misma representación, al inicio de la secuencia de caracteres. Como cada elemento de la cadena se almacena en un byte, y la longitud no es una excepción, las cadenas en Pascal no pueden tener más de 255 caracteres. Las cadenas en C, al no almacenar el tamaño, permite manejar cadenas sin límite de tamaño.

DECLARACIÓN:
Una cadena de caracteres es un arreglo de tipo char, por lo tanto una cadena de caraceres se declara de la siguiente manera:

char identificador [límite];

Por ejemplo:

char nombre[20];
ð donde "nombre" es una cadena de caracteres que puede albergar hasta 19 caracteres, no olvidar que se requiere un espacio para el delimitador de la cadena (cero o '\0'). por eso no se pueden albergar en ella 20 caracteres.
INICIALIZACIÓN:
Como cualquier arreglo, una cadena de caracteres se puede ser inicilizado en el momento de su declaración. Esta opracón se realiza de la siguiente manera:
char nomb1[10] = { 72, 111, 108,  97,    0,  43,    9, 123,   10, 45};  ó
char nomb2[10] = {'H', 'o',   'l',  'a', '\0', '+', '\t',   '{', '\n', 'A'};

En ambos casos se asigna a los elemntos del arreglo la misma información (los números en en nomb1 equivalen a los códigos ASCCI de los caracteres que aparecen en nomb2).
Observe que, según lo expuesto anteriormente, sólo los primeros cuatro datos corresponden a caracteres válidos en las cadenas, ya que el quinto elemento es un 0 (cero) o un '\0', delimitadores de la cadena.
Si recordamos de los arreglos, si se colocan menos valores de inicialización que la capacidad del arreglo, el resto de elementos del arreglo se inicializa en cero. Entonces, si realizamos la siguinte operación:
char nomb3[10] = {'H', 'o', 'l', 'a'};

Esto signfica que: nomb3[0] ï 'H',   nomb3[1] ï 'o',  nomb3[2] ï 'l',  nomb3[3] ï 'a',   nomb3[4] ï 0 ó '\0',   nomb3[5] ï 0 ó '\0', etc., lo que significa que la cadena está perfectamente delimitada.
Otra opción que nos da el compilador para inicializar una cadena de caracteres es haciendo uso de una secuencia de caracteres encerrados entre comillas ("..."), como se muestra a continuación:
char nomb4[10] = "Hola";

Esto es equivalente a la inicialización de la variable nomb3. Incluso se le ha colocado el delimitador en la quinta posición.
LECTURA Y ESCRITURA DE CADENAS DE CARACTERES
El lenguaje C/C++ implementa una serie de funciones que permiten el ingreso y la salida de cadenas de caracteres a, y desde, un programa. Empezaremos analizando las funciones scanf y printf.

La función scanf, como lo indicamos en capítulos anteriores, permite el ingreso de datos desde la consola. Esta misma función, empleando el especificador de formato %s, permite el ingreso de cadenas de caracteres. Observe el siguiente ejemplo:
char nombre[20];
scanf("%s", nombre);


Primero observe que, en la funcion scanf, a la variable nombre no se le antepone el & como a las variables enteras o de punto flotante, esto se debe a que la variable nombre es un arreglo y por lo tanto un puntero que contiene la dirección de inicio del arreglo.
La función scanf irá tomando uno a uno los caracteres del buffer de entrada, y los irá colocando consecutivamente en el arreglo a partir del primer elemento. Al fnal colocará el delimitador 0 ó '\0' en el siguiente elemento, imediatamente después del último caracter ingresado.
La imagen siguiente muestra cómo se realiza este proceso:

La función scanf también se puede emplear con punteros, sin embargo se debe tener especial cuidado en el setido de asegurar antes que el puntero apunte a una dirección válida. En ese sentido, observe las siguientes instrucciones:
char nombre[20];
char *c1, *c2;

scanf("%s", c1);        // Esto es un error lógico, ya que c1 no apunta a una dirección válida.

c2 = nombre;
scanf("%s", c2);        // Esto es correcto.
Un aspecto que se debe tomar en cuenta, cuando se quiere leer una cadena de caracteres empleando scanf es que, al igual con la lectura de números, la exploración de un dato se realiza hasta encontrar un "espacio" (entiéndase por "espacio", un caracter blanco, un caracter de tabulación o un caracter de cambio de línea). Es por esta razón que empleando el especificador de acceso %s en la función scanf sólo se podrán leer palabras sueltas y no frases completas.

La imagen siguiente ilustra este enunciado:

CONJUNTO DE EXPLORACIÓN %[]:   Se denomina "conjunto de exploración" a una lista de caracteres que se coloca en la cadena de formato de la función scanf y permite restringir los caracteres que se pueden ingresar a una cadena de caracteres. Un "conjunto de exploración" se coloca en reemplazo del %s, los caracteres que se desea permitir en el ingreso se colocan uno a continuación del otro entre los paréntesis. Se puede definir también un rango de caracteres, para esto se coloca el acracter inical seguido de un guión (-).

A continuación se muestrán algunos ejemplos:

   

También se puede negar un conjunto, de modo que se permita cualquier caracter menos los contenidos en el conjunto. Esto se logra poniendo el caracter ^ al inicio del conjunto.
Aquí un ejemplo:


La función printf permite, empleando el especificador de formato %s, la impresió del contenido de un arreglo de tipo char, como caracteres en la consola. La función irá tomando cada uno de los elementos del arreglo e irá mostrando en la pantalla cada uno de los caracteres correspondientes, hasta encontrar el delimitador de la cadena (0 ó '\0'). Si el delimitador no se encontrara presente en el arreglo, se seguirían imprimiendo caracteres contenidos en los bytes contigous hasta encontrar uno.


Otro par de funciones que permiten el ingreso y la salida de cadenas de caractres son las funciones gets y puts. La sintaxis de estas funcions es la siguiente:
char * gets(char *);
int puts (char *);


La función gets recibe como parámetro un arreglo de tipo char, en él se almacenará los caracteres provenientes de la consola. A difierencia de prints la lectura con gets no se detendrá cuando se encuentre un espacio en blanco sino hasta encontrar un caracter de cambio de línea. La función devuelve un puntero de tipo char que apunta al argumento, si se pretende leer al final del archivo la función devuelve NULL.


La función puts es equivalente a scanf con la diferencia que cuando puts termina de imprimir los caracteres, envia a la pantalla un carctere de cambio de línea. La funcióm puts devuelve un valor entero positivo si la operación tiene éxito y un valor negativo (por lo general EOF) si hubo un error de lectura.


Finalmente se pueden emplear los objetos cin y cout. El uso de ambos objetos tiene un efecto igual al del %s en las funcions scanf y printf.



También existe un método asociado a cin que permite realizar una operación similas a gets, este método se denomina getline. La manera cómo se utiliza se explica a continuación:

Por ejemplo:

cin.getline(cad, n);
ð donde "cad" es una cadena de caracteres y n es la cantidad máxima de caracteres menos uno, que se podrá leer desde el buffer de entrada. Alfinal coloca el caracter de terminación.


ASIGNACIÓN DE VALORES
Asignar un valor a una variable en un programa quiere decir que colocaremos un valor en la posició de memoria relacionada con la variable. Cuando se hacen operaciones de asignación se emplean por lo general valores constantes, variables o expresiones. Empleando tipos de datos numéricos,esto se realiza de la siguiente manera:
int a, b, c;
a = 234;        // asignación de un valor constante.
b = a;            // asignación de una variable.
c = a + b;      // asignación de una expresión.
En el caso de las cadenas de caracteres, no se puede asignar valores como se hacen con las variables numéricas. La razón de esto es la forma como se definen las cadenas en C., mediante arreglos. Si definimos dos cadenas como char a[20], b[2]; y queremos asignar una a la otra escribiendo la instrucción a = b; se producirá un error de compilación, esto debido a que, como dijimos en el capítulo de arreglos, cuando en un programa se empele el identificador del arreglo sin colocar un índice entre corchetes, se estará haciendo referencia a la dirección de memoria del inicio del área de datos del arreglo. En el caso de la instrucción anterior se estará tratando de asignar la dirección de memoria del área asignada a b a la variable a. Esto es un absurdo, ya que una variable definida como arreglo apunta de manera constante a un área de datos y por lo tanto no se le puede hacer apuntar a otra área de datos.

Por otro lado, un valor constante dado para una cadena de caracteres se representa como una secuencia de caracteres encerrados entre comillas, por ejemplo: "Cadena", "Juan Perez Castro". Cuando se pone un valor constante de este tipo en un programa, el compilador coloca cada uno de los catacteres, incluso el caractere determinación, en un espacio dememoria libre, luego el compilador reemplaza el valor constante por la dirección de inicio del área asignada. Es por esta razón que no se permite tampoco asignar a una cadena de caracteres un valor constante, por que se intentaría modificar la dirección a la que apunta este arreglo.

char nombre[50];
nombre = "Paula Valentina";        // esto es un ERROR de compilación.
Esto no se debe confundir con la inicialización de una cadena de caracteres como vimos al inicio de este capítulo, en estos caso el valor constante es ubicado por el compilador en el espacio asignado al arreglo.

Por otro lado, tampoco se debe confundir con el manejo de punteros ya que:
char *nombre;
nombre = "Naomi Alexandra";      // esto SI es correcto.
esto se debe a que un puntero puede apuntar a cualquier área de datos, en particular a la asignada al valor constante.

La pregunta ahora es ¿cómo hacer la asignación?. La respuesta a esto es sencilla aunque no es tan imediata como una asignación simple, habrá que elaborar un rutina que permita asignar caracter a caracter los elementos de un arreglo al otro. Esta rutina puede ser similar a la que se muestra a continuación:
#include <stdio.h>
void copiar (char [], char []);

int main (void)
{  char nomb1[30], nomb2[30];
   gets(nomb1);
   copiar (nomb2, nomb1);   // se lee: copiar en nomb2 los caracters de nomb1
···
}

void copiar (char destino[], char origen[])
{  int i=0;
   while (origen[i]!= 0)
    { destino[i] = origen[i];  // copiamos caracter a caracter
      i++;
    }
   destino[i] = 0;  // no se debe olvidar de colocar el caracter de terminación
}
Otra versión de esta misma rutina, que aprovecha más las propiedades del lenguaje C en cuanto a punteros se trata, puede ser la siguiente:
#include <stdio.h>
void copiar (char *, char *);

int main (void)
{  char nomb1[30], nomb2[30];
   gets(nomb1);
   copiar (nomb2, nomb1);   // se lee: copiar en nomb2 los caracters de nomb1
···
}

void copiar (char *destino, char *origen)
{   while (*destino++  =  *origen++);
}
Esta rutina, ahora más pequeña que la anterior, lo que hace es lo siguinte: Los punteros destino y origen apuntan inicialmente al primer elemento del arreglo respectivo. Como la condición del while tiene un = (asignación) y no un == (comparación), la orden *destino = *origen coloca el primer caracter de la cadena origen en el primer caracter de la cadena destino. Luego se aplica el operador ++, que está como sufijo, a destino y a origen (no se aplica a *destino ni a *origen por la forma como se agrupa el operador), por lo que cada puntero termina apuntando al siguiente caracter de la cadena. Finalmente el resultado de la asignación es el valor que se emplea para verificar el final de while, si es un caracter cualquiera el que se asignó, el ciclo continua, pero si se asignó el caracter de terminación, que es un valor cero, el ciclo se detiene. Esto último quiere decir que no se necesita dar una orden adicional para asignar el caracter de terminación a la cadena destino. Se debe tener en cuenta que destino y origen son dos parámetros por valor, por lo que no importa que se alteren los lugares a donde apuntan, esto no afecta a los punteros o arreglos de la función que llamó a la rutina copiar.

COMPARACIÓN DE CADENAS
Para comparar dos cadenas de caracteres se debe tener las mismas consideraciones que para la asignación de valores, esto es si nosotros pretendemos compara dos cadena de la siguiente manera:
int cad1[30], cad2[30];
gets(cad1);
gets(cad2);
if (cad1  ==  cad2) printf("Iguales\n");
else printf("Diferentes");
El compilador no detectará errores, sin embargo al ejecutarlo se imprimirá siempre el mensaje Diferentes, sin importar qué valores asignemos a las cadenas. Esto se debe a que lo que se está comparando no son los contenidos de las cadenas, sino las direcciones donde apuntan; al ser arreglos en este caso apuntarán a direcciones diferentes.

Para comparar entonces dos cadenas de caracteres, debemos elaborar una rutina que sea capaz de compara caracter a caracter dos cadenas y así poder determinar, si son iguales, si una es mayor que otra o viceversa. La función que se muestra a continuación realiza esta tarea, para lo cual, devuelve un valor menor que cero si la primera cadena es menor que la segunda, devuelve cero si ambas cadenas son iguales, o un valor mayor que cero si la primera cadena es mayor que la segunda.
#include <stdio.h>
int comparar (char [], char []);

int main (void)
{  char nomb1[30], nomb2[30];
   int resultado;

   gets(nomb1);   gets(nomb2);
   resultado = comparar (nomb1, nomb21);
   if (resultado == 0)    printf("Las cadnas son iguales\n");
   else
      if (resultado < 0)   printf("La primera cadena es menor que la segunda\n");
      else   printf("La primera cadena es mayor que la segunda\n");
···
}

int comparar (char c1[], char c2[])
{  int i;
   for (i=0; c1[i] == c2[i]; i++)
       if (c1[i]==0) return 0;  // si son iguales y su valor es cero entonces
                                         // las cadenas son iguales

    return c1[i]-c2[i];  // si son diferentes, termina el for y se devuelve
                                // la diferencia de los caractres desiguales

}
Otra versión de esta misma rutina empleando punteros puede ser la siguiente:
#include <stdio.h>
int comparar (char *, char *);

int main (void)
{  char nomb1[30], nomb2[30];
   int resultado;
···
}

int comparar (char *c1, char *c2)
{  for (  ; *c1 == *c2; c1++, c2++) if (!*c1) return 0;
   return *c1 - *c2;
}
En esta rutina los punteros c1 y c2 apuntan inicialmente al primer elemento del arreglo respectivo. Si los valores referenciados por c1 y c2 son iguales, se verifica si este valor es cero (0 ó '\0'), si es así se da por teminada la función y se retorna cero. Luego se desplaza los punteros c1 y c2 de modo que apunten al siguiente caracter respectivamente y el ciclo continua. En el momento que se detecte que los valores referenciados son diferentes, se corta el for y se devuelve la diferencia de los valores referenciados.

BIBLIOTECA DE FUNCIONES string.h

Todos los compiladores de C implementan una biblioteca de funciones que permite, de una manera sencila, la manipulación de cadanas de caracteres. Estas funciones trabajan con las cadenas de la misma manera que lo hemos hecho en este capítulo, es decir manipulando caracter por caracter cada uno de los elementos del arreglo. A continuación se muestran algunas de las funciones más comunes:

Función

Prototipo

Significado

strlen

int strlen(const char *cad);

Devuelve el número de caracteres de la cadena "cad". No cuenta el caracteres de terminación.

strcpy

char *strcpy(char *destino, const char *origen);

Copia todos los caracteres de la cadena origen en la cadena destino, incluso el caracter de terminación.
Devuelve un puntero a destino.

strcmp

int strcmp(const char *cad1, const char *cad2);

Compara uno a uno los caracteres de ambas cadenas hasta completarlos todos o hasta encontrar una diferencia.
Devuelve cero (0) si ambas cadenas son iguales, un valor negativo si la primera cadena es menor que las segunda y un valor positivo si la primera es mayor que la segunda.

strcat

char *strcat(char *destino, const char *origen);

Agrega una copia de la cadena origen al final de la cadena destino.
Devuelve la dirección a la que apunta la variable destino.

strstr

char *strstr(const char *cad1, const char *cad2);

Busca la primera ocurrencia de la cadena "cad2" en la cadena "cad1".
Si la encuentra devuelve un puntero que apunta al primer caracter de la ocurrencia en "cad1", si no lo encuentra devuelve NULL.

strchr

char *strchr(char *cad, char c);

Busca la primera ocurrencia del ccaracter contenido en la variable "c".
Si lo encuentra devuelve un puntero que apunta al caracter encontrado en "cad", si no lo encuentra devuelve NULL.

strrchr

char *strrchr(char *cad, char c);

Busca la última ocurrencia del ccaracter contenido en la variable "c".
Si lo encuentra devuelve un puntero que apunta al caracter encontrado en "cad", si no lo encuentra devuelve NULL.

strpbrk

char *strpbrk(const char *cad1, const char *cad2);

Busca la primera ocurrencia de cualquier caracter de la cadena "cad2" en la cadena "cad1".
Si la encuentra devuelve un puntero que apunta al caracter encontrado en la cadena "cad1", si no lo encuentra devuelve NULL.

strupr

char *strupr(char *cad);

Convierte las letras contenidas en la cadena "cad" en mayúsculas.
Devuelva una puntero a la cadena "cad".

strlwr

char *strlwr(char *cad);

Convierte las letras contenidas en la cadena "cad" en minúsculas.
Devuelva una puntero a la cadena "cad".

strrev

char *strrev(char *cad);

Invierte los caracteres contenidos en la cadena "cad".
Devuelva una puntero a la cadena "cad".

Ejemplo:

El siguiente ejemplo muestra cómo se manejan algunas de estas funciones:

Grabar este ejemplo

/* Este programa pretende realizar las operaciones de "buscar y reeeplazar"
   que tienen los procesadores de palabras. El programa lee de la entrada
   estándar una cadena a buscar y una cadena que va a reemplazar, luego lee
   un texto, una línea por vez,  si la cadena buscada se encuentra en la
   línea, se reemplaza por segunda cadena tantas veces como se encuentre.
   Suponer que las líneas nunca superarán los 100 caracteres.
 */

 #include<stdio.h>
 #include<string.h>

 void reemplazar(char *, char *, char *);

 void main (void)
 { char cadBusc[20], cadReem[20];
   char linea[100];

   printf("Ingrese la cadena buscada     : "); gets(cadBusc);
   printf("Ingrese la cadena de reemplazo: "); gets(cadReem);

   // Buscamos y reemplazamos en el texto
   while (gets(linea)) // sale cuando encuentra el final del archivo
    { reemplazar(linea, cadBusc, cadReem);
      puts(linea);
    }
 }

 void reemplazar(char *linea, char *cadBus, char *cadReem)
  {  char *ptLinea=linea, *pos, aux[100];
     // el puntero ptLinea servirá para buscar varias ocurrencias
     // en la cadena. Indica el punto de partida de la búsqueda
     while (1)
      { pos = strstr(ptLinea,cadBus);
        if (pos==NULL) return;
        *pos=0; //pongo un caracter de terminación al inicio de la cadena encontrada
        strcpy(aux,linea); // copio sólo la parte izquierda de la cadena
        strcat(aux,cadReem); // agrega a "aux" la cadena de reeplazo
        strcat(aux,pos+strlen(cadBus)); // copio la parte derecha de la cadena
        strcpy(linea,aux); // copio en "linea" la cadena modificada
        ptLinea= pos+strlen(cadReem);  // desplazo el puntero para permitir ubicar una
                                       // nueva ocurrencia
      }
 }
  



Volver a contenidos AtrásSiguiente