cao lei
Aquí hay un fragmento simple de C++:
int x1 = 10, x2 = 20, y1 = 132, y2 = 12, minx, miny, maxx, maxy;
x1 <= x2 ? minx = x1, maxx = x2 : minx = x2, maxx = x1;
y1 <= y2 ? miny = y1, maxy = y2 : miny = y2, maxy = y1;
cout << "minx=" << minx << "\n";
cout << "maxx=" << maxx << "\n";
cout << "miny=" << miny << "\n";
cout <<" maxy=" << maxy << "\n";
Pensé que el resultado debería ser:
minx=10
maxx=20
miny=12
maxy=132
Pero en realidad el resultado es:
minx=10
maxx=10
miny=12
maxy=132
alguien podria dar una explicacion por que maxx
no es 20
? Gracias.
perreal
Debido a la precedencia del operador, la expresión se analiza de la siguiente manera:
(x1<=x2 ? minx=x1,maxx=x2 : minx=x2), maxx=x1;
puedes resolver esto con:
(x1<=x2) ? (minx=x1,maxx=x2) : (minx=x2, maxx=x1);
Y en realidad no necesitas los primeros dos pares de paréntesis. También revisa esta pregunta.
daniel pescador
La precedencia del operador condicional es mayor que la del operador coma, por lo que
x1<=x2 ? minx=x1,maxx=x2 : minx=x2,maxx=x1;
está entre paréntesis como
(x1<=x2 ? minx=x1,maxx=x2 : minx=x2),maxx=x1;
Por lo tanto, la última asignación se realiza independientemente de la condición.
Para solucionarlo, puede
-
usar paréntesis:
x1 <= x2 ? (minx = x1, maxx = x2) : (minx = x2, maxx = x1);
(usted no necesitar los paréntesis en el
true
rama, pero en mi opinión, es mejor tenerlos también). -
Usa dos condicionales:
minx = x1 <= x2 ? x1 : x2; maxx = x1 <= x2 ? x2 : x1;
-
usar un
if
:if (x1 <= x2) { minx = x1; maxx = x2; } else { minx = x2; maxx = x1; }
Compilado con o sin optimizaciones, el if
version y el condicional simple entre paréntesis con comas producen el mismo ensamblaje tanto en gcc (4.7.2) como en clang (3.2), es razonable esperar eso también de otros compiladores. La versión con los dos condicionales produce un ensamblaje diferente, pero con optimizaciones, ambos compiladores emiten solo uno cmp
instrucción para eso también.
En mi opinión, el if
La versión es la más fácil de verificar, por lo que es preferible.
-
+1 para probar la diferencia en el código generado y proporcionar un argumento de legibilidad.
– esponjoso
19 de mayo de 2013 a las 6:14
esteras petersson
Mientras que otros han explicado cuál es la causa del problema, creo que la solución “mejor” debería ser escribir el condicional con si:
int x1 = 10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy;
if (x1<=x2)
{
minx=x1;
maxx=x2;
}
else
{
minx=x2;
maxx=x1;
}
if (y1<=y2)
{
miny=y1;
maxy=y2;
}
else
{
miny=y2;
maxy=y1;
}
Sí, es varias líneas más largo, pero también es más fácil de leer y borrar exactamente lo que sucede (y si necesita recorrerlo paso a paso en el depurador, puede ver fácilmente en qué dirección va).
Cualquier compilador moderno debería poder convertir cualquiera de estos en asignaciones condicionales bastante eficientes que hagan un buen trabajo al evitar las bifurcaciones (y, por lo tanto, la “mala predicción de bifurcaciones”).
Preparé una pequeña prueba, que compilé usando
g++ -O2 -fno-inline -S -Wall ifs.cpp
Aquí está la fuente (tuve que convertirla en parámetros para asegurarme de que el compilador no solo calculara el valor correcto directamente y simplemente hiciera mov $12,%rdx
pero en realidad hizo una comparación y decidió con es más grande):
void mine(int x1, int x2, int y1, int y2)
{
int minx, miny, maxx, maxy;
if (x1<=x2)
{
minx=x1;
maxx=x2;
}
else
{
minx=x2;
maxx=x1;
}
if (y1<=y2)
{
miny=y1;
maxy=y2;
}
else
{
miny=y2;
maxy=y1;
}
cout<<"minx="<<minx<<"\n";
cout<<"maxx="<<maxx<<"\n";
cout<<"miny="<<miny<<"\n";
cout<<"maxy="<<maxy<<"\n";
}
void original(int x1, int x2, int y1, int y2)
{
int minx, miny, maxx, maxy;
x1<=x2 ? (minx=x1,maxx=x2) : (minx=x2,maxx=x1);
y1<=y2 ? (miny=y1,maxy=y2) : (miny=y2,maxy=y1);
cout<<"minx="<<minx<<"\n";
cout<<"maxx="<<maxx<<"\n";
cout<<"miny="<<miny<<"\n";
cout<<"maxy="<<maxy<<"\n";
}
void romano(int x1, int x2, int y1, int y2)
{
int minx, miny, maxx, maxy;
minx = ((x1 <= x2) ? x1 : x2);
maxx = ((x1 <= x2) ? x2 : x1);
miny = ((y1 <= y2) ? y1 : y2);
maxy = ((y1 <= y2) ? y2 : y1);
cout<<"minx="<<minx<<"\n";
cout<<"maxx="<<maxx<<"\n";
cout<<"miny="<<miny<<"\n";
cout<<"maxy="<<maxy<<"\n";
}
int main()
{
int x1=10, x2=20, y1=132, y2=12;
mine(x1, x2, y1, y2);
original(x1, x2, y1, y2);
romano(x1, x2, y1, y2);
return 0;
}
El código generado se ve así:
_Z4mineiiii:
.LFB966:
.cfi_startproc
movq %rbx, -32(%rsp)
movq %rbp, -24(%rsp)
movl %ecx, %ebx
movq %r12, -16(%rsp)
movq %r13, -8(%rsp)
movl %esi, %r12d
subq $40, %rsp
movl %edi, %r13d
cmpl %esi, %edi
movl %edx, %ebp
cmovg %edi, %r12d
cmovg %esi, %r13d
movl $_ZSt4cout, %edi
cmpl %ecx, %edx
movl $.LC0, %esi
cmovg %edx, %ebx
cmovg %ecx, %ebp
.... removed actual printout code that is quite long and unwieldy...
_Z8originaliiii:
movq %rbx, -32(%rsp)
movq %rbp, -24(%rsp)
movl %ecx, %ebx
movq %r12, -16(%rsp)
movq %r13, -8(%rsp)
movl %esi, %r12d
subq $40, %rsp
movl %edi, %r13d
cmpl %esi, %edi
movl %edx, %ebp
cmovg %edi, %r12d
cmovg %esi, %r13d
movl $_ZSt4cout, %edi
cmpl %ecx, %edx
movl $.LC0, %esi
cmovg %edx, %ebx
cmovg %ecx, %ebp
... print code goes here ...
_Z6romanoiiii:
movq %rbx, -32(%rsp)
movq %rbp, -24(%rsp)
movl %edx, %ebx
movq %r12, -16(%rsp)
movq %r13, -8(%rsp)
movl %edi, %r12d
subq $40, %rsp
movl %esi, %r13d
cmpl %esi, %edi
movl %ecx, %ebp
cmovle %edi, %r13d
cmovle %esi, %r12d
movl $_ZSt4cout, %edi
cmpl %ecx, %edx
movl $.LC0, %esi
cmovle %edx, %ebp
cmovle %ecx, %ebx
... printout code here....
Como puedes ver, mine
y original
es idéntico, y romano
utiliza registros ligeramente diferentes y una forma diferente de cmov
pero por lo demás hacen lo mismo en el mismo número de instrucciones.
-
+1. El código publicado en la pregunta es exactamente el tipo de código que hace que muchas organizaciones simplemente prohíban el operador ternario por completo. El operador ternario tiene su lugar, pero el uso publicado no es ese lugar.
– David Hamman
18 mayo 2013 a las 22:01
-
Re Sí, son varias líneas más largas. — No sería tan malo si hubiera usado 1TBS. 🙂
– David Hamman
18 mayo 2013 a las 22:04
-
@DavidHammen Sí, puedes diseñarlo de otra manera [this is just how I usually write my own code], pero probablemente no (sin hacerlo bastante complicado) reducirlo a 2 líneas … Incluso cuatro líneas razonablemente legibles lo están empujando. Entonces, la declaración sigue siendo “varias líneas más”. Y mi punto era hacerlo más legible, no algo adecuado para una entrada de IOCCC.
– Mats Peterson
18 mayo 2013 a las 22:11
-
+1. El operador condicional no se veía bien aquí. Publiqué otra variante para esto con operadores condicionales ‘cortos’ que mantienen el código tan legible/compacto como podría ser. Lo interesante aquí es cómo se generará el código de máquina.
– Roman Nikitchenko
18 mayo 2013 a las 22:12
-
Una ventaja del operador condicional es que hace mucho más probable que el compilador utilice el
cmov
operaciones que eliminan posibles burbujas de canalización del código.– Zan Lince
18 mayo 2013 a las 22:23
Roman Nikitchenko
Pregunta interesante tanto sobre la precedencia de operaciones como sobre la generación de código.
OK, ,
operación tiene MUY baja prioridad (la más baja, consulte tabla de referencia). Debido a este hecho, su código es el mismo que el de las siguientes líneas:
((x1<=x2) ? minx=x1,maxx=x2 : minx=x2),maxx=x1;
((y1<=y2) ? miny=y1,maxy=y2 : miny=y2),maxy=y1;
En realidad, solo la gramática C/C++ previene primero ,
del mismo comportamiento.
Otro lugar realmente peligroso en la precedencia de las operaciones C/C++ son las operaciones bit a bit y la comparación. Considere el siguiente fragmento:
int a = 2;
int b = (a == 2|1); // Looks like check for expression result? Nope, results 1!
De cara al futuro, recomendaría volver a escribir su fragmento de esta manera manteniendo el equilibrio entre la eficiencia y la legibilidad:
minx = ((x1 <= x2) ? x1 : x2);
maxx = ((x1 <= x2) ? x2 : x1);
miny = ((y1 <= y2) ? y1 : y2);
maxy = ((y1 <= y2) ? y2 : y1);
El hecho más interesante sobre este fragmento de código es que dicho estilo podría resultar en el código más efectivo para algunas arquitecturas como ARM debido a los indicadores de bits de condición en el conjunto de instrucciones de la CPU (la duplicación de condición no cuesta nada y más, apunta al compilador a los fragmentos de código correctos).
Debido a la precedencia del operador:
(x1<=x2 ? minx=x1,maxx=x2 : minx=x2),maxx=x1
Puedes arreglarlo con:
int x1=10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy;
x1<=x2 ? (minx=x1,maxx=x2) : (minx=x2,maxx=x1);
y1<=y2 ? (miny=y1,maxy=y2) : (miny=y2,maxy=y1);
cout<<"minx="<<minx<<"\n";
cout<<"maxx="<<maxx<<"\n";
cout<<"miny="<<miny<<"\n";
cout<<"maxy="<<maxy<<"\n";
En C++ 11 puedes usar std::tie
y std::make_pair
para hacer esto obviamente correcto de un vistazo (TM)
#include <tuple>
#include <utility>
#include <iostream>
using namespace std;
int main()
{
int x1 = 10, x2=20, y1=132, y2=12, minx, miny, maxx, maxy;
tie(minx, maxx) = (x1 <= x2)? make_pair(x1, x2) : make_pair(x2, x1);
tie(miny, maxy) = (y1 <= y2)? make_pair(y1, y2) : make_pair(y2, y1);
cout<<"minx="<<minx<<"\n";
cout<<"maxx="<<maxx<<"\n";
cout<<"miny="<<miny<<"\n";
cout<<"maxy="<<maxy<<"\n";
}
En línea producción.
Esto es semánticamente equivalente a todas las demás soluciones publicadas, y con cualquier compilador de optimización decente, no tiene ningún tipo de sobrecarga. Es sintácticamente mucho mejor porque tiene
- un mínimo de repetición de código,
- las 4 variables asignadas a están todas en el lado izquierdo de la asignación, y
- las 4 variables de origen asignadas están todas a la derecha.
Como una ligera variación que se generaliza para encontrar punteros al elemento mínimo y máximo de las secuencias, podría usar std::minmax_element
y el hecho de que las matrices sin procesar tienen funciones que no son miembros begin()
y end()
(ambas funciones de C++ 11)
#include <algorithm>
#include <tuple>
#include <iostream>
using namespace std;
int main()
{
int x[] = { 10, 20 }, y[] = { 132, 12 }, *minx, *miny, *maxx, *maxy;
tie(minx, maxx) = minmax_element(begin(x), end(x));
tie(miny, maxy) = minmax_element(begin(y), end(y));
cout<<"minx="<<*minx<<"\n";
cout<<"maxx="<<*maxx<<"\n";
cout<<"miny="<<*miny<<"\n";
cout<<"maxy="<<*maxy<<"\n";
}
En línea producción.
Los paréntesis resuelven el problema…
– usuario529758
18 mayo 2013 a las 21:38
Otra razón más para “no tratar de ser inteligente” con expresiones condicionales y uso
if
en cambio. El compilador hará lo mismo de cualquier manera. [assuming you add the relevant parenthesis so that it does what you actually wanted]. Las expresiones ternarias pueden ser útiles a veces, pero este es un buen ejemplo de lo que NO se debe hacer con ellas.– Mats Peterson
18 mayo 2013 a las 21:44
(Y de todos modos, por qué no:
maxx = x1 > x2 ? x1 : x2
?– usuario529758
18 mayo 2013 a las 21:44
Y por qué no
#include<algorithm>
ymaxi = std::max(x1, x2)
?–Eric Jablow
19 de mayo de 2013 a las 0:17
Cuando vi por primera vez el título de esta pregunta, pensé que era va a ser sobre PHP.
– Ray Toal
19 de mayo de 2013 a las 6:01