[引用區別] c++中引用與java中引用區別
綜述:
在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的函式或類的引數傳遞時可以實現與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++ 程式碼
[cpp] view plain copy print?- #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 型別變數必須初始化,這個指標也必須有所指。下面我們再次聚焦到上面這段程式碼,並使用編譯器的那套語法將引用替換掉。
[cpp] view plain copy print?- #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) 中是如何運作的。
[cpp] view plain copy print?- #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;
- }
下面這段程式碼是將上面程式碼中的引用替換之後程式碼,也就是說明我們不依賴編譯器的自動替換功能,手動進行替換也能達到相同的目標。
[cpp] view plain copy print?- #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++ 的引用不是神馬別名,它也會佔用記憶體空間的。
[cpp] view plain copy print?- #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++ 虛擬函式機制的程式碼
[cpp] view plain copy print?- #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中間加個*肯定是指標本身和它指向的內容都是不能被改變的。有了這個規則是不是就好記多了。
什麼還是暈,那就看下面的程式,你把它編譯一下看看錯誤提示就明白了。