Logo UTN

Ministerio de Capital Humano

Universidad Tecnológica Nacional
Facultad Regional Mar del Plata

2026 – Año de la Grandeza Argentina

Cadenas de caracteres en C

Conceptos claros, ejemplos visuales y buenas prácticas.

1. Introducción

En C no existe un tipo de dato nativo llamado string. Para el hardware y el sistema operativo, las palabras son simplemente una secuencia de caracteres individuales uno al lado del otro. Una cadena de caracteres es un arreglo de tipo char con una particularidad: debe terminar con un carácter especial llamado terminador nulo.

2. El terminador nulo ('\0')

El terminador es un carácter especial que simboliza el fin de una cadena. Es un byte con todos sus bits en cero (valor decimal 0). Cuando el sistema operativo o las funciones de la biblioteca estándar leen una cadena, se detienen al encontrar '\0'.

Representación en memoria de char texto[6] = "Hola";
Índice:   0   1   2   3   4   5
Valor:   'H'  'o'  'l'  'a'  '\0'  (basura o cero)
Nota: se necesitan al menos 5 posiciones (4 letras + '\0'). La posición 5 existe pero no se usa.

Diferencia clave: '\0' (cero numérico) no es lo mismo que '0' (el carácter dígito cero, cuyo valor ASCII es 48).

3. Declaración e inicialización

char nombre[20];        // arreglo de 20 char, capacidad para 19 caracteres + '\0'
char saludo[] = "hola mundo";   // tamaño automático: 11 (10 letras + '\0')
char patente[12] = "AB1234CD";  // las posiciones [8] a [11] se llenan con '\0'

4. Entrada y salida (IO) segura

Problema con scanf("%s", ...): se detiene en el primer espacio en blanco. Para leer líneas completas, lo más seguro y recomendado es usar fgets().

char nombre[20];
printf("Ingrese su nombre: ");
fgets(nombre, sizeof(nombre), stdin);
// Eliminar el salto de línea final ('\n') que fgets incluye
nombre[strcspn(nombre, "\n")] = '\0';
Función útil: strcspn(cadena, "\n") devuelve la posición del primer '\n'. Reemplazarlo por '\0' elimina el salto de línea de forma simple.

4.1 Problemas de desbordamiento y limpieza del buffer

Si el usuario ingresa más caracteres de los que permite el arreglo, fgets trunca la cadena y deja el resto en el buffer de entrada. Para mantener el buffer limpio, podemos usar esta función estándar:

void limpiarBuffer(void) {
    int c;
    while ((c = getchar()) != '\n' && c != EOF);
}

Ejemplo de lectura robusta que maneja el desborde:

int scanString(char* destino, int maxLen) {
    fgets(destino, maxLen, stdin);
    int len = strlen(destino);
    if (len > 0 && destino[len-1] == '\n')
        destino[len-1] = '\0';
    else
        limpiarBuffer();
    return strlen(destino);
}

5. Recorrido de strings carácter por carácter

Al ser un arreglo, podemos acceder a cada carácter mediante su índice. Esto refuerza la idea de que un string es un arreglo.

char saludo[] = "Ciencia";
for (int i = 0; i < strlen(saludo); i++) {
    printf("saludo[%d] = %c\n", i, saludo[i]);
}
Salida:
saludo[0] = C
saludo[1] = i
saludo[2] = e
saludo[3] = n
saludo[4] = c
saludo[5] = i
saludo[6] = a

6. El string como una matriz de caracteres

Un string individual es un arreglo unidimensional. Cuando necesitamos guardar varios strings (por ejemplo, una lista de nombres), estamos creando una matriz (arreglo bidimensional) de caracteres.

char nombres[3][20] = {"Ana", "Luis", "Carla"};
Visualización en memoria:
Fila 0 (Ana): 'A' 'n' 'a' '\0' (y luego 16 celdas sin usar)
Fila 1 (Luis): 'L' 'u' 'i' 's' '\0' ...
Fila 2 (Carla):'C' 'a' 'r' 'l' 'a' '\0' ...
Cada fila es un string independiente. Se accede con dos índices: nombres[fila][columna]
printf("%c", nombres[0][0]);   // Imprime 'A'
printf("%c", nombres[2][3]);   // Imprime 'l' (de Carla)

Recorrido de una matriz de strings:

for (int i = 0; i < 3; i++) {
    printf("Nombre %d: %s\n", i, nombres[i]);
    for (int j = 0; nombres[i][j] != '\0'; j++) {
        printf("  letra %d: %c\n", j, nombres[i][j]);
    }
}
Esta analogía es fundamental cuando más adelante trabajes con argv (argumentos de línea de comandos), que es una matriz de strings.

7. Librería estándar <string.h>

Proporciona funciones esenciales para manipular cadenas.

7.1 strlen() - longitud de la cadena

char texto[] = "Hola";
int len = strlen(texto);   // len = 4 (no cuenta el '\0')

7.2 strcpy() - copiar cadenas

char origen[] = "Mundo";
char destino[20];
strcpy(destino, origen);   // destino = "Mundo"
Precaución: strcpy no verifica que el destino tenga suficiente espacio. Si origen es más largo, se producirá un desbordamiento. Usa strncpy para mayor seguridad.

7.3 strcat() - concatenar cadenas

char mensaje[50] = "Hola ";
char mundo[] = "mundo";
strcat(mensaje, mundo);   // mensaje = "Hola mundo"

7.4 strcmp() y strcmpi() - comparar cadenas

strcmp compara sensible a mayúsculas/minúsculas. strcmpi (no estándar, en algunos compiladores _stricmp) ignora diferencias de caso.

if (strcmp(palabra, "salir") == 0) {
    printf("Son iguales");
}
ComparaciónResultado
strcmp("abc","def") < 0 (negativo)
strcmp("def","abc") > 0 (positivo)
strcmp("abc","abc") 0 (iguales)
strcmpi("HOLA","hola") 0 (iguales, ignorando mayúsculas)

8. Diferencia entre char* y char[] (punto clave)

Es común ver dos formas de declarar strings. Aunque parecen similares, tienen diferencias fundamentales en cuanto a mutabilidad y dónde se almacena la cadena.

8.1 Declaración como arreglo (char arr[])

char arr[] = "texto";
arr[0] = 'T';   // válido, ahora "Texto"

8.2 Declaración como puntero a literal (char* ptr)

char* ptr = "texto";
ptr[0] = 'T';   // ERROR: intenta escribir en memoria de solo lectura
ptr = "otro";    // válido: ahora apunta a otro literal
Representación gráfica:
char arr[] = "hola";
[h][o][l][a][\0] (en la pila, modificable)

char* ptr = "hola";
ptr ----> [h][o][l][a][\0] (en zona de solo lectura, no modificable)

8.3 ¿Cuándo usar cada uno?

Error común: Intentar modificar un literal a través de un puntero. El compilador puede no advertirlo, pero en ejecución el programa fallará. Siempre que necesites modificar el texto, usa un arreglo.

8.4 Parámetros de funciones

En los parámetros de una función, char arr[] y char* arr son equivalentes (ambos reciben un puntero). Sin embargo, la distinción anterior aplica al contenido apuntado.

void funcion(char* param) {   // también podría ser char param[]
    param[0] = 'X';   // válido solo si param apunta a memoria modificable
}

Si llamas a funcion("texto"), el literal no podrá modificarse. Si llamas con un arreglo modificable, sí podrás.

9. Ejemplo integrador: procesamiento de una lista de nombres

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

#define CANT 3
#define LONG 50

void limpiarBuffer() {
    int c;
    while ((c = getchar()) != '\n' && c != EOF);
}

int main() {
    char personas[CANT][LONG];
    int i;

    for (i = 0; i < CANT; i++) {
        printf("Nombre %d: ", i+1);
        fgets(personas[i], LONG, stdin);
        personas[i][strcspn(personas[i], "\n")] = '\0';
    }

    printf("\nLista de nombres:\n");
    for (i = 0; i < CANT; i++) {
        printf("%d. %s (longitud: %zu)\n", i+1, personas[i], strlen(personas[i]));
    }
    return 0;
}

10. Resumen y buenas prácticas