1. 值類型和引用類型
1.1 值的存儲方式
所有變量在底層實現中,都會關聯一個具體的“值”,這個值可能存儲在 內存地址 或 寄存器 中。
- 寄存器用于優化常用變量的訪問速度。
- 只有局部、小、頻繁使用的變量才更可能被分配到寄存器中。
- 實際行為由編譯器根據上下文和優化策略決定。
對于值變量而言,這個關聯的值本身就是數據的直接表示。例如,一個 Int64 類型的變量 let x = 5 ,其關聯的值 5 直接存儲在變量的內存空間中,而非指向其他位置。
對于引用變量而言,其關聯的值是一個對對象的引用,該引用通常表現為對象在堆內存中的地址。變量本身并不包含對象的實際數據,而是通過這個引用間接訪問對象的內容。
在 Cangjie 語言中,class 和 Array / ArrayList / HashMap 等 collection 類型 屬于引用類型,struct 和 Int64 / String 等 基礎類型 屬于值類型。
1.2 let 在值類型與引用類型中的行為差異
引用類型 和 值類型 這種分類,直接影響了變量聲明(var
或 let
)與方法調用的規則。
“let 與 var,分別對應不可變和可變屬性,可變性決定了變量被初始化后其值還能否改變,倉頡變量也由此分為不可變變量和可變變量兩類。”
- 值類型特性:struct 實例的變量在聲明為 let 時是完全不可變的,包括其內部的字段;若需修改 struct 實例的狀態,則需使用 var 聲明變量,
var d = Data()
。
- 引用類型特性:引用類型實例的變量是引用(類似指針),let 聲明僅禁止重新賦值引用本身,但允許通過引用修改對象內部狀態。
let struct 與 let class 類似于 C++ 中常量指針常量(const T * const)和指針常量(T * const)的區別 —— 前者指向、指向的內容均不可變,后者指向不可變、指向的內容可變。
這也是為什么即使將 ArrayList 聲明為 let,依然可以向其中添加元素,因為 let 僅阻止對該引用本身的重新賦值,而不影響通過該引用修改對象內部狀態;
同樣地,let it = list.iterator() 可以在遍歷時通過 while (let Some(val) <- it.next())
不斷獲取下一個元素,是因為迭代器對象的狀態變更屬于其內部行為,并不受引用本身不可變性的限制。
2. 復制和傳參的機制
2.1 復制行為解析
值類型變量的賦值或傳參會觸發深拷貝,即完整復制變量的相關數據,原始實例與副本狀態隔離;
引用類型變量的復制或傳參會復制引用(而非對象本身)。
Cangjie 值類型(struct)和引用類型(class)的復制均不會觸發構造函數:
- struct 在賦值或傳參時,直接復制內存數據,不會調用構造函數、重新初始化成員變量。
let d2= d1 的行為并不會調用 init(D: Data) 構造函數。因為 Cangjie 中結構體(struct)是值類型,賦值操作會直接復制內存中的數據,而不是通過構造函數進行初始化。
·
如果 Cangjie 在賦值時真的使用了自定義的拷貝構造函數(如 init(D: Data)),則會導致無限遞歸的問題:因為拷貝構造函數本身在初始化新對象時又會觸發一次賦值,進而再次調用拷貝構造函數,形成循環。
·
因此,這種“反向驗證”說明 Cangjie 在底層對值類型的賦值操作采用的是直接內存復制的方式,而不是依賴用戶定義的構造函數。
- class 的實例通過引用來共享數據,賦值或傳參僅復制引用地址,無需構造新對象。
2.2 值類型中嵌套引用類型
當值類型(如 struct)中包含引用類型(如 ArrayList / class )時,復制值類型會導致以下行為:
- 值類型字段:直接復制數據。修改副本的值類型字段,不影響原始實例。
- 引用類型字段:復制引用地址。副本的引用類型字段與原始實例共享同一對象。
r1.x = 1
:因為 x 是值類型,賦值后修改的是 r2.x ,不影響 r1.x 。
r1.list: 1 2 3
:因為 list 是引用類型,r1.list 和 r2.list 指向同一個 ArrayList,所以 r2 對 list 的修改也影響到 r1 。