Muestra las diferencias entre dos versiones de la página.
Ambos lados, revisión anterior Revisión previa Próxima revisión | Revisión previa | ||
cpp-avanzado:macros [2017/09/22 04:29] santo |
cpp-avanzado:macros [2023/11/29 15:12] (actual) santo [El porqué de la macro forn] |
||
---|---|---|---|
Línea 1: | Línea 1: | ||
- | Notar que en todos los casos, cuando queremos hablar de un rango de números, indicamos sus extremos como ''[A, B)'', es decir, utilizamos como extremos dos números ''A'' y ''B'' de modo que los números del rango son aquellos $x$ que cumplen $A \leq x < B$. Esta elección de "fronteras", que es **cerrada** a izquierda, pero **abierta** a la derecha, es para casi toda tarea de programación la más conveniente, y se utiliza mucho en los lenguajes de programación como C++. También es la que más recomendamos, porque evita muchos errores y es más clara que otras, una vez que uno la conoce. | + | ====== Macros para programación competitiva ====== |
- | Iterar los números desde ''0'' hasta ''n-1'' inclusive, con la variable ''i'' que es creada para el for: | + | |
- | <code> | + | Notar que en todos los casos, cuando queremos hablar de un rango de números, indicamos sus extremos como $[A, B)$, es decir, utilizamos como extremos dos valores ''A'' y ''B'' de modo tal que los números del rango son aquellos $x$ que cumplen $A \leq x < B$. Esta elección de "fronteras", que es **cerrada** a la izquierda, pero **abierta** a la derecha, es para casi toda tarea de programación la más conveniente, y se utiliza mucho en los lenguajes de programación como C++. También es la que más recomendamos, porque evita muchos errores y es más clara que otras, una vez que uno la conoce. |
+ | |||
+ | Para iterar los números desde ''0'' hasta ''n-1'' inclusive, con la variable ''i'' que es creada para el for: | ||
+ | <code cpp> | ||
#define forn(i,n) for(int i=0;i<int(n);i++) | #define forn(i,n) for(int i=0;i<int(n);i++) | ||
</code> | </code> | ||
- | Iterar los números desde ''s'' hasta ''n-1'' inclusive, con la variable ''i'' que es creada para el for: | + | Para iterar los números desde ''s'' hasta ''n-1'' inclusive, con la variable ''i'' que es creada para el for: |
- | <code> | + | <code cpp> |
#define forsn(i,s,n) for(int i=int(s);i<int(n);i++) | #define forsn(i,s,n) for(int i=int(s);i<int(n);i++) | ||
</code> | </code> | ||
- | Iterar los números desde ''n-1'' hasta ''0'' inclusive, **bajando** con la variable ''i'' que es creada para el for: | + | Para iterar los números desde ''n-1'' hasta ''0'' inclusive, **bajando** con la variable ''i'' que es creada para el for: |
- | <code> | + | <code cpp> |
#define dforn(i,n) for(int i=int(n)-1;i>=0;i--) | #define dforn(i,n) for(int i=int(n)-1;i>=0;i--) | ||
</code> | </code> | ||
- | Iterar los números desde ''n-1'' hasta ''s'' inclusive, **bajando** con la variable ''i'' que es creada para el for: | + | Para iterar los números desde ''n-1'' hasta ''s'' inclusive, **bajando** con la variable ''i'' que es creada para el for: |
- | <code> | + | <code cpp> |
#define dforsn(i,s,n) for(int i=int(n)-1;i>=int(s);i--) | #define dforsn(i,s,n) for(int i=int(n)-1;i>=int(s);i--) | ||
</code> | </code> | ||
Las siguientes macros son cómodas para iterar una colección hacia adelante o hacia atrás, si nos interesa tener acceso al iterador y no solamente a los elementos iterados. Eran necesarias (utilizando ''typeof'' en lugar de ''auto'') antes de [[:cpp-avanzado:c_11|c++11]] para iterar [[:cpp-avanzado:set|sets]] y [[:cpp-avanzado:map|maps]], pues no existía el ''foreach''. | Las siguientes macros son cómodas para iterar una colección hacia adelante o hacia atrás, si nos interesa tener acceso al iterador y no solamente a los elementos iterados. Eran necesarias (utilizando ''typeof'' en lugar de ''auto'') antes de [[:cpp-avanzado:c_11|c++11]] para iterar [[:cpp-avanzado:set|sets]] y [[:cpp-avanzado:map|maps]], pues no existía el ''foreach''. | ||
- | <code> | + | <code cpp> |
#define forall(i,c) for(auto i = (c).begin(); i != (c).end(); i++) | #define forall(i,c) for(auto i = (c).begin(); i != (c).end(); i++) | ||
#define dforall(i,c) for(auto i = (c).rbegin(); i != (c).rend(); i++) | #define dforall(i,c) for(auto i = (c).rbegin(); i != (c).rend(); i++) | ||
</code> | </code> | ||
+ | |||
+ | La siguiente sirve para evitar montones de errores peligrosos y muy difíciles de detectar producto de comparar enteros con y sin signo, ya que ''.size()'' retorna un entero sin signo, por lo que al hacerle cuentas resultados como ''-1'' automáticamente pasan a $2^{bits}-1$. El ejemplo más común sería escribir algo como ''if (v.size()-1 >= 0)'', que da siempre ''true'', a diferencia de ''if (SIZE(v)-1 >= 0)'' que se comporta como uno espera. Pasar inmediatamente a entero con signo evita problemas. | ||
+ | <code cpp> | ||
+ | #define SIZE(c) int((c).size()) | ||
+ | </code> | ||
+ | |||
+ | |||
La siguiente es muy útil para utilizar con funciones de STL, donde se suele pedir un rango mediante dos iteradores, para pasar directamente una colección completa. | La siguiente es muy útil para utilizar con funciones de STL, donde se suele pedir un rango mediante dos iteradores, para pasar directamente una colección completa. | ||
- | <code> | + | <code cpp> |
- | #define all(c) (c).begin(),(c).end() | + | #define ALL(c) begin(c), end(c) |
</code> | </code> | ||
- | El uso más común de todos es al llamar a la función ''sort'', con ''sort(all(v))'' por ejemplo, siendo ''v'' un [[:curso-cpp:contenedor-vector|vector]]. Sin embargo es útil con muchísimas funciones de la [[https://www.sgi.com/tech/stl/table_of_contents.html|STL]] | + | El uso más común de esta macro, es para llamar a la función ''sort'', con ''sort(ALL(v))'' por ejemplo, siendo ''v'' un [[:curso-cpp:contenedor-vector|vector]]. Sin embargo es útil con muchísimas funciones de la [[cpp-avanzado:stl:|STL]], como podría ser por ejemplo una instrucción ''find(ALL(v), 27)''. |
- | La siguiente sirve para consultar si un elemento está en un set (o map: en un map, se consulta si el elemento dado es una **clave** del diccionario). | + | La siguiente sirve para consultar si un elemento está en un [[:cpp-avanzado:set|set]] (o [[:cpp-avanzado:map|map]]: en un map, se consulta si el elemento dado es una **clave** del diccionario). |
- | <code> | + | <code cpp> |
- | #define esta(x,c) ((c).find(x) != (c).end()) | + | #define ESTA(x,c) ((c).find(x) != (c).end()) |
</code> | </code> | ||
Las siguientes son muy útiles para buscar y corregir errores en programas: | Las siguientes son muy útiles para buscar y corregir errores en programas: | ||
- | <code> | + | <code cpp> |
#define DBG(x) cerr << #x << " = " << (x) << endl | #define DBG(x) cerr << #x << " = " << (x) << endl | ||
#define RAYA cerr << "===============================" << endl | #define RAYA cerr << "===============================" << endl | ||
</code> | </code> | ||
- | DBG es una macro particularmente bonita y "mágica". Si escribimos en una línea ''DBG(x);'' en el código, siendo ''x'' una variable (o podría ser una expresión), nos mostrará su valor y su nombre por pantalla. Alentamos a probarla para ver su utilidad a la hora de buscar bugs en programas. | + | DBG es una macro particularmente bonita y "mágica". Si escribimos en una línea ''DBG(x);'' en el código, siendo ''x'' una variable (o podría ser una expresión), nos mostrará su valor **y su nombre** por pantalla. Alentamos a probarla para ver su utilidad a la hora de buscar bugs en programas. |
- | Similarmente, ''RAYA;'' muestra una clara línea separadora: esto es especialmente útil cuando tenemos un ''for'' y queremos mostrar los valores en cada iteración. Poniendo un ''RAYA;'', podemos separar con facilidad los valores entre iteraciones. | + | Similarmente, ''RAYA;'' muestra una clara línea separadora: esto es especialmente útil cuando tenemos un ''for'' y queremos mostrar los valores de varias variables **en cada iteración**. Poniendo un ''RAYA;'', podemos separar con facilidad los valores entre iteraciones. |
Los siguientes ''typedef'' son razonables y cómodos en programación competitiva: | Los siguientes ''typedef'' son razonables y cómodos en programación competitiva: | ||
- | <code> | + | <code cpp> |
typedef long long tint; | typedef long long tint; | ||
typedef long double tdbl; | typedef long double tdbl; | ||
- | typedef vector<int> vint; | ||
- | typedef pair<int,int> pint; | ||
- | typedef pair<tint,tint> ptint; | ||
</code> | </code> | ||
+ | |||
+ | O su equivalente más moderno: | ||
+ | |||
+ | <code cpp> | ||
+ | using tint = long long; | ||
+ | using tdbl = long double; | ||
+ | </code> | ||
+ | |||
+ | Utilizar ''tint'' para indicar el **t**ipo del **int** permite cambiar entre ''int'', ''unsigned'', ''long long'', ''unsigned long long'', ''__int128'', ''unsigned char'', etc fácilmente si se usa siempre ''tint'' para los "valores" del programa (mientras que se usa por ejemplo ''int'' para los índices de arreglos y colecciones). | ||
+ | ===== El porqué de la macro forn ===== | ||
+ | |||
+ | <code cpp> | ||
+ | #define forn(i, n) for(int i = 0; i < int(n); i++) | ||
+ | </code> | ||
+ | |||
+ | nos permite, además de parametrizar la cantidad de iteraciones, parametrizar **la variable** que indexa. | ||
+ | Entonces uno puede escribir | ||
+ | <code cpp> | ||
+ | forn(i, 10){ | ||
+ | forn (j, SIZE(v)) { | ||
+ | |||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Uno podría entusiarmarse con las macros y hacer la siguiente macro: | ||
+ | |||
+ | <code cpp> | ||
+ | #define forn(n) for(int i = 0; i < int(n); i++) | ||
+ | </code> | ||
+ | |||
+ | **Es una muy mala idea** definir el ''forn'' sin pasarle la variable para indexar, porque eso nos podría introducir muy fácilmente "bugs ocultos" por no ser suficientemente declarativos y esconder cosas en la macro. En este caso, **escondemos una declaración de variables visibles fuera de la macro**. | ||
+ | |||
+ | Por ejemplo, este código tendría un error oculto usando esa macro | ||
+ | <code cpp> | ||
+ | int i = 24; | ||
+ | forn(10){ | ||
+ | cout << i << endl; | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Un lector cualquiera asumiría que este código escribe 10 veces el número 24, pero se encontraría con que imprime los números del 1 al 10. | ||
+ | |||
+ | En general **el objetivo de las macros no es escribir menos caracteres, sino escribir código que evite bugs** simples por repetición o copy-paste. | ||
+ | |||
+ | Otro ejemplo: | ||
+ | |||
+ | <code cpp> | ||
+ | for(int i=0;i<n;i++){ | ||
+ | for(int j=0;j<m;i++){ | ||
+ | |||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Ese código tiene un bug no evidente a simple vista: en el segundo for incrementa i en vez de j. | ||
+ | En cambio, usando la macro forn como la definimos más arriba, sería | ||
+ | |||
+ | <code cpp> | ||
+ | forn(i, n) { | ||
+ | forn(j, m) { | ||
+ | | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | ¡Donde ese bug es simplemente **imposible** de escribir! Usar ''i'' ambas veces sería el bug más parecido, pero si usamos las [[:cpp-avanzado:opciones-gcc|opciones de compilación]] recomendadas tendremos una advertencia por shadow al hacerlo, e incluso sin el warning es un bug mucho más fácil de detectar y corregir. |