solidity语法2——类型
值类型
以下类型也称为值类型,因为这些类型的变量将始终按值来传递。 也就是说,当这些变量被用作函数参数或者用在赋值语句中时,总会进行值拷贝。
布尔类型
bool
:可能的取值为字面常数值 true
和 false
。
运算符:
!
(逻辑非)&&
(逻辑与, "and" )||
(逻辑或, "or" )==
(等于)!=
(不等于)
运算符 ||
和 &&
都遵循同样的短路( short-circuiting )规则。就是说在表达式 f(x) || g(y)
中, 如果 f(x)
的值为 true
,那么 g(y)
就不会被执行,即使会出现一些副作用。
整型
int
/ uint
:分别表示有符号和无符号的不同位数的整型变量。 支持关键字 uint8
到 uint256
(无符号,从 8 位到 256 位)以及 int8
到 int256
,以 8
位为步长递增。 uint
和 int
分别是 uint256
和 int256
的别名。
定长浮点型
警告
Solidity 还没有完全支持定长浮点型。可以声明定长浮点型的变量,但不能给它们赋值或把它们赋值给其他变量。。
fixed
/ ufixed
:表示各种大小的有符号和无符号的定长浮点型。 在关键字 ufixedMxN
和 fixedMxN
中,M
表示该类型占用的位数,N
表示可用的小数位数。 M
必须能整除 8,即 8 到 256 位。 N
则可以是从 0 到 80 之间的任意数。 ufixed
和 fixed
分别是 ufixed128x19
和 fixed128x19
的别名。
地址类型
address
:地址类型存储一个 20 字节的值(以太坊地址的大小)。 地址类型也有成员变量,并作为所有合约的基础。
运算符:
<=
,<
,==
,!=
,>=
和>
注解
从 0.5.0 版本开始,合约不会从地址类型派生,但仍然可以显式地转换成地址类型。
地址类型成员变量
balance
和transfer
快速参考,请见 地址相关。
可以使用 balance
属性来查询一个地址的余额, 也可以使用 transfer
函数向一个地址发送 以太币Ether (以 wei 为单位):
address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
send
send
是 transfer
的低级版本。如果执行失败,当前的合约不会因为异常而终止,但 send
会返回 false
。
call
,callcode
和delegatecall
此外,为了与不符合 应用二进制接口Application Binary Interface(ABI) 的合约交互,于是就有了可以接受任意类型任意数量参数的 call
函数。 这些参数会被打包到以 32 字节为单位的连续区域中存放。 其中一个例外是当第一个参数被编码成正好 4 个字节的情况。 在这种情况下,这个参数后边不会填充后续参数编码,以允许使用函数签名。
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
nameReg.call("register", "MyName");
nameReg.call(bytes4(keccak256("fun(uint256)")), a);
call
返回的布尔值表明了被调用的函数已经执行完毕(true
)或者引发了一个 EVM 异常(false
)。 无法访问返回的真实数据(为此我们需要事先知道编码和大小)。
可以使用 .gas()
修饰器modifier 调整提供的 gas 数量
namReg.call.gas(1000000)("register", "MyName");
类似地,也能控制提供的 以太币Ether 的值
nameReg.call.value(1 ether)("register", "MyName");
最后一点,这些 修饰器modifier 可以联合使用。每个修改器出现的顺序不重要
nameReg.call.gas(1000000).value(1 ether)("register", "MyName");
定长字节数组
关键字有:bytes1
, bytes2
, bytes3
, ..., bytes32
。byte
是 bytes1
的别名。
运算符:
- 比较运算符:
<=
,<
,==
,!=
,>=
,>
(返回布尔型) - 位运算符:
&
,|
,^
(按位异或),~
(按位取反),<<
(左移位),>>
(右移位) - 索引访问:如果
x
是bytesI
类型,那么x[k]
(其中0 <= k < I
)返回第k
个字节(只读)。
该类型可以和作为右操作数的任何整数类型进行移位运算(但返回结果的类型和左操作数类型相同),右操作数表示需要移动的位数。 进行负数位移运算会引发运行时异常。
成员变量:
.length
表示这个字节数组的长度(只读).
地址字面常数(Address Literals)
比如像 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
这样的通过了地址校验和测试的十六进制字面常数属于 address
类型。 长度在 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
类型),尽管计算的中间结果已经超过了 以太坊虚拟机Ethereum Virtual Machine(EVM) 的机器字长度。 此外, .5 * 8
的结果是整型 4
(尽管有非整型参与了计算)。
只要操作数是整型,任意整型支持的运算符都可以被运用在数值字面常数表达式中。 如果两个中的任一个数是小数,则不允许进行位运算。如果指数是小数的话,也不支持幂运算(因为这样可能会得到一个无理数)。
字符串字面常数
字符串字面常数是指由双引号或单引号引起来的字符串("foo"
或者 'bar'
)。 不像在 C 语言中那样带有结束符;"foo"
相当于 3 个字节而不是 4 个。 和整数字面常数一样,字符串字面常数的类型也可以发生改变,但它们可以隐式地转换成 bytes1
,……,bytes32
,如果合适的话,还可以转换成 bytes
以及 string
。
字符串字面常数支持转义字符,例如 \n
,\xNN
和 \uNNNN
。\xNN
表示一个 16 进制值,最终转换成合适的字节, 而 \uNNNN
表示 Unicode 编码值,最终会转换为 UTF-8 的序列。
十六进制字面常数
十六进制字面常数以关键字 hex
打头,后面紧跟着用单引号或双引号引起来的字符串(例如,hex"001122FF"
)。 字符串的内容必须是一个十六进制的字符串,它们的值将使用二进制表示。
十六进制字面常数跟字符串字面常数很类似,具有相同的转换规则。
枚举类型
函数类型
函数类型是一种表示函数的类型。可以将一个函数赋值给另一个函数类型的变量,也可以将一个函数作为参数进行传递,还能在函数调用中返回函数类型变量。 函数类型有两类:- 内部(internal) 函数和 外部(external) 函数:
内部函数只能在当前合约内被调用(更具体来说,在当前代码块内,包括内部库函数和继承的函数中),因为它们不能在当前合约上下文的外部被执行。 调用一个内部函数是通过跳转到它的入口标签来实现的,就像在当前合约的内部调用一个函数。
外部函数由一个地址和一个函数签名组成,可以通过外部函数调用传递或者返回。
函数类型表示成如下的形式
function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]
与参数类型相反,返回类型不能为空 —— 如果函数类型不需要返回,则需要删除整个 returns (<return types>)
部分。
函数类型默认是内部函数,因此不需要声明 internal
关键字。 与此相反的是,合约中的函数本身默认是 public 的,只有当它被当做类型名称时,默认才是内部函数。
有两种方法可以访问当前合约中的函数:一种是直接使用它的名字,f
,另一种是使用 this.f
。 前者适用于内部函数,后者适用于外部函数。
批注:这里与js不一样。
如果当函数类型的变量还没有初始化时就调用它的话会引发一个异常。 如果在一个函数被 delete
之后调用它也会发生相同的情况。
如果外部函数类型在 Solidity 的上下文环境以外的地方使用,它们会被视为 function
类型。 该类型将函数地址紧跟其函数标识一起编码为一个 bytes24
类型。。
请注意,当前合约的 public 函数既可以被当作内部函数也可以被当作外部函数使用。 如果想将一个函数当作内部函数使用,就用 f
调用,如果想将其当作外部函数,使用 this.f
。
除此之外,public(或 external)函数也有一个特殊的成员变量称作 selector
,可以返回 ABI 函数选择器:
pragma solidity ^0.4.16;
contract Selector {
function f() public view returns (bytes4) {
return this.f.selector;
}
}
如果使用内部函数类型的例子:
pragma solidity ^0.4.16;
library ArrayUtils {
// 内部函数可以在内部库函数中使用,
// 因为它们会成为同一代码上下文的一部分
function map(uint[] memory self, function (uint) pure returns (uint) f)
internal
pure
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) pure returns (uint) f
)
internal
pure
returns (uint r)
{
r = self[0];
for (uint i = 1; i < self.length; i++) {
r = f(r, self[i]);
}
}
function range(uint length) internal pure 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) public pure returns (uint) {
return ArrayUtils.range(l).map(square).reduce(sum);
}
function square(uint x) internal pure returns (uint)