什么时候使用inout参数?


74

将类或原始类型传递给函数时,函数对参数的任何更改都将反映在类外部。这基本上是inout参数应该做的事情。

什么是inout参数的好用例?


2
您确定这种情况也会与作为“常规”参数传入的原始类型一起发生吗?
Thilo

您是否有一个代码示例来显示Swift的行为,如您描述的那样?
Thilo

见我的样本@Thilo
Lucas Huang

Answers:


123

inout意味着修改局部变量还将修改传入的参数。没有它,传入的参数将保持相同的值。尝试在使用引用类型时考虑引用类型,而在inout不使用引用类型时考虑值类型。

例如:

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)

一个好的用例是swap它将修改传入的参数的函数。

Swift 3+注从Swift 3开始inout关键字必须冒号之后和类型之前。例如,Swift 3+现在需要func changeChar(char: inout Character)


2
这是明确定义的行为吗?因为规范似乎说inout函数返回时会被复制回去。那么应该char1已经 a在功能之内了吗?
Thilo

回复:定义明确的行为:@drfi的答案解决了这个问题:“不要依赖于拷贝输入和引用调用之间的行为差​​异”
Thilo

@Thilo您是正确的,此示例包含的代码可能未明确定义,并且至少与inout语言参考不符。我将在示例中添加一个示例。
dfrib

1
请注意,var您的changeNumber函数将在以后的Swift版本中弃用。
timbo 2015年

这只是显示差异的一个示例。这不是实际的做法。
Lucas Huang

43

Apple语言参考:声明-进出参数

作为优化,当参数是存储在内存中物理地址上的值时,在函数体内外都使用相同的存储位置。优化的行为称为“按引用调用”;它满足了复制复制模型的所有要求,同时消除了复制的开销。不要依赖于签入与签出之间的行为差​​异。

如果您有一个函数,该函数需要一个在内存上有点大的值类型作为参数(例如,一个大的结构类型),并且返回相同的类型,最后该函数返回始终仅用于替换调用者参数,inout则为首选作为关联的函数参数。

考虑下面的示例,其中的注释描述了为什么我们要inout在此处使用常规的输入类型的函数:

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

同样,在上面的示例中-忽略内存问题-inout可以作为一种很好的代码习惯,告诉别人阅读我们的代码,我们正在对函数调用者参数进行突变(在函数中,参数&前面的与号表示)呼叫)。以下内容非常简洁地总结了这一点:

如果您希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,请将该参数定义为输入输出参数。

根据Apple语言指南:Functions-In-Out Parameters


有关inout其在内存中的实际处理方式(名称copy-in-copy-out有点误导性……)的详细信息,除了上面语言指南的链接外,请参见以下SO线程:


(编辑附加:附加说明)

上面卢卡斯·黄(Lucas Huang)接受的答案中给出的示例尝试-在函数范围内使用inout参数-访问作为inout参数传递的变量。不建议这样做,并明确警告不要使用ref语言:

即使原始参数在当前作用域中可用,也不要访问作为in-out参数传递的值。函数返回后,您对原件所做的更改将被副本的值覆盖。不要依靠按引用调用优化的实现来尝试避免更改被覆盖

现在,这种情况下的访问是“仅”不可更改的,例如print(...),但是按照惯例,应该避免像这样的所有访问。

应评论员的要求,我将添加一个示例来阐明为什么我们不应该真正使用“作为in-out参数传递的值”进行任何操作。

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.

因此,在这种情况下,inout的行为就像是copy-in-copy-out(而不是通过引用)。我们通过重复以下来自语言参考文档的声明来进行总结:

不要依赖于签入与签出之间的行为差​​异。


我在理解Do not access the value that was passed as an in-out argument陈述时遇到了麻烦,为什么呢,您能举个例子吗?据我了解,无论是inout还是非init的参数都首先会在函数内部被访问,否则我们根本不需要该参数。
永零2015年

当然,我将在示例中添加一个示例。
dfrib

这必须是正确的答案。感谢您的详细评论。
nefarianblack

25

函数参数默认为常量。试图从函数主体中更改函数参数的值会导致编译时错误。这意味着您不能错误地更改参数的值。如果要让函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,请将该参数定义为输入输出参数。

请参见下面的图片以获取描述


2
不错的示例,尽管您应该注意,该副本直接来自Swift文档。
邮票

函数参数是原始数据类型时,默认情况下为常数。
阿米特·里瓦斯塔瓦

0

inout参数允许我们更改值类型参数的数据,并在函数调用完成后保持更改不变。


0

如您所说,如果您使用类,则可以修改类,因为参数是对该类的引用。但这在您的参数是值类型时不起作用(https://docs.swift.org/swift-book/LanguageGuide/Functions.html-输入输出参数部分)

这是使用inout的一个很好的例子(为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
}


0

使用inout参数时swift 4.0工作

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)")
    }
}

0

inout参数:修改传递值和局部变量值。

func doubleInPlace(number: inout Int) {
     number *= 2
     print(number) 
    
 }

 var myNum = 10 doubleInPlace(number: &myNum)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.