Vorteil der Umschaltung der if-else-Anweisung

168

Was ist die beste Vorgehensweise für die Verwendung einer switchAnweisung im Vergleich zur Verwendung einer ifAnweisung für 30 unsignedAufzählungen, bei denen etwa 10 eine erwartete Aktion haben (dies ist derzeit dieselbe Aktion) ? Leistung und Platz müssen berücksichtigt werden, sind jedoch nicht kritisch. Ich habe das Snippet abstrahiert, also hasse mich nicht für die Namenskonventionen.

switch Aussage:

// numError is an error enumeration type, with 0 being the non-error case
// fire_special_event() is a stub method for the shared processing

switch (numError)
{  
  case ERROR_01 :  // intentional fall-through
  case ERROR_07 :  // intentional fall-through
  case ERROR_0A :  // intentional fall-through
  case ERROR_10 :  // intentional fall-through
  case ERROR_15 :  // intentional fall-through
  case ERROR_16 :  // intentional fall-through
  case ERROR_20 :
  {
     fire_special_event();
  }
  break;

  default:
  {
    // error codes that require no additional action
  }
  break;       
}

if Aussage:

if ((ERROR_01 == numError)  ||
    (ERROR_07 == numError)  ||
    (ERROR_0A == numError)  || 
    (ERROR_10 == numError)  ||
    (ERROR_15 == numError)  ||
    (ERROR_16 == numError)  ||
    (ERROR_20 == numError))
{
  fire_special_event();
}
Zing-
quelle
26
Dies wird als "subjektiv" bearbeitet? "Ja wirklich?" Sicherlich ist "subjektiv" für Dinge, die auf die eine oder andere Weise nicht bewiesen werden können?
Alexandra Franks
Sicher können Sie es von dem Punkt aus sehen, an dem der effizienteste Code generiert wird, aber jeder moderne Compiler sollte genauso effizient sein. Am Ende ist dies eher eine Frage der Farbe des Fahrradschuppens.
JFS
8
Ich bin anderer Meinung, ich denke nicht, dass dies subjektiv ist. Ein einfacher ASM-Unterschied ist wichtig. In vielen Fällen können Sie einige Sekunden der Optimierung nicht einfach außer Acht lassen. Und in dieser Frage handelt es sich nicht um einen Religionskrieg oder eine Debatte. Es gibt eine rationale Erklärung dafür, warum man schneller sein würde. Lesen Sie einfach die akzeptierte Antwort.
Chakrit
1
Was schneller ist: stackoverflow.com/questions/6805026/is-switch-faster-than-if
Ciro Santilli 法轮功 冠状 病. 事件 法轮功
@RichardFranks offtopic: Grats! Du bist der erste Mensch, der die Moderation auf SO übernommen hat, den ich je gesehen habe
jungle_mole

Antworten:

162

Schalter verwenden.

Im schlimmsten Fall generiert der Compiler denselben Code wie eine If-else-Kette, sodass Sie nichts verlieren. Wenn Sie Zweifel haben, geben Sie die häufigsten Fälle zuerst in die switch-Anweisung ein.

Im besten Fall findet der Optimierer möglicherweise einen besseren Weg, um den Code zu generieren. Ein Compiler erstellt häufig einen binären Entscheidungsbaum (speichert Vergleiche und Sprünge im Durchschnitt) oder erstellt einfach eine Sprungtabelle (funktioniert überhaupt ohne Vergleiche).

Nils Pipenbrinck
quelle
2
Technisch gesehen wird es immer noch einen Vergleich geben, um sicherzustellen, dass der Wert der Aufzählung in der Sprungtabelle liegt.
0124816
Jep. Das ist richtig. Wenn Sie die Aufzählungen einschalten und alle Fälle behandeln, wird der letzte Vergleich möglicherweise nicht mehr angezeigt.
Nils Pipenbrinck
4
Beachten Sie, dass eine Reihe von Wenns theoretisch so analysiert werden könnte, dass sie mit einem Switch eines Compilers identisch sind. Warum sollten Sie jedoch die Chance nutzen? Mit einem Switch kommunizieren Sie genau das, was Sie möchten, was die Codegenerierung erleichtert.
Jakobengblom2
5
jakoben: Das könnte man machen, aber nur für schalterartige if / else-Ketten. In der Praxis treten diese nicht auf, weil Programmierer Schalter verwenden. Ich habe mich mit Compilertechnologie beschäftigt und vertraue mir: Das Finden solcher "nutzlosen" Konstrukte nimmt viel Zeit in Anspruch. Für Compiler-Leute macht eine solche Optimierung Sinn.
Nils Pipenbrinck
5
@NilsPipenbrinck mit den einfachen Aufbau pseudo-rekursive if- elseKetten in Vorlage Meta - Programmierung und die Schwierigkeiten bei der Erzeugung von switch caseKetten, kann diese Zuordnung wichtiger worden. (und ja, alter Kommentar, aber das Web ist für immer oder zumindest bis zum nächsten Dienstag)
Yakk - Adam Nevraumont
45

Für den Sonderfall, den Sie in Ihrem Beispiel angegeben haben, lautet der klarste Code wahrscheinlich:

if (RequiresSpecialEvent(numError))
    fire_special_event();

Dies verschiebt das Problem natürlich nur in einen anderen Bereich des Codes, aber jetzt haben Sie die Möglichkeit, diesen Test wiederzuverwenden. Sie haben auch mehr Möglichkeiten, wie Sie es lösen können. Sie könnten std :: set verwenden, zum Beispiel:

bool RequiresSpecialEvent(int numError)
{
    return specialSet.find(numError) != specialSet.end();
}

Ich schlage nicht vor, dass dies die beste Implementierung von RequiresSpecialEvent ist, nur dass es eine Option ist. Sie können weiterhin einen Schalter oder eine if-else-Kette oder eine Nachschlagetabelle oder eine Bitmanipulation für den Wert verwenden. Je dunkler Ihr Entscheidungsprozess wird, desto mehr Wert erhalten Sie, wenn Sie ihn in einer isolierten Funktion haben.

Mark Ransom
quelle
5
Das ist so wahr. Die Lesbarkeit ist so viel besser als die switch- und if-Anweisungen. Eigentlich wollte ich so etwas selbst beantworten, aber du hast mich geschlagen. :-)
mlarsen
Wenn Ihre Aufzählungswerte alle klein sind, benötigen Sie keinen Hash, sondern nur eine Tabelle. zB const std::bitset<MAXERR> specialerror(initializer); Verwenden Sie es mit if (specialerror[numError]) { fire_special_event(); }. Wenn Sie die Grenzen überprüfen möchten, bitset::test(size_t)wird eine Ausnahme für Werte außerhalb der Grenzen ausgelöst. ( bitset::operator[]überprüft nicht die Reichweite). cplusplus.com/reference/bitset/bitset/test . Dies wird wahrscheinlich eine vom Compiler generierte Sprungtabelle übertreffen, die implementiert switch, insb. in dem nicht speziellen Fall, in dem dies ein einzelner nicht genommener Zweig ist.
Peter Cordes
@PeterCordes Ich behaupte immer noch, dass es besser ist, die Tabelle in eine eigene Funktion zu versetzen. Wie gesagt, es gibt viele Optionen, die sich eröffnen, wenn Sie das tun. Ich habe nicht versucht, sie alle aufzuzählen.
Mark Ransom
@ MarkRansom: Ich wollte nicht widersprechen, es zu abstrahieren. Gerade als Sie eine Beispielimplementierung mit verwendet haben std::set, dachte ich, ich würde darauf hinweisen, dass es wahrscheinlich eine schlechte Wahl ist. Es stellt sich heraus, dass gcc bereits den OP-Code kompiliert, um eine Bitmap in einem 32-Bit-Sofort zu testen. godbolt: goo.gl/qjjv0e . gcc 5.2 wird dies sogar für die ifVersion tun . Neuere gcc verwenden außerdem den Bit-Test-Befehl, btanstatt ihn zu verschieben, um ein 1Bit an die richtige Stelle zu setzen und zu verwenden test reg, imm32.
Peter Cordes
Diese Bitmap mit sofortiger Konstante ist ein großer Gewinn, da die Bitmap keinen Cache-Fehler enthält. Es funktioniert, wenn die "speziellen" Fehlercodes alle in einem Bereich von 64 oder weniger liegen. (oder 32 für alten 32-Bit-Code.) Der Compiler subtrahiert den kleinsten Fallwert, wenn er nicht Null ist. Das Wichtigste ist, dass neuere Compiler so intelligent sind, dass Sie wahrscheinlich guten Code aus der von Ihnen verwendeten Logik erhalten, es sei denn, Sie weisen sie an, eine umfangreiche Datenstruktur zu verwenden.
Peter Cordes
24

Der Wechsel ist schneller.

Versuchen Sie einfach, 30 verschiedene Werte in einer Schleife einzugeben, und vergleichen Sie sie mit dem gleichen Code mithilfe des Schalters, um festzustellen, wie viel schneller der Schalter ist.

Jetzt hat der Switch ein echtes Problem : Der Switch muss zur Kompilierungszeit die Werte in jedem Fall kennen. Dies bedeutet, dass der folgende Code:

// WON'T COMPILE
extern const int MY_VALUE ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

wird nicht kompiliert.

Die meisten Leute verwenden dann define (Aargh!), Und andere deklarieren und definieren konstante Variablen in derselben Kompilierungseinheit. Beispielsweise:

// WILL COMPILE
const int MY_VALUE = 25 ;

void doSomething(const int p_iValue)
{
    switch(p_iValue)
    {
       case MY_VALUE : /* do something */ ; break ;
       default : /* do something else */ ; break ;
    }
}

Am Ende muss der Entwickler also zwischen "Geschwindigkeit + Klarheit" und "Codekopplung" wählen.

(Nicht, dass ein Schalter nicht so geschrieben werden kann, dass er verdammt verwirrend ist ... Die meisten Schalter, die ich derzeit sehe, gehören zu dieser "verwirrenden" Kategorie "... Aber das ist eine andere Geschichte ...)

Bearbeiten 2008-09-21:

bk1e fügte den folgenden Kommentar hinzu: "Das Definieren von Konstanten als Aufzählungen in einer Header-Datei ist eine weitere Möglichkeit, dies zu handhaben."

Natürlich ist es das.

Der Zweck eines externen Typs bestand darin, den Wert von der Quelle zu entkoppeln. Das Definieren dieses Werts als Makro, als einfache const int-Deklaration oder sogar als Aufzählung hat den Nebeneffekt, dass der Wert eingefügt wird. Sollte sich also die Definition, der Aufzählungswert oder der konstante Wert ändern, wäre eine Neukompilierung erforderlich. Die externe Deklaration bedeutet, dass bei einer Wertänderung keine Neukompilierung erforderlich ist, die Verwendung von switch jedoch nicht möglich ist. Die Schlussfolgerung, Switch zu verwenden, erhöht die Kopplung zwischen dem Switch-Code und den als Fälle verwendeten Variablen . Wenn es in Ordnung ist, verwenden Sie den Schalter. Wenn es nicht ist, dann keine Überraschung.

.

Bearbeiten 2013-01-15:

Vlad Lazarenko kommentierte meine Antwort und gab einen Link zu seiner eingehenden Untersuchung des durch einen Schalter erzeugten Assembler-Codes. Sehr aufschlussreich: http://lazarenko.me/switch/

paercebal
quelle
Das Definieren von Konstanten als Aufzählungen in einer Header-Datei ist eine weitere Möglichkeit, dies zu handhaben.
bk1e
6
Der Wechsel ist nicht immer schneller .
1
@Vlad Lazarenko: Danke für den Link! Es war eine sehr interessante Lektüre.
Paercebal
1
Der Link von @AhmedHussein user404725 ist tot. Zum Glück habe ich es in der WayBack-Maschine gefunden: web.archive.org/web/20131111091431/http://lazarenko.me/2013/01/… . In der Tat kann die WayBack-Maschine ein ziemlicher Segen sein.
Jack Giffin
Vielen Dank, es ist sehr hilfreich
Ahmed Hussein
20

Der Compiler wird es trotzdem optimieren - wählen Sie den Schalter, da er am besten lesbar ist.

Alexandra Franks
quelle
3
Es besteht die Möglichkeit, dass der Compiler if-then-else nicht berührt. In der Tat gccwird das nicht sicher tun (es gibt einen guten Grund dafür). Clang optimiert beide Fälle in eine binäre Suche. Siehe zum Beispiel dies .
7

Der Switch, wenn auch nur zur besseren Lesbarkeit. Riesig, wenn Aussagen meiner Meinung nach schwerer zu pflegen und schwerer zu lesen sind.

FEHLER_01 : // absichtlicher

oder

(ERROR_01 == numError) ||

Letzteres ist fehleranfälliger und erfordert mehr Eingabe und Formatierung als das erste.

scubabbl
quelle
6

Code für die Lesbarkeit. Wenn Sie wissen möchten, was besser funktioniert, verwenden Sie einen Profiler, da Optimierungen und Compiler unterschiedlich sind und Leistungsprobleme selten dort auftreten, wo die Leute glauben, dass sie sind.

Bdoserror
quelle
6

Verwenden Sie den Schalter, es ist das, wofür es ist und was Programmierer erwarten.

Ich würde jedoch die überflüssigen Falletiketten einfügen - nur damit sich die Leute wohl fühlen, habe ich versucht, mich daran zu erinnern, wann / welche Regeln für das Auslassen gelten.
Sie möchten nicht, dass der nächste Programmierer, der daran arbeitet, unnötig über Sprachdetails nachdenken muss (möglicherweise sind Sie es in ein paar Monaten!).

Martin Beckett
quelle
4

Compiler können wirklich gut optimieren switch. Aktuelle gcc ist auch gut in der Optimierung einer Reihe von Bedingungen in einemif .

Ich habe einige Testfälle auf Godbolt gemacht .

Wenn die caseWerte eng zusammen gruppiert sind, sind gcc, clang und icc intelligent genug, um mithilfe einer Bitmap zu überprüfen, ob ein Wert einer der besonderen ist.

zB gcc 5.2 -O3 kompiliert das switchto (und das ifetwas sehr ähnliches):

errhandler_switch(errtype):  # gcc 5.2 -O3
    cmpl    $32, %edi
    ja  .L5
    movabsq $4301325442, %rax   # highest set bit is bit 32 (the 33rd bit)
    btq %rdi, %rax
    jc  .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Beachten Sie, dass es sich bei der Bitmap um unmittelbare Daten handelt, sodass kein potenzieller Daten-Cache-Fehler beim Zugriff darauf oder eine Sprungtabelle auftritt.

gcc 4.9.2 -O3 kompiliert das switchzu einer Bitmap, macht das aber 1U<<errNumbermit mov / shift. Es kompiliert die ifVersion zu einer Reihe von Zweigen.

errhandler_switch(errtype):  # gcc 4.9.2 -O3
    leal    -1(%rdi), %ecx
    cmpl    $31, %ecx    # cmpl $32, %edi  wouldn't have to wait an extra cycle for lea's output.
              # However, register read ports are limited on pre-SnB Intel
    ja  .L5
    movl    $1, %eax
    salq    %cl, %rax   # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
    testl   $2150662721, %eax
    jne .L10
.L5:
    rep ret
.L10:
    jmp fire_special_event()

Beachten Sie, wie 1 von subtrahiert wird errNumber(mit lea, um diese Operation mit einer Bewegung zu kombinieren). Dadurch kann die Bitmap sofort in eine 32-Bit-Version eingepasst werden, wobei die 64-Bit-Version sofort vermieden wirdmovabsq mehr Anweisungsbytes benötigt.

Eine kürzere (im Maschinencode) Sequenz wäre:

    cmpl    $32, %edi
    ja  .L5
    mov     $2150662721, %eax
    dec     %edi   # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
    bt     %edi, %eax
    jc  fire_special_event
.L5:
    ret

(Die Nichtverwendung jc fire_special_eventist allgegenwärtig und ein Compiler-Fehler .)

rep retwird in Zweigzielen und folgenden bedingten Zweigen zum Nutzen des alten AMD K8 und K10 (Pre-Bulldozer) verwendet: Was bedeutet "rep ret"? . Ohne diese Funktion funktioniert die Verzweigungsvorhersage auf diesen veralteten CPUs nicht so gut.

bt(Bittest) mit einem Register arg ist schnell. Es kombiniert die Arbeit, eine 1 um errNumberBits nach links zu verschieben und a zu tuntest , hat aber immer noch eine Latenz von 1 Zyklus und nur einen einzigen Intel-UOP. Mit einem Speicherargument ist es aufgrund seiner viel zu CISC-Semantik langsam: Mit einem Speicheroperanden für die "Bitfolge" wird die Adresse des zu testenden Bytes basierend auf dem anderen Arg (geteilt durch 8) berechnet und isn Es ist nicht auf den 1-, 2-, 4- oder 8-Byte-Block beschränkt, auf den der Speicheroperand zeigt.

Aus den Befehlstabellen von Agner Fog geht hervor , dass ein Shift-Befehl mit variabler Anzahl langsamer ist als ein btneuerer Intel-Befehl (2 Uops anstelle von 1, und Shift erledigt nicht alles, was sonst noch benötigt wird).

Peter Cordes
quelle
4

Die Vorteile von switch () gegenüber anderen Fällen sind: - 1. Switch ist viel effizienter als if else, da jeder Fall nicht vom vorherigen Fall abhängt, im Gegensatz zu anderen Fällen, in denen einzelne Anweisungen auf wahre oder falsche Bedingungen überprüft werden müssen.

  1. Wenn es eine Nr. Gibt. Bei den Werten für einen einzelnen Ausdruck ist die Groß- und Kleinschreibung flexibler, wenn dies der Fall ist, da in anderen Fällen die Beurteilung nur auf zwei Werten basiert, dh entweder wahr oder falsch.

  2. Die Werte in switch sind benutzerdefiniert, während die Werte in if else case auf Einschränkungen basieren.

  3. Im Fehlerfall können Anweisungen im Schalter leicht überprüft und korrigiert werden, was bei if else-Anweisungen vergleichsweise schwierig zu überprüfen ist.

  4. Das Schaltergehäuse ist viel kompakter und leicht zu lesen und zu verstehen.

Vineeth Krishna K.
quelle
2

IMO ist dies ein perfektes Beispiel dafür, wofür Switch Fall-Through gemacht wurde.

Nescio
quelle
in c # ist dies der einzige Fall, in dem Herbstgedanken auftreten. Gutes Argument genau dort.
BCS
2

Wenn Ihre Fälle in Zukunft wahrscheinlich gruppiert bleiben - wenn mehr als ein Fall einem Ergebnis entspricht - ist der Schalter möglicherweise leichter zu lesen und zu warten.

TSomKes
quelle
2

Sie funktionieren gleich gut. Die Leistung ist bei einem modernen Compiler ungefähr gleich.

Ich bevorzuge if-Anweisungen gegenüber case-Anweisungen, da sie lesbarer und flexibler sind. Sie können andere Bedingungen hinzufügen, die nicht auf numerischer Gleichheit basieren, z. B. "|| max <min". Aber für den einfachen Fall, den Sie hier gepostet haben, spielt es keine Rolle, tun Sie einfach das, was für Sie am besten lesbar ist.

SquareCog
quelle
2

Schalter ist definitiv bevorzugt. Es ist einfacher, sich die Liste der Fälle eines Switch anzusehen und sicher zu wissen, was er tut, als die lange if-Bedingung zu lesen.

Die Verdoppelung des ifZustands ist schwer für die Augen. Angenommen, einer der ==wurde geschrieben !=; würdest du es bemerken Oder wenn eine Instanz von 'numError' 'nmuError' geschrieben wurde, die gerade kompiliert wurde?

Ich würde im Allgemeinen lieber Polymorphismus anstelle des Schalters verwenden, aber ohne weitere Details des Kontexts ist es schwer zu sagen.

In Bezug auf die Leistung ist es am besten, einen Profiler zu verwenden, um die Leistung Ihrer Anwendung unter Bedingungen zu messen, die denen entsprechen, die Sie in freier Wildbahn erwarten. Andernfalls optimieren Sie wahrscheinlich am falschen Ort und auf die falsche Weise.

Jay Bazuzi
quelle
2

Ich bin mit der Kompatibilität der Switch-Lösung einverstanden, aber IMO entführen Sie den Switch hier.
Der Zweck des Schalters besteht darin, je nach Wert eine unterschiedliche Handhabung zu haben .
Wenn Sie Ihr Algo in Pseudocode erklären müssten, würden Sie ein if verwenden, weil es semantisch so ist: wenn Whatever_error dies tut ... Es
sei denn, Sie beabsichtigen, Ihren Code eines Tages so zu ändern, dass er für jeden Fehler einen bestimmten Code enthält Ich würde wenn verwenden .

Francesca
quelle
2
Ich bin nicht einverstanden, aus dem gleichen Grund, aus dem ich mit dem Fall-obwohl-Fall nicht einverstanden bin. Ich las den Schalter als "In den Fällen 01,07,0A, 10,15,16 und 20 Feuer Sonderereignis." Es gibt keinen Fall-in für einen anderen Abschnitt. Dies ist nur ein Artefakt der C ++ - Syntax, bei dem Sie das Schlüsselwort 'case' für jeden Wert wiederholen.
MSalters
1

Ich würde die if-Aussage aus Gründen der Klarheit und Konvention auswählen, obwohl ich sicher bin, dass einige anderer Meinung sind. Schließlich möchten Sie etwas tun, ifeine Bedingung ist wahr! Einen Schalter mit einer Aktion zu haben, scheint ein wenig ... unnötig.

William Keller
quelle
1

Ich würde sagen, SWITCH verwenden. Auf diese Weise müssen Sie nur unterschiedliche Ergebnisse implementieren. Ihre zehn identischen Fälle können die Standardeinstellung verwenden. Sollte eine Änderung nur die Änderung explizit implementieren, müssen Sie die Standardeinstellung nicht bearbeiten. Es ist auch viel einfacher, Fälle zu einem SWITCH hinzuzufügen oder daraus zu entfernen, als IF und ELSEIF zu bearbeiten.

switch(numerror){
    ERROR_20 : { fire_special_event(); } break;
    default : { null; } break;
}

Vielleicht testen Sie sogar Ihren Zustand (in diesem Fall numerror) anhand einer Liste von Möglichkeiten, einem Array, damit Ihr SWITCH nicht einmal verwendet wird, es sei denn, es wird definitiv ein Ergebnis geben.

Lewis
quelle
Insgesamt gibt es ungefähr 30 Fehler. 10 erfordern die spezielle Aktion, daher verwende ich die Standardeinstellung für die ~ 20 Fehler, für die keine Aktion erforderlich ist ...
Zing
1

Da Sie nur 30 Fehlercodes haben, codieren Sie Ihre eigene Sprungtabelle und treffen dann alle Optimierungsentscheidungen selbst (der Sprung ist immer am schnellsten), anstatt zu hoffen, dass der Compiler das Richtige tut. Es macht auch den Code sehr klein (abgesehen von der statischen Deklaration der Sprungtabelle). Es hat auch den Nebeneffekt, dass Sie mit einem Debugger das Verhalten zur Laufzeit ändern können, wenn Sie dies benötigen, indem Sie die Tabellendaten direkt durchsuchen.

Greg Whitfield
quelle
Wow, das scheint ein Weg zu sein, ein einfaches Problem in ein komplexes zu verwandeln. Warum sollten Sie sich all diese Mühe machen, wenn der Compiler einen großartigen Job für Sie macht? Außerdem handelt es sich anscheinend um einen Fehlerbehandler, sodass es wahrscheinlich nicht so schnell ist. Ein Schalter ist bei weitem am einfachsten zu lesen und zu warten.
MrZebra
Eine Tabelle ist kaum komplex - tatsächlich ist sie wahrscheinlich einfacher als ein Wechsel zum Code. In der Erklärung wurde erwähnt, dass die Leistung ein Faktor ist.
Greg Whitfield
Das klingt nach vorzeitiger Optimierung. Solange Sie Ihre Enum-Werte klein und zusammenhängend halten, sollte der Compiler dies für Sie tun. Wenn Sie den Schalter in eine separate Funktion stellen, bleibt der Code, der ihn verwendet, schön klein, wie Mark Ransom in seiner Antwort vorschlägt, und bietet den gleichen Vorteil für kleinen Code.
Peter Cordes
Wenn Sie selbst etwas implementieren möchten, machen Sie std::bitset<MAXERR> specialerror;dann eine if (specialerror[err]) { special_handler(); }. Dies ist schneller als ein Sprungtisch, insb. im nicht genommenen Fall.
Peter Cordes
1

Ich bin mir bei Best Practices nicht sicher, aber ich würde den Schalter verwenden - und dann den absichtlichen Durchfall über "Standard" abfangen.

da5id
quelle
1

Ästhetisch tendiere ich dazu, diesen Ansatz zu bevorzugen.

unsigned int special_events[] = {
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20
 };
 int special_events_length = sizeof (special_events) / sizeof (unsigned int);

 void process_event(unsigned int numError) {
     for (int i = 0; i < special_events_length; i++) {
         if (numError == special_events[i]) {
             fire_special_event();
             break;
          }
     }
  }

Machen Sie die Daten etwas intelligenter, damit wir die Logik etwas dümmer machen können.

Mir ist klar, dass es komisch aussieht. Hier ist die Inspiration (wie ich es in Python machen würde):

special_events = [
    ERROR_01,
    ERROR_07,
    ERROR_0A,
    ERROR_10,
    ERROR_15,
    ERROR_16,
    ERROR_20,
    ]
def process_event(numError):
    if numError in special_events:
         fire_special_event()
mbac32768
quelle
4
Eine Sprache , die Syntax ist eine Auswirkung darauf haben , wie wir eine Lösung implementieren ... => Es sieht in C und schön in Python hässlich. :)
Rlerallut
Bitmaps verwenden? Wenn error_0a 0x0a usw. ist, können Sie sie als Bits in eine lange Länge setzen. long long special_events = 1LL << 1 | 1LL << 7 | 1LL << 0xa ... Dann benutze if (special_events & (1LL << numError) fire_special_event ()
paperhorse
1
Yuck. Sie haben eine O (1) Worst-Case-Operation (wenn Sprungtabellen generiert werden) in O (N) Worst-Case (wobei N die Anzahl der behandelten Fälle ist) umgewandelt und eine breakexterne a case(ja, eine Moll -Operation) verwendet Sünde, aber dennoch eine Sünde). :)
Mac
Yuck? Er sagte, Leistung und Platz seien nicht kritisch. Ich schlug einfach eine andere Sichtweise auf das Problem vor. Wenn wir ein Problem so darstellen können, dass Menschen weniger denken können, ist es mir normalerweise egal, ob Computer mehr denken müssen.
mbac32768
1
while (true) != while (loop)

Wahrscheinlich wird die erste vom Compiler optimiert, was erklären würde, warum die zweite Schleife langsamer ist, wenn die Anzahl der Schleifen erhöht wird.

MarioFrost
quelle
Dies scheint ein Kommentar zu McAnix 'Antwort zu sein. Dies ist nur eines der Probleme bei diesem Versuch des Timings im ifVergleich switchzu einer Schleifenendbedingung in Java.
Peter Cordes
1

Beim Kompilieren des Programms weiß ich nicht, ob es einen Unterschied gibt. Aber was das Programm selbst betrifft und den Code so einfach wie möglich zu halten, denke ich persönlich, dass es davon abhängt, was Sie tun möchten. if else if else Aussagen haben ihre Vorteile, die ich denke:

Mit dieser Funktion können Sie eine Variable anhand bestimmter Bereiche testen. Sie können Funktionen (Standard Library oder Personal) als Bedingungen verwenden.

(Beispiel:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 if( a > 0 && a < 5)
   {
     cout<<"a is between 0, 5\n";

   }else if(a > 5 && a < 10)

     cout<<"a is between 5,10\n";

   }else{

       "a is not an integer, or is not in range 0,10\n";

Wenn sonst, wenn sonst, können Aussagen (trotz Ihrer besten Versuche) in Eile kompliziert und chaotisch werden. Switch-Anweisungen sind in der Regel klarer, sauberer und leichter zu lesen. kann aber nur zum Testen gegen bestimmte Werte verwendet werden (Beispiel:

`int a;
 cout<<"enter value:\n";
 cin>>a;

 switch(a)
 {
    case 0:
    case 1:
    case 2: 
    case 3:
    case 4:
    case 5:
        cout<<"a is between 0,5 and equals: "<<a<<"\n";
        break;
    //other case statements
    default:
        cout<<"a is not between the range or is not a good value\n"
        break;

Ich bevorzuge if - else if - else-Aussagen, aber es liegt wirklich an Ihnen. Wenn Sie Funktionen als Bedingungen verwenden möchten oder etwas gegen einen Bereich, ein Array oder einen Vektor testen möchten und / oder es Ihnen nichts ausmacht, sich mit der komplizierten Verschachtelung zu befassen, würde ich die Verwendung von If else if else-Blöcken empfehlen. Wenn Sie anhand einzelner Werte testen möchten oder einen sauberen und leicht lesbaren Block wünschen, würde ich die Verwendung von case-Blöcken (switch ()) empfehlen.

Jordan Effinger
quelle
0

Ich bin nicht die Person, die Ihnen etwas über Geschwindigkeit und Speichernutzung erzählt, aber das Betrachten einer Switch-Anweisung ist verdammt viel einfacher zu verstehen als eine große if-Anweisung (insbesondere 2-3 Monate später).

Ed Brown
quelle
0

Ich weiß, dass es alt ist, aber

public class SwitchTest {
static final int max = 100000;

public static void main(String[] args) {

int counter1 = 0;
long start1 = 0l;
long total1 = 0l;

int counter2 = 0;
long start2 = 0l;
long total2 = 0l;
boolean loop = true;

start1 = System.currentTimeMillis();
while (true) {
  if (counter1 == max) {
    break;
  } else {
    counter1++;
  }
}
total1 = System.currentTimeMillis() - start1;

start2 = System.currentTimeMillis();
while (loop) {
  switch (counter2) {
    case max:
      loop = false;
      break;
    default:
      counter2++;
  }
}
total2 = System.currentTimeMillis() - start2;

System.out.println("While if/else: " + total1 + "ms");
System.out.println("Switch: " + total2 + "ms");
System.out.println("Max Loops: " + max);

System.exit(0);
}
}

Das Variieren der Schleifenzahl ändert sich stark:

Während if / else: 5ms Switch: 1ms Max Loops: 100000

Während if / else: 5ms Switch: 3ms Max Loops: 1000000

Während if / else: 5ms Switch: 14ms Max Loops: 10000000

Während if / else: 5ms Switch: 149ms Max Loops: 100000000

(Fügen Sie weitere Anweisungen hinzu, wenn Sie möchten)

McAnix
quelle
3
Guter Punkt, aber sry, Alter, du bist in der falschen Sprache. Die Sprache ändert sich sehr;)
Gabriel Schreiber
2
Die if(max) breakSchleife läuft unabhängig von der Anzahl der Schleifen in konstanter Zeit? Klingt so, als ob der JIT-Compiler intelligent genug ist, um die Schleife zu optimieren counter2=max. Und vielleicht ist es langsamer als das Umschalten, wenn der erste Aufruf currentTimeMillismehr Overhead hat, weil noch nicht alles JIT-kompiliert ist? Wenn Sie die Schleifen in die andere Reihenfolge bringen, erhalten Sie wahrscheinlich andere Ergebnisse.
Peter Cordes