12_Cairo1.0中的Array(数组)

12_Cairo1.0 中的 Array (数组)

此文章使用的 Cairo 编译器版本：2.0.0-rc0。因为 Cairo 正在快速更新，所以不同版本的语法会有些许不同，未来将会将文章内容更新到稳定版本。

数组是一种非常常用的数据结构，通常代表一组相同类型的数据元素集合。无论是传统可执行程序，还是智能合约，都会使用到数组。

基本介绍

Cairo 中的数组是从核心库 array 中导出的一个数组类型，有着许多不一样的特性：

由于 Cairo 内存模型的特殊性，内存空间一旦被写入就无法覆盖重写，所以 Cairo 数组中的元素不可以修改的，只可以读取。这一点和大多数编程语言不一样

可以在数组的最后面添加一个元素

还可以从数组的最前面删除一个元素

创建数组

所有的数组都是可变变量，所以需要 mut 关键字：

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

数组中可以包含任意类型的元素，因为 Array 里面是一个泛型变量。我们在创建数组的时候，需要指定类型。

Copy
use array::ArrayTrait;

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

上面代码中，编译器不知道 a 这个数组应该装什么类型的数据进去，所以报了错。我们可以这样指定类型：

Copy
use array::ArrayTrait;

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

上面通过添加 felt252 类型数据到数组里，指明数组是 Array 类型的。还可以这样指定：

Copy
use array::ArrayTrait;

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

以上两种方法都可以。

读取数组大小信息

可以读取数组的长度 len()，也可以判断数组是否为空 is_empty()。

Copy
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 中的数组只可以在 末尾添加元素 和 开头删除元素。那我们就来看看相关的代码案例：

Copy
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 模式，来分别处理这两种情况，避免造成：读取超出下标元素的错误。

Copy
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 错误，所以使用它需要谨慎一些。

Copy
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 对象，这个在只读的场景中非常实用。

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

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

数组作为函数的参数

数组是没有实现 Copy trait 的，所以数组作为函数的参数时，会发生 move 操作，所有权会发生变化。

Copy
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) 注释解除，就会报错。

数组的深拷贝

顾名思义，深拷贝是将一个对象的所有的元素、属性，和嵌套的子元素、属性，完全的拷贝出来，形成一个新的对象。这个新的对象的所有数据都和之前的一样，但是它在内存中的地址不一样，他们是数据一致的两个对象。

Copy
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：

Copy
cairo-run --available-gas 200000 $cairo_file

总结

核心库导出了一个数组类型以及相关函数，使您可以轻松地获取您正在处理的数组的长度、并且添加元素或获取特定索引处的元素。尤其有趣的是使用ArrayTrait :: get()函数，因为它返回一个 Option 类型，这意味着如果您尝试访问超出边界的索引，它将返回 None 而不是退出程序，这意味着您可以实现错误管理功能。此外，您可以使用泛型类型与数组一起使用，使得与手动管理指针值的旧方式相比，数组更易于使用。

数组成员函数汇总

Copy
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>;
}

大家加油💪！！