¿Por qué usarías una asignación en una condición?

80

En muchos idiomas, las asignaciones son legales en condiciones. Nunca entendí la razón detrás de esto. Por qué escribirías:

if (var1 = var2) {
  ...
}

en vez de:

var1 = var2;
if (var1) {
  ...
}
lajos
fuente
¿Es esta pregunta realmente tan independiente del lenguaje? ¿Qué idiomas se ven afectados por esto? Sé que C y C ++ son, ¿qué más?
Wolf

Respuestas:

115

Es más útil para bucles que declaraciones if.

while( var = GetNext() )
{
  ...do something with var 
}

Que de otro modo tendría que escribirse

var = GetNext();
while( var )
{
 ...do something
 var = GetNext();
}
Gerald
fuente
14
Estoy de acuerdo, pero escribiría: while ((var = GetNext ())! = 0) {...} sin pensarlo dos veces, en parte porque GCC se queja si no lo hago con mis opciones de compilación predeterminadas.
Jonathan Leffler
1
Cierto. Suponiendo que está usando un lenguaje que admite declaraciones de bucle for, y no está usando var fuera del bucle.
Gerald
1
en algunos idiomas, como ANSI C, las reglas de alcance no permiten este enfoque, @KerrekSB
wirrbel
7
@wirrbel: ¿Te refieres a ANSI C de qué, 1989? ¿Como cuando tenían máquinas de vapor y tarjetas perforadas? Ese puede ser el caso, pero ¿es relevante?
Kerrek SB
3
Esa advertencia no se debe a que haya algún problema con una tarea en un tiempo, sino a que hacer una tarea cuando realmente tenía la intención de hacer una comparación es un error común. Si desea deshacerse de la advertencia, puede hacer el equivalente en JS de lo que sugiere @Jonathan Leffler. Personalmente, probablemente haría eso de todos modos debido a lo mucho que desconfío de las reglas de verdad de JavaScript :) También debería tenerse en cuenta en algunos lenguajes como C # y Java que es la única forma.
Gerald
33

Lo encuentro más útil en cadenas de acciones que a menudo implican detección de errores, etc.

if ((rc = first_check(arg1, arg2)) != 0)
{
    report error based on rc
}
else if ((rc = second_check(arg2, arg3)) != 0)
{
    report error based on new rc
}
else if ((rc = third_check(arg3, arg4)) != 0)
{
    report error based on new rc
}
else
{
    do what you really wanted to do
}

La alternativa (no usar la asignación en la condición) es:

rc = first_check(arg1, arg2);
if (rc != 0)
{
    report error based on rc
}
else
{
    rc = second_check(arg2, arg3);
    if (rc != 0)
    {
        report error based on new rc
    }
    else
    {
        rc = third_check(arg3, arg4);
        if (rc != 0)
        {
            report error based on new rc
        }
        else
        {
            do what you really wanted to do
        }
    }
}

Con una verificación de errores prolongada, la alternativa puede salir del lado derecho de la página, mientras que la versión de asignación en condicional no lo hace.

Las comprobaciones de errores podrían ser también 'acciones' - first_action(), second_action(), third_action()- por supuesto, en lugar de cheques solo. Es decir, se podrían comprobar pasos en el proceso que está gestionando la función. (La mayoría de las veces en el código con el que trabajo, las funciones están en la línea de las verificaciones de condiciones previas, o las asignaciones de memoria necesarias para que la función funcione, o en líneas similares).

Jonathan Leffler
fuente
Sí, en realidad hice esto para evitar el anidamiento excesivo y luego busqué en línea para ver si era una práctica común. Al final, decidí no hacerlo porque en realidad solo tenía un caso más, pero esto parece una razón legítima para poner la asignación en la condición.
Ibrahim
¡Los compañeros de trabajo lo odian! Para mí, al menos en la mayoría de los casos, es mucho más fácil de mantener que un árbol gigante o muchas devoluciones. Prefiero un solo retorno de una función ...
Nathan
un ejemplo razonable, los bloques de acompañamiento hacen que el patrón sea obvio, autoexplicativo. Pero, bueno, el código en bloque ("pseudo") se ve un poco feo, y el estilo inverso (colocar lo que realmente quería hacer al final) no es tan bueno.
Wolf
31

Es más útil si está llamando a una función:

if (n = foo())
{
    /* foo returned a non-zero value, do something with the return value */
} else {
    /* foo returned zero, do something else */
}

Claro, puede poner n = foo (); en una declaración separada, entonces si (n), pero creo que lo anterior es un idioma bastante legible.

Chris Young
fuente
3
Creo que debería encerrarlo entre paréntesis o gcc se quejará con: advertencia: sugerir paréntesis alrededor de la asignación usada como valor de verdad [-Wparentheses] Así: if ((n = foo ())) {// hacer algo con el valor de retorno } else {// foo devolvió cero / NULL, hacer algo más}
Fabio Pozzi
23

Puede ser útil si está llamando a una función que devuelve datos sobre los que trabajar o una marca para indicar un error (o que ya ha terminado).

Algo como:

while ((c = getchar()) != EOF) {
    // process the character
}

// end of file reached...

Personalmente es un idioma que no me gusta mucho, pero a veces la alternativa es más fea.

Michael Burr
fuente
13

GCC puede ayudarlo a detectar (con -Wall) si involuntariamente intenta usar una asignación como valor de verdad, en caso de que le recomiende escribir

if ((n = foo())) {
   ...
}

Es decir, use paréntesis extra para indicar que esto es realmente lo que desea.

JesperE
fuente
1
Bueno saber. Espero que sus compañeros de trabajo lo lean de la misma manera.
Wolf
1
¿Yo? Dios mío, nunca intentaría utilizar la asignación como un valor de verdad. Me refería a lo que recomienda GCC.
JesperE
GCC también puede compilar Fortran . ¿Quizás sea explícito sobre el lenguaje de programación?
Peter Mortensen
9

El idioma es más útil cuando estás escribiendo un whilebucle en lugar de una ifdeclaración. Para una ifdeclaración, puede dividirlo como lo describe. Pero sin esta construcción, tendría que repetirse:

c = getchar();
while (c != EOF) {
    // ...
    c = getchar();
}

o use una estructura de bucle y medio:

while (true) {
    c = getchar();
    if (c == EOF) break;
    // ...
}

Por lo general, preferiría la forma de bucle y medio.

Greg Hewgill
fuente
1
Para un whilebucle, prefiera usar:do { c = getchar(); ... } while (c != EOF);
Scott S
Y forse pueden usar bucles en su lugar: for (char c = getchar(); c != EOF; c= getchar()) { /* do something with c */ }- este es el más conciso, y forsiempre me dice 'bucle', estilísticamente. El pequeño inconveniente es que debe especificar la función que regresa cdos veces.
Scott S
4

La respuesta corta es que los lenguajes de programación orientados a expresiones permiten un código más sucinto. No te obligan a separar los comandos de las consultas .

Hugh Allen
fuente
1
Es una lucha constante para hacer cumplir la codificación sin asignaciones en if (), while () y otras declaraciones similares. C / C ++ son conocidos por sus efectos secundarios que a menudo resultan ser errores, especialmente cuando agregamos los operadores ++ y -.
Alexis Wilke
1
El primer enlace es inútil: C o C ++ parecen no ser ese lenguaje.
Wolf
3

En PHP, por ejemplo, es útil para recorrer los resultados de la base de datos SQL:

while ($row = mysql_fetch_assoc($result)) {
    // Display row
}

Esto se ve mucho mejor que:

$row = mysql_fetch_assoc($result);
while ($row) {
    // Display row
    $row = mysql_fetch_assoc($result);
}
Jeremy Ruten
fuente
4
Esto también tiene el claro efecto secundario de que $ row no vive más allá de ese bucle y, por lo tanto, es elegible para la recolección de basura antes (en idiomas que hacen GC de todos modos).
Darren Greaves
2

La otra ventaja viene durante el uso de gdb. En el siguiente código no se conoce el código de error si fuéramos a un solo paso.

while (checkstatus() != -1) {
    // process
}

Más bien

while (true) {
    int error = checkstatus();
    if (error != -1)
        // process
    else
        //fail
}

Ahora, durante un solo paso, podemos saber cuál fue el código de error de retorno del checkstatus ().

plasmixs
fuente
1
¿Quiere decir que puede ser útil para cambios temporales al depurar código?
Wolf
0

Lo encuentro muy útil con funciones que devuelven opcionales ( boost::optionalo std::optionalen C ++ 17):

std::optional<int> maybe_int(); // function maybe returns an int

if (auto i = maybe_int()) {
    use_int(*i);
}

Esto reduce el alcance de mi variable, hace que el código sea más compacto y no dificulta la legibilidad (lo encuentro).

Lo mismo con los punteros:

int* ptr_int();

if (int* i = ptr_int()) {
    use_int(*i);
}
Julien-L
fuente
Por cierto, si desea limitar el alcance de las variables, puede usar if con inicializadores :)
cigien
-3

La razón es:

  1. Mejora del rendimiento (a veces)

  2. Menos código (siempre)

Tome un ejemplo: hay un método someMethod()y en una ifcondición desea verificar si el valor de retorno del método es null. Si no es así, volverá a utilizar el valor de retorno.

If(null != someMethod()){
    String s = someMethod();
    ......
    //Use s
}

Obstaculizará el rendimiento ya que está llamando al mismo método dos veces. En su lugar use:

String s;
If(null != (s = someMethod())) {
    ......
    //Use s
}
Gangadhar
fuente
¿Qué lenguaje es este? No se compilaría en C # o C ++ (capitalización de "Si").
Peter Mortensen