何时使用 inout 参数?

当将类或基元类型传递到函数中时,函数中对参数所做的任何更改都将反映在类的外部。这基本上与 inout参数应该做的事情相同。

什么是 inout 参数的好用例?

47638 次浏览

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 + Note : 从 Swift 3开始inout关键字必须出现在 之后冒号和类型之前。例如,Swift 3 + 现在需要 func changeChar(char: inout Character)

来自 苹果语言参考: 声明 In-Out 参数:

作为优化,当参数是存储在物理 地址在内存中,相同的内存位置在内部和 优化后的行为称为调用 引用; 它满足拷贝的所有要求 拷贝模式 同时减少了复制的开销。不依赖 关于复制-在复制-出和呼叫之间的行为差异 参考文献。

如果你有一个函数,采取一个有点内存智能的大值类型作为参数(比如说,一个大结构类型) ,并返回相同的类型,最后,函数返回值总是用来替换调用者的参数,那么 inout更喜欢作为相关的函数参数。

考虑下面的例子,其中的注释描述了为什么我们希望在常规的 type-in-return-type 函数上使用 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可以作为一个很好的代码实践,告诉读我们代码的人我们正在改变函数调用者参数(在函数调用参数之前的与号 &隐式地显示)。下面简洁地总结了这一点:

如果希望函数修改参数的值,并且希望 在函数调用结束后要保持的那些更改,定义 而是将该参数作为 in-out 参数。

来自 苹果语言指南: 函数输入输出参数


有关 inout的详细信息以及它在内存中的实际处理方式(名称 copy-in-copy-out有些误导...)——除了上面的语言指南的链接之外——请参阅以下 SO 帖子:


(编辑补充: 附加说明)

上面 Lucas Huang 给出的公认答案中的例子试图——在函数范围内使用 inout参数——访问作为 inout参数传递的变量。不建议这样做,并且明确警告不要在参考文献中这样做:

不要访问作为 in-out 参数传递的值,即使 原始参数在当前范围 中可用 函数返回时,对原始文件的更改将被覆盖 值。 不依赖于 按引用调用优化,以尽量避免更改 覆盖

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

应评论者的要求,我将添加一个例子来阐明为什么我们不应该真正使用 “作为输入输出参数传递的值”做任何事情。

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 (而不是通过引用)。我们通过重复以下来自参考文献语言的声明来总结:

不要依赖于复制入复制出之间的行为差异 然后通过引用打电话。

默认情况下,函数参数是常量。尝试从函数体内更改函数参数的值会导致编译时错误。这意味着您不能错误地更改参数的值。如果希望函数修改参数的值,并且希望这些更改在函数调用结束后仍然存在,则应将该参数定义为 in-out 参数。

see image below for Description

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

如果您使用类,那么,正如您所说的,您可以修改类,因为参数是对类的引用。但是,如果参数是值类型(https://docs.swift.org/swift-book/LanguageGuide/Functions.html-In-Out 参数部分) ,则此方法不起作用

使用 inout 的一个很好的例子是这个(为 CGPoint 定义数学) :

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
}

基本上它是有用的,当你想发挥地址的变量它非常有用的数据结构算法

当使用 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)")
}
}

Inout param: 修改传递和局部变量值。

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

}


var myNum = 10 doubleInPlace(number: &myNum)