Swift 3默默地允许阴影参数

Sea*_*bet 16 swift

我正在切换到Swift,我真的不高兴以下代码在没有警告的情况下编译:

func f(_ x: inout Int?) {
    var x: Int? // <-- this declaration should produce a warning
    x = 105
    if x! < 1000 {}
}

var a: Int? = 3
f(&a)
print("\(a)")
Run Code Online (Sandbox Code Playgroud)

当然,还有Optional(3)执行时的输出.

在此示例中,x局部变量会影响x函数参数.

在项目设置中打开Hidden Local Variables警告(GCC_WARN_SHADOW)不会导致产生警告.

问题:我应该如何让Swift 3编译器警告我这样的阴影?

jlm*_*rph 3

虽然您可能已经找到了有用的解决方案,但 Apple 的函数文档实际上对这种确切的使用类型有评论。您请求回答为什么代码突出显示不会警告您命名冲突,但您可能没有收到任何警告的主要原因是因为inout参数和所有参数并不优先于函数内初始化的变量(它们只是它们在函数内部操作时所代表的值的副本)。因此,正如我将在下面说明的那样,您的函数不会考虑您传入的参数,因为您初始化了一个具有相同名称的新变量。因此,根据参数管理规则,您传递的参数将被完全忽略。我看到你的沮丧,因为在其他一些语言中这将是一个编译器错误。然而,对于这里的输入输出,这根本不是惯例。请参阅此处的文档

\n\n
\n

输入输出参数传递如下:

\n\n

当函数被调用时,参数的值被复制。在函数体内,副本被修改。当函数返回时,copy\xe2\x80\x99s 值被分配给原始参数。此行为称为拷入拷出或按值结果调用。例如,当计算属性或具有观察者的属性作为输入输出参数传递时,其 getter 会作为函数调用的一部分被调用,其 setter 会作为函数返回的一部分被调用。

\n\n

作为一种优化,当参数是存储在内存中物理地址的值时,函数体内部和外部都使用相同的内存位置。优化的行为称为引用调用;它满足拷入\拷出模型的所有要求,同时消除了复制的开销。使用复制输入复制输出给出的模型编写代码,而不依赖于按引用调用优化,以便在有或没有优化的情况下都能正确运行。

\n\n

即使原始参数在当前作用域中可用,也不要访问作为输入输出参数传递的值。当函数返回时,您对原始文件的更改将被副本的值覆盖。不要依赖\n 引用调用优化的实现来尝试防止更改\n 被覆盖。\n [..]

\n
\n\n

在您的情况下,如果您要实际修改您传递的参数,您将使用类似于以下内容的内容:

\n\n
\n

如果需要捕获和改变输入输出参数,请使用显式本地副本,例如在多线程代码中,以确保在函数返回之前完成所有改变。

\n
\n\n
func multithreadedFunction(queue: DispatchQueue, x: inout Int) {\n    // Make a local copy and manually copy it back.\n    var localX = x\n    defer { x = localX }\n\n    // Operate on localX asynchronously, then wait before returning.\n    queue.async { someMutatingOperation(&localX) }\n    queue.sync {}\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

正如您在这里所看到的,虽然 localX 不像您所做的那样被称为 x,但 localX 占用了整个其他内存实例来包含数据。在本例中,它与 x 的值相同,但不是 x 的实例,因此它不会编译为命名错误。\n为了表明当您将 localX 更改为 var x = Int 时这仍然适用?就像你在函数内部所做的那样:

\n\n
func f(_ x: inout Int?) {\n    print(x, "is x")\n    var x: Int? // <-- this declaration should produce a warning\n    print(x, "is x after initializing var x : Int?")\n    x = 105\n    print(x, "is x after giving a value of 105")\n    if x! < 1000 {}\n}\n\nvar a: Int? = 3\nf(&a)\nprint("\\(a)", "is x after your function")\n
Run Code Online (Sandbox Code Playgroud)\n\n

返回:

\n\n
Optional(3) is x\nnil is x after initializing var x: Int?\nOptional(105) is x after giving a value of 105 to x\nOptional(3) is x after your function\n
Run Code Online (Sandbox Code Playgroud)\n\n

为了向您展示这走了多远,我将使用莫森所做的事情来向您展示他的逻辑并没有完全错误,以向您展示约定中的这条规则,同时我同意他没有解决缺乏代码的问题在你的问题中发出警告。

\n\n
func f(_ x: inout Int?) {\n    print(x, "is inout x")\n    var y: Int? // <-- this declaration should produce a warning\n    print(x, "is inout x and ", y, "is y")\n    x = 105\n    print(x, "is inout x and ", y, "is y after giving a value of 105 to inout x")\n    if x! < 1000 {}\n}\n\nvar a: Int? = 3\nf(&a)\nprint("\\(a)", "is x after your function")\n
Run Code Online (Sandbox Code Playgroud)\n\n

印刷:

\n\n
Optional(3) is inout x\nOptional(3) is inout x and  nil is y\nOptional(105) is inout x and  nil is y after giving a value of 105 to inout x\nOptional(105) is x after your function\n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,正如您在第一个函数中看到的那样,您的 inout 参数和一般参数不再优先于内部包含的内容,因为从技术上讲,它在函数内部没有初始化,这就是 inout 约定本身的目的:该函数将该值保存在内存中,为该内存实例分配一个指针,并且当函数结束时,应用于该指针的任何突变都将应用于函数范围之外的原始变量。因此,无论您对它做了什么修改,都var x: Int?不会改变 inout 参数中的变量return,因为您已经覆盖了分配给字母 x 的指针。为了向您展示 的情况并非如此non-inouts,我们将从 x 中分配一个不同的变量:

\n\n
func f(_ x: Int?) {\n    print(x!, "is inout x")\n    var y: Int? // <-- this declaration should produce a warning\n    print(x!, "is inout x and ", y!, "is y")\n    x = 105\n    y = 100\n    print(x!, "is inout x and ", y!, "is y after giving a value of 105 to inout x")\n    if x! < 1000 {}\n}\n\nvar a: Int? = 3\nf(a)\nprint("\\(a!)", "is x after your function")\n
Run Code Online (Sandbox Code Playgroud)\n\n

退货

\n\n
Playground execution failed: error: SomeTest.playground:6:7: error: cannot assign to value: \'x\' is a \'let\' constant\n    x = 105\n
Run Code Online (Sandbox Code Playgroud)\n\n

但是,如果我返回原始函数并将新变量重命名为与参数名称相同的指针:

\n\n
func f(_ x: Int?) {\n    print(x, "is inout x")\n    var x: Int? // <-- this declaration should produce a warning\n    print(x, "is inout x and ")\n    x = 100\n    print(x, "is inout x and ")\n    if x! < 1000 {}\n}\n\nvar a: Int? = 3\nf(a)\nprint("\\(a!)", "is x after your function")\n
Run Code Online (Sandbox Code Playgroud)\n\n

我们得到:

\n\n
Optional(3) is inout x\nnil is inout x and \nOptional(100) is inout x and \n3 is x after your function\n
Run Code Online (Sandbox Code Playgroud)\n\n

总而言之,inout 参数和标准参数永远不会被修改,因为在函数范围内, x 的指针完全被 覆盖Int?

\n\n

这就是为什么您没有收到代码警告,从技术上来说,您不应该收到代码警告,因为围绕参数的约定规定您编写的内容不是编译冲突并且是有效的代码(也许它可能不适合您使用)情况,但按照惯例,确实如此),因此您很可能无法找到一种方法来突出显示此命名问题。

\n