跳到主要內容

Pointer 之雖然我搞不定你但你到底能幹嘛?

Pointer 就是指標! 噗~ 那指標呢? 就是 Pointer! 囧rz...
這就是 Pointer 給我的感覺, 間接來間接去, 永遠搞不懂到底指去哪裡了.
以上只是還不習慣使用 Pointer 的我的無病呻吟.

回歸正題, Pointer 的目的是什麼?
=> 讓 Programmer 能夠跨 Function or Method 去存取共同的記憶體空間.
那記憶體空間裡頭放的是什麼?
最普遍的就是 Primitive Data Type 的資料, 但 Pointer 是很 Powerful 的! 看些範例吧!

[Paradigm 1] Pointer of Primitive Data Type
void addInteger(int *sumPtr, int numberToAdd) {
    *sumPtr += numberToAdd;
}
int main (int argc, const char * argv[]) {
   
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   
int sum = 2;
   
addInteger(&sum, 3);
   
NSLog(@"Sum = %i", sum);
   
[pool drain];
   
return 0;
}
[Output 1]
Sum = 5
Primitive Data Type 泛指程式語言所提供的基本資料型態 (如: int), 詳細說明可以參考 Wikipedia - PrimitiveDataType.
在範例中 Function - addInteger 有兩個 Parameter, 一個是 int 的 Pointer sumPtr, 另一個則是 int numberToAdd. 而 Function 實際做的事情是把 sumPtr 所指向之記憶體空間中的資料取出, 加上 numberToAdd 後, 再回存. 簡單一點就是把 sum 的數值 2 取出, 加上 3, 然後把 5 (2 + 3) 存回 sum 裡, 只是 sum 的資料是透過 Pointer 取得. 值得注意的是, 這個 Function 沒有回傳值,  sum 的數值是利用它的指標 - sumPtr 直接更新.
這一點就和 Java 很不同. 在 Java 中不能操作 Pointer, 必須先將 int 包裝成 Object, 或使用 Wrapped Class, 才能達到類似指標的效果.


[Paradigm 2] Pointer of Structure Data Type
struct rectangle {
    int width;
    int height;
};

int calArea(struct rectangle *r) {

    // 等同於 return (*r).width * (*r).height;
    return r->width * r->height;
}

int main (int argc, const char * argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    struct rectangle rect;
    rect.width = 8;
    rect.height = 4;
    NSLog(@"Area = %i", calArea(&rect));
    [pool drain];
    return 0;
}
[Output 2]
Area = 32
和 Primitive Data Type 一樣, Structure Data Type 也可以利用 Pointer 進行操作. 範例內容也很簡單, 用 Pointer 去操作 rect 並計算其面積.


這兩個範例看起來都沒什麼, 就算不使用 Pointer 也可以達到類似的效果.
看看範例一, 大可使用 int 將加總數值回傳就好了, 為什麼要用 Pointer 來找自己的麻煩呢? 再不然, 也可以把 sum 宣告成 Global 變數, 數值連傳都不用, 不更簡單? 看看範例二, 也可以直接把 rect 傳進 calArea 方法, 為什麼要使用 Pointer?
的確, 使用回傳數值是一種解法, 但是別忘了, 回傳數值永遠只有一個, 如果碰到需要更新兩個以上的資料時該怎麼辦?
的確, 使用 Global 變數也是種解法, 但是碰到不只一個數值需要加總的時候該怎麼辦? 難道要全部宣告成 Global 變數嗎?
的確, 可以不要使用 Structure Data Type 的 Pointer, 但電腦系統中的記憶體並不是能無止盡取用的! 如果 Structure 中包含的變數數量很多, 在有限記憶體空間 (如: 手機系統) 的環境下該怎麼處理? 記憶體能負荷大量資料複製嗎?

這時候, 就可以發現有 Pointer 是多麼值得慶幸的一件事情.

那 Java 該哭哭嗎? 因為在 Java 中根本沒有指標!
其實也不會, 因為 Java 沒有 Structure Data Type, 要達到資料封裝的效果就只能使用 Class, 而 Java 中 Object 的操作其實就是透過指標. 理由是如果將 Object1 assign 給 Object2, 會發現對 Object2 進行操作 Object1 也會連帶進行更動的現象. 也因此可得知 Java 裡 Object 的 assign 只是複製 address, 而不是進行 Object 的 clone.

這樣看來,  Java 沒有 Pointer 的結果, 似乎只造成於沒有 Primitive Data Type 指標的差異, 但透過 Wrapped Class 就可以解決這個問題, 真的只有這樣? 當然不是! 因為指標不只可以指向資料[註].


[Paradigm 3] Pointer of Pointer
void getMessage(NSString **msgPtr) {
    [*msgPtr release];
    *msgPtr = @"I GOT IT!!";
}

int main (int argc, const char * argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    NSString *msg = @"Initial";
    getMessage(&msg);
    NSLog(@"%@", msg);
    [pool drain];
    return 0;
}
[Output 3]
I GOT IT!!
我承認這個範例看起來的確沒什麼, 只是在 Function-getMessage 中, 將 main 裡 msg 中存放的資訊改掉了. 同樣的, 這樣的效果可以透過其它方式達到, 但別忘了回傳值永遠只有一個, 而這就是 Pointer 帶來的便利性.
在 Java 中能辦到這樣的事情嗎? 當然可以, 解決方式是先把 String 用其它物件包起來, 再傳進方法裡. 只是這樣的程式可讀性就降低了, 而且麻煩...
你也知道 Programmer 都很懶...


[Paradigm 4] Pointer of Function
struct rectangle {
    int width;
    int height;
    int (*area)(struct rectangle *);
};

int calArea(struct rectangle *r) {
    return r->width * r->height;
}

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    struct rectangle rect;
    rect.width = 8;
    rect.height = 4;
    rect.area = calArea;
    NSLog(@"Area = %i", rect.area(&rect));
    [pool drain];
    return 0;
}
[Output 4]
Area = 32
指標可以指向 Fuction! 這個範例實際上是想把 Function 也封裝進 structure 中, 看起來就像是一個非物件導向的語言想要擁有物件導向的功能. 而當處在一個沒有物件導向特性的語言中, 了解 Pointer 可以達到這樣的效果, 是非常有利於開發的.
由於我還很淺並沒有看過底層的實作, 說不定在 Java 和 Objective-C 的底層就是這樣把方法和類別進行封裝.

有沒有實際一點的例子? 有!
在 Objective-C 的 Foundation Framework 中有個類別 - NSMutableArray. 這個類別就像是 Java 中的 ArrayList, 可以動態的新增、刪除、修改陣列中的元素.
在這個類別裡頭有下述這個方法:
- (void)sortUsingFunction:(NSInteger (*)(id, id, void *))compare context:(void *)context

compare
The comparison function to use to compare two elements at a time.
context
The context argument to pass to the compare function.
compare 就是一個 Function 的 Pointer, 指向一個如下述範例的 Function:
NSInteger intSort(id num1, id num2, void *context) {
    int v1 = [num1 intValue];
    int v2 = [num2 intValue];
    if (v1 < v2)
        return NSOrderedAscending;
    else if (v1 > v2)

        return NSOrderedDescending;
    else
        return NSOrderedSame;
}
那麼... 在 Java 中能不能達到類似的效果? 唉啊~ 答案還是~ 當然可以!!

在 Java 中有兩個 interface, 一個是 java.lang.Comparable, 一個是 java.util.Comparator. 兩種用法很相似, 都是要去實做出一個可用於比較的方法, 但我個人覺得 Comparator 在使用上和上述的方法比較像.
implements Comparator 要實作 compare 方法, 如下:
public int compare(Object o1, Object o2)
而如何使用 Comparator 進行排序呢? 在 Java 中有個類別 - java.util.Arrays, 定義了這個方法:
public static void sort(Object[] a, Comparator c)
看到這, 也不需要我說明了, 跟 Objective-C 裡的操作方式沒兩樣.

想個老問題, 那麼... 沒有 Function 的 Pointer 其實對 Java 來說也沒什麼差別嘛! 因為只要透過實作介面就可以達到類似的效果.
的確是這樣沒錯!
但請別忘了, 實作介面得先宣告定義介面, 接著得有個類別去實作那個介面, 在使用的時候還得把該類別的物件丟進去, 才能達到類似的效果.
你也知道的... Programmer 就是... 嗯嗯...

我只能說 Pointer 真的是好強大啊!!! 囧rz...

[註] 指標與陣列的關係請參考 Pointer! 不是我排擠你, 而是我搞不定你 囧rz.

留言

Stewart寫道…
在 Paradigm 3 中這樣的使用有點瑕疵,在 getMessage() 重新指定 msgPtr 這個指標變數的內容為一個新 NSString 物件的記憶體位址,就造成沒有任何人參考到原本的 NSString 物件進而導致 memory leak(但是因為有 auto-release 所以才沒有這個問題)

Obj-C 的物件指標可以把它想成是 Java 的 Reference,這樣反而比 C++ 容易懂
阿刃寫道…
謝謝您的指正!!
在這的確可能發生 Memory-leak 的問題.

由於我比較擅長寫 Java,
所以其實我不是很建議用 Java 的 Object reference 來解釋 Objective-C 的物件指標, 因為的確有差異.
且在 Java 中壓根子不需要去想到自行維護記憶體這件事情.
(我自己都常常忘記 maintain 記憶體, 上頭的範例就是個好例子.)

另外, 在這個範例裡不會有 Memory-leak,
因為在這是 NSConstantString,
retain & release 的機制在這沒有效果.
當然為了確保所有 NSString 的延伸類別都可以正確運作, 的確要加入 release.

最後, 謝謝你, 已經更正範例程式碼囉!

這個網誌中的熱門文章

引數?! 參數??!! 什麼鬼啊!!

我想這個不僅是只有我會遇到的問題, 大概也是所有 Programming 的人都有的疑惑 (應該是吧?) 什麼引數?? 什麼參數?? 天啊... 到底是誰翻譯的呀!!!! 就字面上來看... 講句實在話, 我真的不了解, 大概是學藝不精所致. 也許有些人認為 => 管它那麼多!! 寫得出來就好了!! 不過龜毛如我, 我還是想搞清楚定義是什麼!

世界第一! 臺灣的驕傲! iPod 全球最貴 XD!!??

2009 年 9 月 10 日 凌晨 1 點鐘, Apple 於美國舉行了 Apple Special Event September 2009 . 先說點題外話, 這場演說對 Apple 迷而言, 是久違的一場, 也不知道還能有幾場像這場一樣, 由 Steve Jobs 親自站臺. 這場演說的主題, 除了 iTunes 9, iPhone (iPod touch) OS 更新為 3.1 外, 就是 iPod 產品線的更新. 在看完發表會之後, 我去研究了一下新 iPod 的全球售價, 這也是這篇文章的重點, 蘋果臺灣的定價創下了新的紀錄- 套用一下最近常聽到的 Slogan 世界第一, 臺灣的驕傲, iPod 全球最貴!!??

關於 Polymorphism! 是的, 就是多型!

這幾天真的是寫了好多關於程式基礎的東西, 今天心血來潮想寫個物件導向的特性 - Polymorphism. 是的, 你沒看錯, 就是多型. 多型, 這有什麼好寫的呀? 每本物件導向的書籍, 不管是程式語言或是方法論, 一定都會提到的基本特性. 去書局翻翻就有了, 甚至在網路上搜尋 “物件導向” “多型”, 就可以找到讀也讀不完的資料. 在這老調重彈, 似乎有點... 多餘? 會想把這個議題拿出來講, 關鍵就在於我發現 Objective-C 上的多型, 和我以前認識的不太一樣, 更加 Powerful.