FUNCIONES

Motivación

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

  1. int i, n, k , nfactorial=1, kfactorial=1, nkfactorial=1;

  2. int combinaciones;

  3. scanf (“%d %d”,&n,&k);

  4. for (i=1; i<n; i++)

  5. nfactorial=nfactorial*i;

  6. for (i=1; i<k; i++)

  7. kfactorial=kfactorial*i;

  8. for (int i=1; i<n-k; i++)

  9. nkfactorial=nkfactorial*i;

  10. combinaciones=nfactorial/(kfactorial*nkfactorial);

  11. 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

  1. int n,k,combinaciones;

  2. scanf (“%d %d”,&n, &k);

  3. combinaciones=factorial(n)/(factorial(k)*factorial(n-k));

  4. 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:

  1. int factorial (int x)

  2. {

  3. int producto=1, i;

  4. for (i=1; i<=x; i++)

  5. producto=producto*i;

  6. return producto;

  7. }

    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í:

  8. int factorial (int x)

  9. {

  10. int producto=1, i;

  11. for (i=1; i<=x; i++)

  12. producto=producto*i;

  13. return producto;

  14. }

  15. void main()

  16. {

  17. int n, k, comb;

  18. scanf (“%d %d”,&n, &k);

  19. comb=factorial(n)/(factorial(k)*factorial(n-k));

  20. printf ("El número de combinaciones es %d\n",comb);

  21. }

  22. }

Forma general de una función

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.



Invocación de un función



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);

  1. Se evaluá las expresiones que se pasarán como parámetro. Si n=5 y k=3, entonces se calcula n-k=2

  2. Se copia el valor de la expresión en el parámetro correspondiente dla función

  3. Se ejecutan las instrucciones de la función

  4. Se recibe el resultado de la función en el punto de invocación




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):

  1. void dibujarRectangulo(int x, int y)

  2. {

  3. char lineaHor[80],borde[80];

  4. int i;

  5. lineaHor[0]=0;

  6. for (i=0; i<x; i++)

  7. strcat(lineaHor,"*");

  8. strcpy(borde,"*");

  9. for (i=1; i<=x-2; i++)

  10. strcat(borde," ");

  11. strcat(borde,"*");

  12. printf (“%s\n”,lineaHor); // ***********

  13. for (int v=1; v<y-2;v++)

  14. printf (“%s\n”,borde); // * *

  15. printf (“%s\n”,lineaHor); // ***********

  16. }

    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:

  17. void dibujarRectangulo(int x, int y)

  18. {

  19. if (x>80 || y>25) /* dimensiones muy grandes? */

  20. return; /* no se dibuja nada */

  21. char lineaHor[80];

  22. ...

    Forma general de un programa

    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.

  23. int factorial (int x)

  24. {

  25. int producto=1,i;

  26. for (i=1; i<=x; i++)

  27. producto=producto*i;

  28. return producto;

  29. }

  30. int combinatoria(int n, int k)

  31. {

  32. int resultado=factorial(n)/(factorial(k)*factorial(n-k));

  33. return resultado;

  34. }

  35. void main()

  36. {

  37. printf ("Ingresa la cantidad total de elementos\n");

  38. scanf(“%d”,&n);

  39. printf ("Ingresa cunatos elementos hay que escoger\n");

  40. scanf(“%d”,&k);

  41. printf ("Se pueden escoger de %d maneras",combinatoria(n,k));

  42. }

    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.

    Paso de parámetros

    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?

  43. void incrementa(int x, int cuanto)

  44. {

  45. x=x+cuanto;

  46. }

  47. void main()

  48. {

  49. int p=5;

  50. incrementa(p,3);

  51. printf (“%d\n”,p);

  52. }

    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?

  53. int incrementa(int x, int cuanto)

  54. {

  55. x=x+cuanto;

  56. return x;

  57. }

  58. void main()

  59. {

  60. int p=5;

  61. p=incrementa(p,3);

  62. printf (“%d\n”,p);

  63. }

    Los parámetros no están limitados a los tipos int, double y float. También se pueden pasar referencias a estructuras.

    Arreglos como parámetros

    Un arreglo, al ser un objeto, puede pasarse como entrada a una función, basta con pasar una referencia al arreglo

  64. float promedio (float A[], int largo)

  65. {

  66. double suma=0.0;

  67. int i;

  68. for (i=0; i < largo;i++)

  69. suma=suma+A[i];

  70. return suma/largo;

  71. }

  72. void main()

  73. {

  74. int n;

  75. float *notas,p;

  76. printf ("Cuántas notas");

  77. scanf(“%d”,&n);

  78. notas = (float *) malloc (sizeof(float)*n);

  79. for (int i=0;i<n;i++)

  80. scanf(“%f”,&notas[i]);

  81. p=promedio(notas,n);

  82. printf ("El promedio es %1.2f",p);

  83. }

    Otro ejemplo es un función que calcule el máximo valor de un arreglo:

  84. float máximo(float A[], int largo)

  85. {

  86. float m=A[0];

  87. for (int i=1; i<largo ;i++)

  88. if (m<A[i]) m=A[i];

  89. return m;

  90. }

    Arreglos como parámetros:

  91. void suma (float C[], float A[], float B[], int largo)

  92. {

  93. int i;

  94. for (i=0; i< largo;i++)

  95. C[i]=A[i]+B[i];

  96. }

  97. void main()

  98. {

  99. float A[3],B[3],F[3];

  100. A[0]=3; A[1]=4; A[2]=1;

  101. B[0]=4; B[1]=5; B[2]=1;

  102. suma(F,B,A,3); /* en F queda (7,9,2) */

  103. }

    Sin embargo, si la función suma fuera

  104. void suma (float *C, float *A, float *B, int largo) {

  105. C=(float *) malloc (sizeof(float)*largo);

  106. for (int i=0; i<largo ;i++)

  107. C[i]=A[i]+B[i];

  108. }

    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:

  109. float *suma (float A[], float B[], int largo)

  110. {

  111. float *C=(float *) malloc (sizeof(float)*largo);

  112. for (int i=0; i<largo ;i++)

  113. C[i]=A[i]+B[i];

  114. return C;

  115. }

  116. void main ()

  117. {

  118. float A[3],B[3], *F;

  119. A[0]=3; A[1]=4; A[2]=1;

  120. B[0]=4; B[1]=5; B[2]=1;

  121. F=suma(B,A);

  122. }

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

  • Escribe un función que reciba un String y lo devuelva invertido



Errores (demasiado) comunes

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;

}