由于Solidity是一个静态类型的语言,所以编译时需明确指定变量的类型(包括 本地变量
或 状态变量
),solidity
编程语言提供了一些基本类型(elementary types)
可以用来组合成复杂类型。
类型可以与不同运算符组合,支持表达式运算,你可以通过表达式的执行顺序(Order of Evalution of Expressions)来了解执行顺序
值类型(Value Type)
值类型包含
- 布尔(Booleans)
- 整形(Integer)
- 地址(Address)
- 定长字节数组(fixed byte arras)
- 有理数和整形(Rational and Integer Literals,String literals)
- 枚举类型(Enums)
- 函数(Function Types)
为什么会叫值类型,是因为上述这些类型在传值时,总是值传递。比如在函数传参数时,或进行变量赋值时。
引用类型(Reference Types)
复杂类型,占用空间较大的。在拷贝时占用空间较大。所以考虑通过引用传递。常见引用类型有:
- 不定长字节数组(bytes)
- 字符串(string)
- 数组(Array)
- 结构体(Structs)
布尔(Booleans)
bool:可能的取值为常量true 和 false
支持的运算符:
- ! 逻辑非
- && 逻辑与
- || 逻辑或
- == 等于
- != 不等于
注意:&& 和 || 是短路运算符,他只会先执行前面的,如果无法判定结果才会执行后面的,比如 f(x) || g(y),如果f(x)已经判定为false,则结果为false,不会再执行g(y);同理 f(x) && g(y) 若 f(x) 判定为真,则 g(y)也不会再执行。
整形(Integer)
int/uint: 变长的有符号或无符号整形.变量支持的步长以8递增,支持从 uint8 到 uint256,以及 int8 到 int256。需要注意的是,uint 和 int 默认代表的是 uint256 和 int256(2^256,-2^128~2^128)
支持的运算符:
- 比较: <=, <, ==, != ,>=, >,返回值为bool类型。
- 位运算符: & , | , (^异或) , (~非).
- 数学运算: + , -, * , /, (%求余), ( **幂)
整数除法总是截断的,但如果运算符是字面量,则不会截断(后面会进一步提到).另外除 0 会抛出异常.
pragma solidity ^0.4.0;
// simple store example
contract simpleStorage{
uint valueStore;
function add(uint x,uint y) returns (uint z){
z = x + y;
}
function divide() returns (uint z ){
uint x = 1;
uint y = 2;
z = x / y ;
}//将输出0(截断了后面的小数)
}
整数字面量
整数字面量,由包含0-9的数字序列组成,默认被解释为十进制。在solidity中不支持八进制,前导0会被默认忽略。
小数由.组成,在他的左边或者右边至少要包含一个数字如1. , .1 , 1.3均为有效小数
字面量本身支持任何精度,也就是可以不会计算溢出,或除法截断。但当它被转换成对应的非字面量类型,如整数或小数。或者将他们与非字面量进行计算,就不能保证精度了。
pragma solidity ^0.4.0;
contract IntegerLiteral{
function integerTest() return (uint, uint,uint){
//超出运算字长
var i = (2**800 + 1) - 2**800;
var j = 1/3*3;
// 小数运算
var k = 0.5*8;
return(i,j,k)
}
}
总之来说就是,字面量怎么都计算都行,但一旦转为对应的变量后,再计算就不保证精度了。
地址
地址:以太坊地址的长度,大小20个字节,160位,所以可以用一个uint160编码。地址是所有合约的基础,所有的合约都会继承地址对象,也可以随时将一个地址串,得到对应的代码进行调用。当然地址代表一个普通账户时,就没有这么多的的功能了。
0.5.0以后合约不是从地址类型中派生出来的了,但是仍然可以显示地转换为地址。
支持的运算符:
- <= , < , ==, != , >= , >
地址类型的成员:
属性: balance
函数:send(),call(),delegatecall(),callcode()。
地址字面量:
十六进制的字符串,凡是能通过地址合法性检查(address checksum test),就会被认为是地址,
(地址合法性检查:为防止录入地址有误,一种格式化地址后来确认地址有效性的方案)。需要注意的是39到41位长的没有通过地址合法性检查的,会提示一个警告,但会被视为普通的 有理数 字面量。
balance
通过它能够得到一个地址的余额。
pragma solidity ^0.4.0;
contract addressTest{
function getBalance(address addr) returns (uint){
return addr.balance;
}
}
this
如果只是想得到当前合约的余额,也可以这样写
pragma solidity ^0.4.0;
contract addressTest{
function getBalance() returns (uint) {
return this.balance;
}
}
原因是对于合约来说,地址代表的就是合约本身,合约对象默认继承自地址对象,所以内部有地址的属性。
地址的方法 send()
用来向某个地址发送货币(货币单位为 wei)。
pragma solidity ^0.4.0;
//请注意这个仅仅是Demo,请不要用到正式环境
contract PayTest{
//得到当前合约余额
function getBalance() returns (uint) {
return this.balance;
}
//向当前合约存款
function deposit() payable returns(address addr,uint amount, bool success){
//msg.sender 全局变量,调用合约的发起方
//msg.value 全局变量,调用合约的发起方转发的货币量,以wei为单位
//send() 执行的结果
return (msg.sender, msg.value,this.send(msg.value));
}
}
这个合约实现的是充值。this.send(msg.value) 意指向合约自身发送 msg.value 量的以太币。msg.value是合约调用方附带的以太币。
send()方法执行时有一些风险
1. 调用递归深度不能超过1024.
2. 如果gas不够,执行将会失败。
3. 所以使用这个方法一定要检查成功与否。或为保险起见,货币操作时要使用一些最佳实践。
如果执行失败,将会回撤所有交易,所以务必留意返回结果。
现在地址的方法增加了一种更加安全的方法 x.transfer(coin) , 表示向x这个转入coin个以太币
call(),callcode()和delegatecall()
为了同一些不支持ABI协议的进行直接交互(一般的web3.js,solidity都是支持的)。可以使用call()函数,用来向另一个合约发送原始数据。参数支持任何类型任意数量。每个参数会按规则(规则是按ABI)打包成32字节并一一拼接到一起。
call()方法支持ABI协议[ABI]定义的函数选择器。如果第一个参数恰好4个字节,在这种情况下,会被认为根据ABI协议定义的函数器指定的函数签名[ABI] 。所以如果你只是向发送消息体,需要避免第一个参数是4个字节。
call方法返回一个bool值,以表面执行成功还是失败。正常结束返回true,异常终止返回false。我们无法解析返回结果,因为这样我们得事前知道返回的数据的编码和数据大小(这里的潜在假设 是不知道对方使用的协议格式,所以也不知道会返回的结果如何解析)。
同样我们也可以使用 delegatecal(),它与call 方法的区别在于,仅仅是代码会执行,而其它方面,如(存储,余额等)都是用的当前的合约数据。delegatecall() 方法的目的是用来执行另一个合约中的工具库。所以开发者需要保证两个合约中的存储变量能兼容,来保证delegatecall()能顺利执行。
在homestead阶段之前,仅有一个受限的多样的callcode()方法可用,但并未提供对 msg.sender, msg.value 的访问权限。
上面的这三个方法call(),delegatecall(),callcode()都是底层的消息传递调用,最好仅在万不得已才进行使用,因为他们破坏了Solidity的类型安全。
关于 call() 函数究竟发的什么消息体,函数选择器究竟怎么用,参见这里
上述函数都是底层的函数,使用时要异常小心。当调用一个未知的,可能是恶意的合约时,当你把控制权交给它,它可能回调回你的合约,所以要准备好在调用返回时,应对你的状态变量可能被恶意篡改的情况。