Paso de parámetros

Intermedio
C
C
Actualizado: 10/05/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Paso por valor (copia del valor)

Cuando trabajamos con funciones en C, necesitamos entender cómo se transmiten los datos entre ellas. El paso por valor es el mecanismo predeterminado que utiliza C para pasar argumentos a las funciones.

En el paso por valor, cuando llamamos a una función y le pasamos una variable como argumento, C crea una copia del valor de esa variable. La función trabaja con esta copia, no con la variable original. Esto significa que cualquier modificación que hagamos dentro de la función afectará únicamente a la copia, manteniendo intacta la variable original.

Veamos un ejemplo sencillo para entender este concepto:

#include <stdio.h>

void duplicar(int numero) {
    numero = numero * 2;  // Modificamos la copia local
    printf("Dentro de la función: %d\n", numero);
}

int main() {
    int valor = 5;
    
    printf("Antes de llamar a la función: %d\n", valor);
    duplicar(valor);
    printf("Después de llamar a la función: %d\n", valor);
    
    return 0;
}

Si ejecutamos este programa, obtendremos la siguiente salida:

Antes de llamar a la función: 5
Dentro de la función: 10
Después de llamar a la función: 5

¿Qué ha ocurrido? Cuando llamamos a duplicar(valor), C crea una copia del valor 5 y la asigna al parámetro numero de la función. Dentro de la función, multiplicamos numero por 2, obteniendo 10. Sin embargo, este cambio solo afecta a la copia local. Cuando la función termina, esta copia desaparece y nuestra variable original valor en main() permanece sin cambios.

Ventajas del paso por valor

El paso por valor ofrece varias ventajas importantes:

  • Seguridad: La variable original está protegida contra cambios accidentales.
  • Aislamiento: La función trabaja con su propia copia, lo que evita efectos secundarios no deseados.
  • Simplicidad: Es fácil de entender y razonar sobre el comportamiento del código.

Comportamiento con diferentes tipos de datos

El paso por valor funciona de la misma manera con todos los tipos de datos básicos en C:

#include <stdio.h>

void modificar(int entero, float decimal, char caracter) {
    entero = 100;
    decimal = 3.14;
    caracter = 'Z';
    
    printf("Dentro de la función: %d, %.2f, %c\n", entero, decimal, caracter);
}

int main() {
    int a = 1;
    float b = 1.1;
    char c = 'A';
    
    printf("Antes: %d, %.2f, %c\n", a, b, c);
    modificar(a, b, c);
    printf("Después: %d, %.2f, %c\n", a, b, c);
    
    return 0;
}

La salida será:

Antes: 1, 1.10, A
Dentro de la función: 100, 3.14, Z
Después: 1, 1.10, A

Como puedes ver, ninguno de los valores originales cambia, independientemente del tipo de dato.

Consideraciones de memoria

Es importante entender que el paso por valor implica la creación de copias, lo que consume memoria adicional. Para tipos de datos básicos como int, float o char, esto no suele ser un problema ya que ocupan poco espacio.

Sin embargo, cuando trabajamos con estructuras grandes, el paso por valor puede resultar ineficiente:

#include <stdio.h>

struct Persona {
    char nombre[50];
    int edad;
    float altura;
    // Imagina muchos más campos...
};

void mostrarPersona(struct Persona p) {
    // Aquí se crea una copia completa de la estructura
    printf("Nombre: %s, Edad: %d, Altura: %.2f\n", p.nombre, p.edad, p.altura);
}

int main() {
    struct Persona persona = {"Ana", 25, 1.70};
    mostrarPersona(persona);
    return 0;
}

En este ejemplo, al llamar a mostrarPersona(), C crea una copia completa de la estructura persona, lo que puede ser costoso en términos de memoria y rendimiento si la estructura es grande.

Paso por valor con arrays

Los arrays en C tienen un comportamiento especial. Aunque parezca que se pasan por valor, en realidad se pasa la dirección del primer elemento:

#include <stdio.h>

void modificarArray(int numeros[5]) {
    numeros[0] = 100;  // ¡Esto SÍ modifica el array original!
    printf("Dentro de la función: %d\n", numeros[0]);
}

int main() {
    int miArray[5] = {1, 2, 3, 4, 5};
    
    printf("Antes: %d\n", miArray[0]);
    modificarArray(miArray);
    printf("Después: %d\n", miArray[0]);
    
    return 0;
}

La salida será:

Antes: 1
Dentro de la función: 100
Después: 100

Esto ocurre porque, a diferencia de las variables simples, los arrays no se pasan realmente por valor. En su lugar, C pasa la dirección del primer elemento del array, lo que permite a la función modificar el array original. Este comportamiento es importante entenderlo para evitar confusiones.

Cuándo usar el paso por valor

El paso por valor es ideal cuando:

  • Queremos proteger nuestros datos originales de modificaciones.
  • Trabajamos con tipos de datos simples y pequeños.
  • Necesitamos claridad en el código sobre qué valores pueden cambiar.

En resumen, el paso por valor en C crea una copia del valor original, permitiendo que las funciones trabajen con datos sin afectar a las variables originales. Esto proporciona seguridad y aislamiento, aunque implica un pequeño costo en memoria por la creación de copias.

¿Te está gustando esta lección?

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Cambios no afectan variable original

Una de las características fundamentales del paso por valor en C es que los cambios realizados dentro de una función no tienen ningún efecto sobre la variable original que se pasó como argumento. Esta propiedad es crucial para entender el comportamiento de las funciones y para escribir código predecible y seguro.

Cuando pasamos una variable a una función en C, se crea una copia independiente en la memoria. Esta copia existe únicamente dentro del ámbito de la función y se destruye automáticamente cuando la función termina su ejecución. Cualquier modificación que hagamos a esta copia local no afectará a la variable original que existe en la función llamante.

Veamos este concepto con un ejemplo práctico:

#include <stdio.h>

void intentarModificar(int edad) {
    printf("Dentro de la función (antes): %d\n", edad);
    
    // Intentamos modificar el parámetro
    edad = 30;
    
    printf("Dentro de la función (después): %d\n", edad);
    // Al salir de la función, esta copia de 'edad' se destruye
}

int main() {
    int edadUsuario = 25;
    
    printf("En main (antes de llamar): %d\n", edadUsuario);
    
    intentarModificar(edadUsuario);
    
    printf("En main (después de llamar): %d\n", edadUsuario);
    
    return 0;
}

La salida de este programa será:

En main (antes de llamar): 25
Dentro de la función (antes): 25
Dentro de la función (después): 30
En main (después de llamar): 25

Como puedes observar, aunque dentro de la función intentarModificar() cambiamos el valor de edad a 30, la variable original edadUsuario en la función main() mantiene su valor original de 25. Esto demuestra claramente el aislamiento que proporciona el paso por valor.

Múltiples operaciones en la función

Incluso si realizamos múltiples operaciones complejas sobre el parámetro dentro de la función, la variable original permanecerá intacta:

#include <stdio.h>

void operacionesComplejas(float precio) {
    // Realizamos varias operaciones
    precio = precio * 1.21;  // Añadimos IVA
    precio = precio - 5;     // Aplicamos descuento
    precio = precio * 2;     // Duplicamos para una oferta
    
    printf("Precio final calculado en la función: %.2f\n", precio);
}

int main() {
    float precioProducto = 100.0;
    
    printf("Precio original: %.2f\n", precioProducto);
    operacionesComplejas(precioProducto);
    printf("Precio después de llamar a la función: %.2f\n", precioProducto);
    
    return 0;
}

La salida mostrará:

Precio original: 100.00
Precio final calculado en la función: 236.20
Precio después de llamar a la función: 100.00

A pesar de todas las operaciones realizadas dentro de la función, el valor de precioProducto en main() sigue siendo 100.00.

Comportamiento con diferentes tipos de datos

Esta característica del paso por valor se aplica a todos los tipos de datos básicos en C:

#include <stdio.h>

void modificarValores(int a, float b, char c, double d) {
    a += 10;
    b *= 2;
    c = 'X';
    d /= 2;
    
    printf("Valores dentro de la función: %d, %.2f, %c, %.2f\n", 
           a, b, c, d);
}

int main() {
    int entero = 5;
    float decimal = 3.5;
    char caracter = 'A';
    double doble = 100.0;
    
    printf("Valores originales: %d, %.2f, %c, %.2f\n", 
           entero, decimal, caracter, doble);
           
    modificarValores(entero, decimal, caracter, doble);
    
    printf("Valores después de la función: %d, %.2f, %c, %.2f\n", 
           entero, decimal, caracter, doble);
    
    return 0;
}

La salida confirmará que ninguno de los valores originales cambia:

Valores originales: 5, 3.50, A, 100.00
Valores dentro de la función: 15, 7.00, X, 50.00
Valores después de la función: 5, 3.50, A, 100.00

Implicaciones para el diseño de programas

Esta característica del paso por valor tiene importantes implicaciones para el diseño de nuestros programas:

  • Seguridad de datos: Las funciones no pueden modificar accidentalmente variables que no deberían cambiar.
  • Predictibilidad: El comportamiento del código es más fácil de predecir, ya que sabemos que las variables no cambiarán a menos que explícitamente asignemos un nuevo valor.
  • Depuración más sencilla: Al tener menos efectos secundarios, es más fácil identificar dónde se producen los cambios en los datos.

Casos prácticos

Veamos un ejemplo práctico donde esta característica es útil:

#include <stdio.h>

float calcularImpuesto(float monto, float tasa) {
    // Podemos manipular los parámetros sin preocuparnos
    // de afectar a las variables originales
    float impuesto = monto * (tasa / 100);
    return impuesto;
}

int main() {
    float precioProducto = 1000.0;
    float tasaImpuesto = 21.0;  // 21%
    
    // Calculamos el impuesto sin modificar nuestras variables originales
    float impuestoCalculado = calcularImpuesto(precioProducto, tasaImpuesto);
    
    printf("Precio: %.2f\n", precioProducto);
    printf("Tasa de impuesto: %.1f%%\n", tasaImpuesto);
    printf("Impuesto: %.2f\n", impuestoCalculado);
    printf("Total: %.2f\n", precioProducto + impuestoCalculado);
    
    return 0;
}

En este ejemplo, la función calcularImpuesto() puede manipular libremente los valores de monto y tasa sin preocuparse de afectar a las variables originales en main().

Limitaciones y consideraciones

Es importante entender que esta característica tiene algunas limitaciones:

  • Estructuras grandes: Pasar estructuras grandes por valor puede ser ineficiente en términos de memoria y rendimiento.
  • Arrays: Como se mencionó anteriormente, los arrays tienen un comportamiento especial y no siguen estrictamente el principio de paso por valor.
  • Necesidad de retornar valores: Si necesitamos que una función modifique una variable, debemos utilizar el valor de retorno o técnicas más avanzadas como punteros.

En resumen, el hecho de que los cambios realizados dentro de una función no afecten a las variables originales es una característica fundamental del paso por valor en C. Esta propiedad proporciona seguridad y predictibilidad a nuestro código, aunque también implica que debemos utilizar valores de retorno cuando queremos que una función compute y devuelva un nuevo valor basado en sus parámetros.

Usar return para devolver valores

Cuando trabajamos con funciones en C y utilizamos el paso por valor, necesitamos un mecanismo para obtener resultados de nuestras funciones, ya que los cambios realizados a los parámetros no afectan a las variables originales. Aquí es donde entra en juego la instrucción return, que nos permite devolver valores desde una función al código que la llamó.

La instrucción return cumple dos propósitos fundamentales: finalizar la ejecución de la función y proporcionar un valor de resultado al punto donde se realizó la llamada.

Sintaxis básica

La sintaxis para devolver un valor es simple:

tipo_retorno nombre_funcion(parametros) {
    // Código de la función
    return expresion;
}

Donde expresion debe ser compatible con el tipo_retorno declarado para la función.

Veamos un ejemplo sencillo:

#include <stdio.h>

int sumar(int a, int b) {
    int resultado = a + b;
    return resultado;  // Devolvemos el valor calculado
}

int main() {
    int x = 5;
    int y = 3;
    int suma = sumar(x, y);  // Capturamos el valor devuelto
    
    printf("La suma de %d y %d es: %d\n", x, y, suma);
    
    return 0;
}

En este ejemplo, la función sumar() recibe dos enteros, calcula su suma y devuelve el resultado. En main(), capturamos este valor devuelto en la variable suma.

Tipos de datos de retorno

Podemos devolver cualquier tipo de dato básico en C:

#include <stdio.h>

int duplicar(int numero) {
    return numero * 2;
}

float calcularArea(float radio) {
    return 3.14159 * radio * radio;
}

char obtenerCalificacion(int puntuacion) {
    if (puntuacion >= 90) return 'A';
    if (puntuacion >= 80) return 'B';
    if (puntuacion >= 70) return 'C';
    if (puntuacion >= 60) return 'D';
    return 'F';
}

int main() {
    int num = 5;
    float r = 2.5;
    int score = 85;
    
    printf("El doble de %d es: %d\n", num, duplicar(num));
    printf("El área de un círculo con radio %.1f es: %.2f\n", r, calcularArea(r));
    printf("Con %d puntos, la calificación es: %c\n", score, obtenerCalificacion(score));
    
    return 0;
}

Uso directo del valor devuelto

No es necesario almacenar el valor devuelto en una variable intermedia. Podemos usar el resultado directamente:

#include <stdio.h>

float convertirCelsiusAFahrenheit(float celsius) {
    return (celsius * 9/5) + 32;
}

int main() {
    float tempC = 25.0;
    
    // Uso directo en una expresión
    printf("%.1f°C equivale a %.1f°F\n", tempC, convertirCelsiusAFahrenheit(tempC));
    
    // Uso en una condición
    if (convertirCelsiusAFahrenheit(tempC) > 90) {
        printf("¡Hace mucho calor!\n");
    } else {
        printf("La temperatura es agradable.\n");
    }
    
    return 0;
}

Funciones que realizan múltiples cálculos

Cuando una función realiza varios cálculos, podemos usar variables intermedias para mejorar la legibilidad:

#include <stdio.h>

float calcularPrecioFinal(float precioBase) {
    float precioConIVA = precioBase * 1.21;  // Añadimos 21% de IVA
    float precioConDescuento = precioConIVA * 0.95;  // Aplicamos 5% de descuento
    
    return precioConDescuento;  // Devolvemos el precio final
}

int main() {
    float precio = 100.0;
    float precioFinal = calcularPrecioFinal(precio);
    
    printf("Precio base: %.2f€\n", precio);
    printf("Precio final (con IVA y descuento): %.2f€\n", precioFinal);
    
    return 0;
}

Retorno anticipado

Podemos usar return para salir de una función antes de llegar al final, lo que es útil para manejar casos especiales:

#include <stdio.h>

float dividir(float a, float b) {
    // Verificamos si el divisor es cero
    if (b == 0) {
        printf("Error: División por cero no permitida\n");
        return 0;  // Retorno anticipado en caso de error
    }
    
    // Si llegamos aquí, es seguro dividir
    return a / b;
}

int main() {
    float resultado1 = dividir(10.0, 2.0);
    float resultado2 = dividir(5.0, 0.0);
    
    printf("10.0 / 2.0 = %.2f\n", resultado1);
    printf("5.0 / 0.0 = %.2f (valor de retorno por error)\n", resultado2);
    
    return 0;
}

Combinando múltiples funciones

Podemos combinar varias funciones que devuelven valores para resolver problemas más complejos:

#include <stdio.h>

float calcularAreaRectangulo(float largo, float ancho) {
    return largo * ancho;
}

float calcularPerimetroRectangulo(float largo, float ancho) {
    return 2 * (largo + ancho);
}

float calcularCostoMaterial(float area, float precioPorMetroCuadrado) {
    return area * precioPorMetroCuadrado;
}

int main() {
    float largo = 4.5;
    float ancho = 2.8;
    float precioPorM2 = 15.75;
    
    float area = calcularAreaRectangulo(largo, ancho);
    float perimetro = calcularPerimetroRectangulo(largo, ancho);
    float costo = calcularCostoMaterial(area, precioPorM2);
    
    printf("Dimensiones del rectángulo: %.1f x %.1f metros\n", largo, ancho);
    printf("Área: %.2f m²\n", area);
    printf("Perímetro: %.2f m\n", perimetro);
    printf("Costo del material: %.2f€\n", costo);
    
    return 0;
}

Funciones sin valor de retorno (void)

No todas las funciones necesitan devolver un valor. Cuando una función no devuelve nada, usamos el tipo void:

#include <stdio.h>

// Esta función no devuelve ningún valor
void mostrarMenu() {
    printf("===== MENÚ PRINCIPAL =====\n");
    printf("1. Ver datos\n");
    printf("2. Insertar nuevo registro\n");
    printf("3. Modificar registro\n");
    printf("4. Eliminar registro\n");
    printf("0. Salir\n");
    printf("==========================\n");
}

int main() {
    mostrarMenu();  // Llamamos a la función sin capturar ningún valor
    
    return 0;
}

En funciones void, podemos usar return; (sin valor) para terminar la ejecución anticipadamente.

Buenas prácticas al usar return

Para aprovechar al máximo la instrucción return, considera estas recomendaciones:

  • Coherencia en los tipos: Asegúrate de que el tipo de dato que devuelves coincida con el declarado en la función.
  • Nombres descriptivos: Usa nombres de funciones que indiquen claramente qué valor se está devolviendo.
  • Una responsabilidad por función: Cada función debería tener un propósito claro y devolver un resultado relacionado con ese propósito.
  • Documentación: Comenta brevemente qué devuelve la función, especialmente si el valor tiene un significado especial.
#include <stdio.h>

// Devuelve 1 si el número es primo, 0 en caso contrario
int esPrimo(int numero) {
    // Los números menores que 2 no son primos
    if (numero < 2) {
        return 0;
    }
    
    // Verificamos si es divisible por algún número entre 2 y su raíz cuadrada
    for (int i = 2; i * i <= numero; i++) {
        if (numero % i == 0) {
            return 0;  // No es primo
        }
    }
    
    return 1;  // Es primo
}

int main() {
    int num = 17;
    
    if (esPrimo(num)) {
        printf("%d es un número primo\n", num);
    } else {
        printf("%d no es un número primo\n", num);
    }
    
    return 0;
}

En resumen, la instrucción return es fundamental para obtener resultados de funciones cuando usamos paso por valor. Nos permite calcular valores dentro de una función y luego utilizarlos en el código que la llamó, manteniendo la seguridad y predictibilidad que ofrece el paso por valor.

Aprendizajes de esta lección

  • Comprender el concepto de paso por valor y cómo afecta a las variables originales.
  • Identificar las ventajas y limitaciones del paso por valor en C.
  • Reconocer el comportamiento especial de los arrays en el paso por valor.
  • Aprender a utilizar la instrucción return para devolver valores desde funciones.
  • Aplicar buenas prácticas en el diseño de funciones con valores de retorno.

Completa C y certifícate

Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración