1. INTRODUCCION
Si bien ya hemos realizado variadas operaciones de manejo de string , dada su
importancia, pues son cuando menos el medio de comunicación de los programas
con el operador, trataremos acá de sintetizar los conceptos relativos
a los mismos, y resumir aquellas funciones ya vistas, con el agregado de otras
nuevas.
La mayoría de las que veremos a continuación, responden a la norma
ANSI C, por lo que serán independientes del compilador que usemos. Estas
tienen sus prototipos definidos en los archivos de encabezamiento stdio.h, stdlib.h,
string.h y ctype.h.
Agregaremos tambien algunas que caen fuera de la norma, por lo que su portabilidad
a otros compiladores distintos al que fueron extraidas, no es segura. Seran
aquellas declaradas en Headers no citados arriba. Sin embargo, hoy en día
practicamente todos los compiladores las traen ó tienen otras similares,
con nombres parecidos. De cualquier forma, antes de compilar los ejemplos aquí
suministrados, en caso de encontrarse alguna de estas, verifique con su manual
de Libreria la existencia y compatibilidad de la misma.
Refresquemos, antes de comenzar, algunas de las características básicas
de los strings. Estos pueden aparecer en un programa como una constante simbólica,
de la forma siguiente:
#define TITULO "Capitulo 9"
en este caso, en cada lugar donde aparece TITULO se reemplazará esta
constante simbólica por la DIRECCION de la C del texto con que fué
definida .
Así, será correcto escribir:
char *p = TITULO ;
Recordemos tambien que en la memoria, el string se guardará de la siguiente forma:
Donde los números son el código ASCII que representa a cada
caracter del string , en particular , note que 20 corresponde al espacio , terminandose
con un NULL (código 0 ) .
A los efecttos prácticos, para las funciones de manejo de los mismos,
es como si en realidad hubieramos memorizados directamente los caracteres:
El código ASCII de los caracteres imprimibles vá entre el 31
y el 127 , reservándose los códigos entre el 0 y 30 , para los
caracteres de control (retorno de carro, avance de linea, tabulador, etc).
Si en cambio , hubieramos escrito el string de una manera ortográficamente
más correcta :
#define TITULO "Capítulo 9"
(con la i acentuada) estaríamos introduciendo un caracter del conjunto
ASCII Extendido , ya que su código supera a 127 y está representado
por 173 .
Lo correcto en este caso sería definir , aunque muchos compiladores ya
lo presuponen por omisión, para asegurar la portabilidad :
unsigned char *p = TITULO ;
de esta forma se garantiza que el alcance de la variable sea de 255 , ó en su defecto :
int *p = TITULO ;
Es correcto tambien declarar el puntero , y asignarlo posteriormente
char *p ; p = TITULO ;
Esta asignación solo dá , al contenido del puntero la dirección
del string global predefinido .
Sin embargo , si en lugar de un puntero deseamos usar un array , en este caso
es correcta la inicialización del mismo , pero no así su asignación
posterior:
char nombre[] = TITULO ; /* Correcto */ ................. char nombre[11] ; nombre = TITULO ; /* Incorrecto */
Ya que si bien, el nombre de un array es un puntero , es de índole
constante , negándose el compilador a cambiar su dirección.
Si estuvieramos en el caso de ingresar un string variable , por ejemplo leyendolo
desde el teclado , podríamos utilizar un array, de la siguiente forma
:
char nombre[128] ; scanf("%s" , nombre ) ;
en este caso la única precaución es que , el array tenga suficiente
longitud para albergar a cualquier string escrito . En el caso de trabajar bajo
DOS, basta con darle 128 caracteres, ya que el buffer de lectura de ese sistema
operativo no supera dicha cantidad .
Hay que hacer notar que la longitud de un string puede ser mayor que la del
texto válido contenido , ya que este termina donde se encuentra el NULL
, quedando los bytes sobrantes desaprovechados .
Seria incorrecto leer este string mediante un puntero declarado , pero al que
no se le ha reservado memoria:
char *p ; scanf("%s" , p ) /* Incorrecto */
ya que la dirección contenida por p no ha sido inicializada aún con ningun valor válido . Lo correcto en éste caso es:
char *p ; p = (char *)malloc(128 * sizeof(char)) ; scanf("%s" , p ) /* Correcto */
reservando memoria previamente a cargar el string.
Otro punto sobre el que quiero volver, a fín de evitar confusiones, es
el sentido de la constante NULL , y el de variables nulas.
Segun éste se aplique a caracteres, strings ó punteros, su significado
varia:
Hay que recalcar que, practicamente todas las funciones que describiremos a continuación , basan su operatoria en la suposición que los strings que se le pasan como argumento , terminan en el caracter NULL , si por error esto no fuera así , los resultados son catastróficos , produciendose generalmente la destrucción de los datos y el aborto del programa .
2. FUNCIONES DE IMPRESION DE STRINGS
Daremos un análisis de las funciones que permiten la impresión
en pantalla de strings , muchas de ellas pueden obviamente , utilizarse para
imprimir otro tipo de variable , pero aquí sólo describiremos
su aplicación particular sobre el tema de nuestro interes.
PRINTF()
........... p = "Lenguaje C" ; /* 10 caracteres */ ........... printf("|%15s|" , p ) ; /* imprime : | Lenguaje C| */ printf("|%15.8s|" , p ) ; /* " : | Lenguaje| */ printf("|%-15s|" , p ) ; /* " : |Lenguaje C | */ printf("|%-15.8s|" , p ) ; /* " : |Lenguaje | */ printf("|%.6s|" , p ) ; /* " : |Lengua| */ ancho = printf("|%15s|" , p ); /* imprime : | Lenguaje C| */ printf("|%*.8s|" , p , ancho); /* " : | Lenguaje| */
Existe otra función más específica que la anterior ,
aunque más restrigida , puts() .
PUTS()
#include <stdio.h> main() { char p[] = "Uno" , s[] = "Dos" ; puts(p) ; puts(s) ; } /* imprime : Uno Dos */
3. FUNCIONES DE ADQUISICION DE STRING
Cuando se necesita leer un string enviado desde el teclado , se utilizará
alguna de las funciones abajo citadas , debiendose tener los recaudos descriptos
antes , ya que la longitud del mismo es desconocida.
SCANF()
De la misma manera que para printf(), hay funciones menos generales, dedicadas
expresamente a la lectura de strings, como gets(), que veremos a continuación
.
GETS()
4. FUNCIONES DE CONVERSION ENTRE STRING Y VARIABLES NUMERICAS
Puede darse el caso que la información a ingresarse a un programa ejecutable
, por el operador pueda ser en algunos caso un valor numérico y en otros
un string de caracteres . Un problema típico enfrentamos en el ejemplo
en que ingresabamos a nuestra base de datos un articulo , ya sea por su nombre
ó por su número de código .
Más cómodo que escribir dos instancias del programa , una para
cada una de las opciones , es dejar que el operador escriba lo que se le venga
en ganas , leyendolo como un string , luego verificar si éste está
compuesto exclusivamente por números ó posee algun caracter nó
numérico , y actuar en consecuencia .
Para evaluar si un string cae dentro de una categoría dada , es decir
si está compuesto exclusivamente por números , letras, mayúsculas
, minúsculas , caracteres alfanuméricos , etc existen una serie
de funciones , algunas de las cuales ya hemos usado, que describimos a continuación
. Estas deben ser usadas con los strings , analizando caracter a caracter de
los mismos, dentro de un FOR ó un WHILE:
for(i=0 ; palabra[i] != NULL ; i++) { if( isalnum(palabra[i] ) ......................... }
IS.....()
int isalnum( int c ) int isalpha( int c ) int isascii( int c ) int iscntrl( int c ) int isdigit( int c ) int islower( int c ) int isupper( int c ) int ispunct( int c ) int isspace( int c ) int isxdigit( int c )
La Función |
Retorna CIERTO si c es : |
isalnum(c) |
Alfanumérico ( letras ó números ) |
isalpha(c) |
Alfabetico , mayúscula ó minúscula |
isascii(c) |
Si su valor está entre 0 y 126 |
iscntrl(c) |
Si es un caracter de control cuyo ASCII está comprendido entre 0 y 31 ó si es el código de "delete" , 127 . |
islower(c) |
Si es un caracter alfabético minuscula. |
isupper(c) |
Si es un caracter alfabético mayúscula |
isdigit(c) |
Si es un número comprendido entre 0 y 9 |
ispunct(c) |
Si es un caracter de puntuación |
isspace(c) |
Si es el caracter espacio, tabulador, avance de línea, retorno de carro, etc. |
isxdigit(c) |
Si es código correspondiente a un número hexadecimal, es decir entre 0 - 9 ó A - F ó a - f . |
Una vez que sabemos que un string está compuesto por números
, podemos convertirlo en una variable numérica , de cualquier tipo ,
para poder realizar con ella los cálculos que correspondan .
ATOI() , ATOL() , ATOF()
int atoi( const char *s ) long atol( const char *s ) double atof( const char *s )
[espacios , blancos , tabulador , etc] [signo] xxx
donde xxx son caracteres entre 0 y 9 , para atoi() y atol() . Para atof() en cambio , se aceptan :
[espacios , etc] [signo] xxx [.] [ xxx] ó [espacios , etc] [signo] xxx [.] [ xxx] [ e ó E [signo] xxx ]
según se desee usar la convención de punto flotante ó cientifica.
Es posible tambien , aunque menos frecuente , realizar la operación inversa, es decir, convertir un número en un string.
ITOA() , ULTOA()
char *itoa( int numero , char *s , int base ) char *ultoa( unsigned long numero , char *s , int base )
5. DETERMINACION DE LA LONGITUD DE UN STRING
Hemos aplicado anteriormente esta función, damos aquí entonces
, sólo una ampliación de sus caracteristicas.
STRLEN() , _FSTRLEN
size_t strlen( const char *s ) size_t far _fstrlen( const char far *s )
.............. char s[128] ; gets(s) ; p = (char *)malloc( sizeof( strlen(s) + 1 ) ;
6. COPIA Y DUPLICACION DE STRINGS
Vimos que el operador de asignación no está definido para strings
, es decir que hacer p = q , donde p y q son dos arrays , no produce la copia
de q en p y directamente la expresión no es compilada . Si en cambio
p y q son dos punteros a caracteres , la expresión es compilada , pero
no produce el efecto de copia , simplemente , cambia el valor de p , haciendo
que apunte al MISMO string que q . Es decir que no se genera uno nuevo , por
lo que todo lo operado sobre p afectará al original , apuntado por q
.
Para generar entonces , una copia de un string en otro lugar de la memoria ,
se deben utilizar alguna de las funciones abajo descriptas . Hay que diferenciar
la copia de la duplicacion : la primera copia un string sobre un lugar PREVIAMENTE
reservado de memoria ( mediante malloc() , calloc() ó alguna otra función
función de alocación ) , en cambio la duplicación GENERA
el espacio para guardar al nuevo string así creado.
STRCPY()
Existe tambien una función para realizar la copia PARCIAL . Por lo general las funciones que realizan acciones sobre una parte solamente , de los strings , llevan el mismo nombre de las que los afectan totalmente , pero con la adición de la letra "n".
STRNCPY()
#include <string.h> main() { char strvacio[11] ; char strorigen[] = "0123456789" ; char strdestino[] = "ABCDEFGHIJ" ; .................. strncpy( strdestino , strorigen , 5 ) ; strncpy( strvacio , strorigen , 5 ) ; strvacio[5] = '\0' ; ......................... }
Los strings quedarían , luego de la copia :
strdestino[] == 0 , 1 , 2 , 3 , 4 , F , G , H , I , J , \0 strvacio[] == 0 , 1 , 2 , 3 , 4 , \0 , indefinidos
Note que en el caso de strdestino no hizo falta agregar el NULL , ya que éste se generó en la incialización del mismo , en cambio strvacio no fué inicializado , por lo que para terminar el string , luego de la copia , se deberá forzosamente agregar al final del mismo.
La función siguiente permite la duplicación de strings :
STRDUP()
#include <string.h> main() { char *p ; char q[] = "Duplicación de strings" ; p = strdup( q ) ; .................. }
Note que el retorno de la función debe ser siempre asignado a un dado puntero .
7. CONCATENACION DE STRINGS
Se puede, mediante la concatenación de dos ó más strings
, crear otro , cuyo contenido es el agregado del de todos los anteriores .
La concatenación de varios strings puede anidarse , de la siguiente manera
:
strcat( strcat(x , w) , z ) ;
en la cual al x se le agrega a continuación el w , y luego el z . Por supuesto x tiene que tener suficiente longitud como para albergarlos .
STRCAT()
#include <string.h> char z[20] ; main() { char p[20] ; char q[] = "123456789" ; char w[] = "AB" ; char y[20] = "AB" ; strcat( y , q ) ; /* Correcto , el contenido de y[] será: y[] == A,B,1,2,3,4,5,6,7,8,9,\0 */ strcat( z , q ) ; /* Correcto , por ser global z[] quedó inicializado con 20 NULLS por lo que luego de la operación quedará: z[] == 1,2,3,4,5,6,7,8,9,\0 strcat( p , q ) ; /* Error ! p no ha sido inicializado por lo que la función no encuentra el NULL para empezar a agregar , por lo que barre la memoria hasta encontrar alguno, y ahí escribe con resultados, generalmente catastróficos. strcat( w , q ) ; /* Error ! w solo tiene 3 caracteres, por lo el resultado final será: w[] == A,B,1 sin la terminación del NULL por lo que cualquier próxima operación que se haga utilizando este array, como string, fallará rotundamente . {
STRNCAT()
8. COMPARACION DE STRINGS
No debe confundirse la comparación de strings , con la de punteros ,
es decir
if(p == q) { ............
sólo dará CIERTO cuando ambos apunten al MISMO string , siempre y cuando dichos punteros sean " near " ó " huge " . El caso que acá nos ocupa es más general , ya que involucra a dos ó más strings ubicados en distintos puntos de la memoria ( abarca el caso anterior , como situación particular).
STRCMP()
< 0 si s1 es menor que s2 == 0 si s1 es igual a s2 > 0 si s1 es mayor que s2
La comparación se realiza caracter a caracter , y devuelve el resultado
de la realizada entre los primeros dos que sean distintos.
La misma se efectua tomando en cuenta el código ASCII de los caracteres
así será por ejemplo '9' < 'A' , 'A' < 'Z y 'Z' < 'a'
.
STRCMPI()
STRNCMP() , STRNCMPI()
9. BUSQUEDA DENTRO DE UN STRING
Muchas veces dentro de un programa , se necesita ubicar dentro de un string
, a un determinado caracter ó conjunto ordenado de ellos . Para simplificarnos
la tarea existen una serie de funciones de Librería , que toman por su
cuenta la resolución de este problema :
STRCHR() Y STRRCHR()
char *strchr( const char *s1 , int c ) char *strrchr( const char *s1 , int c )
STRBRK()
STRSTR()
STRTOK()
puts("escriba una frase , separando las palabras con espacios") ; gets(s) ; p = strtok(s , " ") ; while(p) { puts(p) ; p = strtok( NULL , " ") ; }
10. FUNCIONES DE MODIFICACION DE STRING
Resulta conveniente a veces uniformizar los string leidos de teclado, antes
de usarlos, un caso típico es tratar de independizarse de la posibilidad
de que el operador escriba, algunas veces con mayúscula y otras con minúscula.
STRLWR() Y STRUPR()
char *strlwr( char *s1 ) char *strupr( char *s1 )