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/18 20:58]
santo [Ejercicios]
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 142: Línea 142:
 </​code>​ </​code>​
  
-Esto debe escribirse **antes del main**, y no adentro. Eso es porque **main es una función como las demás**, y no se permite en C++ escribir una función dentro de otra.+Esto debe escribirse **antes del main**, y no adentro. Eso es porque **main es una función como las demás**, y no se permite en C++ escribir una función dentro de otra. La particularidad que tiene la función ''​main''​ es que es allí donde comienza a ejecutarse el programa: la computadora comienza a leer instrucciones por el ''​main''​.
  
 Una función, al ser un fragmento de programa, puede realizar cálculos y tareas, y puede eventualmente producir un **resultado**. Ese resultado se llama el **valor de retorno** de la función, y se dice que la función **lo devuelve** al usar la instrucción **return**. En el código anterior, la parte de ''​tipo_de_la_respuesta''​ se usa para indicar el tipo que tendrá el **resultado** de la función. Una función, al ser un fragmento de programa, puede realizar cálculos y tareas, y puede eventualmente producir un **resultado**. Ese resultado se llama el **valor de retorno** de la función, y se dice que la función **lo devuelve** al usar la instrucción **return**. En el código anterior, la parte de ''​tipo_de_la_respuesta''​ se usa para indicar el tipo que tendrá el **resultado** de la función.
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 =====
  
 +En C++, además de las funciones que ya vimos, existe un mecanismo relacionado para evitar repetir código, y es la directiva ''#​define''​.
  
 +Mediante un ''#​define'',​ es posible definir lo que se denomina una //macro//, que es una regla para **reemplazar textualmente un fragmento de código en el programa**. Por ejemplo, supongamos que colocamos en cualquier línea de un programa, el siguiente ''#​define'':​
  
 +<​code>​
 +#define declaraEInicializaEnUnValor(nombreVariable,​ valor) int nombreVariable = valor
 +</​code>​
 +
 +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:
 +
 +<​code>​
 +#include <​iostream>​
 +
 +using namespace std;
 +
 +#define declaraEInicializaEnUnValor(nombreVariable,​ valor) int nombreVariable = valor
 +
 +int main()
 +{
 +    declaraEInicializaEnUnValor(x,​ 40);
 +    declaraEInicializaEnUnValor(y,​ 100);
 +    cout << x + y << endl;
 +    return 0;
 +}
 +</​code>​
 +
 +Que muestra por pantalla 140. Notar que luego de los reemplazos, el programa es absolutamente equivalente a si se hubiera escrito directamente:​
 +
 +<​code>​
 +#include <​iostream>​
 +
 +using namespace std;
 +
 +int main()
 +{
 +    int x = 40;
 +    int y = 100;
 +    cout << x + y << endl;
 +    return 0;
 +}
 +</​code>​
 +
 +En general, siempre que podamos conviene utilizar funciones en lugar de ''#​defines'',​ y dejaremos los ''#​defines''​ únicamente para los casos en que no podamos hacer lo mismo con una función. Por ejemplo, en el ejemplo anterior creamos una macro que permite **declarar** una variable y empezar a usarla directamente,​ que es algo que no podríamos haber hecho con una función. A diferencia de una función, una macro hace un **reemplazo totalmente mecánico** de los valores indicados entre paréntesis,​ exactamente igual que si se hiciera copy paste mecánicamente en el código.
 +
 +==== Algunos ejemplos de macros muy útiles ====
 +
 +Especialmente en competencias de programación,​ es muy común tener un fragmento de código como el siguiente:
 +
 +<​code>​
 +for (int numero = 0; numero < valorMaximo;​ numero++)
 +    // instrucciones
 +</​code>​
 +
 +Donde recorremos todos los números desde ''​0''​ hasta ''​valorMaximo - 1''​ inclusive. Notar que al escribir ese fragmento tenemos que escribir la variable ''​numero''​ **tres veces**, lo cual aumenta las chances de equivocarnos (especialmente,​ si hacemos copy + paste de otro for similar, ya que entonces es muy fácil olvidarnos de cambiar **alguna** de las 3 apariciones).
 +
 +Otra variante muy similar sería cuando queremos recorrer todos los índices de un cierto vector:
 +
 +<​code>​
 +for (int var = 0; var < int(vector.size());​ var++)
 +    // instrucciones
 +</​code>​
 +
 +En este caso se agrega la conversión con ''​int()'',​ que debe usarse al comparar con ''​.size()''​.
 +
 +Para programar más fácilmente y con menor chances de errores este tipo de for comunes, podemos utilizar un ''#​define''​ para definir una forma compacta de indicar los elementos importantes que cambian de caso en caso, y que el resto se reemplace mecánicamente siempre igual:
 +
 +<​code>​
 +#define forn(i,n) for(int i=0;​i<​int(n);​i++)
 +</​code>​
 +
 +En este caso, indicamos en el ''#​define''​ el **nombre de la variable** que vamos a declarar, y el **valor tope** hasta el cual vamos a iterar (iteraremos desde ''​0''​ hasta ''​n-1''​ inclusive). Esto es lo único que cambia en estos ejemplos, y el resto es siempre igual.
 +
 +De esta forma, una vez que tenemos este #define el primer for que mostramos nos quedaría simplemente:​
 +
 +<​code>​
 +forn(numero,​ valorMaximo)
 +    // instrucciones
 +</​code>​
 +
 +Y el segundo quedaría:
 +
 +<​code>​
 +forn(var, vector.size())
 +    // instrucciones
 +</​code>​
 +
 +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 ====
 +
 +Hemos mencionado que siempre que podamos hacer algo con funciones en lugar de ''#​define'',​ es conveniente hacerlo con funciones. Esto es porque las funciones son "más seguras"​ de utilizar. Veamos un ejemplo para ver por qué esto es así.
 +
 +Supongamos que queremos tener un fragmento de código para elevar un número al cuadrado. La forma más simple de hacerlo es multiplicar al número con sí mismo, ya que multiplicar es una operación atómica disponible. Podemos entonces pensar en hacer un ''#​define''​ para ello:
  
 <​code>​ <​code>​
 #define cuadrado(x) x*x #define cuadrado(x) x*x
 +</​code>​
  
 +De esta forma, cuando escribamos ''​cuadrado(2)'',​ por ejemplo, se reemplaza por ''​2*2'',​ que es el número al cuadrado como queremos. Sin embargo, el ''#​define''​ que acabamos de definir es muy peligroso de utilizar: imaginemos por ejemplo que lo usamos en la siguiente línea:
 +
 +<​code>​
 cout << cuadrado(2+3) << endl; cout << cuadrado(2+3) << endl;
 </​code>​ </​code>​
  
-Esperamos obtener 25... pero la salida produce 11 :O+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 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 576: Línea 775:
 </​code>​ </​code>​
  
-Pero ahora quedó más feo. Un función como ''​int cuadrado(int x) { return x*x; }'' ​nos hubiera evitado todos estos problemas.+Este ''#​define''​ funciona correctamente,​ pero ahora quedó ​bastante ​más feo y difícil de leerUna 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.
curso-cpp/modularizacion-funciones.txt · Última modificación: 2017/10/29 19:24 por santo