Wann werden inout-Parameter verwendet?

74

Wenn Sie eine Klasse oder einen primitiven Typ an eine Funktion übergeben, wird jede Änderung der Funktion am Parameter außerhalb der Klasse wiedergegeben. Dies ist im Grunde das gleiche, was ein inoutParameter tun soll.

Was ist ein guter Anwendungsfall für einen inout-Parameter?

4thSpace
quelle
2
Sind Sie sicher, dass dies bei einem primitiven Typ geschieht, den Sie auch als "regulären" Parameter übergeben?
Thilo
Haben Sie ein Codebeispiel, das zeigt, wo sich Swift so verhält, wie Sie es beschreiben?
Thilo
Siehe mein Beispiel @Thilo
Lucas Huang

Antworten:

122

inoutbedeutet, dass durch Ändern der lokalen Variablen auch die übergebenen Parameter geändert werden. Ohne sie bleiben die übergebenen Parameter gleich. Versuchen Sie, an den Referenztyp zu denken, wenn Sie ihn verwenden, inoutund an den Werttyp, ohne ihn zu verwenden.

Zum Beispiel:

import UIKit

var num1: Int = 1
var char1: Character = "a"

func changeNumber(var num: Int) {
    num = 2
    print(num) // 2
    print(num1) // 1
}
changeNumber(num1)

func changeChar(inout char: Character) {
    char = "b"
    print(char) // b
    print(char1) // b
}
changeChar(&char1)

Ein guter Anwendungsfall ist die swapFunktion, mit der die übergebenen Parameter geändert werden.

Swift 3+ Hinweis : Ab Swift 3 muss das inoutSchlüsselwort nach dem Doppelpunkt und vor dem Typ stehen. Zum Beispiel benötigt Swift 3+ jetzt func changeChar(char: inout Character).

Lucas Huang
quelle
2
Ist das ein genau definiertes Verhalten? Weil die Spezifikation zu sagen scheint, dass inoutsie zurückkopiert wird, wenn die Funktion zurückkehrt. Sollte also char1schon ainnerhalb der Funktion sein?
Thilo
Betreff: genau definiertes Verhalten: Die Antwort von @ drfi spricht Folgendes an: "Hängen Sie nicht von den Verhaltensunterschieden zwischen dem Einkopieren und dem Aufrufen als Referenz ab"
Thilo
@Thilo Sie haben Recht, dieses Beispiel enthält Code, der sich möglicherweise nicht gut definiert verhält und zumindest nicht mit der inoutSprachreferenz übereinstimmt. Ich werde meiner Antwort ein Beispiel hinzufügen.
dfrib
1
Beachten Sie, dass varIhre changeNumberFunktion in zukünftigen Versionen von Swift nicht mehr unterstützt wird.
Timbo
Es ist nur ein Beispiel, um die Unterschiede zu zeigen. Es ist nicht für die eigentliche Praxis.
Lucas Huang
43

Aus der Apple-Sprachreferenz: Deklarationen - In-Out-Parameter :

Wenn das Argument ein Wert ist, der an einer physischen Adresse im Speicher gespeichert ist, wird zur Optimierung derselbe Speicherort sowohl innerhalb als auch außerhalb des Funktionskörpers verwendet. Das optimierte Verhalten wird als Referenzaufruf bezeichnet. Es erfüllt alle Anforderungen des Copy-In-Copy-Out-Modells und beseitigt gleichzeitig den Kopieraufwand . Hängen Sie nicht von den Verhaltensunterschieden zwischen dem Einkopieren und dem Aufrufen als Referenz ab.

Wenn Sie eine Funktion haben, die einen etwas speichertechnisch großen Werttyp als Argument verwendet (z. B. einen großen Strukturtyp) und denselben Typ zurückgibt, und schließlich die Funktionsrückgabe immer nur zum Ersetzen des Aufruferarguments verwendet wird, dann inoutist als zugehörigen Funktionsparameter zu bevorzugen.

Betrachten Sie das folgende Beispiel, in dem Kommentare beschreiben, warum wir hier inouteine reguläre Type-in-Return-Type-Funktion verwenden möchten :

struct MyStruct {
    private var myInt: Int = 1

    // ... lots and lots of stored properties

    mutating func increaseMyInt() {
        myInt += 1
    }
}

/* call to function _copies_ argument to function property 'myHugeStruct' (copy 1)
   function property is mutated
   function returns a copy of mutated property to caller (copy 2) */
func myFunc(var myHugeStruct: MyStruct) -> MyStruct {
    myHugeStruct.increaseMyInt()
    return myHugeStruct
}

/* call-by-reference, no value copy overhead due to inout opimization */
func myFuncWithLessCopyOverhead(inout myHugeStruct: MyStruct) {
    myHugeStruct.increaseMyInt()
}

var a = MyStruct()
a = myFunc(a) // copy, copy: overhead
myFuncWithLessCopyOverhead(&a) // call by reference: no memory reallocation

Im obigen Beispiel inoutkann das Ignorieren von Speicherproblemen einfach als gute Code-Praxis bevorzugt werden, um demjenigen, der unseren Code liest, mitzuteilen, dass wir &das Argument des Funktionsaufrufers mutieren (implizit durch das kaufmännische Und vor dem Argument in der Funktion dargestellt) Anruf). Das Folgende fasst dies ziemlich gut zusammen:

Wenn eine Funktion den Wert eines Parameters ändern soll und diese Änderungen nach Beendigung des Funktionsaufrufs beibehalten werden sollen, definieren Sie diesen Parameter stattdessen als In-Out-Parameter.

Aus dem Apple Language Guide: Funktionen - In-Out-Parameter .


Einzelheiten dazu inoutund wie es tatsächlich im Speicher behandelt wird (Name copy-in-copy-outist etwas irreführend ...) --- zusätzlich zu den Links zum obigen Sprachhandbuch --- siehe den folgenden SO-Thread:


(Zusatz bearbeiten: Ein zusätzlicher Hinweis)

Das Beispiel in der akzeptierten Antwort von Lucas Huang oben versucht, im Rahmen der Funktion mithilfe eines inoutArguments auf die Variablen zuzugreifen, die als inoutArgumente übergeben wurden. Dies wird nicht empfohlen und wird ausdrücklich davor gewarnt, dies in der Sprache ref:

Greifen Sie nicht auf den Wert zu, der als In-Out-Argument übergeben wurde, auch wenn das ursprüngliche Argument im aktuellen Bereich verfügbar ist . Wenn die Funktion zurückkehrt, werden Ihre Änderungen am Original mit dem Wert der Kopie überschrieben. Verlassen Sie sich nicht auf die Implementierung der Call-by-Reference-Optimierung, um zu verhindern, dass die Änderungen überschrieben werden .

Nun ist der Zugriff in diesem Fall "nur" nicht veränderlich, z. B. print(...)aber jeder Zugriff wie dieser sollte gemäß Konvention vermieden werden.

Auf Anfrage eines Kommentators werde ich ein Beispiel hinzufügen, um zu beleuchten, warum wir mit "dem Wert, der als In-Out-Argument übergeben wurde" eigentlich nichts tun sollten .

struct MyStruct {
    var myStructsIntProperty: Int = 1

    mutating func myNotVeryThoughtThroughInoutFunction (inout myInt: Int) {
        myStructsIntProperty += 1
        /* What happens here? 'myInt' inout parameter is passed to this
           function by argument 'myStructsIntProperty' from _this_ instance
           of the MyStruct structure. Hence, we're trying to increase the
           value of the inout argument. Since the swift docs describe inout 
           as a "call by reference" type as well as a "copy-in-copy-out"
           method, this behaviour is somewhat undefined (at least avoidable).

           After the function has been called: will the value of
           myStructsIntProperty have been increased by 1 or 2? (answer: 1) */
        myInt += 1
    }

    func myInoutFunction (inout myInt: Int) {
        myInt += 1
    }
}

var a = MyStruct()
print(a.myStructsIntProperty) // 1
a.myInoutFunction(&a.myStructsIntProperty)
print(a.myStructsIntProperty) // 2
a.myNotVeryThoughtThroughInoutFunction(&a.myStructsIntProperty)
print(a.myStructsIntProperty) // 3 or 4? prints 3.

In diesem Fall verhält sich das Inout also wie Copy-In-Copy-Out (und nicht als Referenz). Wir fassen zusammen, indem wir die folgende Aussage aus den Sprachreferenzdokumenten wiederholen:

Hängen Sie nicht von den Verhaltensunterschieden zwischen dem Einkopieren und dem Aufrufen als Referenz ab.

dfrib
quelle
Ich hatte Probleme, die Do not access the value that was passed as an in-out argumentAussage zu verstehen . Warum ist das so? Können Sie ein Beispiel geben? Soweit ich weiß, wird zuerst innerhalb der Funktion auf ein Argument zugegriffen, egal ob inout oder nicht, andernfalls benötigen wir das Argument überhaupt nicht.
Wingzero
Klar, ich werde der Antwort ein Beispiel hinzufügen.
dfrib
Dies muss die richtige Antwort sein. Danke für den ausführlichen Kommentar.
Nefarianblack
25

Funktionsparameter sind standardmäßig Konstanten. Der Versuch, den Wert eines Funktionsparameters aus dem Hauptteil dieser Funktion heraus zu ändern, führt zu einem Fehler bei der Kompilierung. Dies bedeutet, dass Sie den Wert eines Parameters nicht versehentlich ändern können. Wenn eine Funktion den Wert eines Parameters ändern soll und diese Änderungen nach Beendigung des Funktionsaufrufs beibehalten werden sollen, definieren Sie diesen Parameter stattdessen als In-Out-Parameter.

Beschreibung siehe Bild unten

Venu Gopal Tewari
quelle
2
Schönes Beispiel, obwohl Sie beachten sollten, dass die Kopie direkt aus den Swift-Dokumenten stammt.
Briefmarke
Funktionsparameter sind standardmäßig konstant, wenn es sich um einen primitiven Datentyp handelt.
Amit Srivastava
0

Mit dem Parameter inout können wir die Daten eines Werttypparameters ändern und Änderungen nach Abschluss des Funktionsaufrufs beibehalten.

HS Progr
quelle
0

Wenn Sie mit Klassen arbeiten, können Sie die Klasse wie gesagt ändern, da der Parameter eine Referenz auf die Klasse ist. Dies funktioniert jedoch nicht, wenn Ihr Parameter ein Werttyp ist ( https://docs.swift.org/swift-book/LanguageGuide/Functions.html - Abschnitt "In-Out-Parameter").

Ein gutes Beispiel für die Verwendung von inout ist dieses (Definition von Mathematik für CGPoints):

func + (left: CGPoint, right: CGPoint) -> CGPoint {
  return CGPoint(x: left.x + right.x, y: left.y + right.y)
}

func += (left: inout CGPoint, right: CGPoint) {
  left = left + right
}
abanet
quelle
0

Grundsätzlich ist es nützlich, wenn Sie mit Adressen von Variablen spielen möchten, was in Datenstrukturalgorithmen sehr nützlich ist

JeeVan TiWari
quelle
0

bei Verwendung des Inout-Parameters Swift 4.0 Work

class ViewController: UIViewController {

    var total:Int = 100

    override func viewDidLoad() {
        super.viewDidLoad()
        self.paramTotal(total1: &total)
    }

    func paramTotal(total1 :inout Int) {
        total1 = 111
        print("Total1 ==> \(total1)")
        print("Total ==> \(total)")
    }
}
user3263340
quelle