c++ - übungen - vba mehrere if bedingungen verknüpfen




Warum scheinbar sinnlose do-while- und if-else-Anweisungen in Makros verwenden? (7)

In vielen C / C ++ - Makros sehe ich den Code des Makros in einer scheinbar sinnlosen do while Schleife. Hier sind Beispiele.

#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else

Ich kann nicht sehen, was do while mache. Warum schreibst du das nicht ohne es?

#define FOO(X) f(X); g(X)

@ jfm3 - Du hast eine nette Antwort auf die Frage. Vielleicht möchten Sie auch hinzufügen, dass das Makro-Idiom auch das möglicherweise gefährlichere (weil kein Fehler ist) unbeabsichtigtes Verhalten mit einfachen 'if' Anweisungen verhindert:

#define FOO(x)  f(x); g(x)

if (test) FOO( baz);

erweitert zu:

if (test) f(baz); g(baz);

Das ist syntaktisch korrekt, also gibt es keinen Compilerfehler, aber hat wahrscheinlich die unbeabsichtigte Konsequenz, dass g () immer aufgerufen wird.


Aus einigen Gründen kann ich die erste Antwort nicht kommentieren ...

Einige von Ihnen haben Makros mit lokalen Variablen gezeigt, aber niemand hat erwähnt, dass Sie nicht einfach irgendeinen Namen in einem Makro verwenden können! Es wird den Benutzer eines Tages beißen! Warum? Weil die Eingabeargumente in Ihre Makrovorlage eingefügt werden. Und in Ihren Makro-Beispielen verwenden Sie den wahrscheinlich am häufigsten verwendeten Variablennamen i .

Zum Beispiel wenn das folgende Makro

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)

wird in der folgenden Funktion verwendet

void some_func(void) {
    int i;
    for (i = 0; i < 10; ++i)
        FOO(i);
}

Das Makro wird nicht die beabsichtigte Variable i verwenden, die am Anfang von some_func deklariert ist, sondern die lokale Variable, die in der do ... while-Schleife des Makros deklariert ist.

Verwenden Sie daher niemals gemeinsame Variablennamen in einem Makro!


Die do ... while und if ... else sind dafür da, dass ein Semikolon nach deinem Makro immer dasselbe bedeutet. Nehmen wir an, Sie hätten etwas wie Ihr zweites Makro.

#define BAR(X) f(x); g(x)

Nun, wenn Sie BAR(X); benutzen würden BAR(X); In einer if ... else Anweisung, in der die Körper der if-Anweisung nicht in geschweifte Klammern eingeschlossen waren, würden Sie eine böse Überraschung bekommen.

if (corge)
  BAR(corge);
else
  gralt();

Der obige Code würde sich erweitern in

if (corge)
  f(corge); g(corge);
else
  gralt();

Das ist syntaktisch inkorrekt, da das else nicht mehr mit dem if verknüpft ist. Es hilft nicht, Dinge in geschweifte Klammern innerhalb des Makros zu wickeln, weil ein Semikolon nach den geschweiften Klammern syntaktisch falsch ist.

if (corge)
  {f(corge); g(corge);};
else
  gralt();

Es gibt zwei Möglichkeiten, das Problem zu beheben. Die erste besteht darin, ein Komma zu verwenden, um Anweisungen innerhalb des Makros zu sequenzieren, ohne ihm die Fähigkeit zu nehmen, sich wie ein Ausdruck zu verhalten.

#define BAR(X) f(X), g(X)

Die obige Version von bar BAR erweitert den obigen Code in das Folgende, was syntaktisch korrekt ist.

if (corge)
  f(corge), g(corge);
else
  gralt();

Dies funktioniert nicht, wenn Sie anstelle von f(X) einen komplizierteren Code haben, der in einen eigenen Block gehen muss, zum Beispiel um lokale Variablen zu deklarieren. Im allgemeinsten Fall besteht die Lösung darin, etwas wie do ... while zu verwenden, um zu bewirken, dass das Makro eine einzelne Anweisung ist, die ein Semikolon ohne Verwirrung nimmt.

#define BAR(X) do { \
  int i = f(X); \
  if (i > 4) g(i); \
} while (0)

Du brauchst nicht zu do ... while du mit etwas kochen könntest, if ... else auch, obwohl wenn if ... else in einem if ... else expandiert if ... else führt es zu einem " dangling else" ", die ein bestehendes dangling-else-Problem noch schwerer zu finden machen könnte, als im folgenden Code.

if (corge)
  if (1) { f(corge); g(corge); } else;
else
  gralt();

Der Punkt besteht darin, das Semikolon in Kontexten zu verwenden, in denen ein baumelndes Semikolon fehlerhaft ist. Natürlich könnte (und sollte wahrscheinlich) an dieser Stelle argumentiert werden, dass es besser wäre, BAR als eine tatsächliche Funktion, nicht als ein Makro zu deklarieren.

Zusammenfassend ist das do ... while es darum geht, die Unzulänglichkeiten des C-Präprozessors zu umgehen. Wenn diese C-Style-Guides Ihnen sagen, dass Sie den C-Preprozessor ablegen sollen, ist das etwas, worüber sie sich Sorgen machen.


Die obigen Antworten erklären die Bedeutung dieser Konstrukte, aber es gibt einen signifikanten Unterschied zwischen den beiden, der nicht erwähnt wurde. In der Tat gibt es einen Grund, das do ... while zu bevorzugen do ... while das Konstruieren if ... else .

Das Problem des if ... else Konstrukts ist, dass es Sie nicht zwingt , das Semikolon zu setzen. Wie in diesem Code:

FOO(1)
printf("abc");

Obwohl wir das Semikolon (aus Versehen) weggelassen haben, wird der Code auf erweitert

if (1) { f(X); g(X); } else
printf("abc");

und kompiliert automatisch (obwohl einige Compiler möglicherweise eine Warnung für nicht erreichbaren Code ausgeben). Aber die printf Anweisung wird niemals ausgeführt.

do ... while construct hat kein solches Problem, da das einzige gültige Token nach while(0) ein Semikolon ist.


Ich glaube nicht, dass es erwähnt wurde

while(i<100)
  FOO(i++);

würde übersetzt werden in

while(i<100)
  do { f(i++); g(i++); } while (0)

Beachten Sie, wie i++ zweimal vom Makro ausgewertet wird. Dies kann zu einigen interessanten Fehlern führen.


Jens Gustedts P99-Präprozessorbibliothek (ja, die Tatsache, dass so etwas existiert, hat mich auch umgehauen!) Verbessert das if(1) { ... } else Konstrukt in kleiner, aber signifikanter Weise, indem es Folgendes definiert:

#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP

Der Grund dafür ist, dass im Gegensatz zu do { ... } while(0) construct, break und continue immer noch innerhalb des gegebenen Blocks arbeiten, aber das ((void)0) einen Syntaxfehler erzeugt, wenn das Semikolon nach dem Makroaufruf, der sonst den nächsten Block überspringen würde. (Es gibt hier kein "dangling else" Problem, da das else an das nächste if bindet, welches das im Makro ist.)

Wenn Sie an den Dingen interessiert sind, die mit dem C-Präprozessor mehr oder weniger sicher erledigt werden können, sollten Sie diese Bibliothek ausprobieren.


Während erwartet wird, dass Compiler das do { ... } while(false); optimieren do { ... } while(false); Schleifen gibt es eine andere Lösung, die dieses Konstrukt nicht benötigt. Die Lösung ist, den Komma-Operator zu verwenden:

#define FOO(X) (f(X),g(X))

oder noch exotischer:

#define FOO(X) g((f(X),(X)))

Während dies mit separaten Anweisungen gut funktioniert, funktioniert es nicht in Fällen, in denen Variablen als Teil von #define konstruiert und verwendet werden:

#define FOO(X) (int s=5,f((X)+s),g((X)+s))

Damit wäre man gezwungen, das Do / While-Konstrukt zu verwenden.





c++-faq