Julia Kontrollfluss

Kontrollflussanweisungen werden implementiert, indem eine oder mehrere Bedingungsanweisungen in einem Programm festgelegt werden. Wenn die Bedingung wahr ist, wird der angegebene Programmcode ausgeführt; wenn die Bedingung falsch ist, wird anderer angegebener Code ausgeführt.

Julia bietet eine große Anzahl von Kontrollflussanweisungen:

  • Zusammengesetzte Ausdrücke:beginund;。

  • Bedingte Ausdrücke:if-elseif-elseund?:(ternärer Operator).

  • Kurzschlussauswertung:logische Operatoren&&(und) und||(oder), sowie verkettete Vergleiche.

  • Schleifenanweisungen:Schleifen:whileundfor。

  • Ausnahmebehandlung:try-catch、errorundthrow。

  • Task (Koroutine):yieldto。


Zusammengesetzte Ausdrücke

begin ... endEin Ausdruck kann nacheinander mehrere Teilausdrücke auswerten und den Wert des letzten Teilausdrucks zurückgeben:

Beispiel

julia> z = begin
           x = 1
           y = 2
           x + y
       end
3

Weil diese Ausdrücke sehr kurz sind, können sie einfach in eine Zeile gesetzt werden, was der Ursprung der;Kette ist:

Beispiel

julia> z = (x = 1; y = 2; x + y)
3

In der tatsächlichen Verwendung ist es nicht erforderlich, dass der begin-Block mehrzeilig ist oder dass die ;-Kette einzeilig ist:

Beispiel

julia> begin x = 1; y = 2; x + y end
3

julia> (x = 1;
        y = 2;
        x + y)
3

Bedingte Ausdrücke

Ein bedingter Ausdruck kann anhand des Werts eines booleschen Ausdrucks entscheiden, welcher Codeblock ausgeführt wird.

if-elseif-else-Syntax:

if boolean_expression 1
   /*Wenn der boolesche Ausdruck1wahr ist, ausführen*/
elseif boolean_expression 2
   /*Wenn der boolesche Ausdruck2wahr ist, ausführen*/
elseif boolean_expression 3
   /*Wenn der boolesche Ausdruck3wahr ist, ausführen*/
else
   /*Wenn keine der oben genannten Bedingungen wahr ist, ausführen*/

Eineif-Anweisung kann von einer optionalenelse if...else-Anweisung gefolgt werden, mit der mehrere Bedingungen getestet werden können.

Bei der Verwendung von if...else if...else-Anweisungen sind folgende Punkte zu beachten:

  • Auf ein if kann null oder ein else folgen, und das else muss nach allen else ifs stehen.
  • Ein if kann von null oder mehr else ifs gefolgt werden, und die else ifs müssen vor dem else stehen.
  • Sobald ein else if erfolgreich übereinstimmt, werden die anderen else ifs oder else nicht getestet.

Nachfolgend eine Analyse der if-elseif-else-Bedingungssyntax:

Beispiel

if x < y
    println("x is less than y")
elseif x > y
    println("x ist größer als y")
else
    println("x ist gleich y")
end

Wenn der Ausdruck x < y wahr ist, wird der entsprechende Codeblock ausgeführt; andernfalls wird der bedingte Ausdruck x > y ausgewertet. Wenn er wahr ist, wird der entsprechende Codeblock ausgeführt; wenn kein Ausdruck wahr ist, wird der else-Codeblock ausgeführt.

Beispiel

julia> function test(x, y)
           if x < y
               println("x is less than y")
           elseif x > y
               println("x ist größer als y")
           else
               println("x ist gleich y")
           end
       end
Teste (generische Funktion mit 1 Methode)

julia> test(1, 2)
x ist kleiner als y

julia> test(2, 1)
x ist größer als y

julia> test(1, 1)
x ist gleich y

Ternär

Der ternäre Operator?:ist ähnlich zur if-elseif-else-Syntax:

a ? b : c

In?Der vorherige Ausdruck a ist ein bedingter Ausdruck. Wenn Bedingung a wahr ist, wertet der ternäre Operator Ausdruck b vor dem : aus; wenn Bedingung a falsch ist, führt er Ausdruck c nach dem : aus.

Hinweis:?und:Leerzeichen davor und danach sind zwingend erforderlich. Ein Ausdruck wie a?b:c ist kein gültiger ternärer Ausdruck (aber Zeilenumbrüche nach ? und : sind erlaubt).

Beispiel

julia> x = 1; y = 2;

julia> println(x < y ? "kleiner als" : "nicht kleiner als")
kleiner als

julia> x = 1; y = 0;

julia> println(x < y ? "kleiner als" : "nicht kleiner als")
nicht kleiner als

Wenn der Ausdruck x < y wahr ist, liefert der gesamte ternäre Operator die Zeichenkette "kleiner als"; andernfalls liefert er die Zeichenkette "nicht kleiner als".

Verkettung und Verschachtelung ternärer Operatoren:

Beispiel

julia> test(x, y) = println(x < y ? "x ist kleiner als y"    :
                            x > y ? "x ist größer als y" : "x ist gleich y")
Teste (generische Funktion mit 1 Methode)

julia> test(1, 2)
x ist kleiner als y

julia> test(2, 1)
x ist größer als y

julia> test(1, 1)
x ist gleich y

Um die verkettete Wertübergabe zu erleichtern, werden die Operatoren von rechts nach links verbunden.

Ähnlich wie bei if-elseif-else werden die Ausdrücke vor und nach:nur dann entsprechend ausgeführt, wenn der Bedingungsausdruck wahr oder falsch ist:

Beispiel

julia> v(x) = (println(x); x)
v (generische Funktion mit 1 Methode)

julia> 1 < 2 ? v("ja") : v("nein")
ja
"ja"

julia> 1 > 2 ? v("ja") : v("nein")
nein
"nein"

Kurzschlussauswertung

In Julia entsprechen die&&- und||-Operatoren dem logischen"und"und"oder"Operationen.

  • Im Ausdrucka && b, der Teilausdruckbwird nur ausgeführt, wennaisttrue; wenn a false ist, wird direkt false zurückgegeben.
  • Im Ausdrucka || b, der Teilausdruckbwird nur ausgeführt, wennaistfalse; wenn a true ist, wird a direkt zurückgegeben.

&& Beispiel

julia> isodd(3) && @warn("Eine ungerade Zahl!")
┌ Warnung: Eine ungerade Zahl!
└ @ Main REPL[5]:1

julia> isodd(4) && @warn("Eine ungerade Zahl!")
false

|| Beispiel

julia> isodd(3) || @warn("Eine ungerade Zahl!")
wahr

julia> isodd(4) || @warn("Eine ungerade Zahl!")
┌ Warnung: Eine ungerade Zahl!
└ @ Main REPL[8]:1

Sowohl && als auch || hängen von der rechten Seite ab, aber && hat eine höhere Priorität als ||. Siehe das Beispiel:

Beispiel

julia> t(x) = (println(x); true)
t (generische Funktion mit 1 Methode)

julia> f(x) = (println(x); false)
f (generische Funktion mit 1 Methode)

julia> t(1) && t(2)
1
2
wahr

julia> t(1) && f(2)
1
2
false

julia> f(1) && t(2)
1
false

julia> f(1) && f(2)
1
false

julia> t(1) || t(2)
1
wahr

julia> t(1) || f(2)
1
wahr

julia> f(1) || t(2)
1
2
wahr

julia> f(1) || f(2)
1
2
false

Schleifenanweisungen

Schleifenanweisungen verwendenwhileundforzwei Schlüsselwörter zur Implementierung.

Nachfolgend ist ein Beispiel für einewhile-Schleife:

Beispiel

julia> i = 1;

julia> while i <= 5
           println(i)
           global i += 1
       end
1
2
3
4
5

Die while-Schleife wertet den Bedingungsausdruck (i <= 5) aus. Solange er wahr ist, führt sie den Rumpf der while-Schleife weiter aus. Wenn der Bedingungsausdruck falsch ist, das heißt, wenn i = 6, endet die Schleife.

Die for-Schleife ist bequemer zu verwenden. Das obige, mit einer for-Schleife implementierte Beispiel lautet wie folgt:

Beispiel

julia> for i = 1:5
           println(i)
       end
1
2
3
4
5

Hier,1:5ist ein Range-Objekt, das die Zahlenfolge 1, 2, 3, 4, 5 repräsentiert.

Die for-Schleife iteriert über diese Werte und weist dabei der Variable i jeweils einen Wert zu.

Ein sehr wichtiger Unterschied zwischen der for-Schleife und der vorherigen while-Schleife ist der Gültigkeitsbereich (Scope), also die Sichtbarkeit von Variablen. Wenn die Variable i nicht in einem anderen Gültigkeitsbereich eingeführt wurde, ist sie innerhalb der for-Schleife nur innerhalb der for-Schleife sichtbar und außerhalb oder danach nicht sichtbar. Sie benötigen eine neue interaktive Sitzungsinstanz oder einen neuen Variablennamen, um dieses Verhalten zu testen:

Beispiel

julia> for j = 1:5
           println(j)
       end
1
2
3
4
5

julia> j
ERROR: UndefVarError: j not defined

Im Allgemeinen können for-Schleifenkomponenten verwendet werden, um über jeden beliebigen Container zu iterieren. In diesem Fall wird im Vergleich zu = das andere (aber vollständig identische) Schlüsselwortinoder∈wird häufiger verwendet, weil es den Code klarer macht:

Beispiel

julia> for i in [1,4,0]
           println(i)
       end
1
4
0

julia> for s ∈ ["foo","bar","baz"]
           println(s)
       end
foo
bar
baz

Der Einfachheit halber können wir eine while-Schleife beenden, bevor die Testbedingung falsch wird, oder eine for-Schleife stoppen, bevor das Ende des iterierten Objekts erreicht ist. Dies kann mit dem Schlüsselwortbreak erfolgen.:

Beispiel

julia> i = 1;

julia> while true
           println(i)
           if i >= 5
               break
           end
           global i += 1
       end
1
2
3
4
5

julia> for j = 1:1000
           println(j)
           if j >= 5
               break
           end
       end
1
2
3
4
5

Ohne das Schlüsselwort break würde die obige while-Schleife niemals von selbst enden, und die for-Schleife würde bis 1000 iterieren. Diese Schleifen können alle break verwenden, um vorzeitig zu beenden.

In manchen Szenarien ist es notwendig, die aktuelle Iteration direkt zu beenden und sofort zur nächsten Iteration überzugehen. DascontinueSchlüsselwort kann verwendet werden, um dies zu erreichen:

Beispiel

julia> for i = 1:10
           if i % 3 != 0
               continue
           end
           println(i)
       end
3
6
9

Dies ist ein etwas konstruiertes Beispiel, denn wir könnten dieselbe Funktionalität prägnanter erreichen, indem wir die Bedingung negieren und den println-Aufruf in einen if-Block setzen. In praktischen Anwendungen gibt es mehr Code, der nach continue ausgeführt wird, und es kann mehrere Stellen geben, an denen continue aufgerufen wird.

Mehrere verschachtelte for-Schleifen können zu einer einzigen äußeren Schleife zusammengefasst werden, mit der sich das kartesische Produkt ihrer iterierten Objekte bilden lässt:

Beispiel

julia> for i = 1:2, j = 3:4
           println((i, j))
       end
(1, 3)
(1, 4)
(2, 3)
(2, 4)

Mit dieser Syntax können Iterationsvariablen weiterhin normal mit Schleifenvariablen zur Indizierung verwendet werden. Zum Beispiel istfor i = 1:n, j = 1:izulässig, aber die Verwendung einer break-Anweisung innerhalb einer Schleife bricht aus der gesamten verschachtelten Schleife aus, nicht nur aus der inneren Schleife. Jedes Mal, wenn die innere Schleife ausgeführt wird, werden den Variablen (i und j) ihre aktuellen Iterationswerte zugewiesen. Zuweisungen an i sind also für nachfolgende Iterationen nicht sichtbar:

Beispiel

julia> for i = 1:2, j = 3:4
           println((i, j))
           i = 0
       end
(1, 3)
(1, 4)
(2, 3)
(2, 4)

Wenn dieses Beispiel neu geschrieben wird, indem für jede Variable das Schlüsselwort for verwendet wird, ist die Ausgabe anders: Die zweite und vierte Variable enthalten 0.

Sie können zip verwenden, um in einer einzigen for-Schleife gleichzeitig über mehrere Container zu iterieren:

Beispiel

julia> for (j, k) in zip([1 2 3], [4 5 6 7])
           println((j,k))
       end
(1, 4)
(2, 5)
(3, 6)

julia> for x in zip(0:15, 100:110, 200:210)
               println(x)
            end
(0, 100, 200)
(1, 101, 201)
(2, 102, 202)
(3, 103, 203)
(4, 104, 204)
(5, 105, 205)
(6, 106, 206)
(7, 107, 207)
(8, 108, 208)
(9, 109, 209)
(10, 110, 210)

// Inkonsistente Anzahlen von Elementen behandeln
julia> for x in zip(0:10, 100:115, 200:210)
               println(x)
            end
(0, 100, 200)
(1, 101, 201)
(2, 102, 202)
(3, 103, 203)
(4, 104, 204)
(5, 105, 205)
(6, 106, 206)
(7, 107, 207)
(8, 108, 208)
(9, 109, 209)
(10, 110, 210)

Die Verwendung von zip erstellt einen Iterator, der ein Tupel von Unteriteratoren für die ihm übergebenen Container ist. Der zip-Iterator iteriert in der Reihenfolge über alle Unteriteratoren und wählt bei der ii-ten Iteration der for-Schleife das ii-te Element jedes Unteriterators aus. Sobald ein Unteriterator erschöpft ist, stoppt die for-Schleife.

for-Anweisungen können mehrere Schleifenbedingungen verschachteln, wobei Kommas,zur Trennung verwendet werden:

Beispiel

julia> for n in 1:5, m in 1:5
                  @show (n, m)
               end
(n, m) = (1, 1)
(n, m) = (1, 2)
(n, m) = (1, 3)
(n, m) = (1, 4)
(n, m) = (1, 5)
(n, m) = (2, 1)
(n, m) = (2, 2)
(n, m) = (2, 3)
(n, m) = (2, 4)
(n, m) = (2, 5)
(n, m) = (3, 1)
(n, m) = (3, 2)
(n, m) = (3, 3)
(n, m) = (3, 4)
(n, m) = (3, 5)
(n, m) = (4, 1)
(n, m) = (4, 2)
(n, m) = (4, 3)
(n, m) = (4, 4)
(n, m) = (4, 5)
(n, m) = (5, 1)
(n, m) = (5, 2)
(n, m) = (5, 3)
(n, m) = (5, 4)
(n, m) = (5, 5)

Komprehensionen

Eine Komprehension ist eine besondere Datenverarbeitungsmethode, mit der aus einer Datenfolge eine neue Datenfolgenstruktur konstruiert werden kann.

Das Format ist wie folgt:

[AusdruckforVariableinListe]
[out_exp_res for out_exp in input_list]

oder

[AusdruckforVariableinListeifcondition]
[out_exp_res for out_exp in input_list if condition]


Beispiel

julia> [X^2 for X in 1:5]
5-element Array{Int64,1}:
 1
 4
 9
 16
 25

Wir können auch den Typ der Elemente angeben, die wir generieren möchten:

Beispiel

julia> Complex[X^2 for X in 1:5]
5-element Array{Complex,1}:
 1 + 0im
 4 + 0im
 9 + 0im
 16 + 0im
 25 + 0im

Arrays durchlaufen

Manchmal möchten wir über jedes Element eines Arrays iterieren, einschließlich der Indexnummer des Elements.

Julia bietet dieenumerate(iter)Funktion; der Parameteriterist ein iterierbares Objekt. Diese Funktion erzeugt die Indexnummern sowie den Wert, der jedem Index entspricht.

Beispiel

julia> a = ["a", "b", "c"];

julia> for (index, value) in enumerate(a)
           println("$index $value")
       end
1 a
2 b
3 c

julia> arr = rand(0:9, 4, 4)
4×4 Array{Int64,2}:
 7 6 5 8
 8 6 9 4
 6 3 0 7
 2 3 2 4
 
julia> [x for x in enumerate(arr)]
4×4 Array{Tuple{Int64,Int64},2}:
 (1, 7) (5, 6) (9, 5) (13, 8)
 (2, 8) (6, 6) (10, 9) (14, 4)
 (3, 6) (7, 3) (11, 0) (15, 7)
 (4, 2) (8, 3) (12, 2) (16, 4)

Ausnahmebehandlung

Während der Programmausführung kann eine Funktion, wenn eine unerwartete Bedingung auftritt, möglicherweise keinen sinnvollen Wert an ihren Aufrufer zurückgeben. In solchen Fällen ist es am besten, die unerwartete Bedingung das Programm beenden zu lassen und Debug-Fehlerinformationen auszugeben, was es Entwicklern erleichtert, ihren Code zu behandeln.

Durch dietry / catchAnweisung können Ausnahmen bequem behandelt werden.

Zum Beispiel löst im folgenden Code die Quadratwurzelfunktion sqrt eine Ausnahme aus. Durch try / catch können wir die Ausnahmeinformationen genau ausgeben.

Beispiel

julia> try
         sqrt("ten")
       catch e
         println("Sie müssen eine Zahl eingeben")
       end
Sie müssen eine Zahl eingeben

finally-Klausel

Wenn Sie mit Zustandsänderungen programmieren oder Ressourcen wie Dateien verwenden, ist es oft notwendig, Aufräumarbeiten (wie das Schließen von Dateien) auszuführen, wenn der Code endet. Da Ausnahmen dazu führen können, dass einige Codeblöcke vor dem normalen Abschluss beendet werden, kann dies die oben genannte Arbeit erschweren. Das Schlüsselwort finally bietet eine Möglichkeit, einen bestimmten Codeabschnitt auszuführen, wenn ein Codeblock verlassen wird, unabhängig davon, wie er verlassen wird.

Hier ist ein Beispiel, das sicherstellt, dass eine geöffnete Datei geschlossen wird:

Beispiel

f = open("file")
try
    # Datei f bearbeiten
finally
    close(f)
end

Wenn der Kontrollfluss den try-Block verlässt (zum Beispiel, wenn return auftritt oder wenn er normal endet), wird close(f) ausgeführt. Wenn der try-Block aufgrund einer Ausnahme verlassen wird, breitet sich die Ausnahme weiter aus. Der catch-Block kann zusammen mit try und finally verwendet werden. In diesem Fall wird der finally-Block ausgeführt, nachdem catch den Fehler behandelt hat.

throw-Funktion

Wir können throw verwenden, um Ausnahmen explizit zu erzeugen.

Wenn eine Funktion beispielsweise nur für nichtnegative Zahlen definiert ist, können Sie throw verwenden, um einen DomainError auszulösen, wenn der Eingabeparameter negativ ist.

Beispiel

julia> f(x) = x>=0 ? exp(-x) : throw(DomainError(x, "argument must be nonnegative"))
f (generische Funktion mit 1 Methode)

julia> f(1)
0.36787944117144233

julia> f(-1)
FEHLER: DomainError mit -1:
Argument muss nichtnegativ sein
Stacktrace:
 [1] f(::Int64) bei ./none:1

Beachten Sie, dass DomainError ohne Klammern keine Exception, sondern ein Exception-Typ ist. Wir müssen ihn aufrufen, um ein Exception-Objekt zu erhalten:

Beispiel

julia> typeof(DomainError(nothing)) <: Exception
wahr

julia> typeof(DomainError) <: Exception
false

Darüber hinaus akzeptieren einige Exception-Typen ein oder mehrere Argumente für die Fehlerberichterstattung:

Beispiel

julia> throw(UndefVarError(:x))
FEHLER: UndefVarError: x nicht definiert

Wir können diesen Mechanismus leicht mit einem benutzerdefinierten Exception-Typ implementieren, indem wir dem Muster von UndefVarError folgen:

Beispiel

julia> struct MyUndefVarError <: Exception
           var::Symbol
       end

julia> Base.showerror(io::IO, e::MyUndefVarError) = print(io, e.var, " nicht definiert")

Task (Koroutine)

Task (Koroutine) bietet nichtlokale Ablaufsteuerung, die es ermöglicht, zwischen vorübergehend angehaltenen Berechnungsaufgaben zu wechseln. Wir werden dies im späteren Kapitel über asynchrone Programmierung ausführlich behandeln.

Andere Erweiterungen