Herramientas de usuario

Herramientas del sitio


curso-cpp:modularizacion-funciones

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
Última revisión Ambos lados, revisión siguiente
curso-cpp:modularizacion-funciones [2017/03/19 20:43]
santo
curso-cpp:modularizacion-funciones [2017/09/22 16:26]
santo [Algunos ejemplos de macros muy útiles]
Línea 93: Línea 93:
 ==== Facilitar un enfoque top-down ==== ==== Facilitar un enfoque top-down ====
  
-Esta ventaja está estrechamente relacionada con la anterior. Supongamos que nos dieran la siguiente ​consiga:+Esta ventaja está estrechamente relacionada con la anterior. Supongamos que nos dieran la siguiente ​consigna:
  
 "Crear un programa que lea dos números ''​a''​ y ''​b'',​ que indican un rango de números (inclusive),​ y calcule y muestre en la pantalla dos valores: La suma de los cuadrados de todos los números primos entre a y b, y además, la suma de todos los números entre a y b que son múltiplos de 3 y de 10, pero no de 30." "Crear un programa que lea dos números ''​a''​ y ''​b'',​ que indican un rango de números (inclusive),​ y calcule y muestre en la pantalla dos valores: La suma de los cuadrados de todos los números primos entre a y b, y además, la suma de todos los números entre a y b que son múltiplos de 3 y de 10, pero no de 30."
Línea 231: Línea 231:
 ===== Parámetros ===== ===== Parámetros =====
  
-No siempre queremos que una función haga exactamente lo mismo cada vez que usa. A veces, queremos que haga **casi** lo mismo, pero cambiando algún **dato** entre usos. Por ejemplo, podríamos querer una función que eleve un número al cuadrado, es decir, que permita calcular ''​x*x''​ si ya tenemos un entero ''​x''​. Así, cuando usamos la función con 3, queremos que devuelva ''​3*3 == 9'',​ y cuando la usamos con -4 queremos que devuelva ''​(-4)*(-4) == 16''​.+No siempre queremos que una función haga exactamente lo mismo cada vez que se usa. A veces, queremos que haga **casi** lo mismo, pero cambiando algún **dato** entre usos. Por ejemplo, podríamos querer una función que eleve un número al cuadrado, es decir, que permita calcular ''​x*x''​ si ya tenemos un entero ''​x''​. Así, cuando usamos la función con 3, queremos que devuelva ''​3*3 == 9'',​ y cuando la usamos con -4 queremos que devuelva ''​(-4)*(-4) == 16''​.
  
 En el ejemplo anterior la función no hace siempre lo mismo, porque a veces hace 3*3 y a veces (-4)*(-4), pero más allá del número que vamos a elevar, las **operaciones** que hace la función son siempre las mismas, y solo cambia este **dato** inicial. A ese **dato que cambia**, lo llamamos en programación un **parámetro** de la función. Una función puede tener 1 o más parámetros,​ o incluso cero: Las funciones que vimos antes tenían cero parámetros. La función de elevar al cuadrado tendría un único parámetro: El número entero que vamos a querer elevar. En el ejemplo anterior la función no hace siempre lo mismo, porque a veces hace 3*3 y a veces (-4)*(-4), pero más allá del número que vamos a elevar, las **operaciones** que hace la función son siempre las mismas, y solo cambia este **dato** inicial. A ese **dato que cambia**, lo llamamos en programación un **parámetro** de la función. Una función puede tener 1 o más parámetros,​ o incluso cero: Las funciones que vimos antes tenían cero parámetros. La función de elevar al cuadrado tendría un único parámetro: El número entero que vamos a querer elevar.
Línea 550: Línea 550:
   * Variables con el mismo nombre pero definidas en funciones distintas, representan variables **diferentes** (como el ''​x''​ del main y el ''​x''​ de la función en el ejemplo anterior de ''​incrementar''​)   * Variables con el mismo nombre pero definidas en funciones distintas, representan variables **diferentes** (como el ''​x''​ del main y el ''​x''​ de la función en el ejemplo anterior de ''​incrementar''​)
  
 +
 +===== Ejemplos de implementación de funciones =====
 +
 +  * Nuestro primer ejemplo es la función ''​main'',​ que ya venimos usando en todos nuestros programas: Es una función que devuelve un ''​int'',​ que usa la computadora para saber si hubo errores. Por convención,​ se debe devolver cero si todo salió bien, y por eso es buena costumbre terminar todos los programas con ''​return 0''​. La función ''​main''​ es importante porque tiene la característica especial de que allí comienzan a ejecutarse todos nuestros programas, aunque tengan otras funciones.
 +  * Como ejemplo de pensamiento top-down, supongamos que debemos realizar un programa que lea una secuencia de números, y luego calcule y muestre por pantalla la suma, el máximo y el mínimo de todos estos números. Podemos programar primero que anda el main de la siguiente manera:<​code>​
 +int main()
 +{
 +    vector<​int>​ v;
 +    v = leerNumeros();​
 +    imprimirResultados(suma(v),​ maximo(v), minimo(v));
 +    return 0;
 +}
 +</​code>​ Donde nos hemos ordenado y hemos logrado **descomponer** el problema entero en tareas más pequeñas. Luego podríamos agregarle las funciones que faltan al programa, para completarlo,​ dejando inalterado el mismo main que ya escribimos: <​code>​
 +vector<​int>​ leerNumeros()
 +{
 +    // instrucciones...
 +}
 +
 +int suma(vector<​int>​ v)
 +{
 +    // instrucciones...
 +}
 +
 +int maximo(vector<​int>​ v)
 +{
 +    // instrucciones...
 +}
 +
 +int minimo(vector<​int>​ v)
 +{
 +    // instrucciones...
 +}
 +
 +void imprimirResultados(int laSuma,int elMaximo, int elMinimo)
 +{
 +    // instrucciones...
 +}
 +</​code>​
 +
 +  * El siguiente es un ejemplo con funciones que calculan áreas de figuras geométricas,​ que muestra como podemos reutilizar ciertas funciones dentro de otras: <​code>​
 +int areaParalelogramo(int base, int altura)
 +{
 +    return base * altura;
 +}
 +int areaCuadrado(int lado)
 +{
 +    return areaParalelogramo(lado,​ lado);
 +}
 +int areaTriangulo(int base, int altura) // Trabaja con enteros: Redondea hacia abajo
 +{
 +    return areaParalelogramo(base,​ altura) / 2;
 +}
 +</​code>​
 +  * El siguiente es un ejemplo de función que recibe dos variables enteras, e intercambia sus valores. ¡Notar el uso del ampersand! <​code>​
 +void intercambiar(int &​variable1,​ int &​variable2)
 +{
 +    int auxiliar = variable1; // Es necesario un auxiliar: ¿Por qué?
 +    variable1 = variable2;
 +    variable2 = auxiliar;
 +} </​code>​ Similarmente,​ el siguiente ejemplo permite "​rotar"​ los valores de tres variables dadas: Es decir, transforma [a,b,c] en [b,c,a]: <​code>​
 +void rotar3(int &a, int &b, int &c)
 +{
 +   int auxiliar = a; // Nuevamente, ¿Por qué es necesario el auxiliar?
 +   a = b;
 +   b = c;
 +   c = auxiliar;
 +}
 +</​code>​
 +===== Algunas funciones predefinidas =====
 +
 +En C++, existen algunas funciones predefinidas que ya existen, y conocerlas puede simplificarnos la tarea de programar ya que nos ahorramos tener que escribirlas nosotros mismos. Mencionamos algunas a continuación:​
 +
 +  * ''​max'':​ Dados dos números, devuelve el máximo. Por ejemplo ''​max(2,​9) == 9''​ y ''​max(5,​3) == 5''​. Similarmente tenemos ''​min''​ para el mínimo. ​
 +  * ''​swap'':​ Intercambia los valores de las dos variables que se le indica. Por ejemplo si ''​x''​ tiene un ''​3'',​ y ''​q[i]''​ tiene un ''​8'',​ luego de hacer ''​swap(x,​q[i])''​ quedará ''​q[i]''​ con un 3 y ''​x''​ con un 8.
 +  * ''​abs'':​ Devuelve el valor absoluto (módulo) de un entero. Por ejemplo ''​abs(-3) == 3'',​ ''​abs(0) == 0''​ y ''​abs(15) == 15''​.
 +
 +Todas estas funciones requieren utilizar ''#​include <​algorithm>''​ para tenerlas disponibles.
 +
 +===== Algunos errores comunes =====
 +
 +   * Pasar a la función una cantidad de parámetros diferente de las que la función necesita, o con el tipo incorrecto. Por ejemplo si tenemos la función <​code>​
 +int mayor(int num1, int num2)
 +{
 +    if (num1 > num2)
 +        return num1;
 +    else
 +        return num2;
 +}
 +</​code>​ serían incorrectas las siguientes llamadas: <​code>​
 +mayor(k,​m,​n) // Pasa 3 parámetros,​ pero la función toma solamente 2
 +mayor(23, "​miliwatt"​) // Pasa 2 parámetros,​ pero el segundo es una cadena y debería ser un int
 +</​code>​
 +   * Diseñar una función con la idea de que **modifique** uno de sus parámetros,​ pero trabajar con una copia por no utilizar el ampersand ''&''​.
 +   * Intentar utilizar un parámetro (con su nombre) **fuera** de una función: Los parámetros solamente están definidos dentro de la función, y no tiene sentido utilizarlos fuera de ella (son variables locales).
 +   * Utilizar una función que todavía no se definió. Se debe programar el código de una función, antes de utilizarla.
 ===== Ejercicios ===== ===== Ejercicios =====
  
Línea 556: Línea 651:
 Por ejemplo, en los ejercicios en los que se hablaba de números primos, se podría escribir una función que toma un ''​int N'',​ y devuelve un ''​bool''​ indicando si es primo. O por ejemplo, escribir una función ''​sumaDeDivisores''​ puede ser útil para escribir de forma más fácil y clara programas que buscan números perfectos. Por ejemplo, en los ejercicios en los que se hablaba de números primos, se podría escribir una función que toma un ''​int N'',​ y devuelve un ''​bool''​ indicando si es primo. O por ejemplo, escribir una función ''​sumaDeDivisores''​ puede ser útil para escribir de forma más fácil y clara programas que buscan números perfectos.
  
 +Otros ejercicios:
 +
 +   * Escribir una función ''​string escribirEnBase(int numero, int base)'',​ que tome un número y una base (Entre 2 y 16 inclusive) y devuelva una cadena con la escritura de ese número en la base indicada.
 +   * Escribir una función ''​int leerNumeroEnBase(string escritura, int base)'',​ que tome la escritura de un cierto número en la base indicada (Entre 2 y 16 inclusive) y devuelva el número en cuestión.
 +
 +Puede ver aquí [[algoritmos-oia:​enteros:​cambio-de-base|cómo realizar cambios de base]].
 ===== Los #define ===== ===== Los #define =====
  
Línea 566: Línea 667:
 </​code>​ </​code>​
  
-Luego realizado ​este ''#​define'',​ si en **cualquier** lugar del programa aparece un ''​declaraEInicializaEnUnValor(x,​ 33)'',​ se **reemplazará textualmente** en ese mismo lugar por un ''​int x = 33''​.+Luego de haber escrito ​este ''#​define'',​ si en **cualquier** lugar del programa aparece un ''​declaraEInicializaEnUnValor(x,​ 33)'',​ se **reemplazará textualmente** en ese mismo lugar por un ''​int x = 33''​.
 Por ejemplo,el siguiente sería un programa válido: Por ejemplo,el siguiente sería un programa válido:
  
 <​code>​ <​code>​
 #include <​iostream>​ #include <​iostream>​
 +
 +using namespace std;
  
 #define declaraEInicializaEnUnValor(nombreVariable,​ valor) int nombreVariable = valor #define declaraEInicializaEnUnValor(nombreVariable,​ valor) int nombreVariable = valor
Línea 587: Línea 690:
 <​code>​ <​code>​
 #include <​iostream>​ #include <​iostream>​
 +
 +using namespace std;
  
 int main() int main()
Línea 643: Línea 748:
 Vemos que ahora solamente hace falta especificar **una vez** el nombre de la variable, y todo lo demás es copiado automáticamente en forma mecánica por el ''#​define''​. Vemos que ahora solamente hace falta especificar **una vez** el nombre de la variable, y todo lo demás es copiado automáticamente en forma mecánica por el ''#​define''​.
  
 +Se puede consultar [[cpp-avanzado:​macros |aquí]] otros ejemplos de macros más avanzadas, muy útiles para programación competitiva,​ además del ''​forn''​ ya mostrado.
 ==== Por qué es mejor usar funciones ==== ==== Por qué es mejor usar funciones ====
  
Línea 661: Línea 767:
 Esperamos obtener 25... pero la salida de este programa produce 11. ¿Por qué ha ocurrido esto? Esperamos obtener 25... pero la salida de este programa produce 11. ¿Por qué ha ocurrido esto?
  
-El motivo es que, como ya hemos mencionado, los ''#​define''​ realizan un reemplazo **mecánico y textual** de los elementos que les indicamos, igual que si hiciéramos copy paste, sin entender el significado de su contenido. Por lo tanto, como el ''#​define''​ indica que debemos cambiar ''​cuadrado(x)''​ por ''​x*x'',​ tenemos que fijarnos ​quien es ''​x'',​ copiarlo dos veces y colocar un asterisco en el medio, textualmente,​ pues eso es lo que hacen los ''#​define''​. En nuestro ejemplo, ''​x''​ es ''​2+3'',​ pues se ha escrito ''​cuadrado(2+3)'':​ Entonces, el fragmento ''​cuadrado(2+3)''​ es reemplazado por ''​2+3*2+3'',​ ya que el ''#​define''​ reemplaza textualmente cada copia de ''​x''​ por el texto indicado. Esta expresión, al no tener paréntesis,​ da por resultado ''​2+6+3=11'',​ pero nosotros queríamos realizar ''​(2+3)*(2+3)=25''​.+El motivo es que, como ya hemos mencionado, los ''#​define''​ realizan un reemplazo **mecánico y textual** de los elementos que les indicamos, igual que si hiciéramos copy paste, sin entender el significado de su contenido. Por lo tanto, como el ''#​define''​ indica que debemos cambiar ''​cuadrado(x)''​ por ''​x*x'',​ tenemos que fijarnos ​quién ​es ''​x'',​ copiarlo dos veces y colocar un asterisco en el medio, textualmente,​ pues eso es lo que hacen los ''#​define''​. En nuestro ejemplo, ''​x''​ es ''​2+3'',​ pues se ha escrito ''​cuadrado(2+3)'':​ Entonces, el fragmento ''​cuadrado(2+3)''​ es reemplazado por ''​2+3*2+3'',​ ya que el ''#​define''​ reemplaza textualmente cada copia de ''​x''​ por el texto indicado. Esta expresión, al no tener paréntesis,​ da por resultado ''​2+6+3=11'',​ pero nosotros queríamos realizar ''​(2+3)*(2+3)=25''​.
  
 Esto podría resolverse si utilizamos **paréntesis en todos lados**: Esto podría resolverse si utilizamos **paréntesis en todos lados**:
Línea 669: Línea 775:
 </​code>​ </​code>​
  
-Este ''#​define''​ funciona correctamente,​ pero ahora quedó bastante más feo y difícil de leer. Una función como ''​int cuadrado(int x) { return x*x; }'' ​nos hubiera evitado todos estos problemas, pues en las funciones **no se hace un reemplazo textual mecánico del código**, sino que se **calcula el valor indicado antes de comenzar a ejecutar la función**. En el ejemplo con la función cuadrado, se calcularía el valor ''​2+3=5''​ antes de iniciar la función, de forma que cuando se comienza a ejecutar la función cuadrado, ya se tiene ''​x=5''​. En este sentido, **las funciones son más inteligentes que el #define**.+Este ''#​define''​ funciona correctamente,​ pero ahora quedó bastante más feo y difícil de leer. Una función como  
 + 
 +<​code>​ 
 +int cuadrado(int x) 
 +{ 
 +    ​return x*x;  
 +} 
 +</​code>​ 
 + 
 +nos hubiera evitado todos estos problemas, pues en las funciones **no se hace un reemplazo textual mecánico del código**, sino que se **calcula el valor indicado antes de comenzar a ejecutar la función**. En el ejemplo con la función cuadrado, se calcularía el valor ''​2+3=5''​ antes de iniciar la función, de forma que cuando se comienza a ejecutar la función cuadrado, ya se tiene ''​x=5''​. En este sentido, **las funciones son más inteligentes que el #define**.
  
 En resumen, utilizaremos ''#​define''​ solamente cuando nos permite hacer algo que con una función no podríamos hacer. Por ejemplo, escribir algún tipo de ''​for''​ común, o resumir alguna instrucción que declare una variable, es algo que no podríamos reemplazar fácilmente por una función. En cambio, un simple cálculo como elevar al cuadrado sí es algo que podríamos hacer fácilmente con una función, y entonces generalmente conviene hacerlo así para evitar posibles errores, y no tener que llenar todo de paréntesis. En resumen, utilizaremos ''#​define''​ solamente cuando nos permite hacer algo que con una función no podríamos hacer. Por ejemplo, escribir algún tipo de ''​for''​ común, o resumir alguna instrucción que declare una variable, es algo que no podríamos reemplazar fácilmente por una función. En cambio, un simple cálculo como elevar al cuadrado sí es algo que podríamos hacer fácilmente con una función, y entonces generalmente conviene hacerlo así para evitar posibles errores, y no tener que llenar todo de paréntesis.
curso-cpp/modularizacion-funciones.txt · Última modificación: 2017/10/29 19:24 por santo