C++和java多型的區別
轉自: http://www.cnblogs.com/plmnko/archive/2010/10/19/1855760.html
http://blog.csdn.net/thinkGhoster/article/details/2307001
http://blog.csdn.net/chenssy/article/details/12786385
C++中,如果父類中的函式前邊標有virtual,才顯現出多型。
如果父類func是virtual的,則
Super *p =new Sub();
p->func(); // 呼叫子類的func
如果不是virtual的,p->func將呼叫父類原來的函式。
Java中,不管寫不寫virtual都是多型的,子類的同名函式會override父類的。與C++很不同的是,初始化的過程也不相同。在還未初始化子類的時候,子類的同名函式就已經覆蓋了父類的了。例如:
public class Super {
public Super() {
System.out.println("super constructor...");
m();
}
protected void m() {
System.out.println("test");
}
}
public class Sub extends Super{
private final Date date;
public Sub(){
date=new Date();}
public void m()
{
System.out.println(date);
}
public static void main(String[] args)
{
Super test1=new Sub();
test1.m(); //執行的子類的m
}
}
new Sub的時候首先呼叫Super,Super建構函式呼叫的m就已經是被Sub覆蓋的m,所以會print出null(因為日期沒有初始化)。所以在java中,不要在父類建構函式中呼叫外部可改變的方法,有可能會輸出可改變方法中還沒初始化的東西。
但是,同樣的初始化在C++中,初始化一個子類的時候,父類呼叫的m,是父類自己的m,不會呼叫子類的。
——————
另外一個參考也很有用:http://7880.com/info/Article-51701560.html,如下:
C++和java中多型機制的異同
以前我有個錯誤的觀點:即使在C++和java中多型性的實現機制可能不同,但它們的表現形式應該相同,也就是說如果程式碼結構相同,那麼執行結果也應該相同。可惜事與願違,事情並不總是你想象中的那樣子。(在看下文以前,你最好先考慮一下這個問題,你有什麼看法呢?)
ok,讓我們進入正題。
首先本文不討論物件導向程式設計的基本概念,如封裝、繼承和資料抽象等,這方面的資料現在應該多如牛毛,只是稍微提一下多型性的概念。根據Bjarne Stoustrup的說法,多型性其實就是方法呼叫的機制,也就是說當在編譯時無法確定一個物件的實際型別時,應當能夠在執行時基於物件的實際型別來決定呼叫的具體方法(動態繫結)。
我們先來看一下在C++中的函式呼叫方式:
Ø 普通函式呼叫:具體呼叫哪個方法在編譯時間就可以決定(通過查詢編譯器的符號表),同時在使用標準過程呼叫機制基礎上增加一個表示物件身份的指標(this指標)。
Ø 虛擬函式呼叫:函式呼叫依賴於物件的實際型別,一般地說,物件的實際型別只能在執行時間才能確定。虛擬函式一般要有兩個步驟來支援,首先每一個類產生出一堆指向虛擬函式的指標,放在表格中,這個表格就叫虛擬函式表(virtual table);然後每一個類物件(class object)會新增一個指向相關虛擬函式表(virtual table)的指標,通常這個指標叫做vptr。
在java中又是如何的呢?恩,區別還是滿大的。在java虛擬機器中,類例項的引用就是指向一個控制程式碼(handle)的指標,而該控制程式碼(handle)其實是一對指標:其中一個指標指向一張表,該表格包含了物件的方法列表以及一個指向類物件(表示物件型別)的指標;另一個指標指向一塊記憶體地址,該記憶體是從java堆中為物件的資料而分配出來的。
唔,你要說了,好象差不多嘛,不是都要維護一張函式表嗎?別急,讓我們先看一下例子,這樣你就能更好的理解它們之間的區別到底有多大了。
下面是C++和java的例子,不看後面的答案,你能夠正確說出它們的執行結果嗎?
例1:C++
class Base
{
public:
Base()
{
init();
}
virtual ~Base() {}
public:
virtual void do_init()
{
init();
}
protected:
virtual void init()
{
cout << "in Base::init()" << endl;
}
};
class Derived : public Base
{
public:
Derived()
{
init();
}
protected:
void init()
{
cout << "in Derived::init()" << endl;
}
};
int main(int argc, char* argv[])
{
Base* pb;
pb = new Derived();
delete pb;
return 0;
}
例2:java
class Base
{
public Base()
{
init();
}
protected void init()
{
System.out.println("in Base::init()");
}
public void do_init()
{
init();
}
}
class Derived extends Base
{
public Derived()
{
init();
}
protected void init()
{
System.out.println("in Derived::init()");
}
}
public class Test
{
public static void main(String[] args)
{
Base base = new Derived();
}
}
例1的執行結果是:
in Base::init()
in Derived::init()
例2的執行結果是:
in Derived::init()
in Derived::init()
看了結果後,你是馬上頓悟呢抑或是處於疑惑中呢?ok,我們來分析一下兩個例子的執行過程。
首先看一下例1(C++的例子):
1. Base* pb; 只是宣告,不做什麼。
2. pb = new Derived();
1) 呼叫new操作符,分配記憶體。
2) 呼叫基類(本例中是Base)的建構函式
3) 在基類的建構函式中呼叫init(),執行程式首先判斷出當前物件的實際型別是Base(Derived還沒構造出來,當然不會是Derived),所以這裡呼叫的是Base::init()。
4) 呼叫派生類(本例中是Derived)的建構函式,在這裡同樣要呼叫init(),執行程式判斷出當前物件的實際型別是Derived,呼叫Derived::init()。
3. delete pb; 無關緊要。
例2(java的例子)的執行過程:
1. Base base = new Derived();
1) 分配記憶體。
2) 呼叫基類(本例中是Base)的建構函式
3) 在基類的建構函式中呼叫init(),執行程式首先判斷出當前物件的實際型別是Derived(對,Derived已經構造出來,它的函式表當然也已經確定了)所以這裡呼叫的是Derived::init()。
4) 呼叫派生類(本例中是Derived)的建構函式,在這裡同樣要呼叫init(),執行程式判斷出當前物件的實際型別是Derived,呼叫Derived::init()。
明白了吧。java中的類物件在構造前(呼叫建構函式之前)就已經存在了,其函式表和物件型別也已經確定了,就是說還沒有出生就已經存在了。而C++中只有在構造完畢後(所有的建構函式都被成功呼叫)才存在,其函式表和物件的實際型別才會確定。所以這兩個例子的執行結果會不一樣。當然,構造完畢後,C++與java的表現就都一樣了,例如你呼叫Derived::do_init()的話,其執行結果是:
in Derived::init()。
個人認為,java中的多型實現機制沒有C++中的好。還是以例子說明吧:
例子3:C++
class Base
{
public:
Base()
{
init();
}
virtual ~Base() {}
protected:
int value;
virtual void init()
{
value = 100;
}
};
class Derived : public Base
{
public:
Derived()
{
init();
}
protected:
void init()
{
cout << "value = " << value << endl;
// 做一些額外的初始化工作
}
};
int main(int argc, char* argv[])
{
Base* pb;
pb = new Derived();
delete pb;
return 0;
}
例4:java
class Base
{
public Base()
{
init();
}
protected int value;
protected void init()
{
value = 100;
}
}
class Derived extends Base
{
public Derived()
{
init();
}
protected void init()
{
System.out.println("value = " + value);
// 做一些額外的初始化工作
}
}
public class Test
{
public static void main(String[] args)
{
Base base = new Derived();
}
}
例3的執行結果是:
value = 10
例4的執行結果是:
value = 0
value = 0
從以上結果可以看出,java例子中應該被初始化的值(這裡是value)沒有被初始化,派生類根本不能重用基類的初始化函式。試問,如果初始化要在構造時完成,並且初始化邏輯比較複雜,派生類也需要額外的初始化,派生類是不是需要重新實現基類的初始化函式呢?這樣的物件導向方法好不好呢?歡迎大家討論。
作者的聯絡方式:smart_ttc@yahoo.com.cn
Reference:
1. Stanley B. Lippman:深度探索C++物件模型(Inside The C++ Object Model)。
---- 侯捷譯,華中科技出版社 2001
——————
另外一個關於java的例子:
http://blog.csdn.net/lzz313/archive/2009/06/16/4274936.aspx
class Parent{
int x=10;
public Parent(){
add(2);
}
void add(int y){
x+=y;
}
}
class Child extends Parent{
int x=9;
void add(int y){
x+=y;
}
public static void main(String[] args){
Parent p=new Child();
System.out.println(p.x);
}
}
問輸出結果是什麼?
答案應該是10。
要理解結果為什麼是10,需要首先明白下面的知識:
(1)方法和變數在繼承時的隱藏與覆蓋
隱藏:若B隱藏了A的變數或方法,那麼B不能訪問A被隱藏的變數或方法,但將B轉換成A後可以訪問A被隱藏的變數或者方法。
覆蓋:若B覆蓋了A的變數或者方法,那麼不僅B不能訪問A被覆蓋的變數或者方法,將B轉換成A後同樣不能訪問A被覆蓋的變數或者方法。
(2)Java中變數與方法在繼承中的隱藏與覆蓋規則:
一、父類的例項變數和類變數能被子類的同名變數隱藏。
二、父類的靜態方法被子類的同名靜態方法隱藏,父類的例項方法被子類的同名例項方法覆蓋。
三、不能用子類的靜態方法隱藏父類的例項方法,也不能用子類的例項方法覆蓋父類的靜態方法,否則編譯器會異常。
四、用final關鍵字修飾的最終方法不能被覆蓋。
五、變數只能被隱藏不會被覆蓋,子類的例項變數可以隱藏父類的類變數,子類的類變數也可以隱藏父類的例項變數。
在上面的試題中,子類Child的例項方法add(int y)覆蓋了父類Parent的例項方法add(int y),而子類的例項變數x則是隱藏了父類的例項變數x。
Child物件的初始化過程是:
首先為父類的例項變數x分配記憶體空間,因為在定義變數x時為它賦了值(int x=10),所以會同時將這個值賦給x。
其次呼叫父類的無參建構函式,Parent的建構函式中做的唯一的事情就是呼叫了add(2);
第三、由於子類的add(int y)方法覆蓋了父類的方法,所以add(2)實際呼叫的是子類的方法,在子類的add方法中做了如下操作x+=j;在這裡由於子類的例項變數x隱藏了父類的例項變數x,所以這條語句是針對子類本身的,但是這時還沒有為子類的實力變數x分配空間,它的預設值是0,加2之後是2。
第四、父類初始化完畢後接著初始化子類,為子類的x分配記憶體空間並將它賦值為9,之前的add(2)操作白瞎了。
再次注意Parent p=new Child();這條語句,它是用父類的引用指向子類的物件,而前面已經說過變數只會被隱藏不會被覆蓋,所以這時的p.x值應該是父類的10,而不是子類的9;
如果將輸出語句換成下面的語句結果就是9了:
System.out..println(((Child)p).x); //首先將p轉換成Child型別
相關文章
- 關於java的引用和c++的區別JavaC++
- Java 重寫和過載區別——物件導向的多型性分析Java物件多型
- JAVA 與 C++ 的區別JavaC++
- java 型別資訊 instanceof 和 isInstance區別Java型別
- JAVA與C++的多型異同JavaC++多型
- JAVA 基本型別與 引用型別區別Java型別
- Java的基本型別和引用型別Java型別
- c#中值型別和引用型別的區別C#型別
- Java 泛型中? super T和? extends T的區別Java泛型
- Java中基本資料型別和包裝型別有什麼區別?Java資料型別
- js基本型別和引用型別區別JS型別
- C和C++區別C++
- Java泛型T與?的區別Java泛型
- Golang的值型別和引用型別的範圍、儲存區域、區別Golang型別
- C++的多型C++多型
- java基本型別和包裝型別的“==”和equals()方法Java型別
- JS 的型別(null 和 undefined 的區別)JS型別NullUndefined
- Java long型別和Long型別的那些事Java型別
- C++中運算子 &和&&、|和|| 的區別C++
- java泛型中<?>和<T>有什麼區別?Java泛型
- java 方法修改主函式裡基本資料型別和引用資料型別的區別Java函式資料型別
- 【Java】equals 和 == 的區別Java
- Java和Javascript的區別JavaScript
- java和html的區別JavaHTML
- 從賦值看基本型別和引用型別的區別賦值型別
- Python引用型別和值型別的區別與使用Python型別
- Java多執行緒--併發和並行的區別Java執行緒並行
- C++中的向上型別轉換和向下型別轉換C++型別
- C++ 中四種強制型別轉換的區別C++型別
- C++中的return和exit區別C++
- c++中指標和引用的區別?C++指標
- C++中 struct 和 class 的區別C++Struct
- C++中指標和引用的區別C++指標
- C++中struct 和 class的區別C++Struct
- C/C++——sizeof和strlen的區別C++
- rust trait 關聯型別和泛型的區別RustAI型別泛型
- 基礎-JAVA集合型別主要區別Java型別
- JavaScript 基本資料型別和引用型別的區別詳解JavaScript資料型別