翻译原文
date:20170617
Solidity是静态类型语言,这意味着每个变量的类型必须在编译的时候指定(或者知晓,查看以下类型推断章节)变量类型。Solidity提供了多种简单的类型,可以通过简单类型组合成复杂的类型。
另外,类型可以与包含操作符的表达式互动。浏览不同的操作符,参看操作符优先级章节。
值类型
以下的类型通常称为值类型,因为这些类型的变量通常传入的是值。例如在给函数传入参数的时候或者在赋值的时候,传入的是值的拷贝。
布尔类型(Booleans)
bool
:可能的值只有true
或者false
.
操作符:
-
!
逻辑非 -
&&
逻辑与 -
||
逻辑或 -
==
等 -
!=
不等
操作符||
和&&
实行的是短路规则。这意味着:在表达式f(x)||g(y)
中,如果f(x)
的值是true
,g(y)
将不会执行。
整形(Intergers)
int
/uint
:多种大小的有符号整数或者无符号整数。关键词uint8
到unint256
每8位为一步(无符号从8位到256位)和int8
到int256
。特别的,uint
和int
是uint256
和int256
的别名。
操作符:
- 比较符:
<=
,<
,==
,!=
,>=
,>
(返回值是bool
类型) - 位操作符:
&
,|
,^
(按位或),~
(按位非) - 算术符:
+
,-
,一元-
,一元+
,*
,/
,%
(求余),**
(?exponentiation),<<
(左移),>>
(右移动)
除法通常是会被截断的(它只是编译成EVM的DIV操作码),但是如果两个操作数都是字面量或者字面表达式.(?Division always truncates (it just is compiled to the DIV opcode of the EVM), but it does not truncate if both operators are literals (or literal expressions).)
被0除或者对0取模会抛出运行时异常。
移位操作符的返回值的类型通常是左边操作数的类型。表达式x<<y
等价于x * 2**y
,x>>y
等价于x / 2**y
。这意味着负数的移位符号位会不变(ps:-2 << 2 = -8)。目前移动负数位将会抛出运行时异常。(ps:2<<-2就会异常)
警告:Solidity负数的向右移位和其他的语言是不一样的。在Solidity中,右移对应的是除法,所以右移负数的结果趋向于0(被截断了)。其他语言中,右移负数的值是除法不截断(趋向于负无穷大)(?In other programming languages the shift right of negative values works like division with rounding down (towards negative infinity).)
地址类(Address)
address
是一个20字节的值(也是以太坊地址的长短)。Address也有成员变量,是所有合约的基础。
操作符:
-
<=
,<
,==
,!=
,>=
和>
地址类的成员变量
-
balance
和transfer
快速浏览,参看地址类相关信息.
可以通过地址的balance
属性查看账号余额,可以通过transfer
将以太币发送给其他账号。
address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
注意:如果x
是合约地址,合约代码(更具体些:是回调函数)将会于transefer
函数一同执行(这是EVM的限制,而且不能被阻止)。如果执行过程中,gas消耗完毕了,或者执行失败,转账就会被退回,并抛出异常。
-
send
发送是于transfer
相对应的。如果执行失败,合约并不马上停止,抛出异常,而是会返回false
.
警告:使用send
会些风险:如果堆栈深度超过1024(该问题通常是调用端产生的),gas不足,会导致转账失败。所以为了使以太币能够安全转账,使用send
就得检查返回结果。或者直接使用transfer
会更好:使用接受者取回钱币的模式。(?use a pattern where the recipient withdraws the money)
-
call
,callcode
和delegatecall
另外,为了给那些没有依附到ABI(Application Binary Interface)的合约提供接口,call
函数可以接受任意多个不同类型的参数。这些参数会扩展到32位,并且串联起来。但是第一个参数是编码为4个字节的。?In this case, it is not padded to allow the use of function signatures here.
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
nameReg.call("register", "MyName");
nameReg.call(bytes4(keccak256("fun(uint256)")), a);
call
函数返回一个布尔量,用来说明调用的函数是正确执行(返回true)还是发生异常(返回false)。所以我们不可能得到真实的数据返回。(为了得到真实数据,我们需要了解编码和大小。?for this we would need to know the encoding and size in advance)
一个类似的方法,函数delegatecall
与call
不同的是,只是用了指定地址的地址码,其他的参数(storage,余额等)用的都是当前合约的。使用delegatecall
的目的是使用保存在其他合约中的库。用户必须确认确认两个合约的storage必须合适才行。在homestead版本之前,只有一个callcode
参数可用,但是不能获取到msg.sender
和msg.value
.
所有这三个参数call
,delegatecall
和callcode
都是非常低级的函数,并且只在迫不得已的情况下使用。因为他们会危害Solidity的类型安全。
以上三个函数都可以使用.gas()
函数,但是delegatecall
并不支持.value()
函数。
注意:所有合约都继承了address的成员变量,所以可以通过this.balance
来访问当前合约的余额
警告:这些函数都是底层函数,并小心使用。任意未知的合约可能是恶意的。如果你调用它,你就把控制权交给了它,它可以反过来再调用你。所以执行完毕之后,你的变量可能已经发生改变了。
固定大小的字节数组
bytes1
,bytes2
,bytes3
,...bytes32
。byte
是bytes1
的别名。
操作:
- 比较:
<=
,<
,==
,!=
,>=
,>
(返回bool
) - 位操作:
&
,|
,^
(按位异或),~
(按位取反),<<
(左移),>>
(右移) - 索引:如果
x
的类型是bytesI
,那么x[k]
中,有0 <= k <= I
,返回第k
位(只读)。
移位操作符的右操作数可以是任意整数类型(但是返回做操作数的类型)。右操作数表示的是移位的个数。如果右操作数是负数,那么会引起运行时异常。
成员变量:
-
.length
返回字节数组的长度(只读)
动态大小的字节数组
bytes
:动态大小的字节数组,参看Arrays,不是一种值类型。
string
:动态大小的UTF-8编码的字符串,参看Arrays,不是一种值类型。
根据经验来说,如果需要用到任意长度的字节数据,那么使用bytes
;如果需要用到任意长度的字符串(UTF-8)数据,那么使用string
.如果你可以确定长度,那么使用bytes1
到bytes32
,因为它们会便宜些。
固定长度的数字
//todo 待完善
地址字面量
通过传递地址检验和测试的十六进制字面量是address
类型,例如0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
。39位到41位长度的十六进制字面量,没有通过检验和测试的会产生警告,并且作为平常的有理数字面量。
有理数整形字面量
整形变量是由0-9组成的一串数字。它们是十进制的。例如69
就是69。Solidity中并没有八进制,以0开头的是不合法的。
十进制小数,以.
隔开,左右必须至少有一个字符,例如1.
,.1
和1.3
。
科学表示法也是支持的,基数可以是小数,但是指数不可以。例如2e10
,-2e10
,2e-10
,2.5e1
。
数字字面量表达式保持精确的值。直到类型变为非字面量类型(例如和非字面量的值一起使用)。这意味着数字字面量的计算不会溢出或者除法的时候不会截断。
例如,(2**800 + 1) - 2**800
的结果是常量1
(类型是uint8
类型)。尽管中间值超过了机器字的大小。(? although intermediate results would not even fit the machine word size)另外,.5*8
的结果是4
(因为表达式中没有用到非整数)。
如果结果不是整数,会选择合适的ufixed
或者fixed
类型来表示它们。大小会计算出足够它们使用的大小。(大概有理数中最差的情况。)
在表达式var x = 1/4;
中,x
会使用ufixed0x8
类型,但是在var x = 1/3
中,将会使用ufixed0x256
类型。因为1/3
在二进制中无法精确表示。所以会是一个精确值。
可以用在整数中的任何操作符,也可以用在数字字面量表达式中,只要操作数是整数。如果有操作数是小数,位操作符是不可以使用的。指数如果是小数,指数运算也是不允许的。(因为会产生物理数)。
注意:Solidity对于每种有理数,都有一个数字字面量类型。整数字面量和有理数字面量都属于数字字面量类型。另外,所有的数字字面量表达式(例如只包含数字字面量和操作符的表达式)都是数字字面量类型。所以数字字面量表达式1+2
和2+1
都是属于相同的数字字面量类型——有理数——3
注意:很多有尽十进制小数,如5.3743
,在二进制表示法中是无尽的。5.3743
的正确类型是ufixed8x248
因为这样可以更加精确的表示该数。如果你想用其他类型,如ufixed(像ufixed128x128
),你必须要指明期望的精度:x + ufixed(5.3743)
警告:在早期版本中,整数字面量的除法会有截断。但是现在会转换为有理数。例如5 / 2
并不等于2
,而是2.5
注意:当数字字面量表达式与非字面量类型一起使用的时候,会转变为非字面量类型。在如下所示的例子中,尽管我们知道赋给b
的值是整形,但是在表达式中间,仍然使用固定大小的浮点类型(非有理数字面量),所以代码不会编译。
uint128 a = 1;
uint128 b = 2.5 + a + 0.5;
字符串字面量
字符串字面量由单引号或者双引号包裹(“foo”或者'bar')。?They do not imply trailing zeroes as in C; "foo"
代表3个字节,不是四个。?As with integer literals, their type can vary, but they are implicitly convertible to bytes1
, ..., bytes32
, if they fit, to bytes
and to string
.
字符串字面量支持换码符(escape characters),例如\n
,\xNN
和\uNNNN
。\xNN
使用十六进制的值,并插入合适的字节数据(?\xNN takes a hex value and inserts the appropriate byte,)。\uNNNN
使用的是unicode码,插入的是UTF-8的字符串。(?while \uNNNN takes a Unicode codepoint and inserts an UTF-8 sequence)
十六进制字面量
十六进制数据以hex
关键字打头,并且通过双引号或者单引号包裹(hex"001122FF"
)。该字面量内容必须是十六进制数据的字符串,值必须是代表这些字符的二进制数据。(?Their content must be a hexadecimal string and their value will be the binary representation of those values.)
十六进制字面量有点像字符串字面量,并且也有一些转换限制。
枚举类型
枚举类型为用户提过了一种在Solidity中自定义类型的方法。他们可以方便的转换为整形,或者从整形转换而来。但是隐式变换是不允许的。显式变换在代码执行的时候检查值的范围,执行失败会产生一个异常。枚举类型至少需要一个数字。
pragma solidity ^0.4.0;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() {
choice = ActionChoices.GoStraight;
}
// 由于枚举类型不是ABI的一部分。getChoice函数会自动的变换为“getChoice() returns (uint8)”。整数类型的大小为能够保存所有enum值的大小。例如:如果你有更大的数值,就会使用uint16等。
function getChoice() returns (ActionChoices) {
return choice;
}
function getDefaultChoice() returns (uint) {
return uint(defaultChoice);
}
}
函数类型
函数类型是函数的类型。函数类型的变量可以通过函数赋值,函数类型的参数可以传递给函数。函数也可以通过函数返回。函数类型分为两种-内部函数和外部函数。
内部函数只能在当前合约的内部使用(更加准确的说,是当前代码单元里,代码单元中包含内部库函数和继承函数),因为他们不能在合约外部使用。调用内部函数就像跳转到函数的标签处,就像在合约内部调用函数。(?Calling an internal function is realized by jumping to its entry label, just like when calling a function of the current contract internally.)
外部函数由地址和函数签名组成。他们可以从外部函数调用中被传递或者返回。(?External functions consist of an address and a function signature and they can be passed via and returned from external function calls.)
函数类型的格式如下:
function (<parameter types>) {internal|external} [constant] [payable] [returns (<return types>)]
与参数类型做对比,返回类型不能为空,如果函数没有返回,那么returns (<return types>)
都应该删除。
默认情况下,函数类型是内部的,所以internal
关键字可以删除。
在当前合约中可以由两种方法调用函数:要么直接通过函数名称,f
或者使用this.f
。第一种是内部函数调用,后一种是外部函数。
如果函数类型变量没有初始化,那么调用就会引发异常。如果你对函数delete
之后,再调用,同样会引发这个错误。
如果外部函数类型在Solidity外部调用,他们会被当作function
类型,该类型将地址和函数识别码一同以bytes24
类型编码。
需要注意的是,当前合约的公有函数既可以用作内部函数,也可以用作外部函数。如果想用作内部函数,那么直接通过f
(函数名称)来调用,或者如果想用作外部函数,通过this.f
来调用。
以下的例子显示了如何使用内部函数类型:
pragma solidity ^0.4.5;
library ArrayUtils {
// 内部函数可以在内部库内部使用,因为他们的执行上下文一致
function map(uint[] memory self, function (uint) returns (uint) f)
internal
returns (uint[] memory r)
{
r = new uint[](self.length);
for (uint i = 0; i < self.length; i++) {
r[i] = f(self[i]);
}
}
function reduce(
uint[] memory self,
function (uint, uint) returns (uint) f
)
internal
returns (uint)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal returns (uint[] memory r) {
r = new uint[](length);
for (uint i = 0; i < r.length; i++) {
r[i] = i;
}
}
}
contract Pyramid {
using ArrayUtils for *;
function pyramid(uint l) returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal returns (uint) {
return x * x;
}
function sum(uint x, uint y) internal returns (uint) {
return x + y;
}
}
如下例子,说明如何使用外部函数:
pragma solidity ^0.4.11;
contract Oracle {
struct Request {
bytes data;
function(bytes memory) external callback;
}
Request[] requests;
event NewRequest(uint);
function query(bytes data, function(bytes memory) external callback) {
requests.push(Request(data, callback));
NewRequest(requests.length - 1);
}
function reply(uint requestID, bytes response) {
// 这里需要确认返回的数据来自受信任的源
requests[requestID].callback(response);
}
}
contract OracleUser {
Oracle constant oracle = Oracle(0x1234567); // 已知合约
function buySomething() {
oracle.query("USD", this.oracleResponse);
}
function oracleResponse(bytes response) {
require(msg.sender == address(oracle));
// 使用数据
}
}
lambda表达式或者行内函数还在计划开发进程中,尚未支持。
引用类型
复杂的类型,例如,某种类型,总是不能很好的适应256位大小,相比较我们之前的类型,需要更加小心处理。因为拷贝它们可能会产生很大的花费。我们必须考虑将它们保存在内存(非连续的)中还是在storage(状态变量保存的地方)中。
数据位置(Data location)
每种复杂的类型,例如数组和结构体,有一种另外的注释,数据位置(data location),关于是否保存在内存中,还是在Storage里。根据上下文,会有个默认的类型。但是我们也可以通过添加storage
或者memory
关键字来改变这个类型。函数的参数(包括返回值)都是保存在memory
中的,本地变量,状态变量默认是storage
。
还有第三种数据位置,“calldata”,这是一个保存函数参数的非连续区,不可修改区。外部函数的参数(不包含返回参数)强制的保存在"calldata"中,和memory差不多。
理解数据位置是非常重要的,因为它改变了赋值的行为:storage和内存间的赋值,以及赋值给转台变量(或者从其他变量赋值)总是会生成一个独立的拷贝。赋值给本地变量只是赋值了一个引用。并且这个引用总是指向状态变量,即使这个状态变量在程序运行期间发生了变化。换句话说,在内存数据之间的赋值不会生成一个数据拷贝。
pragma solidity ^0.4.0;
contract C {
uint[] x; // 该数据保存在storage中
// memoryArray的数据保存在memory中
function f(uint[] memoryArray) {
x = memoryArray; // 没毛病,将整个数组保存到storage中
var y = x; // 没毛病,给指针赋值,y的数据位置为storage
y[7]; // 可以的,返回第8个元素
y.length = 2; // 可以的,通过y来改变x
delete x; // 可以的,清空array,并且也改变了y
// 下面的语句是错误的,本该在storage中生成一个暂时的,没有命名的Array /
// 但是storage已经静态的被分配了:
// y = memoryArray;
// 以下语句也是错误的,因为它会重置指针,但是它并没有实质的指向。
// delete y;
g(x); // 调用g函数,参数为x的引用
h(x); // 调用h函数,并且在memory中生成一个单独的,暂时的,x的拷贝
}
function g(uint[] storage storageArray) internal {}
function h(uint[] memoryArray) {}
}
总结
强制数据位置:
- 外部函数的参数(不包括返回值):calldata
- 状态变量:storage
默认数据位置:
- 函数的参数(包括返回值):内存
- 所有其他本地变量:storage
数组
数组可以在编译的时候确定其长度,或者它们可以动态变化。对于storage数组,元素类型可以是任意的(例如,其他数组,映射或者结构体);对于内存数组,它不能成为映射,而且如果它是公有函数的参数,那么只能是ABI类型。
一个具有k
长度的,元素类型为T
的数组表示为T[k]
,动态长度的数组为T[]
。例如,一个包含有五个动态数组的数组,可以表示为uint[][5]
(注意这里和其他的语言的记法相反)。获取到第三个数组中的第二个元素,可以用x[2][1]
(索引从0开始,这里获取数据和定义是两种相反的方式,如x[2]
shaves off one level in the type from the right)
bytes
和string
是特殊的数组。bytes
就相当于是byte[]
,但是它位于calldata,而且比较紧凑(?but it is packed tightly in calldata)。string
相当于是bytes
,但是目前为止不能修改长度和字符索引。
所以bytes
和byte[]
相比,会优先选择byte[]
,因为更加便宜。
注意:如果你想要获取string的byte表达,可以使用bytes(s).length
/bytes(s)[7] = x;
。你要注意的是,你访问的是UTF-8的低级字节表达,不是单独的一个字符。
数组也可以是public
的,solidity会生成它的getter函数。调用getter函数必须要有一个索引参数。
分配内存数组
在内存中创建一个可变长度的数组可以通过new
关键字来实现。为了与storage数组相抵制,内存数组不能通过改变.length
来改变长度。
pragma solidity ^0.4.0;
contract C {
function f(uint len) {
uint[] memory a = new uint[](7);
bytes memory b = new bytes(len);
// 这里,a.length == 7,b.length == len
a[6] = 8;
}
}
数组字面量/线性数组
数组字面量,是写作表达式一样的数组,它不是马上被赋值到一个变量中。
pragma solidity ^0.4.0;
contract C {
function f() {
g([uint(1), 2, 3]);
}
function g(uint[3] _data) {
// ...
}
}
数组字面量的类型是固定长度的内存数组,元素类型为给出元素的公共类型。[1,2,3]
的类型是uint[8] memory
,因为所有元素的类型是uint8
。正因为如此,在上面例子中,第一个元素就很有必要转换为uint
。需要注意的是,当前固定长度的内存数组不能赋值给可变长度的内存数组,所以如下的代码是不可行的:
program solidity ^0.4.0
contract C {
function f() {
//
uint[] x = [uint(1),3,4];
}
}
未来会把这个限制移除,但是现在有些难题尚未解决,比如如何在ABI中传递数组。
成员变量
length:
数组有一个length
成员变量来指示数组的长度。动态长度数组保存在storage中(不是在内存中),能够动态变化长度。程序不会自动的访问长度以外的元素。内存数组一旦创建,它的长度是固定的(但是是动态的,例如可以在运行的时候决定长度)
push:
动态storage数组和bytes
(不是string
)有一个成员函数,称为push
,可以用来在数组末尾添加元素。该函数返回新的长度。
警告:在外部函数中还不能使用二维数组
**警告:由于EVM的限制,不能在外部函数调用中返回动态内容。在contract C { function f() returns (uint[]) { ... } }
中的函数f
,如果在web3中调用,将会返回数据,但是在solidity中调用,不会返回数据。
现在一个变通的方法是返回一个静态的足够大的数组。
**
pragma solidity ^0.4.0;
contract ArrayContract {
uint[2**20] m_aLotOfIntegers;
// 注意这里定义的不是两个动态数组,而是一个动态数组,其元素为长度为2的数组。
bool[2][] m_pairsOfFlags;
// newPairs会保存在memory中 - 函数参数的默认类型。
function setAllFlagPairs(bool[2][] newPairs) {
// 赋值给storage数组,替换整个数组。
m_pairsOfFlags = newPairs;
}
function setFlagPair(uint index, bool flagA, bool flagB) {
// 越界访问将会产生异常
m_pairsOfFlags[index][0] = flagA;
m_pairsOfFlags[index][1] = flagB;
}
function changeFlagArraySize(uint newSize) {
// 如果数组的长度变小了,那么多余的元素将会被删除
m_pairsOfFlags.length = newSize;
}
function clear() {
// 清空数组
delete m_pairsOfFlags;
delete m_aLotOfIntegers;
// 清空数组
m_pairsOfFlags.length = 0;
}
bytes m_byteData;
function byteArrays(bytes data) {
// byte 数组 ("bytes") 是不同的,因为它们保存时没有保留空余的空间
// 但是以"uint8[]"的类型来对待
// ?byte arrays ("bytes") are different as they are stored without padding,
// ?but can be treated identical to "uint8[]"
m_byteData = data;
m_byteData.length += 7;
m_byteData[3] = 8;
delete m_byteData[2];
}
function addFlag(bool[2] flag) returns (uint) {
return m_pairsOfFlags.push(flag);
}
function createMemoryArray(uint size) returns (bytes) {
// 动态的内存数组通过new关键字生成。
uint[2][] memory arrayOfPairs = new uint[2][](size);
// 生成一个动态byte数组
bytes memory b = new bytes(200);
for (uint i = 0; i < b.length; i++)
b[i] = byte(i);
return b;
}
}
结构体
Solidity提供了定义类型的方法:通过结构体。例子如下所示:
pragma solidity ^0.4.11;
contract CrowdFunding {
// 定义新的类型,包含两个数据字段
struct Funder {
address addr;
uint amount;
}
struct Campaign {
address beneficiary;
uint fundingGoal;
uint numFunders;
uint amount;
mapping (uint => Funder) funders;
}
uint numCampaigns;
mapping (uint => Campaign) campaigns;
function newCampaign(address beneficiary, uint goal) returns (uint campaignID) {
campaignID = numCampaigns++; // campaignID 是返回值变量
// 生成一个新的结构体数据,并保存在storage中。 We leave out the mapping type.
campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}
function contribute(uint campaignID) payable {
Campaign c = campaigns[campaignID];
// 生成一个新的内存数据。通过给定的值进行初始化,并拷贝到storage中。
// 你也可以通过 Funder(msg.sender, msg.value) 进行初始化.
c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
c.amount += msg.value;
}
function checkGoalReached(uint campaignID) returns (bool reached) {
Campaign c = campaigns[campaignID];
if (c.amount < c.fundingGoal)
return false;
uint amount = c.amount;
c.amount = 0;
c.beneficiary.transfer(amount);
return true;
}
}
合约并不提供众筹合约的所有函数,但是它包含了理解结构体的基本概念。结构体类型可以用在数组和映射中,并且它本身可以有映射和数组。
结构体类型包含本身的类型的字段是不可以的,尽管这个结构体是映射的值类型(?although the struct itself can be the value type of a mapping member.)。这个限制是很有必要的,因为结构体的大小应该有限。
要注意在所有函数中,结构体类型是怎样赋值给本地变量的(默认是storage数据位置)。它并没有复制结构体,而是保存了他的引用,所以通过本地变量,直接给所引用的结构体成员赋值也会改变数据。
当然,你也可以直接访问结构体的字段,不需要将它赋值给一个本地变量,例如campaigns[campaignID].amount = 0
.
映射
映射类型通过这样的形式定义:mapping(_KeyType => _ValueType)
。这里_KeyType
可以是除了映射,动态长度的数组,合约,枚举和结构体之外的任何类型。_ValueType
可以是所以的类型,包括映射。
映射可以看成是一个hash表,这个表的所有可能的键初始化为byte表示全为0的值:这个是默认值.相似的:键数据不是存储在映射里,而是键的keccak256
hash,用来查找对应的值。
所以映射没有长度或者键值的概念,也不是集合。(?Because of this, mappings do not have a length or a concept of a key or value being “set”.)
映射只允许状态变量(或者在内部函数中的storage引用类型)。
可以设置映射为public
,solidity会自动生成getter。_KeyType
是getter函数必须的参数,返回值为_ValueType
。
_ValueType
值同样可以是映射,会递归产生getter函数。(?The _ValueType can be a mapping too. The getter will have one parameter for each _KeyType, recursively.)
pragma solidity ^0.4.0;
contract MappingExample {
mapping(address => uint) public balances;
function update(uint newBalance) {
balances[msg.sender] = newBalance;
}
}
contract MappingUser {
function f() returns (uint) {
return MappingExample(<address>).balances(this);
}
}
注意:映射不可以遍历,但是可以在此基础上生成新的结构。例如,可遍历映射
使用LValues的操作符
如果a
是一个LValue(例如,一个变量或者是可以被赋值something),那么可以使用以下的操作符。
a += e
是和a = a + e
一样的。操作符-=
,*=
,/=
,%=
,|=
,&=
和^=
也是对应的。a++
和a--
分别对应于a += 1
或a -= 1
。但是表达式本身还是保持a
的原始值。相反的,--a
和++a
同样是对a加上1,但是返回了改变后的值。
删除
delete a
将a类型的初始值赋给a。例如,对于整形,这个相当于是a = 0
。它也可以用于数组,将一个长度为0的动态长度数组赋值给变量,或者是一个与之具有相通长度的静态数组,但是所有元素都被初始化的数组。对于结构体,将初始化所有的字段。
delete
对整个映射是无效的(因为key可以是任何值,而且通常我们都不知道)。所以,如果是delete结构体,它将会重置所有非映射类型的字段,并且将递归字段,直到遇上映射。
delete a
其实是对a重新赋值,例如重新赋值为一个对象。
pragma solidity ^0.4.0;
contract DeleteExample {
uint data;
uint[] dataArray;
function f() {
uint x = data;
delete x; // 将x设置为0,并不影响其他数据
delete data; //将data设置,不会影响到x,因为x保存的是值的拷贝
uint[] y = dataArray;
delete dataArray; // 将dataArray.length设置为0, 但是因为uint[]是一个复杂的对象,所以y的值也被影响,因为y是storage对象的引用。
// 另一方面: "delete y"是无效的,因为引用一个storage对象必须要有一个storage对象。
}
}
初级类型的转换
隐式的转换
如果操作符被用于不同的类型时,编译器会尝试对一个操作数进行隐式的转换成另一个操作数的类型(赋值的时候也是一样的)。一般情况下,如果语意上有意义,并且没有信息损失,值类型之间的隐式转换是可行的。uint8
可以转换为uint16
,int128
可以转换为int256
,但是int8
不能转换为uint256
(因为uint256
不能存储负数)。另外,无符号整形可以转换为同样大小的或者更大的bytes,但是反过来不行。任何可以转换为uint160
的类型可以转换为address
.
显式的转换
如果编译器不允许隐式转换,但是你知道你要怎么做扽时候,可以用显式转换。需要注意的是,显式转换可能会导致有不期望的结果,所以要充分测试,并且保证结果是你期望的。下面的例子说明了从负数的int8
转换为uint
类型:
int8 y = -3;
uint x = uint(y);
这个代码端最后,x
的值变为0xfffff..fd
(64个十六进制字符),值为-3。
如果将类型显式转换为更小的类型,那么高位会被裁切掉:
uint32 a = 0x12345678;
uint16 b = uint16(a); // 现在b的值为0x5678
类型推断
简便起见,并不是总是需要为变量显式的指定类型,编译器会自动的对第一个表达式的类型进行引用,并赋值:
uint24 x = 0x123;
var y = x;
这里,y
的类型将会是uint24
。函数参数或者返回值不能用var
.
警告:类型只能从第一个表达式推断,所以下面的例子是个死循环。因为i
的类型为uint8
,该类型的所有值都是小于2000
的。for (var i = 0; i < 2000; i++) { ... }