此文章运用的Cairo编译器版本:2.0.0-rc0。因为Cairo正在快速更新,所以不同版本的语法会有少许不同,未来将会将文章内容更新到稳定版本。
数组是一种十分常用的数据结构,通常代表一组相同类型的数据元素集合。无论是传统可执行程序,仍是智能合约,都会运用到数组。
根本介绍
Cairo中的数组是从中心库 array
中导出的一个数组类型,有着许多不一样的特性:
- 因为Cairo内存模型的特殊性,内存空间一旦被写入就无法掩盖重写,所以Cairo数组中的元素不能够修改的,只能够读取。这一点和大多数编程语言不一样
- 能够在数组的最后面增加一个元素
- 还能够从数组的最前面删去一个元素
创立数组
所有的数组都是可变变量,所以需求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>;
}
大家加油!!