solidity数据类型分为以下几种:
1、整型
- int(有符号整型,有正有负)
- uint(无符号整型,无负数)
- 以8位为区间,支持int8,int16,int24 至 int256,uint同理。 int默认为int256,uint默认为uint256
2、布尔类型
true或者false
3、地址类型
address: 20 字节长度的值(以太坊的地址),地址类型有很多成员变量,是所有合约的基础。
地址类型的成员
- balance 和 transfer
可以通过地址的balance属性来查看一个地址的余额,发送以太币(单位为:wei)到一个地址可以使用 transfer方法
address x = 0x123; address myAddress = this; if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
注意:如果x是一个合约地址,它的代码(如果存在的话,更明确是为 fallback 函数)将会和 transfer 调用一起被执行(这是EVM的限制,是不可阻止的)。如果执行过程中gas不够或是失败,当前合约会终止抛出异常
-
send
send方法和transfer很相似,但是比transfer更低级。如果send失败,当前的合约不会中断,也不会抛出异常,会返回一个false。
注意:使用send有一些风险:如果调用栈深度超过1024或是gas不够,所有的转让操作都会失败,为了更安全的以太币转移,如果用send就必须每次都要检查返回值,使用transfer方法会更好;
- call, callcode, delegatecall
而且,call方法可以和没有依附于ABI上的合约进行交互,它提供了任意多个任意类型的参数。这些参数填充成32字节,并连接起来。有情况如果第一个参数被加密成4个字节,就会抛出异常,这种情况下,是不允许使用这个方法。
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2; nameReg.call("register", "MyName"); nameReg.call(bytes4(keccak256("fun(uint256)")), a);
call 返回一个boolean值,被调用函数终止返回true或是有EVM异常就返回false。不会返回访问的具体数据(对此,我们需要预先知道编码方式和大小)。
同样的方式,delegatecall也可以使用,和call的唯一区别是 delegatecall会使用给定地址的代码。所有其他方面(存储,余额等)都是从当前合约中获取。delegatecall的作用是使用其他合约里的library代码。用户需要确保两个合约的存储布局适用于 deletegatecall的使用。
call, delegatecall和call都是很低层次的方法,只有在实在没办法的时候才使用,这三个方法会破坏Solidity里的类型安全。
三个方法都可以使用 .gas(),而 .value() 不适合deletegatecall.
注意:所有的合约都继承了地址相关成员方法。所以可以使用 this.balance 来查询当前合约的余额
⚠️ 这些方法都是低层数的方法,使用的时候一定要小心。如果使用不当,任何未知的合约都可能别破坏。 你应该移交控制到可以通过回调到你自己合约的那个合约里,这样通过返回值就可以更新你自己的state变量
4、枚举类
enum WeekDays { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday }
5、 字节数组
定长字节数组
关键字有:bytes1
, bytes2
, bytes3
, ..., bytes32
。byte
是 bytes1
的别名。
5.1 运算符:
- 比较运算符:
<=
,<
,==
,!=
,>=
,>
(返回布尔型) - 位运算符:
&
,|
,^
(按位异或),~
(按位取反),<<
(左移位),>>
(右移位) - 索引访问:如果
x
是bytesI
类型,那么x[k]
(其中0 <= k < I
)返回第k
个字节(只读)。 -
该类型可以和作为右操作数的任何整数类型进行移位运算(但返回结果的类型和左操作数类型相同),右操作数表示需要移动的位数。 进行负数位移运算会引发运行时异常。
成员变量:
.length
表示这个字节数组的长度(只读).
5.2 可变字节数据
bytes: 动态大小byte数组,不是一个值类型
string:动态大小UTF-8编码string,不是一个值类型
作为一个经验法则,对于任意长度的raw数据,使用bytes。对于任意长度的string(UTF-8)数据,使用string。如果可以限定bytes到一定长度,优先使用byte1到byte32,这32个byte类型开销更小
6、数组
数组在编译期间固定大小或是声明为一个动态数组, 对于存储数组,数组里的元素类型是可以为任意的(可以是其他数组,mapping或是structs).对于内存数组,如果是一个public方法的实参,元素不能是mapping,而且必须是ABI类型。
如果数组的长度为x, 元素类型为T,则可以写成 T[x],一个动态长度的数组,可以写成 T[]。例如,一个数组为5的动态数组,类型为uint,可以写成 uint[][5]。访问第二个uint中的第三个元素,可以使用 T[2][1] (数组下标从0开始)。bytes和string是特殊的数组,bytes类似于byte[],但是它在calldata里是紧凑存放。string等于bytes,但是不允许根据长度或下标访问。所以从廉价程度来说,相对于byte[],更优先使用bytes。
分配内存数组
在内存里创建一个可变长度数组可以使用new关键字,和存储空间数组相反,它不能通过 .length 重新设置长度。
pragma solidity ^0.4.0; contract C { function f(uint len) { uint[] memory a = new uint[](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] memory ,因为包含的每个元素类型都为 uint8。 所以有必要把前面例子的第一个元素类型改成 uint。需要注意:固定长度的内存数组是不可以赋值给 动态长度的内存数组。下面的例子是错误的。
pragma solidity ^0.4.0; contract C { function f() { // The next line creates a type error because uint[3] memory // cannot be converted to uint[] memory. uint[] x = [uint(1), 3, 4]; }
这个限制现有有计划会在后续版本里去掉。
成员
- length
数组有个length成员来表示数组元素的个数。动态数组可以在存储空间(不是内存)里通过改变 length值来重新设置长度。
- push
动态存储数组 和 bytes(不是string)可以使用push来增加一个元素到数组的尾部。这个方法将返回一个新的长度。
7、结构
Solidity提供了一种用户自定义格式的新类型。例如:
pragma solidity ^0.4.11; contract CrowdFunding { // Defines a new type with two fields. 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 is return variable // Creates new struct and saves in storage. We leave out the mapping type. campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); } function contribute(uint campaignID) payable { Campaign c = campaigns[campaignID]; // Creates a new temporary memory struct, initialised with the given values // and copies it over to storage. // Note that you can also use Funder(msg.sender, msg.value) to initialise. 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; } }
结构可以用在mapping和数组的内部,当然结构本身也可以包含mapping或是数组。虽然结构本身可以作为mapping的成员值,但是结构不可以包含它本身类型。这个约束是有必要的,是为了防止出现结构套结构,无限的循环套用。注意:结构是赋值给一个局部变量(默认为存储空间数据位置),不是拷贝结构,而是存储一个引用指向局部变量。当然,不用赋值给局部变量,也可以直接访问结构体的成员 campaigns[campaignID].amount = 0
8、Mapping
Mappings类型定义为 mapping(_KeyType => _ValueType),这里 _KeyType可以是除了mapping外的其他任意类型。 _ValueType 可以是任意类型,包括mapping。Mapping可以看成是一个哈希表,初始化每个存在的key,对应的value的值会初始化为所有的字节都为0。mapping的key事实上不是存在mapping中,只有key的keccak256 哈希才用于查找值。所以,mapping是没有长度和设置key和value的概念。mapping只允许静态变量或是内部方法中的存储空间引用类型。
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); } }
数据位置
所有的复杂类型,即 数组 和 结构 类型,都有一个额外属性,“数据位置”,说明数据是保存在 内存memory 中还是 存储storage 中。 根据上下文不同,大多数时候数据有默认的位置,但也可以通过在类型名后增加关键字 storage
或 memory
进行修改。 函数参数(包括返回的参数)的数据位置默认是 memory
, 局部变量的数据位置默认是 storage
,状态变量的数据位置强制是 storage
(这是显而易见的)。
也存在第三种数据位置, calldata
,这是一块只读的,且不会永久存储的位置,用来存储函数参数。 外部函数的参数(非返回参数)的数据位置被强制指定为 calldata
,效果跟 memory
差不多。
数据位置的指定非常重要,因为它们影响着赋值行为: 在 存储storage 和 内存memory 之间两两赋值,或者 存储storage 向状态变量(甚至是从其它状态变量)赋值都会创建一份独立的拷贝。 然而状态变量向局部变量赋值时仅仅传递一个引用,而且这个引用总是指向状态变量,因此后者改变的同时前者也会发生改变。 另一方面,从一个 内存memory 存储的引用类型向另一个 内存memory 存储的引用类型赋值并不会创建拷贝。
pragma solidity ^0.4.0; contract C { uint[] x; // x 的数据存储位置是 storage // memoryArray 的数据存储位置是 memory function f(uint[] memoryArray) public { x = memoryArray; // 将整个数组拷贝到 storage 中,可行 var y = x; // 分配一个指针(其中 y 的数据存储位置是 storage),可行 y[7]; // 返回第 8 个元素,可行 y.length = 2; // 通过 y 修改 x,可行 delete x; // 清除数组,同时修改 y,可行 // 下面的就不可行了;需要在 storage 中创建新的未命名的临时数组, / // 但 storage 是“静态”分配的: // y = memoryArray; // 下面这一行也不可行,因为这会“重置”指针, // 但并没有可以让它指向的合适的存储位置。 // delete y; g(x); // 调用 g 函数,同时移交对 x 的引用 h(x); // 调用 h 函数,同时在 memory 中创建一个独立的临时拷贝 } function g(uint[] storage storageArray) internal {} function h(uint[] memoryArray) public {} }
总结
- 强制指定的数据位置:
-
- 外部函数的参数(不包括返回参数): calldata
- 状态变量: storage
- 默认数据位置:
-
- 函数参数(包括返回参数): memory
- 所有其它局部变量: storage