此文章运用的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,假如下标超出数组的规模,将会导致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>;
}

大家加油!!