DEV Community

Cadenas de caracteres en C

Si venías de programar en BASIC (Microsoft BASIC y algunos otros), o quizás Python, y has empezado a programar en C, te habrás encontrado con una paradoja: mientras en estos lenguajes de programación se manejan las cadenas de caracteres como un tipo de datos más, en C el manejo de cadenas de caracteres es... un tanto pedestre.

Y es que, efectivamente, el lenguaje de programación C nos acerca muchísimo al hardware (de hecho, no se considera ya un lenguaje de programación de alto nivel, sino de nivel medio). Las cadenas de caracteres no son sino vectores o arrays de caracteres, con un carácter centinela para indicar el fin de la cadena de caracteres.

En cualquier caso, el manejo de cadenas de caracteres en C da nombre a un tipo de cadenas de caracteres: las C-strings (cadenas de caracteres de C), o NUL-terminated strings (cadenas de caracteres terminadas con un nulo).

Cadena de caracteres terminada en nulo

El carácter NUL es el carácter número 0 del código ASCII, y se representa directamente mediante un 0, o bien mediante el carácter '\0' (la barra inclinada a la izquierda permite representar un carácter mediante su código).

A través de la cabecera string.h, el lenguaje de programación C nos ofrece varias funciones para el manejo de cadenas. En la siguiente tabla aparecen las funciones más importantes. Se usa n para indicar un número de caracteres, y s para indicar char[], o también (es equivalente en este caso) char *.

Función Explicación
n = strlen(s) Calcular longitud de cadena
strncpy(s1, s2, n) Copiar s2 en s1
strncat(s1, s2, n) Concatenar s2 en s1
strncmp(s1, s2, n) Comparar s1 y s2
memset(s1, ch, n) Iniciar s1 con carácter ch
strchr(s1, ch) Puntero a ch en s1
strrchr(s1, ch) Puntero al último ch en s1
strstr(s1, s2) Puntero a s2 en s1
s2 = strdup(s1) Duplica la cadena s1 en s2

Hay que tener en cuenta que, en realidad, hay tres tipos de cadenas de caracteres, como aparece más abajo. Téngase en cuenta que, como una variable char[] es también un puntero a la primera posición del array, para el propósito de esta tabla char * y char[] son equivalentes.

Tipo de dato Explicación
const char [] Literal, como "hola"
char[] Un array en el stack
char * Un array en el heap

Por ejemplo, en la siguiente función tenemos la aparición de los tres tipos.

#define MAX_CHAR_BUFFER 4095


char * duplica_en_mays(const char *s)
{
    char buffer[MAX_CHAR_BUFFER + 1];
    const char * ch_a_omitir = " \t\r\n";
    const char * ptr1 = s;
    char * ptr2 = buffer;

    while ( *ptr1 != '\0'
         && MAX_CHAR_BUFFER > ( ptr1 - s ) )
    {
        if ( strchr( ch_a_omitir, *ptr1 ) == NULL ) {
            *ptr2 = toupper( *ptr1 );
            ++ptr2;
        }

        ++ptr1;
    }

    *ptr2 = '\0';
    return strdup( buffer );
}
Enter fullscreen mode Exit fullscreen mode

La gran mayoría de las funciones que manejan cadenas en C tienen este aspecto: creación de punteros, aritmética de punteros (para recorrer una cadena), etc. En los lenguajes de programación recientes las cadenas de caracteres se manejan internamente de la misma forma, pero normalmente no es algo que esté a la vista del usuario.

Tenemos un buffer de caracteres de 4Kb de longitud (es importante recordar que hay que reservar un carácter más para el cero final). Asumimos que será suficiente para el tipo de trabajos a realizar por la función. Vamos a pasar a mayúsculas los caracteres en s, pero también vamos a eliminar los espacios, tabuladores y cambios de línea, por lo que es posible que nos sobren algunos caracteres, o también es posible que no llegue el espacio (con lo que truncaremos la cadena s).

Con un puntero ptr1 vamos recorriendo (apuntando a cada carácter) la cadena de caracteres en s, mientras que con ptr2 marcamos el primer carácter libre en buffer. Cada vez que pasamos por un carácter, comprobamos si está en la lista de caracteres a omitir con strchr(), y si no es así, guardamos la versión en mayúsculas en el hueco apuntado por ptr2, y lo incrementamos en 1.

Haya sido omitido o no el carácter, se incrementará ptr1 para valorar el siguiente, hasta llegar al cero que indica fin de cadena.

Una vez que se ha llegado al cero, en el hueco apuntado por ptr2 se guarda ese NUL como centinela.

Al final, se duplica la cadena a devolver en el heap, y se devuelve con el tamaño exacto necesario(1).

Podemos utilizar la función así:

int main(int argc, char *argv[])
{
    char * convertido = duplica_en_mays( "   Esta es una prueba!   " );

    printf( "%s\n", convertido );
    free( convertido );
    return EXIT_SUCCESS;
}
Enter fullscreen mode Exit fullscreen mode

Es muy importante entender la noción de responsabilidad. Cuando desconocemos el tamaño de una cadena de caracteres, es muy típico calcular y reservar en el heap (de otra forma, crearíamos un simple vector con un tamaño fijo). Esto implica que debemos liberar la memoria de aquello de lo que nos hacemos responsables. Dicho de otra forma, la función duplica_en_mays() nos devuelve un resultado com la responsabilidad de liberar el espacio ocupado por ese resultado.

Las cadenas de caracteres terminadas por nulos son las más sencillas de explicar, pero también las más complicadas de gestionar de forma correcta. Al manejarnos directamente con la memoria, mediante punteros y aritmética de punteros, es sumamente fácil acabar con un error de memoria, bien sea porque no se ha reservado memoria suficiente, porque no se ha incluido el cero al final de la cadena, o un montón de tanto errores como despistes.

(1) Sí, se puede evitar la copia de la cadena de caracteres, y el uso del heap marcando el array buffer como static, pero así encaja mejor como ejemplo.

Top comments (0)