函数调用(Function Calls)
内部函数调用(Internal Function Calls)
当前合约的函数可以直接内部(Internal)调用,也可以递归地调用,比如这个古怪的例子:
pragma solidity ^0.4.16;
contract C {
function g(uint a) public pure returns (uint ret) { return f(); }
function f() internal pure returns (uint ret) { return g(7) + f(); }
}
这些函数调用在EMV里翻译成简单的jumps语句。结果是当前内存没有被清除, 即通过内存引用的函数是非常高效的。只有同一个合约里的函数可在内部被调用。
外部函数调用(External Function Calls)
表达式 this.g(8);
和 c.g(2);
(c是一个合约的实例) 也是有效的函数调用,不过被称为外部函数调用, via a message call and not directly via jumps. 请注意构造函数不能这样被调用, 因为这时合约还没有被创建。
其他合约的函数也是外部调用。对于外部调用,所有函数参数必须被复制到内存中。
当调用其他合约的函数时, 发送的金额(Wei)和gas可以通过.value()
和 .gas()
来设置:
pragma solidity ^0.4.0;
contract InfoFeed {
function info() public payable returns (uint ret) { return 42; }
}
contract Consumer {
InfoFeed feed;
function setFeed(address addr) public { feed = InfoFeed(addr); }
function callFeed() public { feed.info.value(10).gas(800)(); }
}
修饰符payable
用在函数info
上,因为如果不这么做的话,.value()
将不可用。
注意表达式InfoFeed(addr)
执行显式类型转换,意思是“我们知道给定的地址的合约类型是InfoFeed
”并且不执行构造函数。 必须非常谨慎地处理显式类型转换。永远不要在不确定类型的情况下调用一个合同中的函数。
我们也可以直接使用函数setFeed(InfoFeed _feed) { feed = _feed; }
。 注意feed.info.value(10).gas(800)
只是在本地设置函数调用时需要的gas的值和数量,只有最后的括号结束后才完成实际的调用。
如果调用的合约不存在(帐户不包含代码)、被调用的合约内部抛出异常或gas不足,会导致异常。
警告 |
---|
与另一合约的任何交互都会带来潜在的危险,特别是如果在事先不知道合约的源代码。当前合约将控制权移交给被调用的合约,任何事情都将可能发生。即使被调用的合约继承自已知的父合约,继承的合约也只需要具有正确的接口,其内部实现可以是任意的,从而对调用者构成威胁。 |
另外,在调用你的系统的其他合同或在第一次调用返回之前回到当前合约时也要小心。因为被调用的合约可以通过其函数调用,改变当前合约的状态变量。因此,在对你的合约中状态变量进行任何更改之后,调用外部函数,这样你的合约就不易受到重用。 |
具名调用和匿名函数参数(Named Calls and Anonymous Function Parameters)
函数调用参数如果被包含在{ }
中,可以以任何顺序给出。参数列表必须与函数声明中的参数列表重合,但可以按任意顺序排列:
pragma solidity ^0.4.0;
contract C {
function f(uint key, uint value) public {
// do something
}
function g() public {
// named arguments
f({value: 2, key: 3});
}
}
省略函数参数名(Omitted Function Parameter Names)
可以省略未使用的参数(尤其是返回值)的名称。这些参数仍然存在于堆栈上,但它们是不可访问的。
pragma solidity ^0.4.16;
contract C {
// 省略参数名
function func(uint k, uint) public pure returns(uint) {
return k;
}
}
通过new
创建合约(Creating Contracts via new
)
合约可以使用new
关键字创建新合约。必须事先知道要创建的合约的完整代码,因此递归创建依赖(recursive creation-dependencies)是不可能的。
pragma solidity ^0.4.0;
contract D {
uint x;
function D(uint a) public payable {
x = a;
}
}
contract C {
D d = new D(4); // 将作为C构造函数的一部分执行。
function createD(uint arg) public {
D newD = new D(arg);
}
function createAndEndowD(uint arg, uint amount) public payable {
// 创建同时发送ether
D newD = (new D).value(amount)(arg);
}
}
正如在这个例子中所看到的,创建D
的实例时使用.value()
可以直接发送ether,但是不能限制gas量。如果由于堆栈、没有足够的余额或其他问题创建失败,则引发异常。
表达式的计算次序(Order of Evaluation of Expressions)
表达式的计算顺序是不确定的(准确地说是, 顺序表达式树中的子节点表达式计算顺序是不确定的的, 但他们对节点本身,计算表达式顺序当然是确定的)。只保证语句执行顺序,以及布尔表达式的短路规则。
赋值(Assignment)
析构赋值并返回多个值(Destructuring Assignments and Returning Multiple Values)
Solidity内部允许元组类型,即一系列的不同类型的对象的大小在编译时是一个常量。这些元组可以用来同时返回多个值,并且同时将它们分配给多个变量(或左值运算):
contract C {
uint[] data;
function f() returns (uint, bool, uint) {
return (7, true, 2);
}
function g() {
// Declares and assigns the variables. Specifying the type explicitly is not possible. 声明和赋值变量,不必显示定义类型
var (x, b, y) = f();
// Assigns to a pre-existing variable. 赋值给已经存在的变量
(x, y) = (2, 7);
// Common trick to swap values -- does not work for non-value storage types. 交换值的技巧-对非值存储类型不起作用
(x, y) = (y, x);
// Components can be left out (also for variable declarations). 元素可排除(对变量声明也适用)
// If the tuple ends in an empty component, 如果元组是以空元素为结尾
// the rest of the values are discarded. 值的其余部分被丢弃
(data.length,) = f(); // Sets the length to 7 设定长度为7
// The same can be done on the left side. 同样可以在左侧做
(,data[3]) = f(); // Sets data[3] to 2 将data[3] 设为2
// Components can only be left out at the left-hand-side of assignments, with
// one exception: 组件只能在赋值的左边被排除,有一个例外
(x,) = (1,);
// (1,) is the only way to specify a 1-component tuple, because (1) is (1,)是定义一个元素的元组,(1)是等于1
// equivalent to 1.
}
}
数组和结构体的组合(Complications for Arrays and Structs)
对于像数组和结构体这样的非值类型,赋值的语义更复杂些。赋值到一个状态变量总是需要创建一个独立的拷贝。另一方面,对基本类型来说,赋值到一个局部变量需要创建一个独立的拷贝, 即32字节的静态类型。如果结构体或数组(包括bytes
和string
)从状态变量被赋值到一个局部变量, 局部变量则保存原始状态变量的引用。第二次赋值到局部变量不修改状态,只改变引用。赋值到局部变量的成员(或元素)将改变状态。