Escribe un programa que determine la cantidad de combinaciones que se pueden realizar tomando k elementos de un grupo de n elementos, es decir,
Por ejemplo,
La solución es
int i, n, k , nfactorial=1, kfactorial=1, nkfactorial=1;
int combinaciones;
scanf (%d %d,&n,&k);
for (i=1; i<n; i++)
nfactorial=nfactorial*i;
for (i=1; i<k; i++)
kfactorial=kfactorial*i;
for (int i=1; i<n-k; i++)
nkfactorial=nkfactorial*i;
combinaciones=nfactorial/(kfactorial*nkfactorial);
printf ("El número de combinaciones es %d\n",combinaciones");
Si te fijas, las líneas 4-5, 6-7 y 8-9 hacen practicamente lo mismo: calcular el factorial de un número (n, k y n-k respectivamente).
C permite definir funciones, que son subprogramas que llevan a cabo tareas específicas. A modo de ejemplo, si suponemos que existe una función de nombre factorial que recibe un número entero y devuelve su factorial, entonces el programa queda
int n,k,combinaciones;
scanf (%d %d,&n, &k);
combinaciones=factorial(n)/(factorial(k)*factorial(n-k));
printf ("El número de combinaciones es %d\n",combinaciones");
El programa no sólo ha quedado mucho más pequeño, sino que además es más fácil de entender.
la función factorial
El problema ahora es escribir la función factorial:
int factorial (int x)
{
int producto=1, i;
for (i=1; i<=x; i++)
producto=producto*i;
return producto;
}
Línea(s) |
Explicación |
|
5 |
El encabezado de la función especifica qué devuelve la función, el nombre de la función y que es lo que recibe. En este caso, la función se llama factorial, recibe un entero x y su resultado es un entero. |
|
7 a 11 |
Se calcula el factorial con un ciclo for. La variable auxiliar producto contiene el valor. |
|
11 |
El resultado de la función factorial es producto |
El programa completo queda así:
int factorial (int x)
{
int producto=1, i;
for (i=1; i<=x; i++)
producto=producto*i;
return producto;
}
void main()
{
int n, k, comb;
scanf (%d %d,&n, &k);
comb=factorial(n)/(factorial(k)*factorial(n-k));
printf ("El número de combinaciones es %d\n",comb);
}
}
Una función consta de dos partes principales: su encabezado y su cuerpo. El encabezado especifica en forma precisa el nombre de la función, qué parámetros recibe y qué devuelve.
int |
factorial |
(int x) |
|
|
|
tipo |
nombre |
(parámetros) |
El cuerpo de la función lleva a cabo el "trabajo sucio" y tiene como restricción que tiene que devolver un valor cuyo tipo sea el especificado en el encabezamiento. Suele terminar con la instrucción return. Esta instrucción devuelve el valor de la expresión que le acompaña y termina la ejecución de la función. En la figura de la derecha se esquematiza lo que hace una función: recibe parametros y devuelve un resultado. Para el usuario dla función lo único necesario es saber qué hace la función, que devuelve y que recibe, el código del cuerpo dla función no le interesa. La figura muestra algo que no debes olvidar: a pesar de que un función puede tener varias entradas, solo arroja un resultado. No puedes escribir un función que devuelva más de un resultado; en la práctica se puede devolver una referencia a un objeto (como un arreglo) que dentro tenga varios valores. |
|
En la línea 23 se invoca al función factorial. La forma general de invocación a un función es
factorial |
(k) |
|
|
nombre |
expresión(es) |
Cómo la función devuelve un valor, hay que asignarlo a una variable o usarlo dentro de una expresión:
int total=factorial(10);
printf ("El factorial de 5 es %d\n,factorial(5));
¿Qué hace C al ver una invocación? Consideremos el ejemplo:
int x=factorial(n-k);
|
|
Tiene que haber correspondencia entre los valores que se le entregan al función y la declaración de los parámetros de la función. |
Considera la función
char *nombreRey (char *nombre, int repeticion)
Esta función recibe un nombre y "número" de un rey y devuelve su nombre con el número romano:
char *rey=nombreRey ("Enrique",8);
printf (rey); // escribe Enrique VIII
Es erroneo escribir
char *rey=nombreRey (8,"Enrique")
pues la función nombreRey espera que el primer parámetro sea una expresión de tipo String.
funciones void
Los funciones que no devuelven un valor se declaran como void (vacíos):
void dibujarRectangulo(int x, int y)
{
char lineaHor[80],borde[80];
int i;
lineaHor[0]=0;
for (i=0; i<x; i++)
strcat(lineaHor,"*");
strcpy(borde,"*");
for (i=1; i<=x-2; i++)
strcat(borde," ");
strcat(borde,"*");
printf (%s\n,lineaHor); // ***********
for (int v=1; v<y-2;v++)
printf (%s\n,borde); // * *
printf (%s\n,lineaHor); // ***********
}
Como no se devuelve ningún valor, tampoco está la instrucción return. El único contexto en el cual se utiliza return en un función void es cuando uno quiere terminar la ejecución dla función si se cumple alguna condición: |
void dibujarRectangulo(int x, int y)
{
if (x>80 || y>25) /* dimensiones muy grandes? */
return; /* no se dibuja nada */
char lineaHor[80];
...
Un programa puede ser visto como un conjunto de funciones que trabajan en forma conjunta para llevar a cabo un objetivo. La forma del programa es
#include ....
tipo nombreFuncion1 (parámetros)
{
...
}
tipo nombreFuncion2 (parámetros)
{
...
}
tipo nombreFuncion3 (parámetros)
{
...
}
void main()
{
...
invocaciones a los distintos funciones
...
}
El siguiente programa es un ejemplo de programa con más de un función.
int factorial (int x)
{
int producto=1,i;
for (i=1; i<=x; i++)
producto=producto*i;
return producto;
}
int combinatoria(int n, int k)
{
int resultado=factorial(n)/(factorial(k)*factorial(n-k));
return resultado;
}
void main()
{
printf ("Ingresa la cantidad total de elementos\n");
scanf(%d,&n);
printf ("Ingresa cunatos elementos hay que escoger\n");
scanf(%d,&k);
printf ("Se pueden escoger de %d maneras",combinatoria(n,k));
}
Si te fijas, la función combinatoria a su vez llama a la función factorial. Una función siempre puede llamar a otra función, no hay restricciones. En el siguiente capítulo veremos que incluso se puede llamar a sí mismo. |
El paso de los parámetros en C es por valor, es decir, la expresión en la invocación de la función se copia en el parámetro correspondiente de la función.
Esto quiere decir que si la variable de la función se modifica la variable en la invocación permanece inalterada.
¿Qué hace el siguiente programa?
void incrementa(int x, int cuanto)
{
x=x+cuanto;
}
void main()
{
int p=5;
incrementa(p,3);
printf (%d\n,p);
}
La respuesta es que escribe 5 en la pantalla, y no 8 como podría suponerse. Sigamos la ejecución del programa para ver esto claramente:
Línea(s) |
Ejecución |
49 |
Se le asigna 5 a p p __5__ |
50 |
Se llama a incrementa con los valores (5,3) |
43 |
El valor 5 se copia en una nueva variable x. El valor 3 se copia en cuanto x __5__ p __5__ |
45 |
x se incrementa en 3 x __8__ p __5__ |
51 |
se escribe el valor de p, que es 5 |
¿Cómo sería el programa anterior escrito correctamente?
int incrementa(int x, int cuanto)
{
x=x+cuanto;
return x;
}
void main()
{
int p=5;
p=incrementa(p,3);
printf (%d\n,p);
}
Los parámetros no están limitados a los tipos int, double y float. También se pueden pasar referencias a estructuras.
Un arreglo, al ser un objeto, puede pasarse como entrada a una función, basta con pasar una referencia al arreglo
float promedio (float A[], int largo)
{
double suma=0.0;
int i;
for (i=0; i < largo;i++)
suma=suma+A[i];
return suma/largo;
}
void main()
{
int n;
float *notas,p;
printf ("Cuántas notas");
scanf(%d,&n);
notas = (float *) malloc (sizeof(float)*n);
for (int i=0;i<n;i++)
scanf(%f,¬as[i]);
p=promedio(notas,n);
printf ("El promedio es %1.2f",p);
}
Otro ejemplo es un función que calcule el máximo valor de un arreglo:
float máximo(float A[], int largo)
{
float m=A[0];
for (int i=1; i<largo ;i++)
if (m<A[i]) m=A[i];
return m;
}
Arreglos como parámetros:
void suma (float C[], float A[], float B[], int largo)
{
int i;
for (i=0; i< largo;i++)
C[i]=A[i]+B[i];
}
void main()
{
float A[3],B[3],F[3];
A[0]=3; A[1]=4; A[2]=1;
B[0]=4; B[1]=5; B[2]=1;
suma(F,B,A,3); /* en F queda (7,9,2) */
}
Sin embargo, si la función suma fuera
void suma (float *C, float *A, float *B, int largo) {
C=(float *) malloc (sizeof(float)*largo);
for (int i=0; i<largo ;i++)
C[i]=A[i]+B[i];
}
Invocar la función suma no tendría ningún efecto en el programa principal ¿Por qué? La razón es que lo que se le pasa a la función suma es una referencia al arreglo F. En la línea 105 a C se le asigna una nueva referencia a un arreglo recién creado. En las línea 107 se trabaja sobre este nuevo arreglo. Al retornar a la línea 108, el arreglo referenciado por F no se ha tocado. Es más, el nuevo arreglo creado en 105 se ha perdido, pues no se tiene ninguna referencia a él en el programa principal.
Podría hacerse creando un nuevo arreglo dentro de la función y devolviendo una referencia a él, pero sólo con memoria dinámica, no estática:
float *suma (float A[], float B[], int largo)
{
float *C=(float *) malloc (sizeof(float)*largo);
for (int i=0; i<largo ;i++)
C[i]=A[i]+B[i];
return C;
}
void main ()
{
float A[3],B[3], *F;
A[0]=3; A[1]=4; A[2]=1;
B[0]=4; B[1]=5; B[2]=1;
F=suma(B,A);
}
La función main
A esta altura, resulta evidente que el "main" que uno escribe en el programa es un función más. En su declaración se especifica que no recibe parámetros y que tampoco devuelve nada... Pero en realidad SI LOS RECIBE!!!!! (chachachachaaaan!!!!)
La verdadera declaración de main es:
main(int argc, char *argv[])
El arreglo argv tiene argc elementos, los cuales son punteros a los strings que son las palabras de la línea de comandos, comenzando por argv[0], que es el nombre del ejecutable. Por ejemplo, sea el programa echo.c escrito como:
main(int argc, char *argv[])
{
int i;
for(i=1; i<argc; ++i)
printf("%s ", argv[i]);
}
Al compilar y luego ejecutar este programa de la forma:
% gcc -o echo echo.c
% echo hola y chao
Los valores de los parámetros de entrada son:
Pon tus conocimientos a prueba
|
Los errores más comunes están relacionados con el paso y devolución de valores a funciones: No hacer nada con el valor devuelto por un función: |
int n;
scanf(%d,&n)
factorial(n);
Lo correcto es
int n;
scanf(%d,&n);
printf (%d,factorial(n));
o
int n,f;
scanf(%d,&n)
f=factorial(n);
printf (%d,f);
Tratar un función void como si retornara un valor
printf ("El rectangulo que usted desea es %s\n",dibujarRectangulo(h,v));
Lo correcto es
printf ("El rectangulo que usted desea es\n");
dibujaRectangulo(h,v);
Poner los [ ] al llamar a un función que recibe arreglos
float notas[100],p;
...
p=promedio(notas[100]);
lo correcto es:
p=promedio(notas);
Leer los parámetros dentro de la función
Este es sin duda el error más comun y el conceptualmente más grave. Consiste en leer del teclado los valores de los parámetros de un función, siendo que estos se reciben del program principal:
int suma(int a, int b)
{
scanf(%d,&a);
scanf(%d,&b);
return a+b;
}
Lo correcto es leer los datos (si es que son leídos) en la función principal
int suma(int a, int b)
{
return a+b;
}
void main ()
{
int a,b;
scanf(%d,&a);
scanf(%d,&b);
printf (%d\n,suma(a,5));
}
Problema propuesto: Analice las siguientes funciones para asignar memoria a un puntero y vea si son correctas o no.
void f(int *p, int n)
{
p = (int *) malloc (sizeof (int) *n);
}
int *f (int *p, int n)
{
p = (int *) malloc (sizeof (int) * n);
return p;
}
int *f (int n)
{
int *p;
p = (int *) malloc (sizeof (int) * n);
return p;
}