來看一段簡單的範例程式:
C++:
/* main.cpp */
#include "Point.h"
int main() {
Point p1(5, 5);
Point p2 = p1;
cout cout
p1.setX(500);
p1.setY(500);
cout cout return 0;
}
#include "Point.h"
int main() {
Point p1(5, 5);
Point p2 = p1;
cout cout
p1.setX(500);
p1.setY(500);
cout cout return 0;
}
Java:
/* main.java */
public class Main {
public static void main(String args[]) {
Point p1 = new Point(5,5);
Point p2 = p1;
System.out.println("p1 is : " + p1);
System.out.println("p2 is : " + p2);
p1.setX(500);
p1.setY(500);
System.out.println("p1 is : " + p1);
System.out.println("p2 is : " + p2);
}
}
public class Main {
public static void main(String args[]) {
Point p1 = new Point(5,5);
Point p2 = p1;
System.out.println("p1 is : " + p1);
System.out.println("p2 is : " + p2);
p1.setX(500);
p1.setY(500);
System.out.println("p1 is : " + p1);
System.out.println("p2 is : " + p2);
}
}
猜猜它們的結果會不會一樣!?
先補上其他部分的程式碼:
C++:
/* Point.h*/
#include <iostream>
using namespace::std;
class Point {
private:
int posX;
int posY;
public:
Point();
Point(int x, int y);
void setX(int x);
void setY(int y);
int getX() const;
int getY() const;
};
ostream&
operator
如果你不熟析Java,那麼它的結果可能會讓你有點吃驚:
在Java中,p2的內容也一併被修改了!
這是因為在Java中,所有的物件,即所有非原生資料型態(primitive data type)的變數,都是參考!
何謂參考(reference)?wiki中對參考的解釋,我覺得寫的不錯:「a reference is an object containing information about how to locate and access the particular data item, as opposed to containing the data itself.」
參考的概念很像指標,它們同樣只保存關於「如何存取某筆資料」的資訊(像是指標中所指向的記憶體位址);但並不保存「該筆資料」本身。
因此,上述的範例中,兩種語言在寫法上雖然非常相似,但是所描述的行為卻是不同的。
我下方的圖簡單說明:
在C++中,「p2 = p1」的意義是將p1物件的內容複製一份給p2;
然而在Java中,p2與p1都是參考,因此「p2 = p1」的意義是將p1所指向的物件的參考複製一份給p2。
這樣的作法有點類似C/C++中的指標。
若將上述的C++範例改寫為使用指標的話:
每種程式語言對於參考的支援也都不盡相同。
在Java中所有的物件都是一個參考,這是Java的特性之一。Java的參考不支援算術運算,也不能對其取址,不過可以改變該參考所指之處。
像上述的Java範例中,你可以將p2改為指向p3所指向的物件實體:
如此一來,p2與p3所指向的就是同一個物件實體了。
C++中也支援參考,寫法如下:
上例中,p2宣告為一個參考,與p1指向同一塊記憶體。
注意這邊&是寫在資料型態後面,代表的是參考,是C++才有的寫法,C並不支援。
(在C中,有個很類似的寫法,就是寫在變數(variable)前面的 & ,代表針對該變數取址,通常作為指標用。
此兩處的&所代表的意義是不同的,請小心不要混淆。)
C++中的參考,必須在宣告時就指定其所指之處,且不得改變。
在C++中,使用參考存取記憶體中的資料時,不需要像指標變數那般,使用不同的語法(syntax)來存取所指向的資料;
像是使用「*」運算子取值,或是使用「->」運算子存取成員(member)。
因此,一個宣告為參考的變數,在使用上就如一般的變數般:
因此,C++的參考,更像是一個別名(Alias)。
但注意!千萬別學Java企圖用「=」(assignment operator)讓C++中的參考指向別處,像下面這個例子(程式碼接續上例):
在上例中,「p2 = p3」等同於「p1 = p3」!
結果不僅p2還是與p1指向同一塊記憶體,且p1的內容也被修改了。
(更別異想天開寫出「&p2 = &p3」...即便是在C也不允許修改變數位址的,否則就天下大亂了...。)
前幾個例子其實看不太出在C++使用參考的好處,
事實上,C++的參考,比較常用的場合是在呼叫函式時,傳遞參數給函式的時候,稱為call by reference。在C++的標準函式庫中,很常見到這樣的作法,如srting類別的compare函式:
而需要注意的事也一樣,就是在函式中若修改了該參數的內容,則原本作為參數的變數也會一併被修改。這也是為什麼compare的str參數前面還加上了const。
回到最一開始的範例:
同樣是類似的寫法,在C++中代表的是變數內容的複製,在Java中代表的卻是參考的複製,這是兩者產生的結果有所不同的原因。
那麼,在Java中,要如何做才能真正複製一個物件呢?
答案是使用Object類別的clone方法。
首先你必須先實作Cloneable這個介面:
但你還需要覆寫(override)Object類別中的public Object clone()這個方法(method):
為什麼說通常呢?因為物件複製(object copy)的問題還不僅如此,
上例中以super呼叫了父類別Object的clone方法來複製物件,但是Object類別的clone方法執行的是shallow copy,也就是說,如果當欲複製之物件的成員中,也有物件的時候,該成員只會將其參考複製而已!
(反之,則稱為deep copy。)
如果你希望該成員也要進行複製,那麼你也必須針對該成員所屬的類別作一樣的事:
實作Cloneable介面、覆寫clone方法!
同樣的問題連C++也會遇到,就是當欲複製之物件的成員中,有指標的時候。
C++中的同類別的物件在複製的時候,是透過copy constructor或copy assignment operator,兩者都是C++的special member functions,由編譯器(compiler)自動實作,而有需求的話programmer可自行override。
而預設上,無論是copy constructor還是copy assignment operator都是執行shallow copy。因此,若你希望執行deep copy,則必須將兩者覆寫。
/* Point.cpp */
#include "Point.h"
Point::Point(int x, int y) : posX(x), posY(y) {
}
void Point::setX(int x) {
posX = x;
}
void Point::setY(int y) {
posY = y;
}
int Point::getX() const {
return posX;
}
int Point::getY() const {
return posY;
}
ostream&
operator
Java:
/* Point.java */
public class Point {
public Point(int x, int y) {
posX = x;
posY = y;
}
public void setX(int x) {
posX = x;
}
public void setY(int y) {
posY = y;
}
public String toString() {
return "("+ posX + "," + posY + ")";
}
private int posX;
private int posY;
}
如果你不熟析Java,那麼它的結果可能會讓你有點吃驚:
C++:
C:\Point>bcc32 Point.cpp main.cpp
C:\Point>Point.exe
p1 is : (5,5)
p2 is : (5,5)
p1 is : (500,500)
p2 is : (5,5)
Java:
C:\Point>javac Point.java
C:\Point>java Point
p1 is : (5,5)
p2 is : (5,5)
p1 is : (500,500)
p2 is : (500,500)
在Java中,p2的內容也一併被修改了!
這是因為在Java中,所有的物件,即所有非原生資料型態(primitive data type)的變數,都是參考!
參考是什麼!?
何謂參考(reference)?wiki中對參考的解釋,我覺得寫的不錯:「a reference is an object containing information about how to locate and access the particular data item, as opposed to containing the data itself.」
參考的概念很像指標,它們同樣只保存關於「如何存取某筆資料」的資訊(像是指標中所指向的記憶體位址);但並不保存「該筆資料」本身。
因此,上述的範例中,兩種語言在寫法上雖然非常相似,但是所描述的行為卻是不同的。
我下方的圖簡單說明:
C++:
Java:
Point p1(5,5);
p1
XXXX : 0000
5
XXXX : 0004
5
XXXX : 0008
XXXX : 000C
Point p1 = new Point(5,5);
p1
XXXX : 0000
XXXX : 0100
XXXX : 0004
XXXX : 0008
XXXX : 000C
. . .
Instance of
XXXX : 0100
5
Point Object
XXXX : 0104
5
XXXX : 0108
XXXX : 010C
Point p2 = p1;
p1
XXXX : 0000
5
XXXX : 0004
5
p2
XXXX : 0008
5
XXXX : 000C
5
Point p2 = p1;
p1
XXXX : 0000
XXXX : 0100
p2
XXXX : 0004
XXXX : 0100
XXXX : 0008
XXXX : 000C
. . .
Instance of
XXXX : 0100
5
Point Object
XXXX : 0104
5
XXXX : 0108
XXXX : 010C
在C++中,「p2 = p1」的意義是將p1物件的內容複製一份給p2;
然而在Java中,p2與p1都是參考,因此「p2 = p1」的意義是將p1所指向的物件的參考複製一份給p2。
這樣的作法有點類似C/C++中的指標。
若將上述的C++範例改寫為使用指標的話:
Point* p1 = new Point(5,5);
Point* p2 = p1;
...
p1->setX(500);
...
那其行為與結果就會與上述的Java範例中類似了。Point* p2 = p1;
...
p1->setX(500);
...
C++與Java中的參考
每種程式語言對於參考的支援也都不盡相同。
在Java中所有的物件都是一個參考,這是Java的特性之一。Java的參考不支援算術運算,也不能對其取址,不過可以改變該參考所指之處。
像上述的Java範例中,你可以將p2改為指向p3所指向的物件實體:
Java:
Point p3 = new Point(7, 7);
p2 = p3;
p2 = p3;
p1
XXXX : 0000
XXXX : 0100
p2
XXXX : 0004
XXXX : 0108
p3
XXXX : 0008
XXXX : 0108
XXXX : 000C
. . .
Instance of
XXXX : 0100
5
Point Object
XXXX : 0104
5
Instance of
XXXX : 0108
7
Point Object
XXXX : 010C
7
如此一來,p2與p3所指向的就是同一個物件實體了。
C++中也支援參考,寫法如下:
C++:
Point& p2 = p1;
p1, p2
XXXX : 0000
5
XXXX : 0004
5
XXXX : 0008
XXXX : 000C
上例中,p2宣告為一個參考,與p1指向同一塊記憶體。
注意這邊&是寫在資料型態後面,代表的是參考,是C++才有的寫法,C並不支援。
(在C中,有個很類似的寫法,就是寫在變數(variable)前面的 & ,代表針對該變數取址,通常作為指標用。
此兩處的&所代表的意義是不同的,請小心不要混淆。)
C++中的參考,必須在宣告時就指定其所指之處,且不得改變。
在C++中,使用參考存取記憶體中的資料時,不需要像指標變數那般,使用不同的語法(syntax)來存取所指向的資料;
像是使用「*」運算子取值,或是使用「->」運算子存取成員(member)。
因此,一個宣告為參考的變數,在使用上就如一般的變數般:
C++:
p1.setX(6)
p1, p2
XXXX : 0000
6
XXXX : 0004
5
XXXX : 0008
XXXX : 000C
p2.setY(6); // 等同於 p1.setY(6);
p1, p2
XXXX : 0000
6
XXXX : 0004
6
XXXX : 0008
XXXX : 000C
因此,C++的參考,更像是一個別名(Alias)。
但注意!千萬別學Java企圖用「=」(assignment operator)讓C++中的參考指向別處,像下面這個例子(程式碼接續上例):
C++:
Point p3(7,7);
p1, p2
XXXX : 0000
6
XXXX : 0004
6
p3
XXXX : 0008
7
XXXX : 000C
7
p2 = p3;
p1, p2
XXXX : 0000
7
XXXX : 0004
7
p3
XXXX : 0008
7
XXXX : 000C
7
在上例中,「p2 = p3」等同於「p1 = p3」!
結果不僅p2還是與p1指向同一塊記憶體,且p1的內容也被修改了。
(更別異想天開寫出「&p2 = &p3」...即便是在C也不允許修改變數位址的,否則就天下大亂了...。)
前幾個例子其實看不太出在C++使用參考的好處,
事實上,C++的參考,比較常用的場合是在呼叫函式時,傳遞參數給函式的時候,稱為call by reference。在C++的標準函式庫中,很常見到這樣的作法,如srting類別的compare函式:
int string::compare( const string& str ) const;
這樣做的好處,與C的傳址呼叫(call by address)是一樣的,就是避免傳遞大型資料結構時,複製資料所需的時間。而需要注意的事也一樣,就是在函式中若修改了該參數的內容,則原本作為參數的變數也會一併被修改。這也是為什麼compare的str參數前面還加上了const。
回到最一開始的範例:
C++:
/* main.cpp */
#include "Point.h"
int main() {
Point p1(5, 5);
Point p2 = p1;
cout
Java:
/* main.java */
public class Main {
public static void main(String args[]) {
Point p1 = new Point(5,5);
Point p2 = p1;
System.out.println("p1 is : " + p1);
System.out.println("p2 is : " + p2);
p1.setX(500);
p1.setY(500);
System.out.println("p1 is : " + p1);
System.out.println("p2 is : " + p2);
}
}
同樣是類似的寫法,在C++中代表的是變數內容的複製,在Java中代表的卻是參考的複製,這是兩者產生的結果有所不同的原因。
物件的複製
那麼,在Java中,要如何做才能真正複製一個物件呢?
答案是使用Object類別的clone方法。
首先你必須先實作Cloneable這個介面:
public class Point implements Cloneable{
...
幸運的是Cloneable本身並沒有定義任何成員需要實作。...
但你還需要覆寫(override)Object類別中的public Object clone()這個方法(method):
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
接著,只要把p2 = p1改成:return super.clone();
}
Point p2 = (Point) p1.clone();
通常就可以了...為什麼說通常呢?因為物件複製(object copy)的問題還不僅如此,
上例中以super呼叫了父類別Object的clone方法來複製物件,但是Object類別的clone方法執行的是shallow copy,也就是說,如果當欲複製之物件的成員中,也有物件的時候,該成員只會將其參考複製而已!
(反之,則稱為deep copy。)
如果你希望該成員也要進行複製,那麼你也必須針對該成員所屬的類別作一樣的事:
實作Cloneable介面、覆寫clone方法!
同樣的問題連C++也會遇到,就是當欲複製之物件的成員中,有指標的時候。
C++中的同類別的物件在複製的時候,是透過copy constructor或copy assignment operator,兩者都是C++的special member functions,由編譯器(compiler)自動實作,而有需求的話programmer可自行override。
而預設上,無論是copy constructor還是copy assignment operator都是執行shallow copy。因此,若你希望執行deep copy,則必須將兩者覆寫。
全站熱搜