值與引用
值(value)與引用(reference)因其天生的對立性,提供了乙個二分法(dichotomy)的準則。
把資料分成兩類:
值——具有某種型別的資料
引用——可用來獲取特定資料的值
把變數分成兩類:
值變數——表示值的變數
引用變數——表示引用的變數
把資料型別分成兩類:
值型別——能直接被訪問的資料型別
引用型別——借助引用才能被訪問的資料型別
把物件分成兩類: 值型別物件 引用型別物件
(按此定義,c++是沒有引用型別的,但這裡有兩處令人疑惑的地方。一、c++有一種被稱為引用(reference)的型別(一種被指標更安全的引用);二、指標(包括第一點的特定引用)具有引用功能,可認為屬於引用端的引用型別,在上文提到的引用型別都是指被引用端的)
以下言論主要限於c++、j**a和c#三種語言。
先簡單說說記憶體分配,不同語言採取的記憶體分配策略不盡相同,相同的語言也可能有不用的實現,但一般都有三種基本機制,按靈活度遞增依次為:靜態分配(static allocation)、棧分配(stack allocation)和堆分配(heap allocation)。其中靜態分配發生於編譯期,在靜態記憶體區內為全域性變數、靜態變數、常數變數(在一些語言實現中為常數預備了常數變數儲存區)等安排空間。棧分配與堆分配發生在執行期,但前者一般在編譯器就可確定待分配記憶體的空間大小和生命週期(例外,c提供了非標準的alloca函式,可讓程式設計師在棧上分配動態大小的記憶體,但仍由編譯器釋放),後者則可能推遲到執行期。棧記憶體區用於存放由new運算子、malloc函式等動態分配而得的空間(c++中,new分配的free store和由malloc分配的棧記憶體區有可能不一致;c#中new產生的值型別物件也可能在棧上)。
棧分配是基於簡單的堆疊結構,因此效率很高。另外通常每乙個執行緒都有獨立的棧區。故而棧變數天然是執行緒安全(thread-safe)的。棧分配的主要缺點就是須提前分配記憶體,而且棧記憶體總容量有限,容易發生棧溢位(stack overflow)。至於棧記憶體的靜態有效期,優劣參半:無需擔心記憶體管理的同時無法突破作用域。
堆分配雖然比棧分配更強大靈活,但其複雜的演算法影響了時間效率,記憶體碎片、元資料開銷和可能的記憶體洩漏等問題也影響了空間效率。此外程式設計師還要負擔更多的記憶體管理、執行緒安全等方面的責任。
乙個變數是在棧還是堆與是值變數還是引用變數無關。比如j**a中,區域性變數總在棧裡,而例項變數總在堆裡,與變數型別無關。其次,『被引用的物件總在堆中』的說法在j**a和c#中成立,但在c++中則未必——c++也可在棧上的物件建立引用。由於c++只有引用型別的變數,卻沒有引用型別的物件,因此更保險的說法是:引用型別物件總在堆中。值物件不是總在棧中,這是乙個著名的誤解,正確的說法是:值物件可能再棧中。假如它嵌在引用型別物件當中,那麼將與後者一道分配在堆中。
值變數與引用變數的區別好比名詞與代詞。對值變數而言,它對應的是目標資料;對引用變數而言,它對應的不是目標資料本身,而是用來訪問目標資料的資料,可以說是一種元資料(關於資料的資料)。舉例來說,引用最原始的形式是指標,任何有效的非空指標對應的資料都是乙個記憶體位址——目標資料的位址。
//j**asometype是引用型別,a是引用變數。由於a是區域性變數,所以分配在棧上的,但它所指代的物件本身則是分配在堆上。sometype a=new sometype();
//上面的**實際上是2步原子操作,如下
sometype a;
a=new sometype();
j**a和c++是兩種極端,j**a中無法自定義值型別(j**a中的基本型別就是值型別)而c++中無法自定義引用型別(c++中雖不能建立引用型別,卻可以建立引用)。c#則是兼收幷蓄,可以自定義引用型別和值型別。假如上述**是c#**,並且sometype不是引用型別的class而是值型別的struct的話,那麼系統將不在堆中分配記憶體,而在棧中直接分配乙個物件。
//c++如下值與引用本來是相對的概念,引用也是一種特殊的值——包含被引用物件的位址資訊的值。再者,j**a與c#中的引用變數在形式上兼具雙重身份:以引用的身份被調動方法,以值的身份被賦值或作為引數傳遞;c++中的指標是一種引用,但在指標的指標即二重指標的面前,它搖身一變成了後者的值。sometype* a=new sometype();//堆上
sometype a=sometype();//棧上 簡化sometype a;
在呼叫函式時,需要引數傳遞,即有乙個將實際引數對映到形式引數的過程。最常見有兩種機制,一種是按值傳遞,函式收到的是實際引數的值——按位拷貝(bitwise copy);另一種是按引用傳遞,函式收到的是實際引數的引用——記憶體位址。此外還有拷貝-恢復(copy-restore),按名呼叫(call-by-name),巨集展開(macro expansion)等機制。
j**a中只有按值傳遞,沒有按引用傳遞!
在引用變數作為引數按值傳遞後,方法體獲得了物件引用的拷貝,與原引用對應著同乙個物件,故仍可以操作該物件。j**a方法儘管不能改變引用原件,即不能為其重新賦值,但仍可通過引用復件來改變物件的內容。「j**a按值傳遞物件引用」的說法就合情合理。
//c++c++中string為值型別 加上」&「實現值型別的引用傳遞,而c#中string是引用型別,使用關鍵字ref表示傳進去的是string引用的引用,而不是拷貝。雖然看上去很迷,但是由此可以得出「j**a按值傳遞物件引用」的結論。static void change(string& s)
//c#
static void change(ref string s)
當乙個物件給乙個變數賦值或作為引數按值傳遞是,c++中複製的是該物件的值,而j**a只複製的卻是該物件的引用。因此c++有專門的賦值運算符合複製建構函式,而j**a則沒有。如果j**a要達到複製物件值的目的,不能隱式地通過變數賦值或引數傳遞,只能顯式地重新構造物件或者通過轉殖(clone)、序列化(serialization)等手段。這就是值語義與引用語義的區別。
我的《冒號課堂》學習筆記 值與引用(2)語義型別
值與引用 值語義的物件是獨立的,語義的物件卻是允許共享的。由於j a不支援值型別物件,j a程式設計師才更需要加強這方面的意識。語法和語義並不總是一致的 語法上的值型別可能在語義上是引用型別,語法上的引用型別可能在語義上是值型別。永遠不要忘記乙個基本原則 語法只是手段,語義才是目的。為了判斷乙個型別...
右值引用與模板學習筆記
include using namespace std intmain1 void 右值是乙個和運算過程相匹配的臨時物件,這個臨時物件在於其所對應的語句執行完畢之後,就銷毀了 所以,我們無法從語法層面上直接訪問。左值是乙個有名字,有固定位址的表示式 int a 10 int b a const in...
我的python學習筆記1
createdmd5listcode.py 沒有系統的學習過一門程式語言,即使學校學習時也不過聽說過而已。工作之後越發感覺到coding的強大,遂又拾起來開始漫漫學習之路。朋友推薦學習python,所以就從python開始。工作上剛好可以提供一些需求,來幫助我確定乙個短期的目標,以實現一些工作上的便...