本文共 16012 字,大约阅读时间需要 53 分钟。
Solidity是一种静态类型的语言,这意味着每个变量(州和地方)的类型需要被指定的(或至少已知的 - 见下文型扣)在编译时。 Solidity提供了几种可以组合形成复杂类型的基本类型。
另外,类型可以在含有运算符的表达式与彼此交互。 对于操作的快速参考,请参阅运算符的优先顺序。
以下类型也称为值类型,因为这些类型的变量将始终按值传递,即当它们用作函数参数或分配时,它们始终被复制。
bool
:可能的值是常量true
和false
。
操作:
!
(逻辑否定)&&
(逻辑连接,“和”)||
(逻辑分离,“或”)==
(相等)!=
(不等式)运算符||
和&&
应用常见的短路规则。 这意味着在表达式f(x) || g(y)
,如果f(x)
评估为真,即使可能有副作用,也不会评估g(y)
。
int/uint
:各种大小的有符号和无符号整数。 关键字uint8
到uint256
的步幅是8(无符号的8到256位)和int8
到int256
。 uint
和int
分别是uint256
和int256
的别名。
操作:
<=
,<
,==
,!=
,>=
,>
(评估为bool)&
,|
,^
(按位异或),〜
(按位取反)+
, -
,一元 -
,一元 +
,*
,/
,%
(其余),**
(幂),<<
(左移),>>
(右移)除法总是截断(它只是编译为EVM的DIV操作码),但如果两个运算符都是文字(或文字表达式),则它不会截断。
除零和模量具有零引发运行时异常。
移位操作的结果是左操作数的类型。 表达式x << y
等价于x * 2 ** y
,x >> y
等价于x / 2 ** y
。 这意味着移位负数符号延伸。 移位操作使用负数会引发运行时异常。
警告:
由符号整数类型的负值的右移位所产生的结果是从那些其他的编程语言产生的不同。 在“Solidity”中,将右图转换为除法,所以偏移的负值将向零舍入(截断)。 在其他编程语言中,负值的转移权就像划分为舍入(朝向负无穷大)。
地址:保存一个20字节值(Ethereum地址的大小)。 地址类型也有成员,并作为所有合约的基础。
操作
<=
, <
, ==
, !=
, >=
和 >
balance
和 transfer
有关快速参考,请参阅。
可以使用财产余额查询地址的余额,并使用传输函数将以太网(以wei为单位)发送到地址:
address x = 0x123;address myAddress = this;if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
如果x是合同地址,则其代码(更具体地说:其回退函数(如果存在))将与传送调用一起执行(这是EVM的限制,不能防止)。 如果执行任务无法运行,或以任何方式失败,则以太网传输将被恢复,并且当前的合同将以异常方式停止。
send
send是低级对等的转账。 如果执行失败,当前合同将不会以异常方式停止,但发送将返回false。
call
, callcode
and delegatecall
此外,为了与不遵守ABI的合同进行接口,提供了任意数量的任意类型参数的函数调用。 这些参数被填充到32字节并连接。 一个例外是第一个参数被编码到正好四个字节的情况。 在这种情况下,这里没有填充以允许使用功能签名。
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;nameReg.call("register", "MyName");nameReg.call(bytes4(keccak256("fun(uint256)")), a);
call
返回一个布尔值,指示调用的函数是否终止(true
)或引起EVM异常(false
)。 不可能访问返回的实际数据(为此,我们需要提前知道编码和大小)。
以类似的方式,可以使用函数delegatecall
:区别在于仅使用给定地址的代码,所有其他方面(存储,余额,…)均取自当前的合同。 委托人的目的是使用存储在另一个合同中的库代码。 用户必须确保两个合同中的存储布局适合委托使用。 在homestead之前,只有一个名为callcode
的有限变体才可用,不能访问原始的msg.sender
和msg.value
值。
所有三个功能call
, callcode
和delegatecall
都是非常低级的功能,只能作为最后的手段,因为它们打破了Solidity的类型安全性。
.gas()
选项可用于所有三种方法,而delegatecall
不支持.value()
选项。
所有的合约都继承地址的成员,所以它是可以查询使用this.balance
当前合约的余额。
不鼓励使用callcode
,将来会被删除。
所有这些功能都是低级别的功能,应小心使用。 具体而言,任何未知的合约可能是恶意的,如果你调用它,你交出控制权,以该合同可能又回调到你的合约,所以要改变你的状态变量,当调用返回准备。
bytes1
,bytes2
,bytes3
,…,bytes32
。 byte
是bytes1
的别名。
操作:
<=
,<
,==
,!=
,>=
,>
(评估为bool)&
,|
,^
(按位异或),〜
(逐位否定),<<
(左移),>>
(右移)bytesI
,则0 <= k <I
的x [k]
返回第k
个字节(只读)。移位操作符以任何整数类型作为右操作数(但将返回左操作数的类型),表示要移位的位数。 移动负数将导致运行时异常。
成员:
.length
产生字节数组的固定长度(只读)。
bytes:
动态大小的字节数组,请参见数组。 不是价值型! string: 动态尺寸的UTF-8编码字符串,请参见数组。 不是价值型!作为一个经验法则,用于任意长度的原始字节数据和字符串的任意长度的字符串(UTF-8)数据字节。 如果可以将长度限制在一定数量的字节,那么请务必使用bytes1至bytes32之一,因为它们便宜得多。
固体点数目前尚未完全支持。 它们可以被声明,但不能被分配到或来自。
通过地址校验和测试的十六进制文字,例如0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF是地址类型。 长度在39到41位之间的十六进制文字不通过校验和测试会产生警告,并被视为常规有理数字文字。
整数文字由0-9范围内的数字序列组成。 它们被解释为小数。 例如,69意味着六十九。 八进制文字不存在于粘性和前导零无效。
小数部分文字由一个.
形成。 在一侧至少有一个数字。 例子包括1.
,.1
和1.3
。
也支持科学符号,其中基数可以分数,而指数不能。 实例包括2e10
,-2e10
,2e-10
,2.5e1
。
数字字面表达式保持任意精度,直到它们转换为非文字类型(即通过将它们与非文字表达式一起使用)。 这意味着计算不会溢出,而在数字文字表达式中,分割不会截断。
例如,(2**800 + 1) - 2**800
导致常数1
(类型uint8
),尽管中间结果甚至不适合机器字大小。 此外,.5 * 8
导致整数4
(尽管在其间使用非整数)。
只要操作数为整数,任何可应用于整数的运算符也可应用于数字文字表达式。 如果两者中的任何一个是分数的,则不允许位操作,如果指数是分数(因为可能导致非有理数),则不允许求幂)。
Solidity有一些文本类型为每个有理数。 整数文字和理性数字字面值属于数字文字类型。 此外,所有数字字面值表达式(即仅包含数字文字和运算符的表达式)都属于数字字面值类型。 所以数字文字表达式1 + 2
和2 + 1
都属于理性数字3的相同数字字面值类型。
用于在早期版本中截断的整数文字的分割,但现在将转换为有理数,即5/2
不等于2
,但为2.5
因为它们与非文字表达式中使用的数字面表达式尽快转换成非文字类型。 尽管我们知道,在下面的例子中分配给b中的表达式的值的计算结果为一个整数,但局部表达2.5 + a
不类型检查所以代码不编译.
uint128 a = 1;uint128 b = 2.5 + a + 0.5;
字符串文字用双引号或单引号("foo"
或'bar'
)编写。 它们并不意味着像C中那样为零; "foo"
代表三个不是四个字节。 与整数文字一样,它们的类型可以有所不同,但它们可以隐式转换为bytes1
,…,bytes32
(如果它们适合)到字节和字符串。
字符串文字支持转义字符,例如\n
,\xNN
和\uNNNN
。 \xNN
取十六进制值并插入相应的字节,而\ uNNNN
则使用Unicode代码点并插入UTF-8序列。
Hexademical Literals以关键字hex为前缀,以双引号或单引号(hex"001122FF"
)括起来。 它们的内容必须是十六进制字符串,它们的值将是这些值的二进制表示。
Hexademical Literals像字符串文字,具有相同的可转换性限制。
枚举是在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的一部分,所以对于Solidity之外的所有事务,“getChoice”的签名将自动更改为“getChoice()return(uint8)”)。 所使用的整数类型足够大以容纳所有枚举值,即如果您有更多值,则将使用`uint16`等等。 function getChoice() returns (ActionChoices) { return choice; } function getDefaultChoice() returns (uint) { return uint(defaultChoice); }}
函数类型是函数的类型。 函数类型的变量可以从函数分配,函数类型的函数参数可以用于将函数传递给函数并从函数调用返回函数。 功能类型有两种功能 - 内部和外部功能:
内部函数只能在当前合约内部更详细地调用(更具体地说是在当前代码单元内部,也包括内部函数库和继承函数),因为它们不能在当前契约的上下文之外执行。 调用内部函数是通过跳转到其入口标签来实现的,就像在内部调用当前合同的函数一样。
外部功能由一个地址和一个函数签名,他们可以通过传递和外部函数调用返回。
功能类型记为如下:
function () { internal|external} [pure|constant|view|payable] [returns ( )]
function ( <参数类型> ) { internal|external} [pure|constant|view|payable] [returns ( <返回类型> )] 返回类型> 参数类型>
与参数类型相反,返回类型不能为空 - 如果函数类型不返回任何东西,则returns (<return types>)
部分必须被省略。
缺省情况下,函数类型为internal
,内部关键字可以省略。 与此相反,合同函数本身默认为公用,只能作为类型的名称中使用时,默认为内部。
在当前合约中有两种访问函数的方法:直接以其名称,f
或使用this.f
. 前者将导致内部功能,后者在外部功能中。
如果函数类型变量未初始化,则调用它将导致异常。 如果在使用delete
之后调用函数也会发生这种情况。
如果在Solidity的上下文中使用外部函数类型,则将它们视为函数类型,它以单个字节24类型将该地址后跟功能标识符编码在一起。
请注意,现行合约的公共函数既可以作为内部函数也可以用作外部函数。 要使用f
作为内部函数,只需使用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) { 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); //r为定义一个固定长度的数组 for (uint i = 0; i < r.length; i++) { r[i] = i; // 以此写入0 1 2 .. } }}//金字塔contract Pyramid { using ArrayUtils for *; //参数,整形;返回整形 function pyramid(uint l) returns (uint) { //得到一个数组形如 arr = [0,1,2,3,4] ,各项的平方后返回新的数组,数组的各个元素相加求和 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); //通过callback方法执行 }}//定义合约contract OracleUser { Oracle constant oracle = Oracle(0x1234567); // 已知的合约 //调用 function buySomething() { //调用外部合约的方法,使用的第二个参数为当前合约的方法 oracle.query("USD", this.oracleResponse); } //参数为字节 function oracleResponse(bytes response) { require(msg.sender == address(oracle)); //如果消息发送的人和。。。相等 // Use the data }}
Lambda或内联函数已计划但尚未支持。
复杂类型,即不总是适合256位的类型,必须比我们已经看到的值类型更仔细地处理。 由于复制它们可能相当昂贵,我们必须考虑我们是否希望将它们存储在内存中(不是持久存储的)或存储(保存状态变量的位置)。
每个复杂类型,即数组和结构体,都有一个额外的注释,即“数据位置”,关于它是存储在内存中还是存储器中。 根据上下文,总是默认,但可以通过将存储或内存附加到类型来覆盖。 函数参数(包括返回参数)默认值是内存,局部变量默认是存储和位置被迫储存状态变量(显然)。
还有第三个数据位置calldata,它是一个不可修改的非持久性区域,其中存储了函数参数。 外部函数的函数参数(不返回参数)被强制调用数据,并且主要表现为内存。
因为他们改变了分配的行为数据的位置是很重要的:存储和内存,并以一个状态变量(甚至是从其他状态变量)之间的分配总是创建一个独立的副本。 本地存储变量的分配只能分配一个引用,而且该引用总是指向状态变量,即使后者在此同时被更改。 另一方面,从存储的存储引用类型到另一存储器引用类型的分配不会创建副本。
pragma solidity ^0.4.0;contract C { uint[] x; // x的数据位置是存储 // memoryArray的数据位置是内存 function f(uint[] memoryArray) { x = memoryArray; // 工作,将整个数组复制到存储 var y = x; // 工作,分配一个指针,y的数据位置是存储 y[7]; // fine,返回第8个元素 y.length = 2; // fine, 通过y修改x delete x; // fine, 清除数组,修改y // 以下不工作; 它将需要在存储中创建一个新的临时未命名数组,但存储是“静态”分配的: // y = memoryArray; //这也不行,因为它会“重置”指针,但没有明确的位置,它可以指向。 删除y; g(x); // 调用 g,移交到x的引用 h(x); // 调用 h ,在内存中创建一个独立的临时副本 } function g(uint[] storage storageArray) internal { } function h(uint[] memoryArray) { }}
总结:
强制数据位置:
- 外部函数的参数(不返回):calldata - 状态变量:存储默认数据位置:
- 函数的参数(也返回):内存 - 所有其他局部变量:存储数组可以具有编译时固定大小,也可以是动态的。 对于存储阵列,元素类型可以是任意的(也就是其他数组,映射或结构)。 对于存储器阵列,它不能是映射,如果它是公共可见函数的参数,则必须是ABI类型。
固定大小k和元素类型T的数组被写为T [k]
,动态大小的数组为T []
。 例如,uint的5个动态数组的数组是uint [] [5]
(注意,与其他语言相比,符号是相反的)。 要访问第三个动态数组中的第二个uint,您使用x [2] [1]
(索引为零,访问以相反的方式工作,即x [2]将类型中的一个级别 正确的)。
类型字节和字符串的变量是特殊的数组。 一个字节类似于byte [],但是它被紧密地打包在calldata中。 字符串等于字节,但不允许长度或索引访问(现在)。
因此,字节应该始终优先于byte [],因为它成本更低。
如果要访问字符串s
的字节表示,请使用bytes(s).length
/ bytes(s)[7] = 'x'
;. 请记住,您正在访问UTF-8表示的低级字节,而不是单个字符!
可以将数组标记为public,并且Solidity创建一个getter。 数字索引将成为必需的参数。
在内存中创建可变长度的数组可以使用new
关键字来完成。 与存储阵列相反,不能通过分配给.length
成员来调整存储器阵列的大小。
pragma solidity ^0.4.0;contract C { function f(uint len) { uint[] memory a = new uint[](7); //第7个 bytes memory b = new bytes(len); // Here we have a.length == 7 and 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]的类型是uint8 [3]内存,因为这些常量的类型是uint8。 因此,有必要将上述示例中的第一个元素转换为uint。 请注意,目前,固定大小的存储阵列不能分配给动态大小的存储器阵列,即不可能实现以下功能:
// 这不会编译。pragma solidity ^0.4.0;contract C { function f() { //下一行创建一个类型错误,因为uint [3]内存 //无法转换为uint []内存。 uint[] x = [uint(1), 3, 4]; }}
计划在将来删除这个限制,但由于在ABI中如何传递数组,因此目前还会产生一些并发症。
length:
数组有一个长度成员来保存它们的元素数量。 可以通过更改.length成员来调整动态数组的大小(不在内存中)。 尝试访问当前长度之外的元素时,不会自动发生。 一旦创建了存储器阵列的大小是固定的(但是动态的,即它可以依赖于运行时参数)。push:
动态存储阵列和字节(不是字符串)具有称为push的成员函数,可用于在数组的末尾追加元素。 该函数返回新的长度。在外部函数中不可能使用阵列数组。
由于EVM的限制,不可能从外部函数调用返回动态内容。 contract C { function f() returns (uint[]) { ... }
将返回一个如果从web3.js调用的东西,但是如果从Solidity调用则返回。
现在唯一的解决方法是使用大型静态大小的数组。
pragma solidity ^0.4.0;contract ArrayContract { uint[2**20] m_aLotOfIntegers; // Note that the following is not a pair of dynamic arrays but a // dynamic array of pairs (i.e. of fixed size arrays of length two). // 注意,以下不是一对动态数组,但对一个动态阵列(长度为两个的固定大小阵列即)。 bool[2][] m_pairsOfFlags; // newPairs存储在内存中 - 函数参数的默认值 function setAllFlagPairs(bool[2][] newPairs) { // 分配到一个存储阵列替换整个阵列 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) { // 字节数组(“bytes”)是不同的,因为它们在没有填充的情况下被存储,但可以被视为与“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); // 创建一个动态字节数组: 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 是返回值 // 重新建立新的结构并保存在存储中。 我们忽略了映射类型。 campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); } function contribute(uint campaignID) payable { Campaign storage c = campaigns[campaignID]; // 创建一个新的临时内存结构,用给定值初始化并将其复制到存储。 //你也可以通过 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 storage c = campaigns[campaignID]; if (c.amount < c.fundingGoal) return false; uint amount = c.amount; c.amount = 0; c.beneficiary.transfer(amount); return true; }}
合约没有提供众筹合约的全部功能,但它包含理解结构所必需的基本概念。 结构类型可以在映射和数组中使用,它们本身可以包含映射和数组。
结构体不可能包含自己类型的成员,尽管struct本身可以是映射成员的值类型。 这个限制是必要的,因为结构的大小必须是有限的。
请注意,在所有函数中,将struct类型分配给局部变量(默认存储数据位置)。 这不会复制结构体,而只存储一个引用,以便对局部变量的成员的赋值实际写入状态。
当然,您也可以直接访问结构的成员,而不必将其分配给本地变量,如广告系列[campaignID] .amount = 0。
映射类型被声明为mapping(_KeyType => _ValueType)
。 这里_KeyType
可以是几乎任何类型,除了映射,动态大小的数组,契约,枚举和结构体。 _ValueType
实际上可以是任何类型,包括映射。
映射可以看作是虚拟初始化的,使得每个可能的键都存在,并被映射到一个值,其字节表示全为零:一个类型的默认值。 相似之处在此结束,但是:密钥数据实际上并不存储在映射中,只有其keccak256哈希用于查找该值。
因此,映射没有长度或概念的键或值被设置。
映射只允许用于状态变量(或内部函数中的存储引用类型)。
有可能public
标记映射,并具有Solidity创建一个getter。 _KeyType将成为getter的必需参数,它将返回_ValueType。
_ValueType也可以是映射。 getter将递归地为每个_KeyType设置一个参数。
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) { MappingExample m = new MappingExample(); m.update(100); return m.balances(this); }}
如果a
是一个LValue(即,可以分配给一个变量或东西),下面的运算符可作为简写:
a += e
等价于a = a + e
。 相应地定义运算符 -=
,*=
,/=
,%=
,a |=
,&=
和^=
。 a++
和a--
等价于a += 1
/ a -= 1
,但表达式本身仍然具有以前的值a
。 相反,--a
和++ a
对于a而言具有相同的效果,但在更改后返回值。
delete a
将类型的初始值分配给a。即 对于整数,它等效于a = 0,但它也可以用于数组,其中分配长度为零的动态数组或与所有元素重置的长度相同的静态数组。 对于结构体,它会为所有成员重新分配一个结构体。
删除对整个映射没有影响(因为映射的关键字可能是任意的,并且通常是未知的)。 所以,如果你删除一个结构体,它将会重置所有不是映射的成员,也可以递归到成员中,除非是映射。 但是,可以删除单个键及其映射到的内容。
pragma solidity ^0.4.0;contract DeleteExample { uint data; uint[] dataArray; function f() { uint x = data; delete x; // 将x设置为0,不影响数据 delete data; // 将数据设置为0,不影响仍然保存副本的x uint[] y = dataArray; delete dataArray; // 这将dataArray.length设置为零,但是由于uint []是一个复杂对象,所以受影响也是存储对象的别名。另一方面:“delete y”无效,因为引用存储的本地变量的赋值 对象只能由现有的存储对象进行。 }}
如果运算符应用于不同类型,编译器将尝试将其中一个操作数隐式转换为其他类型(对于赋值也是如此)。 一般来说,值类型之间的隐式转换是有可能的,如果它在语义上有意义且没有信息丢失:uint8可以转换为uint16和int128到int256,但int8不能转换为uint256(因为uint256不能保持为-1)。 此外,无符号整数可以转换为相同或更大尺寸的字节,但反之亦然。 任何可以转换为uint160的类型也可以转换为地址。
如果编译器不允许隐式转换,但是你知道你正在做什么,那么有时可以使用显式类型转换。 请注意,这可能会给您一些意想不到的行为,所以一定要测试,以确保结果是你想要的! 以下示例将负的int8转换为uint:
int8 y = -3;uint x = uint(y);
在这段代码片段末尾,x将具有0xfffff..fd(64个十六进制字符)的值,在256位的二进制补码表示中为-3。
如果一个类型被明确转换为较小的类型,则高阶位被切断:
uint32 a = 0x12345678;uint16 b = uint16(a); // b will be 0x5678 now
为方便起见,并不总是需要明确指定变量的类型,编译器会根据分配给该变量的第一个表达式的类型自动推断:
uint24 x = 0x123;var y = x;
这里,y的类型将是uint24。 对于函数参数或返回参数,不能使用var。
该类型仅从第一个赋值中推导出来,所以以下代码段中的循环是无限的,因为我将具有类型uint8,并且此类型的任何值都小于2000.对于for (var i = 0; i < 2000; i++) { ... }
转载地址:http://njpxx.baihongyu.com/