wake-up-neo.com

Der ternäre (bedingte) Operator in C

Was ist der Bedarf für den bedingten Operator? Funktionell ist es redundant, da es ein if-else-Konstrukt implementiert. Wenn der bedingte Operator effizienter ist als die entsprechende if-else-Zuweisung, warum kann if-else nicht vom Compiler effizienter interpretiert werden?

51
Bongali Babu

Der ternäre Operator ist eine Vereinfachung der Syntax und der Lesbarkeit, keine Leistungsverknüpfung. Die Menschen sind aufgrund von Bedingun- gen unterschiedlicher Komplexität gespalten. Bei kurzen Bedingungen kann es jedoch hilfreich sein, einen einzeiligen Ausdruck zu haben.

Da es sich dabei um einen Ausdruck handelt, wie Charlie Martin schrieb, bedeutet dies, dass er auf der rechten Seite einer Anweisung in C erscheinen kann.

61
John Feminella

In C besteht der wahre Nutzen darin, dass es sich um einen Ausdruck anstelle einer Anweisung handelt. Das heißt, Sie können es auf der rechten Seite (RHS) einer Anweisung haben. So können Sie bestimmte Dinge präziser schreiben.

152
Charlie Martin

Einige der anderen Antworten sind großartig. Ich bin jedoch überrascht, dass niemand erwähnt hat, dass er dazu beitragen kann, const Korrektheit auf kompakte Weise durchzusetzen.

Etwas wie das:

const int n = (x != 0) ? 10 : 20;

im Grunde ist n eineconst, deren Anfangswert von einer Bedingungsanweisung abhängt. Die einfachste Alternative ist, n nicht zu const zu machen. Dies würde es einer gewöhnlichen if ermöglichen, sie zu initialisieren. Wenn Sie jedoch const wünschen, kann dies nicht mit einer gewöhnlichen if durchgeführt werden. Der beste Ersatz, den Sie machen könnten, wäre die Verwendung einer Hilfsfunktion wie folgt:

int f(int x) {
    if(x != 0) { return 10; } else { return 20; }
}

const int n = f(x);

aber die ternäre if-Version ist viel kompakter und wohl lesbarer.

81
Evan Teran

Es ist entscheidend für die Verschleierung von Code wie folgt:

Look->       See?!

No
:(
Oh, well
);
36
Artelius

Kompaktheit und die Fähigkeit, ein Wenn-Dann-Sonstiges-Konstrukt in einen Ausdruck einzufügen.

11
tvanfosson

Es gibt viele Dinge in C, die technisch nicht benötigt werden, da sie mehr oder weniger leicht in Bezug auf andere Dinge implementiert werden können. Hier ist eine unvollständige Liste:

  1. während
  2. for
  3. funktionen
  4. structs

Stellen Sie sich vor, wie Ihr Code ohne diese aussehen würde und Sie könnten Ihre Antwort finden. Der ternäre Operator ist eine Form des "syntaktischen Zuckers", die das Schreiben und Verstehen von Code erleichtert, wenn sie mit Sorgfalt und Können verwendet wird.

10

Manchmal ist der ternäre Operator der beste Weg, um die Arbeit zu erledigen. Insbesondere wenn Sie möchten, dass das Ergebnis des Ternärs ein l-Wert ist.

Dies ist kein gutes Beispiel, aber ich zeichne ein leeres Zeichen auf etwas besseres. Eine Sache ist sicherer, es ist nicht oft, wenn Sie wirklich das Ternary verwenden müssen, obwohl ich es immer noch ziemlich benutze.

const char* appTitle  = amDebugging ? "DEBUG App 1.0" : "App v 1.0";

Eine Sache, vor der ich warnen möchte, ist das Aneinanderreihen von Ternaren. Sie werden real
problem zur wartungszeit:

int myVal = aIsTrue ? aVal : bIsTrue ? bVal : cIsTrue ? cVal : dVal;

EDIT: Hier ist ein möglicherweise besseres Beispiel. Sie können den ternären Operator verwenden, um Referenzen und const-Werte zuzuweisen, an die Sie sonst eine Funktion schreiben müssten, um damit umzugehen:

int getMyValue()
{
  if( myCondition )
    return 42;
  else
    return 314;
}

const int myValue = getMyValue();

...könnte werden:

const int myValue = myCondition ? 42 : 314;

Was besser ist, ist eine fragwürdige Frage, die ich nicht debattieren möchte.

9
John Dibling

Da dies noch niemand erwähnt hat, besteht der einzige Weg, um kluge printf-Anweisungen zu erhalten, darin, den ternären Operator zu verwenden:

printf("%d item%s", count, count > 1 ? "s\n" : "\n");

Einschränkung: Es gibt einige Unterschiede in der Rangfolge der Operatoren, wenn Sie von C nach C++ wechseln, und können von den subtilen Fehlern überrascht werden, die daraus entstehen.

8
dirkgently

Die Tatsache, dass der ternäre Operator ein Ausdruck und keine Anweisung ist, ermöglicht die Verwendung in Makroerweiterungen für funktionsartige Makros, die als Teil eines Ausdrucks verwendet werden. Const war möglicherweise nicht Teil des ursprünglichen C, der Makro-Vorprozessor geht jedoch weit zurück.

Ein Ort, an dem ich es gesehen habe, ist in einem Array-Paket, das Makros für gebundene Array-Zugriffe verwendete. Die Syntax für eine geprüfte Referenz war etwas wie aref(arrayname, type, index), wobei Arrayname tatsächlich ein Zeiger auf eine Struktur war, die die Arraygrenzen enthielt, und ein vorzeichenloses Char-Array für die Daten. Typ war der tatsächliche Datentyp und Index war Index. Die Erweiterung war ziemlich haarig (und ich werde es nicht auswendig tun), aber es wurden einige ternäre Operatoren verwendet, um die gebundene Überprüfung durchzuführen.

Sie können dies nicht als Funktionsaufruf in C ausführen, da für das zurückgegebene Objekt ein Polymorphismus erforderlich ist. Daher war ein Makro erforderlich, um die Typumwandlung im Ausdruck durchzuführen. In C++ könnten Sie dies als überladenen Funktionsaufruf mit Vorlage (wahrscheinlich für Operator []) ausführen, aber C verfügt nicht über solche Funktionen.

Edit: Hier ist das Beispiel, über das ich gesprochen habe, aus dem Berkeley CAD Array-Paket (Glu 1.4 Edition). Die Dokumentation der Verwendung von array_fetch lautet:

type
array_fetch(type, array, position)
typeof type;
array_t *array;
int position;

Holen Sie ein Element aus einem Array. EIN Laufzeitfehler tritt bei dem Versuch auf .__ auf. Referenz außerhalb der Grenzen von Array. Es gibt keine Typprüfung dass der Wert an der angegebenen Position ist tatsächlich der Typ, der verwendet wird, wenn das Array dereferenzieren.

und hier ist die Makrodefinition von array_fetch (beachten Sie die Verwendung des ternären Operators und des Komma-Sequenzierungsoperators, um alle Teilausdrücke mit den richtigen Werten in der richtigen Reihenfolge als Teil eines einzelnen Ausdrucks auszuführen):

#define array_fetch(type, a, i)         \
(array_global_index = (i),              \
  (array_global_index >= (a)->num) ? array_abort((a),1) : 0,\
  *((type *) ((a)->space + array_global_index * (a)->obj_size)))

Die Erweiterung für array_insert (die das Array bei Bedarf wie ein C++ - Vektor vergrößert) ist noch haariger und erfordert mehrere verschachtelte ternäre Operatoren. 

8
dewtell

Es ist syntaktischer Zucker und eine praktische Abkürzung für if/else-Blöcke, die nur eine Anweisung enthalten. Funktionell sollten beide Konstrukte identisch funktionieren.

4
Dana the Sane

Der ternäre Operator hat möglicherweise mehr Leistung als eine normale Klausel "else", sonst kann dies in eingebetteten Anwendungen kritisch sein, aber auch die Compiler-Optimierung kann diesen Unterschied reduzieren.

wie bei dwn gesagt, Leistung war einer der Vorteile beim Aufstieg komplexer Prozessoren, MSDN-Blog Nichtklassisches Prozessorverhalten: Wie etwas schneller sein kann, als es nicht zu tun gibt ein Beispiel, in dem der Unterschied zwischen ternär ( bedingter Operator und if/else-Anweisung.

gib den folgenden Code ein:

#include <windows.h>
#include <stdlib.h>
#include <stdlib.h>
#include <stdio.h>

int array[10000];

int countthem(int boundary)
{
 int count = 0;
 for (int i = 0; i < 10000; i++) {
  if (array[i] < boundary) count++;
 }
 return count;
}

int __cdecl wmain(int, wchar_t **)
{
 for (int i = 0; i < 10000; i++) array[i] = Rand() % 10;

 for (int boundary = 0; boundary <= 10; boundary++) {
  LARGE_INTEGER liStart, liEnd;
  QueryPerformanceCounter(&liStart);

  int count = 0;
  for (int iterations = 0; iterations < 100; iterations++) {
   count += countthem(boundary);
  }

  QueryPerformanceCounter(&liEnd);
  printf("count=%7d, time = %I64d\n",
         count, liEnd.QuadPart - liStart.QuadPart);
 }
 return 0;
}

die Kosten für unterschiedliche Grenzen sind sehr unterschiedlich und merkwürdig (siehe Originalmaterial). während wenn ändern:

 if (array[i] < boundary) count++;

zu

 count += (array[i] < boundary) ? 1 : 0;

Die Ausführungszeit ist jetzt unabhängig vom Grenzwert, da:

der Optimierer konnte den Zweig aus dem ternären Ausdruck entfernen.

aber auf meinem desktop intel i5 cpu/windows 10/vs2015 ist mein Testergebnis mit dem msdn-blog ganz anders.

bei Verwendung des Debug-Modus, wenn/else Kosten:

count=      0, time = 6434
count= 100000, time = 7652
count= 200800, time = 10124
count= 300200, time = 12820
count= 403100, time = 15566
count= 497400, time = 16911
count= 602900, time = 15999
count= 700700, time = 12997
count= 797500, time = 11465
count= 902500, time = 7619
count=1000000, time = 6429

und ternäre Betreiberkosten:

count=      0, time = 7045
count= 100000, time = 10194
count= 200800, time = 12080
count= 300200, time = 15007
count= 403100, time = 18519
count= 497400, time = 20957
count= 602900, time = 17851
count= 700700, time = 14593
count= 797500, time = 12390
count= 902500, time = 9283
count=1000000, time = 7020 

Wenn der Freigabemodus verwendet wird, falls/else kosten:

count=      0, time = 7
count= 100000, time = 9
count= 200800, time = 9
count= 300200, time = 9
count= 403100, time = 9
count= 497400, time = 8
count= 602900, time = 7
count= 700700, time = 7
count= 797500, time = 10
count= 902500, time = 7
count=1000000, time = 7

und ternäre Betreiberkosten:

count=      0, time = 16
count= 100000, time = 17
count= 200800, time = 18
count= 300200, time = 16
count= 403100, time = 22
count= 497400, time = 16
count= 602900, time = 16
count= 700700, time = 15
count= 797500, time = 15
count= 902500, time = 16
count=1000000, time = 16

der ternäre Operator ist langsamer als if/else-Anweisung auf meiner Maschine!

gemäß verschiedenen Compiler-Optimierungstechniken kann sich der interne Operator und if/else möglicherweise sehr unterschiedlich verhalten.

1
Brent81

ternary = einfache Form von if-else. Es ist meistens zur besseren Lesbarkeit verfügbar.

0
Alphaneo
  • Einige der unscheinbareren Operatoren in C gibt es nur, weil sie die Implementierung verschiedener funktionsartiger Makros als einen einzigen Ausdruck ermöglichen, der ein Ergebnis zurückgibt. Ich würde sagen, dass dies der Hauptzweck ist, warum die ?:- und ,-Operatoren existieren dürfen, obwohl ihre Funktionalität ansonsten überflüssig ist.

    Nehmen wir an, wir möchten ein funktionsartiges Makro implementieren, das den größten von zwei Parametern zurückgibt. Es würde dann wie folgt aufgerufen werden:

    int x = LARGEST(1,2);
    

    Die einzige Möglichkeit, dies als funktionsähnliches Makro zu implementieren, wäre

    #define LARGEST(x,y) ((x) > (y) ? (x) : (y))
    

    Mit einer if ... else-Anweisung wäre dies nicht möglich, da sie keinen Ergebniswert zurückgibt. Hinweis)

  • Der andere Zweck von ?: ist, dass in einigen Fällen die Lesbarkeit tatsächlich erhöht wird. Meistens ist if...else besser lesbar, aber nicht immer. Nehmen Sie zum Beispiel lange, sich wiederholende switch-Anweisungen:

    switch(something)
    {
      case A: 
        if(x == A)
        {
          array[i] = x;
        }
        else
        {
          array[i] = y;
        }
        break;
    
      case B: 
        if(x == B)
        {
          array[i] = x;
        }
        else
        {
          array[i] = y;
        }
        break;
      ...
    }
    

    Dies kann durch das weitaus lesbarere ersetzt werden

    switch(something)
    {
      case A: array[i] = (x == A) ? x : y; break;
      case B: array[i] = (x == B) ? x : y; break;
      ...
    }
    
  • Bitte beachten Sie, dass ?: never zu einem schnelleren Code als if-else führt. Das ist ein seltsamer Mythos, der von verwirrten Anfängern geschaffen wurde. Bei optimiertem Code ergibt ?: in der überwiegenden Mehrzahl der Fälle die gleiche Leistung wie if-else

    Wenn überhaupt, kann ?: langsamer als if-else sein, da er mit obligatorischen impliziten Typ-Promotionen versehen ist, sogar des Operanden, der nicht verwendet wird. ?: kann jedoch niemals schneller als if-else sein.


Hinweis) Nun wird natürlich jemand streiten und sich fragen, warum er keine Funktion verwendet. Wenn Sie eine Funktion verwenden können, ist dies immer einem funktionsähnlichen Makro vorzuziehen. Manchmal können Sie jedoch keine Funktionen verwenden. Nehmen wir zum Beispiel an, dass x im obigen Beispiel im Dateibereich deklariert ist. Der Initialisierer muss dann ein konstanter Ausdruck sein und darf daher keinen Funktionsaufruf enthalten. Andere praktische Beispiele, bei denen Sie funktionsähnliche Makros verwenden müssen, umfassen die typsichere Programmierung mit _Generic oder "X-Makros".

0
Lundin