swift指標(指針)問題

swift語言不鼓勵使用指標,因此不提供有如C/C++中的*方法。但有些時候我們也需要直接修改內存地址上的數據,爲解決這個問題,主要有兩種方法:

  • 使用不安全的指標:按是否可修改、是否使用原始數據、是否管理緩衝區分爲如下8種類型
不可修改可修改緩衝修改及緩衝
原始數據UnsafeRawPointerUnsafeMutableRawPointerUnsafeRawBufferPointerUnsafeMutableRawBufferPointer
類型數據UnsafePointerUnsafeMutablePointerUnsafeBufferPointerUnsafeMutableBufferPointer
  • 使用類:Class。

需要注意的是,swift並不會自動管理不安全指標的內存,即使用者需要自行分配內存、初始化內容、釋放內容以及釋放內存,這一點與C較爲相似。因此使用不安全指標時應特別小心。

爲安全起見,如果只需要在程式中修改某些位置的值,可考慮使用類。當類實例作爲參數傳遞時,實際上是傳遞的參考而非拷貝。利用這個特性,我們可以實現一部分指標的功能。

在下面這個例子中,我們定義只有一個整型變量的類class ref,並將該類的實例作爲參數傳遞給函數func classVar(_ input: ref):

class ref {
    var x: Int
    
    init(_ x: Int) {
        self.x = x
    }
}

func classVar(_ input: ref) {
    input.x = -1
}

let input = ref(1)
classVar(input)
print(input.x)

//output: -1

在這裏,儘管實例input是常值型(let),但由於類實例參數是以參考傳遞,因此函數classVar可以修改input中的變量var x。

如果不使用類,則須要修改傳入參數的屬性爲inout,這樣普通參數會以copy-in copy-out機制傳遞(可能會被優化為參考傳遞)。但此時傳入的參數必須爲可修改(mutable)變量的地址,即:

var input = 1

func normalVar(_ input: inout Int) {
    input = -1
}

normalVar(&input)
print(input)

//output: -1

下面我們再看一個例子。在這個例子裏,我們用兩種方法新建兩個ref型數組,然後取得並修改它們最後一個元素。

let classArray1 = [ref(1),ref(1)]
let classArray2 = [ref](repeating: ref(1), count: 2)

let arrayLast1 = classArray1.last!
let arrayLast2 = classArray2.last!

arrayLast1.x = -1
arrayLast2.x = -1

for element in classArray1 {
    print(element.x)
}

for element in classArray2 {
    print(element.x)
}

//output: classArray1: 1, -1; classArray2: -1, -1

可見,由於類實例傳遞參考,arrayLast與數組最後一個元素共用內存地址,這與其他類型(如數、字符串等)有很大不同。而對於第二種初始化方法 (repeating: Element, count: Int),其重複元素是實例的參考,使用此方法時須小心。

利用類實例傳遞參考的特性,我們可以用類來定義鏈表、樹等結構,例如:

class linkedList {
    var val: Int
    var next: linkedList?
    init(_ val: Int, _ next: linkedList?) {
        self.val = val
        self.next = next
    }
}

本身作爲數據結構類型,使用結構(struct)定義更爲合理。但是swift中結構實例是按拷貝傳遞,因此我們必須轉而使用類來定義它們。

作爲這部分的應用,我們來看LeetCode 110題平衡樹。在陳生給出的後序遍歷cpp解答中,用到了指標&height去修改母節點的lheight或rheight。

bool isBalanced(TreeNode* root) {
    stack<StkItem> stk;
    int height = 0;
    if( root ) stk.emplace( root, height );
    while( !stk.empty() ){
        auto &top = stk.top();
        if( top.is_visited ){
            if( top.lheight - top.rheight > 1 || top.rheight - top.lheight > 1 ) 
                return false;
            top.height = max(top.lheight, top.rheight) + 1;//此處通過指標修改母節點的lheight或rheight。
            stk.pop();
        }else{
            if( top.p->right ) stk.emplace( top.p->right, top.rheight );
            if( top.p->left ) stk.emplace( top.p->left, top.lheight );
            top.is_visited = true;
        }
    }
    return true;
}

struct StkItem{
    TreeNode *p;
    bool is_visited = false;
    int lheight = 0, rheight = 0;
    int &height;
    StkItem( TreeNode *_p, int &_height ): p(_p), height(_height){}
};

swift版本中,我們將lheight和rheight定義爲一個超類height,並將stackItem定義爲height的子類來爲子節點獲取樹高變量。stackItem中引入元素parent: height以便之後修改母節點的樹高;引入direction變量來確定某子節點是母節點的左或右節點。

private class height {
    var leftHeight: Int = 0
    var rightHeight: Int = 0
}

private class stackItem: height {
    let p: TreeNode
    var visited: Bool = false
    var direction: Bool
    var parent: height
    
    init(_ p: TreeNode, _ parent: height, _ direction: Bool) {
        self.p = p
        self.parent = parent
        self.direction = direction
    }
}

在初始化實例stackItem(top.p.left!, top, false)時(記爲s),可以將子類top傳給超類height。此時top中只有超類成員會被傳遞,並在s中生成一個指向{top.leftHeight, top.rightHeight}的超類實例,相當於s.parent.leftHeight = &top.leftHeight; s.parent.rightHeight = &top.rightHeight。

func isBalanced(_ root: TreeNode?) -> Bool {
    if (root == nil) {
        return true
    }
    
    var stack = [stackItem]()
    stack.append(stackItem(root!,height(),false))
    
    while !stack.isEmpty {
        let top = stack.last!
        if top.visited {
            if ((top.leftHeight - top.rightHeight) > 1 || (top.rightHeight - top.leftHeight) > 1) {
                return false
            }
            let parentHeight = max(top.leftHeight,top.rightHeight) + 1
            if top.direction { //修改母節點樹高
                top.parent.rightHeight = parentHeight
            } else {
                top.parent.leftHeight = parentHeight
            }
            _ = stack.removeLast()
        } else {
            if (top.p.left != nil) {
                stack.append(stackItem(top.p.left!,top,false))
            }
            if (top.p.right != nil) {
                stack.append(stackItem(top.p.right!,top,true))
            }
            top.visited = true
        }
    }
    return true
}

這樣我們就通過類實現了指標的功能。

留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *