Swift function that takes in array giving error: &

2020-04-21 05:34发布

问题:

So I'm writing a lowpass accelerometer function to moderate the jitters of the accelerometer. I have a CGFloat array to represent the data and i want to damp it with this function:

// Damps the gittery motion with a lowpass filter.
func lowPass(vector:[CGFloat]) -> [CGFloat]
{
    let blend:CGFloat = 0.2

    // Smoothens out the data input.
    vector[0] = vector[0] * blend + lastVector[0] * (1 - blend)
    vector[1] = vector[1] * blend + lastVector[1] * (1 - blend)
    vector[2] = vector[2] * blend + lastVector[2] * (1 - blend)

    // Sets the last vector to be the current one.
    lastVector = vector

    // Returns the lowpass vector.
    return vector
}

In this case, lastVector is defined as follows up at the top of my program:

var lastVector:[CGFloat] = [0.0, 0.0, 0.0]

The three lines in the form vector[a] = ... give me the errors. Any ideas as to why i am getting this error?

回答1:

That code seems to compile if you pass the array with the inout modifier:

func lowPass(inout vector:[CGFloat]) -> [CGFloat] {
    ...
}

I'm not sure whether that's a bug or not. Instinctively, if I pass an array to a function I expect to be able to modify it. If I pass with the inout modifier, I'd expect to be able to make the original variable to point to a new array - similar to what the & modifier does in C and C++.

Maybe the reason behind is that in Swift there are mutable and immutable arrays (and dictionaries). Without the inout it's considered immutable, hence the reason why it cannot be modified.

Addendum 1 - It's not a bug

@newacct says that's the intended behavior. After some research I agree with him. But even if not a bug I originally considered it wrong (read up to the end for conclusions).

If I have a class like this:

class WithProp {
    var x : Int = 1

    func SetX(newVal : Int) {
        self.x = newVal
    }
}

I can pass an instance of that class to a function, and the function can modify its internal state

var a = WithProp()

func Do1(p : WithProp) {
    p.x = 5 // This works
    p.SetX(10) // This works too
}

without having to pass the instance as inout. I can use inout instead to make the a variable to point to another instance:

func Do2(inout p : WithProp) {
    p = WithProp()
}

Do2(&a)

With that code, from within Do2 I make the p parameter (i.e. the a variable) point to a newly created instance of WithProp.

The same cannot be done with an array (and I presume a dictionary as well). To change its internal state (modify, add or remove an element) the inout modifier must be used. That was counterintuitive.

But everything gets clarified after reading this excerpt from the swift book:

Swift’s String, Array, and Dictionary types are implemented as structures. This means that strings, arrays, and dictionaries are copied when they are assigned to a new constant or variable, or when they are passed to a function or method.

So when passed to a func, it's not the original array, but a copy of it - Hence any change made to it (even if possible) wouldn't be done on the original array.

So, in the end, my original answer above is correct and the experienced behavior is not a bug

Many thanks to @newacct :)



回答2:

Since Xcode 6 beta 3, modifying the contents of an Array is a mutating operation. You cannot modify a constant (i.e. let) Array; you can only modify a non-constant (i.e. var) Array.

Parameters to a function are constants by default. Therefore, you cannot modify the contents of vector since it is a constant. Like other parameters, there are two ways to be able to change a parameter:

  • Declare it var, in which case you can assign to it, but it is still passed by value, so any changes to the parameter has no effect on the calling scope.
  • Declare it inout, in which case the parameter is passed by reference, and any changes to the parameter is just like you made the changes on the variable in the calling scope.

You can see in the Swift standard library that all the functions that take an Array and mutate it, like sort(), take the Array as inout.

P.S. this is just like how arrays work in PHP by the way



回答3:

Edit: The following worked for Xcode Beta 2. Apparently, the syntax and behavior of arrays has changed in Beta 3. You can no longer modify the contents of an array with subscripts if it is immutable (a parameter not declared inout or var):

Not valid with the most recent changes to the language

The only way I could get it to work in the play ground was change how you are declaring the arrays. I suggest trying this (works in playground):

import Cocoa
let lastVector: CGFloat[] = [0.0,0.0,0.0]

func lowPass(vector:CGFloat[]) -> CGFloat[] {
    let blend: CGFloat = 0.2
    vector[0] = vector[0] * blend + lastVector[0] * ( 1 - blend)
    vector[1] = vector[1] * blend + lastVector[1] * ( 1 - blend)
    vector[2] = vector[2] * blend + lastVector[2] * ( 1 - blend)
    return vector
}

var test = lowPass([1.0,2.0,3.0]);


回答4:

Mainly as a followup for future reference, @newacct's answer is the correct one. Since the original post showed a function that returns an array, the correct answer to this question is to tag the parameter with var:

func lowPass(var vector:[CGFloat]) -> [CGFloat] {
    let blend:CGFloat = 0.2

    // Smoothens out the data input.
    vector[0] = vector[0] * blend + lastVector[0] * (1 - blend)
    vector[1] = vector[1] * blend + lastVector[1] * (1 - blend)
    vector[2] = vector[2] * blend + lastVector[2] * (1 - blend)

    // Sets the last vector to be the current one.
    lastVector = vector

    // Returns the lowpass vector.
    return vector
}