本篇文章介绍了如何定义和调用函数
函数是 Move 语言中的代码构建块,它们被用于定义模块的逻辑和行为。在Move 语言中,函数是可重用的代码单元,可以在其他函数中调用或作为执行的入口点。本文将全面介绍 Move 语言中函数的概念、语法、使用方式以及相关实践。
Move语言中,使用 fun 关键字来定义函数。函数声明包括可见性修饰符、入口修饰符(可选)、函数名称、类型参数(可选)、参数列表、返回类型和函数体。一个基本的函数声明语法如下:
<visibility> <entry> fun <identifier><[type_parameters: constraint],*>([identifier: type],*): <return_type> <function_body>
下面是一个名为 foo
的函数,类型参数 T1
、T2
表示范型,函数返回值为元组,函数体并未实现复杂逻辑,而是直接把入参返回。
fun foo<T1, T2>(x: u64, y: T1, z: T2): (T2, T1, u64) { (z, y, x) }
默认情况下,模块中的函数只能在同一模块内部被调用,这种内部函数也称为私有函数。要允许其他模块访问,必须使用 public 或 public(package) 修饰符声明函数为公共可见。
public(friend)
语法已被废弃。module hello02::target {
public fun foo(): u64 { 10 }
public(package) fun bar(): u64 { 20 }
}
// 可以从另一模块调用`foo`
module hello02::caller {
use std::debug;
#[test]
fun test_calls_target() {
let res1 = hello02::target::foo();
let res2 = hello02::target::bar();
debug::print(&res1);
debug::print(&res2);
}
}
在调用函数时,需要为每个参数提供对应的实参值。实参可以是字面量,也可以是其他表达式的结果。
module hello02::example {
public fun foo(x: u64): u64 { x }
public fun bar(x: u64, y: u64): u64 { x + y }
}
module hello02::other {
fun calls() {
hello02::example::foo(42); // 字面量实参
hello02::example::bar(1, 2 * 3);// 表达式实参
}
}
对于泛型函数,可以显式提供类型参数,也可以让编译器自动推断
module hello02::example02 {
public fun id<T>(x: T): T { x }
}
module hello02::other02 {
fun calls() {
// 推断T为u64
hello02::example02::id(0);
// 显式指定T为u256
hello02::example02::id<u256>(0);
}
}
调用函数时,可以使用完全限定名(包括模块地址)或别名(通过use
引入)
函数体中最后一个表达式的值就是函数的返回值。Move 中通过函数末尾的表达式代替了return
语句。如果函数体为空,则返回()。同时Move支持多返回值,通过元组类型声明,并在函数体中返回相应的元组表达式。
注意:表达式的结尾没有分号。这一点与很多其他语言不同。
fun double(x: u64): u64 {
x + x // 这个表达式的值就是函数返回值
}
fun do_nothing(): () {} // 返回()
fun get_name_and_age(): (vector<u8>, u8) {
(b"Alice", 25)
}
调用函数时,需要使用解构赋值来获取各个返回值,如果只需要部分返回值,可以使用_
忽略其他值
let (name, age) = get_name_and_age();
let (name, _) = get_name_and_age();
Move 还支持return关键字显式返回,这在提前退出复杂的控制流时很有用:
fun index_of<T>(v: &vector<T>, target: &T): Option<u64> {
let mut i = 0;
let n = vector::length(v);
while (i < n) {
// 显式返回
if (vector::borrow(v, i) == target) return option::some(i);
i = i + 1
};
option::none()
}
代码解释:遍历向量数组,如果向量 v 的第 i 个值等于 target,则返回 i
module hello::funcs_module {
/// 计算两个u64数值的和
public fun add(x: u64, y: u64): u64 {
x + y
}
/// 返回传入字符串的长度
public fun string_length(s: vector<u8>): u64 {
vector::length(&s)
}
/// 返回传入vector中最大值和最小值
public fun vector_min_max(v: &mut vector<u64>): (u64, u64) {
let mut min = vector::pop_back(v);
let mut max = min;
loop {
if (vector::is_empty(v)) break;
let val = vector::pop_back(v);
if (val < min) min = val;
if (val > max) max = val
};
(min, max)
}
#[test]
public fun test_vector_funcs() {
assert!(add(1, 2) == 3, 0);
assert!(string_length(b"hello") == 5, 0);
let mut v = vector[1, 5, 3, 7, 2];
let (min, max) = vector_min_max(&mut v);
assert!(min == 1, 0);
assert!(max == 7, 0);
}
}
表达式是一个代码单元,用于返回一个值。跟它相对的概念是语句(Statement),语句则更侧重于执行一些操作或控制程序流程,而不一定返回值
在 Move 语言中,大多数语法结构都被设计为表达式,比如:字面量、运算符操作、函数调用及代码块,而 let
语句用于绑定一个值到一个变量,就属于语句而非表达式。总的来说,在Move语言中,除了let
语句(声明语句)之外,几乎所有内容都是表达式。
注意:区分语句和表达式的一个简单方法就是有无分号;
,有的话就是语句,执行操作但不返回结果;没有的话就是表达式,执行计算并返回结果。
在 Move 语言中,字面量是表达式的一种基本形式。Move支持多种类型的字面量,包括布尔值、整数、十六进制整数、字节数组等。这些字面量可以被用作变量的初始值,或者作为函数参数传递。例如:
let b = true; // true是一个字面量
let n = 1000; // 1000是一个字面量
let h = 0x0A; // 0x0A是一个字面量
let v = b"hello"; // b"hello"是一个字节向量字面量
let x = x"0A"; // x"0A"是一个字节向量字面量
let c = vector[1, 2, 3]; // vector[1, 2, 3]是一个向量字面量
算术运算符、逻辑运算符和位运算符用于对值执行运算。运算的结果是一个值,因此运算符本身也是一种表达式
let sum = 1 + 2; // 1 + 2是一个表达式
let sum = (1 + 2); // 同一个表达式,使用了括号
let is_true = true && false; // true && false是一个表达式
let is_true = (true && false); // 同一个表达式,使用了括号
代码块使用一对花括号 {} 来表示,它是一系列语句和表达式的集合,它返回块中最后一个表达式的值,如果没有表达式,编译器会自动插入一个单元值()——一个空的表达式。简单来说,我们可以把带分号;
的命令行视为语句,反之视为表达式。
// 一个包含let语句和表达式的代码块
let sum = {
let a = 1;
let b = 2;
a + b // 这个表达式的值就是代码块的返回值
};
let none = {
let a = 1;
let b = 2;
a + b; // 分号结尾,不会返回这个值
// 编译器会自动插入一个空表达式`()`
};
函数调用 add(1, 2)
是一个表达式,它调用一个函数并返回函数体中最后一个表达式的值。在 add
函数内部,a + b
算术运算也是一个表达式。
fun add(a: u8, b: u8): u8 {
a + b
}
#[test]
fun some_other() {
let sum = add(1, 2); // add(1, 2)是一个表达式,类型为u8
}
module hello::expressions_module {
// 字面量表达式
const OFFSET: u8 = 32;
const FLAG: bool = true;
// 函数定义及调用
fun add(a: u64, b: u64): u64 { a + b }
public fun examples(x: u64, y: u64): u64 {
let is_gt = x > y; // 运算符表达式
// 代码块和let绑定
let z = {
let tmp = if (is_gt) {
x + (OFFSET as u64) // 常量字面量
} else {
y // 变量表达式
};
add(tmp, y) // 函数调用
};
// 向量字面量
let vec = vector[1, 2, 3];
// 字节向量字面量
let bytes = b"hello";
// z 后面没有分号,作为表达式返回
z
}
}
控制流对于编程语言来说是很重要的。控制流语句控制程序中的执行流程,包括做出决策、重复代码块以及提前退出代码块,决定了整个应用的流程。
Move 中的几个控制流语句:
if
和 if-else
:决定是否执行代码块loop
和 while
:重复代码块break
和 continue
语句: 提前退出循环return
语句:提早退出函数,并返回值 if
和 if-else
是条件语句的关键词,并基于 boolean
来决定是否执行代码块,当 if-else
备用上时,基于 boolean
来决定该执行此代码块,或其他代码块。以下是条件语句的语法:
if (<bool_expression>) <expression>;
if (<bool_expression>) <expression> else <expression>;
循环是应用重要的部分。它们重复执行任务,实现自动化、数据处理和数据集合的迭代。需要注意的是,执行循环任务可能会导致无限循环。在区块链上,也可能会因此导致 gas 耗尽而使交易被终止。
Move 有两种内置循环类型: while
和 loop
。以下是循环语句的语法:
while (<bool_expression>) { <expressions>; };
loop { <expressions>; };
循环可以透过重复的执行来达到我们要的结果。当条件达到后,就可以退出循环。这时,我们可以用 break
退出循环。
如果要跳过迭代,进行到下一个代码块,那我们就用 continue
。
break
和 continue
可以和 loop
还有 while
一起用。
在特定的结果达成后,可以用 return
提早退出函数,并返回一个值。语法如下:
return <expression>
module hello::hello_module {
//条件语句
#[test]
fun test_if_else() {
let x = 5;
let y = if (x > 0) {
1 //预期结果
} else {
0
};
assert!(y == 1, 0);
}
#[test]
fun test_break_loop() {
let mut x = 0;
// 这将循环直到“x”等于 5。
loop {
x = x + 1;
// 如果 `x` 是 5,则退出循环。
if (x == 5) {
break // 退出循环
}
};
assert!(x == 5, 0);
}
#[test]
fun test_continue_loop() {
let mut x = 0;
// 这将循环直到“x”等于 10.
loop {
x = x + 1;
// 如果“x”是奇数,则跳过其余的迭代。
if (x % 2 == 1) {
continue // 跳过剩余的迭代.
};
std::debug::print(&x);
// 如果“x”是10,则退出循环。
if (x == 10) {
break // 推出循环。
}
};
assert!(x == 10, 0); // 10
}
/// 如果 `x` 大于 0 且不为 5,则此函数返回 `true`,
/// 否则返回“false”。
fun is_positive(x: u8): bool {
if (x == 5) {
return false
};
if (x > 0) {
return true
};
false
}
#[test]
fun test_return() {
assert!(is_positive(5) == false, 0);
assert!(is_positive(0) == false, 0);
assert!(is_positive(1) == true, 0);
}
}
和其他许多语言一样, Move 编译器允许定义可以在结构体实例上调用的方法 (method)。这让结构体的字段更容易地在函数内操作。
如果函数的第一个参数是同一个模块的结构体,那么简单的用 .
就可以调用该方法。不然, 就需要用标准函数调用语法:
ModuleName::function_name<TypeArguments>(arguments)
为了避开名称冲突,或帮助取一个好记的名字,我门可以为方法在输入时取个别名。要注意的是,如果结构体不是在同一个模块,别名的可见性不能是 public
。
// 用于本地方法关联
use fun function_path as Type.method_name;
// 以别名导出
public use fun function_path as Type.method_name;
module hello::hero {
/// 代表hero的结构体。
public struct Hero has drop {
health: u8,
mana: u8,
}
/// 创建新hero
public fun new(): Hero { Hero { health: 100, mana: 100 } }
/// 施展法术的一种方法,消耗mana。
public fun heal_spell(hero: &mut Hero) {
hero.health = hero.health + 10;
hero.mana = hero.mana - 10;
}
/// 返回hero健康值的方法。
public fun health(hero: &Hero): u8 { hero.health }
/// 返回hero法力值的方法。
public fun mana(hero: &Hero): u8 { hero.mana }
#[test]
// 测试 `Hero` 结构的方法。
fun test_methods() {
let mut hero = new();
hero.heal_spell();
assert!(hero.health() == 110, 1);
assert!(hero.mana() == 90, 2);
}
}
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!