swift語言不鼓勵使用指標,因此不提供有如C/C++中的*方法。但有些時候我們也需要直接修改內存地址上的數據,爲解決這個問題,主要有兩種方法:
- 使用不安全的指標:按是否可修改、是否使用原始數據、是否管理緩衝區分爲如下8種類型
不可修改 | 可修改 | 緩衝 | 修改及緩衝 | |
原始數據 | UnsafeRawPointer | UnsafeMutableRawPointer | UnsafeRawBufferPointer | UnsafeMutableRawBufferPointer |
類型數據 | UnsafePointer | UnsafeMutablePointer | UnsafeBufferPointer | UnsafeMutableBufferPointer |
- 使用類: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
}
這樣我們就通過類實現了指標的功能。