Hier ist ein vereinfachtes Beispiel. Grundsätzlich werden Zeichenfolgen aus einer Zeichenfolgenliste überprüft. Wenn die Prüfung erfolgreich ist, wird diese Zeichenfolge ( filterStringOut(i);
) entfernt, und es ist nicht mehr erforderlich, andere Prüfungen fortzusetzen. Also continue
zum nächsten String.
void ParsingTools::filterStrings(QStringList &sl)
{
/* Filter string list */
QString s;
for (int i=0; i<sl.length(); i++) {
s = sl.at(i);
// Improper length, remove
if (s.length() != m_Length) {
filterStringOut(i);
continue; // Once removed, can move on to the next string
}
// Lacks a substring, remove
for (int j=0; j<m_Include.length(); j++) {
if (!s.contains(m_Include.at(j))) {
filterStringOut(i);
/* break; and continue; */
}
}
// Contains a substring, remove
for (int j=0; j<m_Exclude.length(); j++) {
if (s.contains(m_Exclude.at(j))) {
filterStringOut(i);
/* break; and continue; */
}
}
}
}
Wie soll man die äußere Schleife innerhalb einer verschachtelten Schleife fortsetzen?
Meine beste Vermutung ist, goto
ein Etikett am Ende der äußeren Schleife zu verwenden und zu platzieren. Das veranlasste mich, diese Frage zu stellen, da Tabu sein goto
kann.
Im c ++ IRC-Chat wurde vorgeschlagen, die for
Schleifen in Bool-Funktionen zu platzieren, die true zurückgeben, wenn eine Prüfung bestanden wurde. somit
if ( containsExclude(s)) continue;
if (!containsInclude(s)) continue;
oder dass ich einfach einen lokalen Booleschen break
Wert erstelle, ihn auf true setze , bool überprüfe und fortfahre, wenn true.
Da ich dies in einem Parser verwende, muss ich in diesem Beispiel die Leistung priorisieren. Ist dies eine Situation, in der goto
es noch nützlich ist, oder ist es ein Fall, in dem ich meinen Code neu strukturieren muss?
goto
trotz ihres schlechten Rufs über zu emulieren . Fürchte keine Namen - fürchte Konzepte.Antworten:
Nicht verschachteln: stattdessen in Funktionen konvertieren. Lassen Sie diese Funktionen zurückkehren,
true
wenn sie ihre Aktion ausführen und die nachfolgenden Schritte übersprungen werden können.false
Andernfalls. Auf diese Weise vermeiden Sie das gesamte Problem, wie Sie aus einer Ebene ausbrechen, in einer anderen fortfahren usw., während Sie nur die Aufrufe verketten||
(dies setzt voraus, dass C ++ die Verarbeitung eines Ausdrucks auf a beendettrue
; ich denke, das tut es).Ihr Code könnte also wie folgt aussehen (ich habe C ++ seit Jahren nicht mehr geschrieben, daher enthält er wahrscheinlich Syntaxfehler, sollte Ihnen aber die allgemeine Idee geben):
quelle
ParsingTools::filterStrings
) diefilterStringOut(i)
Funktion aufrufen , wie in der Antwort vonAus der Vogelperspektive würde ich den Code so umgestalten, dass er so aussieht ... (im Pseudocode ist es zu lange her, dass ich C ++ berührt habe)
Dies ist meiner Meinung nach sauberer, weil es klar trennt, was eine richtige Zeichenfolge ausmacht und was Sie tun, wenn dies nicht der Fall ist.
Sie können sogar noch einen Schritt weiter gehen und integrierte Filtermethoden wie verwenden
myProperStrings = allMyStrings.filter(isProperString)
quelle
Mir gefällt sehr gut, wie @dagnelies anfängt . Kurz und auf den Punkt. Eine gute Verwendung der Abstraktion auf hoher Ebene. Ich optimiere nur die Signatur und vermeide ein unnötiges Negativ.
Mir gefällt jedoch, wie @DavidArno die Anforderungstests als einzelne Funktionen aufteilt . Sicher wird das Ganze länger, aber jede Funktion ist wunderbar klein. Ihre Namen vermeiden die Notwendigkeit von Kommentaren, um zu erklären, was sie sind. Ich mag es einfach nicht, dass sie die zusätzliche Verantwortung übernehmen, anzurufen
filterStringOut()
.Übrigens, ja, C ++ stoppt die Auswertung einer
||
Kettetrue
, solange Sie den||
Operator nicht überladen haben . Dies wird als Kurzschlussauswertung bezeichnet . Dies ist jedoch eine triviale Mikrooptimierung, die Sie beim Lesen des Codes ignorieren können, solange die Funktionen nebenwirkungsfrei sind (wie die folgenden).Folgendes sollte die Definition einer Ablehnungszeichenfolge klar machen, ohne Sie durch unnötige Details zu ziehen:
Die Notwendigkeit,
filterStringOut()
die Anforderungstestfunktionen aufzurufen , wird kürzer und ihre Namen sind viel einfacher. Ich habe auch alles, wovon sie abhängig sind, in ihre Parameterliste aufgenommen, um es einfacher zu machen, sie zu verstehen, ohne nach innen zu schauen.Ich fügte hinzu
requiredSubstring
undforbiddenSubstring
für die Menschen. Werden sie dich verlangsamen? Testen und herausfinden. Es ist einfacher, lesbaren Code tatsächlich schnell zu machen, als vorzeitig optimierten Code lesbar oder tatsächlich schnell zu machen.Wenn sich herausstellt, dass die Funktionen Sie verlangsamen, schauen Sie sich Inline-Funktionen an, bevor Sie die Menschen unlesbarem Code aussetzen. Gehen Sie auch hier nicht davon aus, dass dies Ihnen Geschwindigkeit verleiht. Prüfung.
Ich denke, Sie werden feststellen, dass diese für Loops besser lesbar als verschachtelt sind. Diese, kombiniert mit den
if
's, gaben Ihnen ein echtes Pfeil-Anti-Muster . Ich denke, die Lehre hier ist, dass winzige Funktionen eine gute Sache sind.quelle
! isProperString
eher alsisImproperString
beabsichtigt. Ich neige dazu, Negationen in Funktionsnamen zu vermeiden. Stellen Sie sich vor, Sie müssen später überprüfen, ob es sich tatsächlich um eine richtige Zeichenfolge handelt. Sie benötigen diese,!isImproperString
die meiner Meinung nach aufgrund der doppelten Negation eher zu Verwirrung neigt.Verwenden Sie einfach ein Lambda für das Prädikat und nutzen Sie dann die Leistung von Standardalgorithmen und Kurzschlüssen. Keine Notwendigkeit für einen verschlungenen oder exotischen Kontrollfluss:
quelle
Es besteht auch die Möglichkeit, den Inhalt der äußeren Schleife (die Sie fortsetzen möchten) zu einem Lambda zu machen und einfach zu verwenden
return
.Es ist überraschend einfach, wenn Sie Lambdas kennen. Sie beginnen Ihr Loop-Interieur im Grunde mit
[&]{
und beenden es mit}()
; innen können Sie es jederzeit verwendenreturn;
, um es zu verlassen:quelle
continue
break
return
continue
continue
Ich denke, @dganelies hat die richtige Idee als Ausgangspunkt, aber ich denke, ich würde einen Schritt weiter gehen: Schreiben Sie eine generische Funktion, die für (fast) jeden Container, jedes Kriterium und jede Aktion dasselbe Muster ausführen kann:
Sie
filterStrings
würden dann einfach die Kriterien definieren und die entsprechende Aktion ausführen:Natürlich gibt es auch andere Möglichkeiten, um dieses Grundproblem anzugehen. Wenn Sie beispielsweise die Standardbibliothek verwenden, möchten Sie anscheinend etwas in derselben allgemeinen Reihenfolge wie
std::remove_if
.quelle
Mehrere Antworten deuten auf einen wichtigen Refaktor des Codes hin. Dies ist wahrscheinlich kein schlechter Weg, aber ich wollte eine Antwort geben, die eher der Frage selbst entspricht.
Regel 1: Profil vor der Optimierung
Profilieren Sie die Ergebnisse immer, bevor Sie eine Optimierung versuchen. Wenn Sie dies nicht tun, verschwenden Sie möglicherweise viel Zeit.
Davon abgesehen ...
So wie es ist, habe ich diese Art von Code persönlich auf MSVC getestet. Die Booleschen Werte sind der richtige Weg. Nennen Sie den Booleschen etwas semantisch Bedeutendes
containsString
.Unter MSVC (2008) optimierte der Compiler im Release-Modus (typische Optimierungseinstellungen) eine ähnliche Schleife auf genau den gleichen Satz von Opcodes wie die
goto
Version. Es war klug genug zu sehen, dass der Wert des Booleschen Werts direkt mit der Steuerung des Flusses verbunden war und alles wegwarf. Ich habe gcc nicht getestet, aber ich gehe davon aus, dass es ähnliche Arten der Optimierung durchführen kann.Dies hat den Vorteil
goto
, dass Puristen, diegoto
es für schädlich halten, einfach keine Bedenken äußern , ohne die Leistung einer einzelnen Anweisung zu beeinträchtigen.quelle