綜述:
在Java中,類實例聲明和構造是分開。"T a;"是聲明,而"a=new T();"纔是構造。引用聲明和C++相同。但是Java的機制與C++不同,Java中的引用也叫句柄,或者說句柄纔是其真實名稱。類聲明的都是一個句柄,調用構造函數纔會使得句柄指向類實例。因此Java中沒有類似於C++中的複製函數,因爲Java的複製都是直接複製句柄內容。例如"T b=a;"只不過是將a的句柄複製(賦值)給了b,從而b也指向a指向的類實例。可以看出Java與C++在此處的不同,Java依然只有一個實例,而C++則存在了兩個實例。
所以在函數中,Java的形參都是入參的句柄複製,並且是淺複製(只複製該句柄,而不復制句柄指向的下一層句柄)。因此在函數中,直接修改形參是不能改變入參的。但是如果修改形參指向的對象的下一層句柄則會修改入參。因此在Java中不存在像C/C++中一樣的Swap函數。函數的返回值,也是句柄複製。如果在函數中構造一個局部變量類實例,那麼是可以返回到外部的,當然那個局部變量的句柄是不存在了。Java中要複製對象,需要重載clone函數,並且要分清是淺複製還是深複製(完全構造一個新對象,兩者的內部數據和實例不相同)。
c++ 與java引用具體比較:
c++中一個引用指向的地址不會改變,改變的是指向地址的內容,然而java中引用指向的地址在變!!
如果非要對比着看,那麼Java中的“引用”倒是和C/C++的指針更像一些,和C++的“引用”很不一樣。
java去除指針概念,就用引用羅...
你看 java:
A a = new A(1);
A b = new A(2);
b = a;
沒有問題,a 和 b引用同一個對象A(2),原來的A(1)成爲沒有被引用的對象。 垃圾回收機制會在之後的某個時刻把A(1)幹掉。
而C++則不然。C++的引用就語義上說是“別名”【本質是個const指針,又叫指針常量】,而並不是指針的另一種用法:
A a = A(1);
A b = A(2);
A& c = b; //c 是 b的別名
c = a; //並不是 c 引用 a,而是拷貝操作 c.operator= ( a )
就語言機制來說,java的引用是用來管理和命名對象;
而,C++的引用機制是很純粹的,就是別名而已。
每種語言的特性都是整體的有機部分。
我們知道, java的引用機制是一個很複雜的機制。他必須區分“基本對象”和“複合對象”,你可以想象一下,如果其中沒有基本對象,那麼我們如何完成對象的複製? 唯一的解決方案是提供兩個等於號,或者一律用構造函數.... 但是綜合來看,他和垃圾回收形成了相當完美的組合方案。
而C++ 的引用機制爲運算符重載提供了大幅度的支持。C++ 的引用是用類“模擬”基本對象的根本要求。 如果C++使用java那種引用,那麼原本漂亮的 operator[]、 proxy class 等就很難實現了。 更進一步, C++ 的運算符重載對 C++ 的模版機制提供了強力的支持
在c++中,引用只是對於一個變量起的別名,一旦定義就無法修改,即無法再指向其他變量,如程序中,對於a的引用的任何操作都等同於對於a的操作。
java定義的引用並不是這樣。在java中,引用相當與指針,它與c中的指針主要有兩個區別:一是引用不能進行地址操作,如數組的加一 操作,相當於引用只是只是指向數據的一個副本,而不是數據本身,這樣就避免了由於對於地址的誤操作而改變其他變量的值,甚至危害到系統的安全。二是 java中的引用只能指向對象,他的引用是在實例化對象時系統直接生成的,因此對於普通數據類型是不能進行引用定義的,如果要對普通數據類型進行函數調用 時的地址傳遞(即java中的引用傳遞),必須把數據封裝到類中。
java的這種特性使得在java的函數或類的參數傳遞時可以實現與c中指針相同的功能。
具體應用:
指針和引用在C++中很常用,但是對於它們之間的區別很多初學者都不是太熟悉,下面來談談他們2者之間的區別和用法。
1.指針和引用的定義和性質區別:
(1)指針:指針是一個變量,只不過這個變量存儲的是一個地址,指向內存的一個存儲單元;而引用跟原來的變量實質上是同一個東西,只不過是原變量的一個別名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定義了一個整形變量和一個指針變量p,該指針變量指向a的存儲單元,即p的值是a存儲單元的地址。
而下面2句定義了一個整形變量a和這個整形a的引用b,事實上a和b是同一個東西,在內存佔有同一個存儲單元。
(2)可以有const指針,但是沒有const引用;
(3)指針可以有多級,但是引用只能是一級(int **p;合法 而 int &&a是不合法的)
(4)指針的值可以爲空,但是引用的值不能爲NULL,並且引用在定義的時候必須初始化;
(5)指針的值在初始化後可以改變,即指向其它的存儲單元,而引用在進行初始化後就不會再改變了。
(6)"sizeof引用"得到的是所指向的變量(對象)的大小,而"sizeof指針"得到的是指針本身的大小;
(7)指針和引用的自增(++)運算意義不一樣;
2.指針和引用作爲函數參數進行傳遞時的區別。
(1)指針作爲參數進行傳遞:
#include<iostream> using namespace std; void swap(int *a,int *b) { int temp=*a; *a=*b; *b=temp; } int main(void) { int a=1,b=2; swap(&a,&b); cout<<a<<" "<<b<<endl; system("pause"); return 0; }
結果爲2 1;
用指針傳遞參數,可以實現對實參進行改變的目的,是因爲傳遞過來的是實參的地址,因此使用*a實際上是取存儲實參的內存單元裏的數據,即是對實參進行改變,因此可以達到目的。
再看一個程序;
#include<iostream> using namespace std; void test(int *p) { int a=1; p=&a; cout<<p<<" "<<*p<<endl; } int main(void) { int *p=NULL; test(p); if(p==NULL) cout<<"指針p爲NULL"<<endl; system("pause"); return 0; }
運行結果爲:
0x22ff44 1
指針p爲NULL
大家可能會感到奇怪,怎麼回事,不是傳遞的是地址麼,怎麼p回事NULL?事實上,在main函數中聲明瞭一個指針p,並賦值爲NULL,當調用test函數時,事實上傳遞的也是地址,只不過傳遞的是指地址。也就是說將指針作爲參數進行傳遞時,事實上也是值傳遞,只不過傳遞的是地址。當把指針作爲參數進行傳遞時,也是將實參的一個拷貝傳遞給形參,即上面程序main函數中的p何test函數中使用的p不是同一個變量,存儲2個變量p的單元也不相同(只是2個p指向同一個存儲單元),那麼在test函數中對p進行修改,並不會影響到main函數中的p的值。
如果要想達到也同時修改的目的的話,就得使用引用了。
2.將引用作爲函數的參數進行傳遞。
在講引用作爲函數參數進行傳遞時,實質上傳遞的是實參本身,即傳遞進來的不是實參的一個拷貝,因此對形參的修改其實是對實參的修改,所以在用引用進行參數傳遞時,不僅節約時間,而且可以節約空間。
看下面這個程序:
#include<iostream> using namespace std; void test(int &a) { cout<<&a<<" "<<a<<endl; } int main(void) { int a=1; cout<<&a<<" "<<a<<endl; test(a); system("pause"); return 0; }
輸出結果爲: 0x22ff44 1
0x22ff44 1
再看下這個程序:
這足以說明用引用進行參數傳遞時,事實上傳遞的是實參本身,而不是拷貝。
所以在上述要達到同時修改指針的目的的話,就得使用引用了。
#include<iostream> using namespace std; void test(int *&p) { int a=1; p=&a; cout<<p<<" "<<*p<<endl; } int main(void) { int *p=NULL; test(p); if(p!=NULL) cout<<"指針p不爲NULL"<<endl; system("pause"); return 0; }
輸出結果爲:0x22ff44 1
指針p不爲NULL
C++ 引用的本質?深入分析C++引用:
引言
我選擇寫 C++ 中的引用是因爲我感覺大多數人誤解了引用。而我之所以有這個感受是因爲我主持過很多 C++ 的面試,並且我很少從面試者中得到關於 C++ 引用的正確答案。
那麼 c++ 中引用到底意味這什麼呢?通常一個引用讓人想到是一個引用的變量的別名,而我討厭將 c++ 中引用定義爲變量的別名。這篇文章中,我將盡量解釋清楚, c++ 中根本就沒有什麼叫做別名的東東。
背景
在 c/c++ 中,訪問一個變量只能通過兩種方式被訪問,傳遞,或者查詢。這兩種方式是:
1. 通過值 訪問 / 傳遞變量
2. 通過地址 訪問 / 傳遞變量 – 這種方法就是指針
除此之外沒有第三種訪問和傳遞變量值的方法。引用變量也就是個指針變量,它也擁有內存空間。最關鍵的是引用是一種會被編譯器自動解引用的指針。很難相信麼?讓我們來看看吧。。。
下面是一段使用引用的簡單 c++ 代碼
- #include <iostream.h>
- int main()
- {
- int i = 10; // A simple integer variable
- int &j = i; // A Reference to the variable i
- j++; // Incrementing j will increment both i and j.
- // check by printing values of i and j
- cout<< i << j <<endl; // should print 11 11
- // Now try to print the address of both variables i and j
- cout<< &i << &j <<endl;
- // surprisingly both print the same address and make us feel that they are
- // alias to the same memory location.
- // In example below we will see what is the reality
- return 0;
- }
引用其實就是 c++ 中的指針常量。表達式 int &i = j; 將會被編譯器轉化成 int *const i = &j; 而引用之所以要初始化是因爲 const 類型變量必須初始化,這個指針也必須有所指。下面我們再次聚焦到上面這段代碼,並使用編譯器的那套語法將引用替換掉。
- #include <iostream.h>
- int main()
- {
- int i = 10; // A simple integer variable
- int *const j = &i; // A Reference to the variable i
- (*j)++; // Incrementing j. Since reference variables are
- // automatically dereferenced by compiler
- // check by printing values of i and j
- cout<< i << *j <<endl; // should print 11 11
- // A * is appended before j because it used to be reference variable
- // and it should get automatically dereferenced.
- return 0;
- }
讀者一定很奇怪爲什麼我上面這段代碼會跳過打印地址這步。這裏需要一些解釋。因爲引用變量時(使用變量時)會被編譯器自動解引用的,那麼一個諸如 cout << &j << endl; 的語句,編譯器就會將其轉化成語句 cout << &*j << endl; 現在 &* 會相互抵消,這句話變的毫無意義,而 cout 打印的 j 值就是 i 的地址,因爲其定義語句爲 int *const j = &i;
所以語句 cout << &i << &j << endl; 變成了 cout << &i << &*j << endl; 這兩種情況都是打印輸出 i 的地址。這就是當我們打印普通變量和引用變量的時候會輸出相同地址的原因。
下面給出一段複雜一些的代碼,來看看引用在級聯 (cascading) 中是如何運作的。
- #include <iostream.h>
- int main()
- {
- int i = 10; // A Simple Integer variable
- int &j = i; // A Reference to the variable
- // Now we can also create a reference to reference variable.
- int &k = j; // A reference to a reference variable
- // Similarly we can also create another reference to the reference variable k
- int &l = k; // A reference to a reference to a reference variable.
- // Now if we increment any one of them the effect will be visible on all the
- // variables.
- // First print original values
- // The print should be 10,10,10,10
- cout<< i << "," << j << "," << k << "," << l <<endl;
- // increment variable j
- j++;
- // The print should be 11,11,11,11
- cout<< i << "," << j << "," << k << "," << l <<endl;
- // increment variable k
- k++;
- // The print should be 12,12,12,12
- cout<< i << "," << j << "," << k << "," << l <<endl;
- // increment variable l
- l++;
- // The print should be 13,13,13,13
- cout<< i << "," << j << "," << k << "," << l <<endl;
- return 0;
- }
下面這段代碼是將上面代碼中的引用替換之後代碼,也就是說明我們不依賴編譯器的自動替換功能,手動進行替換也能達到相同的目標。
- #include <iostream.h>
- int main()
- {
- int i = 10; // A Simple Integer variable
- int *const j = &i; // A Reference to the variable
- // The variable j will hold the address of i
- // Now we can also create a reference to reference variable.
- int *const k = &*j; // A reference to a reference variable
- // The variable k will also hold the address of i because j
- // is a reference variable and
- // it gets auto dereferenced. After & and * cancels each other
- // k will hold the value of
- // j which it nothing but address of i
- // Similarly we can also create another reference to the reference variable k
- int *const l = &*k; // A reference to a reference to a reference variable.
- // The variable l will also hold address of i because k holds address of i after
- // & and * cancels each other.
- // so we have seen that all the reference variable will actually holds the same
- // variable address.
- // Now if we increment any one of them the effect will be visible on all the
- // variables.
- // First print original values. The reference variables will have * prefixed because
- // these variables gets automatically dereferenced.
- // The print should be 10,10,10,10
- cout<< i << "," << *j << "," << *k << "," << *l <<endl;
- // increment variable j
- (*j)++;
- // The print should be 11,11,11,11
- cout<< i << "," << *j << "," << *k << "," << *l <<endl;
- // increment variable k
- (*k)++;
- // The print should be 12,12,12,12
- cout<< i << "," << *j << "," << *k << "," << *l <<endl;
- // increment variable l
- (*l)++;
- // The print should be 13,13,13,13
- cout << i << "," << *j << "," << *k << "," << *l <<endl;
- return 0;
- }
我們通過下面代碼可以證明 c++ 的引用不是神馬別名,它也會佔用內存空間的。
- #include <iostream.h>
- class Test
- {
- int &i; // int *const i;
- int &j; // int *const j;
- int &k; // int *const k;
- };
- int main()
- {
- // This will print 12 i.e. size of 3 pointers
- cout<< "size of class Test = " << sizeof(class Test) <<endl;
- return 0;
- }
結論
我希望這篇文章能把 c++ 引用的所有東東都解釋清楚,然而我要指出的是 c++ 標準並沒有解釋編譯器如何實現引用的行爲。所以實現取決於編譯器,而大多數情況下就是將其實現爲一個 const 指針。
引用支持 c++ 虛函數機制的代碼
- #include <iostream.h>
- class A
- {
- public:
- virtual void print() { cout<<"A.."<<endl; }
- };
- class B : public A
- {
- public:
- virtual void print() { cout<<"B.."<<endl; }
- };
- class C : public B
- {
- public:
- virtual void print() { cout<<"C.."<<endl; }
- };
- int main()
- {
- C c1;
- A &a1 = c1;
- a1.print(); // prints C
- A a2 = c1;
- a2.print(); // prints A
- return 0;
- }
上述代碼使用引用支持虛函數機制。如果引用僅僅是一個別名,那如何實現虛函數機制,而虛函數機制所需要的動態信息只能通過指針才能實現,所以更加說明引用其實就是一個 const 指針。
補充:const 指針(指針常量)與指向const的指針(常量指針)
當使用帶有const的指針時其實有兩種意思。一種指的是你不能修改指針本身的內容,另一種指的是你不能修改指針指向的內容。聽起來有點混淆一會放個例子上來就明白了。
先說指向const的指針,它的意思是指針指向的內容是不能被修改的。它有兩種寫法。
const int*
p; (推薦)
int const*
p;
第一種可以理解爲,p是一個指針,它指向的內容是const int 類型。p本身不用初始化它可以指向任何標示符,但它指向的內容是不能被改變的。
第二種很容易被理解成是p是一個指向int的const指針(指針本身不能被修改),但這樣理解是錯誤的,它也是表示的是指向const的指針(指針指向的內容是不能被修改的),它跟第一種表達的是一個意思。爲了避免混淆推薦大家用第一種。
再說const指針,它的意思是指針本身的值是不能被修改的。它只有一種寫法
int*
const p=一個地址; (因爲指針本身的值是不能被修改的所以它必須被初始化)
這種形式可以被理解爲,p是一個指針,這個指針是指向int 的const指針。它指向的值是可以被改變的如*p=3;
還有一種情況是這個指針本身和它指向的內容都是不能被改變的,請往下看。
const int*
const p=一個地址;
int const*
const p=一個地址;
看了上面的內容是不是有點暈,沒關係,你不用去背它,用的多了就知道了,還有個技巧,通過上面的觀察我們不難總結出一點規律,是什麼呢?也許你已經看出來了,什麼!竟然沒看也來,那隻好還得聽我嘮叨了。這個規律就是: 指向const的指針(指針指向的內容不能被修改)const關健字總是出現在*的左邊而const指針(指針本身不能被修改)const關健字總是出現在*的右邊,那不用說兩個const中間加個*肯定是指針本身和它指向的內容都是不能被改變的。有了這個規則是不是就好記多了。
什麼還是暈,那就看下面的程序,你把它編譯一下看看錯誤提示就明白了。
2
3 using namespace std;
4
5 int main(int argc, char *argv[])
6 {
7 int a=3;
8 int b;
9
10 /*定義指向const的指針(指針指向的內容不能被修改)*/
11 const int* p1;
12 int const* p2;
13
14 /*定義const指針(由於指針本身的值不能改變所以必須得初始化)*/
15 int* const p3=&a;
16
17 /*指針本身和它指向的內容都是不能被改變的所以也得初始化*/
18 const int* const p4=&a;
19 int const* const p5=&b;
20
21 p1=p2=&a; //正確
22 *p1=*p2=8; //不正確(指針指向的內容不能被修改)
23
24 *p3=5; //正確
25 p3=p1; //不正確(指針本身的值不能改變)
26
27 p4=p5;//不正確 (指針本身和它指向的內容都是不能被改變)
28 *p4=*p5=4; //不正確(指針本身和它指向的內容都是不能被改變)
29
30 return 0;
31 }