StarknetAstro

StarknetAstro

12_Cairo1.0中的Array(數組)

12_Cairo1.0 中的 Array (數組)#

此文章使用的 Cairo 編譯器版本:2.0.0-rc0。因為 Cairo 正在快速更新,所以不同版本的語法會有些許不同,未來將會將文章內容更新到穩定版本。

數組是一種非常常用的數據結構,通常代表一組相同類型的數據元素集合。無論是傳統可執行程序,還是智能合約,都会使用到數組。

基本介紹#

Cairo 中的數組是從核心庫 array 中導出的一个數組類型,有著許多不一樣的特性:

  1. 由於 Cairo 內存模型的特殊性,內存空間一旦被寫入就無法覆蓋重寫,所以 Cairo 數組中的元素不可以修改的,只可以閱讀。這一點和大多數編程語言不一樣
  2. 可以在數組的最後面添加一個元素
  3. 還可以從數組的最前面刪除一個元素

創建數組#

所有的數組都是可變變量,所以需要 mut 關鍵字:

fn create_array() -> Array<felt252> {
    let mut a = ArrayTrait::new(); 
    a.append(0);
    a.append(1);
    a
}

數組中可以包含任意類型的元素,因為 Array 裡面是一個泛型變量。我們在創建數組的時候,需要指定類型。

use array::ArrayTrait;

fn main() {
    let mut a = ArrayTrait::new();
    // error: Type annotations needed.
}

上面代碼中,編譯器不知道 a 這個數組應該裝什麼類型的數據進去,所以報了錯。我們可以這樣指定類型:

use array::ArrayTrait;

fn main() {
    let mut a = ArrayTrait::new();
    a.append(1);
}

上面通過添加 felt252 類型數據到數組裡,指明數組是 Array 類型的。還可以這樣指定:

use array::ArrayTrait;

fn main() {
    let b = ArrayTrait::<usize>::new(); 
}

以上兩種方法都可以。

讀取數組大小信息#

可以讀取數組的長度 len(),也可以判斷數組是否為空 is_empty()

use array::ArrayTrait;
use debug::PrintTrait;
use option::OptionTrait;

fn main() {
    let mut a = ArrayTrait::new();

    a.append(1);

    // 判斷數組是否為空
    a.is_empty().print();

    // 查看數組的長度
    a.len().print();
}

添加 & 刪除元素#

前文提到,Cairo 中的數組只可以在 末尾添加元素 和 開頭刪除元素。那我們就來看看相關的代碼案例:

use array::ArrayTrait;
use debug::PrintTrait;
use option::OptionTrait;

fn main() {
    let mut a = ArrayTrait::new();

	// 末尾添加元素
    a.append(1);
    
    // 刪除第一個元素
    let k = a.pop_front();
    k.unwrap().print();
}

末尾添加元素比較簡單,刪除第一個元素 pop_front 方法會將被刪除的元素返回,並且是一個Option類型的值。這裡使用了 unwrap 方法將 Option 類型的值轉換為原有的類型。

獲取數組中的元素#

有兩種方法可以獲取數組中的元素:get 函數 和 at 函數。

get 函數#

get 函數是一個相對安全的選項,它返回一個 Option 類型的值。如果訪問的下標沒有超出數組的範圍,那麼就是 Some;如果超出下標,就會返回 None。這樣,我們就可以結合 Match 模式,來分別處理這兩種情況,避免造成:讀取超出下標元素的錯誤。

use array::ArrayTrait;
use debug::PrintTrait;
use option::OptionTrait;
use box::BoxTrait;

fn main() {
    let mut a = ArrayTrait::new();

    a.append(1);
    let s = get_array(0,a);
    s.print();
}

fn get_array(index: usize, arr: Array<felt252>) -> felt252 {
	// 下標是 usize 類型
    match arr.get(index) {
        Option::Some(x) => {
	        // * 是從副本中獲取原值的符號
	        // 返回的是 BoxTrait,所以需要使用 unbox 解開包裹
            *x.unbox()
        },
        Option::None(_) => {
            panic(arr)
        }
    }
}

上面涉及到的 BoxTrait 會在未來講解官方核心庫的時候進行講解,有關副本和引用相關的內容參看Cairo1.0 中的值傳遞和引用傳遞

at 函數#

at 函數將會直接返回對應下標元素的 snapshot,注意這裡只是單個元素的 snapshot,所以我們需要使用*操作符將這個 snapshot 背後的值取出來。另外,如果下標超出數組的範圍,將會導致 panic 錯誤,所以使用它需要謹慎一些。

use array::ArrayTrait;
use debug::PrintTrait;
use option::OptionTrait;

fn main() {
    let mut a = ArrayTrait::new();

    a.append(100);
    let k = *a.at(0);
    k.print();
}

snap 函數#

snap 函數將會獲得數組的 snapshot 對象,這個在只讀的場景中非常實用。

use core::array::SpanTrait;
use array::ArrayTrait;

fn main() {
    let mut a = ArrayTrait::new();
    a.append(100);
    let s = a.span();
}

數組作為函數的參數#

數組是沒有實現 Copy trait 的,所以數組作為函數的參數時,會發生 move 操作,所有權會發生變化。

use array::ArrayTrait;
fn foo(arr: Array<u128>) {}

fn bar(arr: Array<u128>) {}

fn main() {
    let mut arr = ArrayTrait::<u128>::new();
    foo(arr);
    // bar(arr);
}

上面如果將 bar (arr) 註釋解除,就會報錯。

數組的深拷貝#

顧名思義,深拷貝是將一個對象的所有的元素、屬性,和嵌套的子元素、屬性,完全的拷貝出來,形成一個新的對象。這個新的對象的所有數據都和之前的一樣,但是它在內存中的地址不一樣,他們是數據一致的兩個對象。

use array::ArrayTrait;
use clone::Clone;

fn foo(arr: Array<u128>) {}

fn bar(arr: Array<u128>) {}

fn main() {
    let mut arr = ArrayTrait::<u128>::new();
    let mut arr01 = arr.clone();
    foo(arr);
    bar(arr01);
}

上面代碼延續了上個例子。arr01 就是由 arr 深拷貝出來的新數組,拷貝需要借助官方庫中的 Clone trait。

我們從深拷貝的定義中就可以發現,它是非常消耗資源的。clone 成員方法使用 loop 循環,將數組的元素一個個拷貝出來。所以執行這個 cairo 文件時,需要指定 gas:

cairo-run --available-gas 200000 $cairo_file

總結#

核心庫導出了一个數組類型以及相關函數,使您可以輕鬆地獲取您正在處理的數組的長度、並且添加元素或獲取特定索引處的元素。尤其有趣的是使用ArrayTrait :: get()函數,因為它返回一個 Option 類型,這意味著如果您嘗試訪問超出邊界的索引,它將返回 None 而不是退出程序,這意味著您可以實現錯誤管理功能。此外,您可以使用泛型類型與數組一起使用,使得與手動管理指針值的舊方式相比,數組更易於使用。

數組成員函數匯總#

trait ArrayTrait<T> {
	// 創建一個數組
    fn new() -> Array<T>;
    
	// 給數組末尾添加一個元素
    fn append(ref self: Array<T>, value: T);
    
    // 刪除數組最前面一個元素,並且將這個元素以option的形式返回
    fn pop_front(ref self: Array<T>) -> Option<T> nopanic;
    
    // 獲得某個下標的option值,也是返回option類型
    fn get(self: @Array<T>, index: usize) -> Option<Box<@T>>;
    
    // 獲得某個下標的值
    fn at(self: @Array<T>, index: usize) -> @T;
    
    // 返回數組長度
    fn len(self: @Array<T>) -> usize;
    
    // 判斷數組是否為空
    fn is_empty(self: @Array<T>) -> bool;
    
    // 獲得一個 snapshot
    fn span(self: @Array<T>) -> Span<T>;
}

大家加油💪!!

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。