Funciones en C y C++. En este capítulo aprenderás a dividir tu programa en bloques lógicos y reutilizables llamados funciones. Las funciones son esenciales para escribir código más limpio, modular y fácil de mantener. Permiten separar responsabilidades, evitar repeticiones y facilitar la depuración. Veremos cómo declarar, definir y utilizar funciones, cómo pasarles datos a través de parámetros, cómo devolver valores con return
, y también entenderemos la diferencia entre variables locales y globales. Además, exploraremos funciones que no devuelven nada (void
), funciones con y sin argumentos, y cómo organizar correctamente el flujo de un programa a través de múltiples llamadas a funciones. Manos a la obra!!!!
4.1 Definición y declaración de funciones en C
¿Qué es una función?
Una función en C es un bloque de código reutilizable que realiza una tarea específica. Permite dividir un programa en pequeñas secciones más manejables. C siempre comienza ejecutando la función principal main()
, pero puedes definir tantas funciones auxiliares como necesites.
Ventajas de usar funciones:
- Mejora la organización del código.
- Evita repeticiones innecesarias.
- Facilita la depuración.
- Aumenta la reutilización de código.
- Hace que el programa sea más modular.
Estructura de una función en C
Una función típica en C consta de tres partes:
- Declaración (prototipo): Le indica al compilador la existencia de una función antes de usarla.
- Definición: Es donde se escribe el cuerpo de la función.
- Llamada: Es cuando se ejecuta la función desde otra parte del código (por ejemplo, desde
main()
).
Declaración de la función (Prototipo)
Un prototipo es una definición previa de una función que informa al compilador sobre:
- El tipo de retorno.
- El nombre de la función.
- Los tipos y número de parámetros.
En la definición de un prototipo no se escribe el contenido de la función, se indica únicamente la estructura con los datos que va a devolver, cual es el nombre de la función y qué típos y parámetros va a devolver. La definición del contenido se realizará más adelante.
Sintaxis:
tipo_retorno nombre_funcion(tipo1 parametro1, tipo2 parametro2, ...);
Ejemplo:
int sumar(int a, int b);
– Definición de la función
Aquí es donde realmente se escribe el código que ejecuta la función. Debe coincidir exactamente con el prototipo (si existe uno), o sea, debe tener el mismo tipo de dato que devuelve, nombre de función y tipo y parámetros que se le pasarán a la función.
Sintaxis:
tipo_retorno nombre_funcion(tipo1 parametro1, tipo2 parametro2, ...) {
// instrucciones
return valor; // si no es void
}
Ejemplo:
int sumar(int a, int b) {
int resultado = a + b;
return resultado;
}
– Llamada a la función
Una vez definida la función, puedes invocarla (llamarla) desde main()
u otra función.
Ejemplo:
#include <stdio.h>
int sumar(int a, int b); // declaración del prototipo de la función
int main() {
int x = 5, y = 7;
int resultado = sumar(x, y); // llamada a la función
printf("La suma es: %d\n", resultado);
return 0;
}
int sumar(int a, int b) { // definición de la función
return a + b;
}
@Tip. En C, si defines una función después de main()
, necesitas declarar su prototipo antes de main()
para que el compilador sepa que existe. Si la defines antes de main()
, puedes omitir el prototipo.
Funciones que no devuelven valor: void
Si una función no necesita devolver un resultado, puedes usar void
como tipo de retorno.
Ejemplo:
void saludar() {
printf("Hola, bienvenido al programa.\n");
}
int main() {
saludar(); // llamada
return 0;
}
También puedes definir funciones sin parámetros, si no requieren datos externos para trabajar.
Funciones sin parámetros
Ejemplo:
int obtenerCinco() {
return 5;
}
int main() {
printf("La función devolvió: %d\n", obtenerCinco());
return 0;
}
Resumen:
Elemento | Propósito |
---|---|
Declaración | Informa al compilador de la existencia de la función |
Definición | Escribe el código real que ejecuta la función |
Llamada | Ejecuta la función desde otro punto del programa |
void | Se usa si la función no necesita retornar un valor |
Parámetros | Permiten enviar datos a la función para que los use internamente |
4.2 Parámetros y argumentos en funciones
¿Qué son los parámetros y argumentos?
Cuando defines una función en C, puedes especificar parámetros, que son variables locales a la función utilizadas para recibir valores externos. Estos valores se pasan cuando se llama a la función, y en ese momento se denominan argumentos.
- Parámetros: Son las variables que aparecen en la definición y declaración de la función.
- Argumentos: Son los valores reales que se pasan a la función en el momento de su invocación.
Ejemplo básico con parámetros y argumentos
#include <stdio.h>
void mostrarSuma(int a, int b); // declaración
int main() {
int x = 10, y = 20;
mostrarSuma(x, y); // argumentos: x, y
return 0;
}
void mostrarSuma(int a, int b) { // parámetros: a, b
int suma = a + b;
printf("La suma es: %d\n", suma);
}
Explicación:
mostrarSuma
es una función que toma dos enteros como parámetros.- Al llamarla con
x
yy
, se están pasando argumentos. - Internamente, la función trabaja con
a
yb
(copias dex
yy
).
Tipos de paso de parámetros
En C existen dos formas principales de pasar valores a una función:
– Paso por valor (por defecto en C)
Cuando pasamos variables como parámetros, C crea copias de esos valores. Cualquier cambio que hagamos a los parámetros no afecta a las variables originales.
Ejemplo:
void incrementar(int num) {
num++;
printf("Dentro de la función: %d\n", num);
}
int main() {
int valor = 5;
incrementar(valor);
printf("Fuera de la función: %d\n", valor);
return 0;
}
Resultado:
Dentro de la función: 6
Fuera de la función: 5
El valor de valor
no cambia fuera de la función porque se pasó por valor.
– Paso por referencia (mediante punteros)
Para modificar una variable original dentro de una función, debemos pasar su dirección de memoria mediante un puntero. (La parte de punteros la veremos con mayor profundidad en el capítulo 6)
Ejemplo:
void incrementar(int *num) {
(*num)++; // desreferencia y suma
}
int main() {
int valor = 5;
incrementar(&valor); // pasamos la dirección
printf("Nuevo valor: %d\n", valor);
return 0;
}
Resultado:
Nuevo valor: 6
Al pasar la dirección de valor
, la función trabaja directamente sobre la variable original.
Funciones con múltiples parámetros
Las funciones en C pueden tener tantos parámetros como necesites. Solo tienes que asegúrate de que coincidan en tipo y cantidad con los argumentos al invocar la función.
Ejemplo:
float calcularPromedio(int a, int b, int c) {
return (a + b + c) / 3.0;
}
int main() {
float promedio = calcularPromedio(8, 9, 10);
printf("Promedio: %.2f\n", promedio);
return 0;
}
Funciones sin parámetros
En algunos casos, una función puede no requerir ningún parámetro.
void saludar() {
printf("Hola desde una función sin parámetros\n");
}
int main() {
saludar();
return 0;
}
Resumenindo:
Concepto | Descripción |
---|---|
Parámetro | Variable usada en la definición de una función |
Argumento | Valor que se pasa al llamar una función |
Paso por valor | Se pasa una copia del valor. La variable original no se modifica |
Paso por referencia | Se pasa la dirección de memoria. La variable original sí se modifica |
Funciones sin parámetros | No requieren datos externos |
4.3 Valor de retorno de funciones
¿Qué es el valor de retorno?
En C, una función puede devolver un valor al lugar desde donde fue llamada. Esto permite que las funciones no solo realicen tareas, sino que también produzcan resultados que pueden ser almacenados o utilizados directamente en otras expresiones.
Una función puede devolver:
- Un número
- Un carácter
- Un valor lógico
- Un puntero
- E incluso, ningún valor (en cuyo caso su tipo es
void
)
Sintaxis general
tipo nombreFuncion(parámetros) {
// cuerpo de la función
return valor;
}
tipo
: el tipo de dato que devuelve la función (por ejemplo,int
,float
,char
, etc.)return
: instrucción que envía un valor de vuelta a quien llamó a la función
Ejemplo básico con int
como valor de retorno
#include <stdio.h>
int sumar(int a, int b) {
return a + b;
}
int main() {
int resultado = sumar(3, 7);
printf("Resultado: %d\n", resultado);
return 0;
}
Salida:
Resultado: 10
La función sumar
devuelve un entero. En main()
, almacenamos ese valor en la variable resultado
para luego manipularlo, en este caso imprimirlo por pantalla.
Funciones que devuelven float
#include <stdio.h>
float promedio(float a, float b) {
return (a + b) / 2;
}
int main() {
float media = promedio(8.5, 9.5);
printf("Promedio: %.2f\n", media);
return 0;
}
Salida:
Promedio: 9.00
Funciones que devuelven char
#include <stdio.h>
char obtenerInicial(char nombre[]) {
return nombre[0];
}
int main() {
char inicial = obtenerInicial("Carlos");
printf("Inicial: %c\n", inicial);
return 0;
}
Salida:
Inicial: C
Uso del valor devuelto directamente
No es obligatorio almacenar el valor devuelto en una variable. Puedes utilizarlo directamente:
#include <stdio.h>
int cuadrado(int n) {
return n * n;
}
int main() {
printf("El cuadrado de 5 es: %d\n", cuadrado(5));
return 0;
}
Funciones sin valor de retorno (void
)
Cuando una función no devuelve ningún valor, se declara con el tipo void
y no utiliza return
con un valor.
#include <stdio.h>
void saludar() {
printf("Hola, mundo\n");
}
int main() {
saludar(); // simplemente ejecuta, no retorna nada
return 0;
}
return
sin valor en funciones void
En funciones void
, puedes usar return;
por sí solo para terminar la función antes de tiempo:
void verificarEdad(int edad) {
if (edad < 18) {
printf("Eres menor de edad\n");
return;
}
printf("Eres mayor de edad\n");
}
Consideraciones importantes
- El tipo de dato del
return
debe coincidir con el tipo declarado de la función. - Si no hay instrucción
return
, pero la función tiene un tipo que no es void, el comportamiento es indefinido. - Puedes usar
return
para salir anticipadamente de una función.
4.4 Funciones con múltiples valores (usando punteros)
¿Por qué usar punteros para múltiples valores?
En C, una función solo puede devolver un único valor directamente mediante la instrucción return
. Sin embargo, en muchos casos, necesitamos que una función nos devuelva más de un resultado.
Como C no permite retornar múltiples valores de forma directa como en otros lenguajes (Python, por ejemplo), la forma de lograrlo es utilizando punteros como parámetros de salida. Esto nos permite modificar variables definidas en la función que llama.
¿Cómo funciona?
Cuando pasamos una variable por referencia (es decir, usando un puntero), cualquier cambio realizado sobre esa variable dentro de la función afecta directamente al valor original fuera de la función.
Ejemplo práctico:
Vamos a realizar una función para obtener el cociente y el residuo de una división. Al ser más de un valor lo que necesitamos devolver ya no nos vale con el tipo de dato que devuelve la función, por lo que para ello pasámos los datos por referencia (la dirección de memoria de las variables cociente y resto)
#include <stdio.h>
void dividir(int a, int b, int *cociente, int *residuo) {
*cociente = a / b;
*residuo = a % b;
}
int main() {
int a = 17, b = 5;
int c, r;
dividir(a, b, &c, &r);
printf("Cociente: %d, Residuo: %d\n", c, r);
return 0;
}
En el emplo tenemos que:
*cociente
y*residuo
son punteros que apuntan a las variablesc
yr
enmain
.- Al hacer
*cociente = a / b;
, estamos modificando el valor dec
directamente. - Se pasan las direcciones con
&c
y&r
al llamar adividir
.
Veamos otro ejemplo de como obtener el máximo y el mínimo entre dos números
#include <stdio.h>
void maxMin(int x, int y, int *max, int *min) {
if (x > y) {
*max = x;
*min = y;
} else {
*max = y;
*min = x;
}
}
int main() {
int a = 8, b = 3;
int mayor, menor;
maxMin(a, b, &mayor, &menor);
printf("Mayor: %d, Menor: %d\n", mayor, menor);
return 0;
}
Punteros como mecanismo de «retorno múltiple»
En realidad, la función no devuelve nada directamente (su tipo es void
), pero al modificar los valores que apunta cada puntero, conseguimos múltiples «valores de salida».
Esto también sirve para:
- Cálculos con varios resultados (como coordenadas)
- Intercambio de valores entre variables
- Estructuras que deben llenarse parcialmente
- Leer múltiples entradas desde una misma función
4.5 Alcance y duración de las variables
Comprender el alcance (scope) y la duración (lifetime) de las variables en C es fundamental. Este conocimiento es especialmente importante cuando se trabaja con funciones, punteros y estructuras de control.
Alcance de las variables
El alcance de una variable indica la parte del programa donde dicha variable es visible y accesible.
– Alcance local
Las variables declaradas dentro de una función o bloque ({}
) tienen alcance local. Solo pueden ser usadas dentro de ese bloque.
#include <stdio.h>
void ejemplo() {
int x = 10; // x es local a la función ejemplo
printf("%d\n", x);
}
int main() {
ejemplo();
// printf("%d\n", x); // ERROR: x no es visible aquí
return 0;
}
– Alcance global
Las variables declaradas fuera de cualquier función tienen alcance global. Son accesibles desde cualquier función en el mismo archivo (o incluso desde otros archivos con extern
).
#include <stdio.h>
int x = 5; // Variable global
void mostrar() {
printf("x vale: %d\n", x);
}
int main() {
x = 10;
mostrar(); // Imprime 10
return 0;
}
– Alcance de bloque
Las variables pueden declararse dentro de bloques como condicionales o bucles. Solo son accesibles dentro de ese bloque.
if (1) {
int y = 42;
printf("%d\n", y);
}
// printf("%d\n", y); // ERROR: y ya no existe aquí
Duración de las variables
La duración de una variable determina cuánto tiempo existe en memoria.
– Automática (por defecto)
Las variables locales tienen duración automática. Se crean al entrar en el bloque y se destruyen al salir.
void f() {
int a = 3; // Automática: se crea cada vez que se llama la función
}
– Estática
Las variables con la palabra clave static
mantienen su valor entre llamadas a funciones. Solo se inicializan una vez.
#include <stdio.h>
void contarLlamadas() {
static int veces = 0;
veces++;
printf("Esta función se ha llamado %d veces\n", veces);
}
int main() {
contarLlamadas();
contarLlamadas();
contarLlamadas();
return 0;
}
Resultado:
Esta función se ha llamado 1 veces
Esta función se ha llamado 2 veces
Esta función se ha llamado 3 veces
– Duración dinámica (con malloc)
Las variables creadas con malloc
, calloc
, etc., tienen duración dinámica y deben liberarse manualmente con free()
.
Esto se cubre más adelante cuando trabajemos con memoria dinámica.
Reglas comunes a tener en cuenta
- Evita variables globales innecesarias, pueden causar conflictos en programas grandes.
- Utiliza variables locales cuando no se necesite persistencia entre llamadas.
- Usa
static
solo cuando sea necesario mantener estado. - Declara las variables lo más cerca posible de su uso para mejorar la legibilidad.
- Evita el uso de variables con el mismo nombre en distintos alcances.
Veamos con un ejemplo final comparativo su uso.
#include <stdio.h>
int global = 100; // Variable global
void funcion() {
static int llamada = 0; // se mantiene entre llamadas
int local = 5; // se crea y destruye en cada llamada
llamada++;
printf("Llamada %d - Local: %d, Global: %d\n", llamada, local, global);
}
int main() {
funcion();
funcion();
funcion();
return 0;
}
Recuerda.
Término | ¿Qué significa? | Cuándo se usa |
---|---|---|
Alcance | Zona del programa donde la variable es visible | Local, global, de bloque |
Duración | Tiempo de vida de la variable en memoria | Automática, estática, dinámica |
static | Mantiene el valor entre llamadas | Contadores, estados internos |
extern | Permite usar variables globales de otros archivos | En programas grandes, múltiples archivos |
4.6 Recursividad
La recursividad es un concepto fundamental en programación que consiste en que una función se llama a sí misma para resolver un problema que puede dividirse en subproblemas similares de menor tamaño. En C, esto se logra permitiendo que una función invoque su propio nombre desde su cuerpo.
¿Por qué usar recursividad?
La recursividad es especialmente útil cuando:
- El problema tiene una estructura divisible en subproblemas más pequeños, similares entre sí.
- Es más natural o legible expresar la solución de forma recursiva (por ejemplo, estructuras de árbol, algoritmos matemáticos como factorial, Fibonacci, etc.).
- La alternativa iterativa sería más complicada o menos clara.
Estructura de una función recursiva
Una función recursiva debe cumplir con dos reglas esenciales:
- Caso base: condición que detiene la recursión (evita llamadas infinitas).
- Llamada recursiva: la función se llama a sí misma con un subproblema más pequeño.
ipo nombre_funcion(parámetros) {
if (condición_base)
return resultado;
else
return nombre_funcion(subproblema);
}
Ejemplo clásico: Factorial de un número
El factorial de un número n
se define como:
n! = n * (n-1)!
- Caso base:
0! = 1
#include <stdio.h>
int factorial(int n) {
if (n == 0) // Caso base
return 1;
else
return n * factorial(n - 1); // Llamada recursiva
}
int main() {
int resultado = factorial(5);
printf("El factorial de 5 es: %d\n", resultado);
return 0;
}
Salida:
El factorial de 5 es: 120
Otro ejemplo: Serie de Fibonacci
La serie de Fibonacci es: 0, 1, 1, 2, 3, 5, 8, ...
, donde cada término es la suma de los dos anteriores:
fib(0) = 0
fib(1) = 1
fib(n) = fib(n-1) + fib(n-2)
#include <stdio.h>
int fibonacci(int n) {
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
for (int i = 0; i < 10; i++) {
printf("%d ", fibonacci(i));
}
return 0;
}
Salida:
0 1 1 2 3 5 8 13 21 34
Ventajas y desventajas
Ventajas:
- Código más limpio y claro para ciertos problemas.
- Solución natural para problemas de estructura recursiva (árboles, combinaciones, etc.).
Desventajas:
- Puede consumir más memoria (por las llamadas anidadas en la pila).
- Menor rendimiento que versiones iterativas si no se optimiza.
- Riesgo de desbordamiento de pila si no se maneja bien el caso base.
Consejos para usar recursividad
- Asegúrate de que siempre haya un caso base claramente definido.
- Intenta evitar recursión innecesaria si el problema se resuelve fácilmente con un bucle.
Conclusión
En este capítulo aprendimos a utilizar funciones en C para organizar el código de forma modular y reutilizable. Vimos cómo se declaran y definen, cómo retornar valores y cómo pasar parámetros, ya sea por valor o por referencia (con punteros). También exploramos la recursividad como técnica para resolver problemas dividiéndolos en subproblemas similares. Gracias a las funciones, los programas en C se vuelven más claros, estructurados y fáciles de mantener. Sigamos con unos ejercicios prácticos.
Ejercicio 1: Calcular el cuadrado de un número
Enunciado:
Escribe una función llamada cuadrado()
que reciba un número entero como parámetro y devuelva su cuadrado. Luego, desde la función main()
, pide al usuario que introduzca un número y muestra su cuadrado utilizando la función.
Enunciado:
Escribe una función llamada
cuadrado()
que reciba un número entero como parámetro y devuelva su cuadrado. Luego, desde la función main()
, pide al usuario que introduzca un número y muestra su cuadrado utilizando la función.Código:
#include <stdio.h>
int cuadrado(int n) {
return n * n;
}
int main() {
int numero, resultado;
printf("Introduce un número: ");
scanf("%d", &numero);
resultado = cuadrado(numero);
printf("El cuadrado de %d es %d\n", numero, resultado);
return 0;
}
Explicación:
La función cuadrado()
toma un entero y devuelve su cuadrado multiplicándolo por sí mismo. En main()
, usamos scanf()
para leer un número y luego llamamos a la función para obtener y mostrar el resultado. Es un ejemplo básico de paso de parámetros y retorno de valores.
Ejercicio 2: Intercambiar dos números usando paso por referencia
Enunciado:
Escribe una función llamada intercambiar()
que reciba dos punteros a enteros y los intercambie entre sí. Luego, desde main()
, pide dos números al usuario y muestra el resultado después del intercambio.
Enunciado:
Escribe una función llamada
intercambiar()
que reciba dos punteros a enteros y los intercambie entre sí. Luego, desde main()
, pide dos números al usuario y muestra el resultado después del intercambio.Código:
#include <stdio.h>
void intercambiar(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x, y;
printf("Introduce dos números:\n");
printf("x = ");
scanf("%d", &x);
printf("y = ");
scanf("%d", &y);
intercambiar(&x, &y);
printf("Después del intercambio:\n");
printf("x = %d, y = %d\n", x, y);
return 0;
}
Explicación:
La función intercambiar()
utiliza punteros para modificar directamente los valores de x
e y
. Esto demuestra cómo pasar argumentos por referencia permite cambiar valores fuera del alcance local de la función. Se usa una variable temporal para realizar el intercambio.
Ejercicio 2: Intercambiar dos números usando paso por referencia
Enunciado:
Escribe una función llamada intercambiar()
que reciba dos punteros a enteros y los intercambie entre sí. Luego, desde main()
, pide dos números al usuario y muestra el resultado después del intercambio.
Enunciado:
Escribe una función llamada
intercambiar()
que reciba dos punteros a enteros y los intercambie entre sí. Luego, desde main()
, pide dos números al usuario y muestra el resultado después del intercambio.Código:
#include <stdio.h>
void intercambiar(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x, y;
printf("Introduce dos números:\n");
printf("x = ");
scanf("%d", &x);
printf("y = ");
scanf("%d", &y);
intercambiar(&x, &y);
printf("Después del intercambio:\n");
printf("x = %d, y = %d\n", x, y);
return 0;
}
Explicación:
La función intercambiar()
utiliza punteros para modificar directamente los valores de x
e y
. Esto demuestra cómo pasar argumentos por referencia permite cambiar valores fuera del alcance local de la función. Se usa una variable temporal para realizar el intercambio.
Bibliografía del tutorial de C/C++.
- C/C++. Curso de programación. Autor: Miguel Angel Acera (Editorial: Anaya Multimedia)
- C/C++. Curso de programación. Autor: Francisco José Ceballos (Editorial: RA-MA)
- Un recorrido por C++. Autor Bjarne Stroustrup (Editorial: Anaya Multimedia)
- 115 Ejercicios resueltos de programación C++. Autor Jorge Fernando Betancourt e Inma Yolanda Polanco (Editorial: RA-MA)