C

C

Tutorial C: Manipulación avanzada de cadenas

Explora los especificadores de formato avanzados en C y aprende a manipular cadenas eficientemente con funciones de `<string.h>` y `strtok()`.

Aprende C y certifícate

Uso avanzado de printf() y scanf() (especificadores de formato)

Los especificadores de formato en printf() y scanf() permiten controlar con precisión cómo se muestran y se leen los datos.

En printf(), los modificadores ofrecen mayor control sobre la salida. Por ejemplo, para especificar un ancho mínimo de campo, se coloca un número antes del especificador:

printf("%10d", 16);

Este código imprimirá el número 16 alineado a la derecha en un campo de 10 caracteres. Para alinear a la izquierda, se utiliza el flag -:

printf("%-10d", 16);

Los flags modifican el comportamiento del formato:

  • + : Muestra siempre el signo, positivo o negativo.
  • (espacio) : Añade un espacio si no hay signo.
  • 0 : Rellena con ceros en lugar de espacios.

Por ejemplo:

printf("%+08d", 123);

Esto imprimirá +0000123, llenando con ceros hasta completar 8 caracteres.

La precisión se especifica con un punto seguido de un número y afecta a los números de punto flotante y las cadenas. Por ejemplo, para limitar los decimales:

printf("%.2f", 3.14159);

Esto mostrará 3.14. Para cadenas, la precisión limita el número de caracteres impresos:

printf("%.5s", "Programación");

El resultado será Progr.

Los modificadores de longitud ajustan el tamaño de los datos:

  • h : Indica short para enteros.
  • l : Indica long para enteros, o double para flotantes.
  • ll : Indica long long.

Al imprimir un long long int:

long long int num = 1234567890123LL;
printf("%lld", num);

En scanf(), los especificadores también incluyen modificadores y permiten leer datos con precisión. Para leer un double:

double valor;
scanf("%lf", &valor);

Es crucial utilizar el modificador correcto para evitar errores de interpretación.

Además, scanf() permite limitar la cantidad de caracteres leídos. Por ejemplo, para leer una cadena de máximo 9 caracteres:

char nombre[10];
scanf("%9s", nombre);

Esto previene desbordamientos de búfer, mejorando la seguridad del programa.

Una característica avanzada es el uso de scansets, que permiten definir conjuntos de caracteres a leer:

char entrada[50];
scanf("%[A-Za-z ]", entrada);

Este código leerá letras mayúsculas, minúsculas y espacios, hasta encontrar un carácter que no pertenezca al conjunto.

También es posible omitir asignaciones utilizando el asterisco *:

int dia, año;
char mes[10];
scanf("%d %*s %d", &dia, &año);

Aquí, scanf() ignora la entrada del mes y solo asigna dia y año.

En printf(), los caracteres de escape permiten añadir formato adicional:

  • \n : Nueva línea.
  • \t : Tabulación.
  • \\ : Barra invertida.

Por ejemplo:

printf("Ruta: C:\\Archivos\\%s\n", nombreArchivo);

Esto imprimirá la ruta con las barras invertidas correctamente escapadas.

El manejo avanzado de especificadores de formato potencia la capacidad de interactuar con el usuario y de presentar datos de forma clara y precisa.

Funciones de <string.h> (copias, concatenación, comparación)

Las funciones de la biblioteca estándar <string.h> proporcionan herramientas para manipular cadenas de caracteres en C. Estas funciones permiten realizar operaciones comunes como copiar, concatenar y comparar cadenas, facilitando el manejo de texto en los programas.

Copia de cadenas

Para copiar el contenido de una cadena a otra, se utilizan las funciones strcpy() y strncpy().

  • **strcpy(destino, origen)**: copia la cadena desde origen hasta destino, incluyendo el carácter nulo \0 final.
  char fuente[] = "Hola Mundo";
  char destino[20];
  strcpy(destino, fuente);

Es importante asegurarse de que el tamaño del destino sea suficiente para contener la cadena de origen, ya que strcpy() no verifica los límites y puede causar desbordamientos de búfer.

  • **strncpy(destino, origen, n)**: copia como máximo n caracteres desde origen hasta destino.
  char fuente[] = "Programación en C";
  char destino[10];
  strncpy(destino, fuente, 9);
  destino[9] = '\0'; // Garantizar que la cadena está terminada en '\0'

Al usar strncpy(), es recomendable añadir manualmente el carácter nulo al final, ya que esta función no lo añade si se alcanza el límite n.

Concatenación de cadenas

La concatenación une el contenido de dos cadenas. Para ello, se emplean strcat() y strncat().

  • **strcat(destino, origen)**: añade la cadena origen al final de destino.
  char saludo[50] = "Hola, ";
  char nombre[] = "Ana";
  strcat(saludo, nombre);
  // Ahora saludo es "Hola, Ana"

Es crucial que el array destino tenga espacio suficiente para albergar la cadena resultante, evitando comportamientos indefinidos.

  • **strncat(destino, origen, n)**: añade como máximo n caracteres de origen al final de destino.
  char mensaje[20] = "Número: ";
  char numero[] = "123456789";
  strncat(mensaje, numero, 3);
  // Ahora mensaje es "Número: 123"

Esta función es útil para controlar la cantidad de caracteres concatenados y proteger la integridad de la memoria.

Comparación de cadenas

Para comparar cadenas y determinar su orden lexicográfico o igualdad, se utilizan strcmp() y strncmp().

**strcmp(cadena1, cadena2)**: compara cadena1 y cadena2 carácter por carácter.

Retorna un valor negativo si cadena1 es menor que cadena2.

Retorna 0 si ambas cadenas son iguales.

Retorna un valor positivo si cadena1 es mayor que cadena2.

  char palabra1[] = "Hola";
  char palabra2[] = "Hola";
  if (strcmp(palabra1, palabra2) == 0) {
      printf("Las cadenas son iguales.\n");
  }
  • **strncmp(cadena1, cadena2, n)**: compara los primeros n caracteres de las cadenas.
  char codigo1[] = "ABC123";
  char codigo2[] = "ABC999";
  if (strncmp(codigo1, codigo2, 3) == 0) {
      printf("Los códigos tienen el mismo prefijo.\n");
  }

Esta función es útil cuando solo interesa comparar una parte de las cadenas, mejorando la eficiencia en algunos casos.

Otras funciones útiles

Además de las anteriores, <string.h> ofrece otras funciones que complementan la manipulación de cadenas:

  • **strlen(cadena)**: devuelve la longitud de cadena, sin contar el carácter nulo.
  char texto[] = "Longitud";
  size_t len = strlen(texto);
  printf("La longitud es %zu.\n", len);
  • **strchr(cadena, c)**: busca el primer carácter c en cadena y devuelve un puntero a su posición.
  char frase[] = "Buscar en esta frase.";
  char *ptr = strchr(frase, 'e');
  if (ptr != NULL) {
      printf("Carácter encontrado en la posición: %ld.\n", ptr - frase);
  }
  • **strstr(cadena1, cadena2)**: busca la subcadena cadena2 dentro de cadena1.
  char texto[] = "Aprendiendo C es divertido.";
  char *resultado = strstr(texto, "C");
  if (resultado) {
      printf("Subcadena encontrada: %s.\n", resultado);
  }

Buenas prácticas y consideraciones

Al manipular cadenas en C es fundamental:

  • Verificar siempre los límites de los arrays para evitar errores de segmentación y vulnerabilidades de seguridad.
  • Inicializar las cadenas y, si es necesario, asegurarse de que estén terminadas en **\0**.
  • Utilizar funciones como strncpy() y strncat() para limitar la cantidad de datos procesados.
  • Recordar que las funciones de <string.h> operan con cadenas de caracteres, que deben ser arrays de char terminados en \0.

El dominio de estas funciones permite realizar operaciones complejas con cadenas y es esencial en el desarrollo de software en C, donde el manejo eficiente y seguro de la memoria es clave.

Tokenización de cadenas con strtok()

La tokenización de cadenas es un proceso esencial en programación que permite dividir una cadena en partes más pequeñas denominadas tokens o delimitadores. En C, la función strtok() de la biblioteca estándar <string.h> es una herramienta fundamental para realizar esta tarea.

Uso básico de strtok()

La función strtok() se utiliza para separar una cadena en tokens basándose en un conjunto de delimitadores especificados. Su prototipo es el siguiente:

char *strtok(char *str, const char *delim);
  • str: Es un puntero a la cadena que se desea tokenizar. En la primera llamada, se pasa la cadena original; en llamadas subsecuentes, se pasa NULL para continuar la tokenización.
  • delim: Es una cadena que contiene todos los caracteres que se consideran delimitadores.

Nota importante: strtok() modifica la cadena original, reemplazando los delimitadores encontrados por el carácter nulo \0. Por ello, es recomendable trabajar con una copia de la cadena si es necesario preservar el contenido original.

Ejemplo de tokenización

Supongamos que tenemos una cadena que contiene una lista de frutas separadas por comas:

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

int main() {
    char cadena[] = "manzana,banana,cereza,uvas";
    char *token;

    token = strtok(cadena, ",");
    while (token != NULL) {
        printf("Fruta: %s\n", token);
        token = strtok(NULL, ",");
    }

    return 0;
}

Salida:

Fruta: manzana
Fruta: banana
Fruta: cereza
Fruta: uvas

En este ejemplo:

  • La primera llamada a strtok(cadena, ",") devuelve un puntero al primer token, que es "manzana".
  • En las llamadas subsecuentes, se pasa NULL como primer argumento para continuar desde la posición anterior.
  • El bucle continúa hasta que strtok() devuelve NULL, indicando que no hay más tokens disponibles.

Consideraciones sobre strtok()

  • No es reentrante: strtok() utiliza una variable interna estática para almacenar el estado entre llamadas. Esto significa que no es segura en entornos multihilo. Si se necesita una versión reentrante, se debe utilizar strtok_r() en sistemas que la soporten.
  • Modifica la cadena original: Al insertar caracteres nulos \0 en los delimitadores, se altera la cadena original. Si se requiere conservarla, es necesario trabajar con una copia.

Tokenización con múltiples delimitadores

strtok() permite especificar una cadena de delimitadores, por lo que puede separar tokens usando distintos caracteres separadores. Por ejemplo:

char texto[] = "uno;dos,tres cuatro";
char *delimitadores = " ,;"; // Espacio, coma y punto y coma
char *token;

token = strtok(texto, delimitadores);
while (token != NULL) {
    printf("Token: %s\n", token);
    token = strtok(NULL, delimitadores);
}

Salida:

Token: uno
Token: dos
Token: tres
Token: cuatro

En este caso, strtok() separa la cadena cada vez que encuentra un espacio, una coma o un punto y coma.

Uso de strtok_r() para aplicaciones multihilo

En entornos donde se requiere seguridad en hilos, se utiliza la función strtok_r(), que es la versión reentrante de strtok(). Su prototipo es:

char *strtok_r(char *str, const char *delim, char **saveptr);
  • str: Cadena a tokenizar (solo en la primera llamada).
  • delim: Cadena con los delimitadores.
  • saveptr: Puntero a un puntero que mantiene el contexto entre llamadas.

Ejemplo:

char cadena[] = "rojo:verde:azul";
char *token;
char *resto = cadena;

while ((token = strtok_r(resto, ":", &resto))) {
    printf("Color: %s\n", token);
}

Salida:

Color: rojo
Color: verde
Color: azul

Aquí, resto mantiene el estado necesario para continuar la tokenización sin utilizar variables estáticas internas, lo que hace que strtok_r() sea segura en contextos multihilo.

Tokenización anidada

En casos más complejos, como analizar una cadena con diferentes niveles de delimitadores, es posible realizar tokenizaciones anidadas. Sin embargo, debido a la forma en que strtok() maneja el estado interno, se debe tener cuidado para evitar conflictos.

Ejemplo de tokenización anidada:

char data[] = "nombre1,apellido1;nombre2,apellido2;nombre3,apellido3";
char *resto = data;
char *registros;

while ((registros = strtok_r(resto, ";", &resto)))
{
    char *campo = strtok(registros, ",");
    while (campo != NULL)
    {
        printf("%s\n", campo);
        campo = strtok(NULL, ",");
    }
}

Salida:

Campo: nombre1
Campo: apellido1
Campo: nombre2
Campo: apellido2
Campo: nombre3
Campo: apellido3

En este ejemplo:

  • Primero, se tokeniza la cadena datos separando los registros por punto y coma ;.
  • Luego, se tokeniza cada registro separando los campos por coma ,.
  • Debido a que strtok() utiliza una variable estática interna, la tokenización anidada funciona correctamente en este caso porque se restablece el contexto en cada nivel.

Buenas prácticas al usar strtok()

  • Copiar la cadena original: Si es necesario conservar la cadena original, se debe trabajar con una copia.
  char *cadena_original = "ejemplo de cadena";
  char *copia_cadena = strdup(cadena_original);
  // Utilizar 'copia_cadena' para tokenizar
  • Verificar punteros nulos: Siempre se debe comprobar que el puntero devuelto por strtok() no es NULL antes de usarlo.
  char *token = strtok(cadena, delimitadores);
  if (token != NULL) {
      // Procesar el token
  }
  • Considerar alternativas: Para aplicaciones que requieren mayor flexibilidad o seguridad, considerar otras funciones o bibliotecas, como strsep() o funciones personalizadas de tokenización.

Limitaciones de strtok()

  • No adecuado para separar tokens vacíos: Si se requieren tokens vacíos (como en el caso de dos delimitadores consecutivos), strtok() los omite. En ese caso, strtok_r() o strsep() pueden ser más apropiadas.
  • Uso en bucles anidados: Al utilizar strtok() en bucles anidados, se debe tener cuidado con el estado interno para evitar resultados inesperados.
  • No es segura para hilos: Como se mencionó, para aplicaciones multihilo, es preferible utilizar strtok_r().

Aplicaciones prácticas

La tokenización es útil en diversas situaciones, como:

  • Análisis de líneas de comandos: Separar argumentos y opciones introducidos por el usuario.
  • Procesamiento de archivos de texto: Extraer campos de datos delimitados por caracteres específicos.
  • Implementación de protocolos: Interpretar mensajes o comandos estructurados en campos.

Ejemplo práctico de procesamiento de líneas:

char linea[] = "GET /index.html HTTP/1.1";
char *metodo = strtok(linea, " ");
char *ruta = strtok(NULL, " ");
char *version = strtok(NULL, " ");

printf("Método: %s\n", metodo);
printf("Ruta: %s\n", ruta);
printf("Versión: %s\n", version);

Salida:

Método: GET
Ruta: /index.html
Versión: HTTP/1.1

En este ejemplo, se separa una línea de petición HTTP en sus componentes principales.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

Plan mensual

19.00 € /mes

Precio normal mensual: 19 €
47 % DE DESCUENTO

Plan anual

10.00 € /mes

Ahorras 108 € al año
Precio normal anual: 120 €
Aprende C online

Todas las lecciones de C

Accede a todas las lecciones de C y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a C y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender el uso avanzado de printf() y scanf() con especificadores de formato en C.
  • Aprender a controlar la salida y entrada de datos mediante flags, modificadores y precisiones.
  • Practicar la manipulación de cadenas con funciones de <string.h>.
  • Utilizar strtok() para la tokenización de cadenas y entender su uso en aplicaciones multihilo.