c++ - operator - obtuviste una respuesta inesperada de la expresión x? y: z
operator precedence java (6)
Debido a la precedencia del operador, la expresión se analiza así:
(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 dos primeros pares de paréntesis. También revise esta pregunta .
Aquí hay un simple fragmento 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
¿Podría alguien dar una explicación de por qué maxx no es 20? Gracias.
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 de forma obvia (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";
}
output línea.
Esto es semánticamente equivalente a todas las otras soluciones publicadas, y con cualquier compilador de optimización decente, no tiene gastos generales en absoluto. Es sintácticamente mucho mejor porque tiene
- un mínimo de repetición de código,
- los 4 asignados a variables están todos en el lado izquierdo de la tarea, y
- Las 4 variables asignadas están todas a la derecha.
Como una pequeña variación que se generaliza para encontrar punteros al elemento mínimo y máximo de las secuencias, puede usar std::minmax_element
y el hecho de que las matrices sin std::minmax_element
tienen funciones no miembros begin()
y end()
(ambas características 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";
}
output línea.
Interesante pregunta tanto sobre la precedencia de operaciones como sobre la generación de código.
OK, la operación tiene una prioridad MUY baja (la más baja, ver 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 de C / C ++ son las operaciones y comparación a nivel de bits. Considere sobre 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 eficiencia y 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 este 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 condiciones no cuesta nada y más, el compilador apunta a los fragmentos de código correctos).
La precedencia del operador condicional es mayor que la del operador de 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;
Así, la última tarea se realiza independientemente de la condición.
Para arreglarlo, puedes
utilizar paréntesis:
x1 <= x2 ? (minx = x1, maxx = x2) : (minx = x2, maxx = x1);
(no necesita los paréntesis en la rama
true
, pero es IMO mejor tenerlos también).utilizar dos condicionales:
minx = x1 <= x2 ? x1 : x2; maxx = x1 <= x2 ? x2 : x1;
usa un
if
:if (x1 <= x2) { minx = x1; maxx = x2; } else { minx = x2; maxx = x1; }
Compilado con o sin optimizaciones, la versión if
y el condicional único entre paréntesis con comas producen el mismo ensamblaje tanto en gcc (4.7.2) como en clang (3.2), es razonable esperar que también de otros compiladores. La versión con los dos condicionales produce un ensamblaje diferente, pero con optimizaciones, ambos compiladores emiten solo una instrucción cmp
para eso también.
En mi opinión, la versión if
es la más fácil de verificar if
es correcta, así que es preferible.
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:
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, pero también es más fácil de leer y borrar exactamente lo que sucede (y si necesita avanzar en el depurador, puede ver fácilmente hacia dónde se dirige).
Cualquier compilador moderno debería poder convertir cualquiera de estos en asignaciones condicionales bastante eficientes que hagan un buen trabajo al evitar las ramas (y por lo tanto, la "mala predicción de las ramas").
Preparé una pequeña prueba, que compilé usando
g++ -O2 -fno-inline -S -Wall ifs.cpp
Aquí está la fuente (tuve que hacer los parámetros para asegurar que el compilador no solo calculó el valor correcto directamente y solo mov $12,%rdx
, sino que hizo una comparación y decidió con 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 puede ver, el mine
y el original
son idénticos, y el romano
utiliza registros ligeramente diferentes y una forma diferente de cmov
, pero de lo contrario hacen lo mismo en el mismo número de instrucciones.