Solidity变量类型分为两大类——值类型、引用类型
值类型:变量的存储空间存的是变量的数据
引用类型:变量的存储空间存的是变量数据所在的存储空间的地址
注意:值传递和引用传递。值类型的变量,赋值是简单的值传递,即两个变量占有独立的存储区域。引用类型赋值传递的是变量的引用,即两个变量指向同一存储区域*
bool: 只有两种值true和false(默认false)。
支持的运算符:
实例:
bool a = true;
bool b = !a;
// a == b -> false
// a != b -> true
// a || b -> true
// a && b -> false
逻辑与(&&)和逻辑或(||)都遵循短路原则,即如果根据前一个表达式可以得到运算结果,则不会执行后面的表达式
操作
比较:<=,<,==,!=,>=,>(结果为bool)
位操作符:&,|,^(按位异或),~(按位取反)
算术运算符:+, - ,一元 - ,一元 +,*,/,%(取余),**(幂),<<(左移),>>(右移)
注意:
- 除零和取余有零引发异常。
- 左移几位和右移几位相当于乘以或者除以2的几次方,如果参数为负数的话会引发异常。
- 在Solidity中不支持八进制。
- 整形的上溢和下溢
注意:Solidity 还没有完全支持定长浮点型。可以声明定长浮点型的变量,但不能给它们赋值或把它们赋值给其他变量。
fixed/ufixed 各种大小的有符号和无符号定点小数,ufixedMxN and fixedMxN关键字M代表定点数占用的二进制位数,N代表定点数能表示多少位小数,M必须是8-256之间的,以8为步幅的整数,N必须是0-80之间的整数,ufixed 和fixed 默认为ufixed128x18和fixed128x18
bytes1, … ,bytes32,允许值以步长1递增。byte默认表示bytes1。
操作:
注意:
地址类型有两种形式,大致相同:
运算符
<=, <, ==, !=, >= 和 >
address payable地址成员:
.balance(uint256)
.transfer(uint256 amount)
范例:
pragma solidity ^0.5.0;
contract AddressExample {
constructor() public{
}
function giveEthersTo(address payable _toAccount,uint _amount) public{
if (address(this).balance >= _amount){
_toAccount.transfer(_amount);
}
}
function getBalance() view public returns(uint){
return address(this).balance;
}
function() payable external {
}
}
如果合约地址调用transfer,那么需要合约地址有payable类型的回退函数,并且会随着转账一块执行回退函数中的代码,如果因为回退函数中的代码执行把gas消耗光了,EVM会抛出异常,转账也会被回退。
send是低级对等的转账。执行失败,不会抛出异常,会返回false,需要支付2300gas的费用,不可以调整。推荐使用transfer而不使用send
.send(uint256 amount) returns (bool)
<address>.call(...) returns (bool)
<address>.callcode(...) returns (bool)
<address>.calldelegate(...) returns (bool)
为了和非ABI协议的合约进行交互,可以使用call() 函数, 它用来向另一个合约发送原始数据,支持任何类型任意数量的参数,每个参数会按规则(ABI协议)打包成32字节并一一拼接到一起。如下面的例子:
address(nameReg).call(abi.encodeWithSignature("register(string)", "MyName"));
call函数返回一个bool值,以表明执行成功与否。正常结束返回true,异常终止返回false。但无法获取到结果数据。还可以提供.gas()修饰器进行调用:
address(nameReg).call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName"));
类似还可以提供附带以太币:
address(nameReg).call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
修饰器可以混合使用,修饰器调用顺序无所谓。
address(nameReg).call.gas(1000000).value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
仅执行代码,而其它方面,如(存储,余额等)都是用的当前的合约的数据。
delegatecall()方法的目的是用来执行另一个合约中的库代码。所以开发者需要保证两个合约中的存储变量能兼容(这两个合约的存储中的变量定义顺序需要一致,以便被调用的合约代码可以正确地通过变量名访问合约的存储变量),来保证delegatecall()能顺利执行。
与delegatecall类似,但不能获取委托方的msg.sender,msg.value,在未来也会将其移除。
上面的这三个方法call(),delegatecall(),callcode()都是底层的消息传递调用,最好仅在万不得已才进行使用,因为他们破坏了Solidity的类型安全。
.gas() 在call(), callcode() 和 delegatecall() 函数下都可以使用, delegatecall()不支持.value()。
pragma solidity ^0.5.0;
contract Person{
uint public age = 10;
string public name="zhangsan";
constructor() public{
}
event log(address, uint);
function update(string memory _name, uint _num) payable public {
emit log(msg.sender, msg.value);
name = _name;
age = _num;
}
}
contract CallTest{
uint public age = 10;
string public name = "zhangsan";
function callByFun(address addr)public returns (bool){
bytes memory payload = abi.encodeWithSignature("update(string,uint256)", "lisi", 99);
(bool success, bytes memory returnData) = address(addr).call(payload);
return success;
}
}
注意
- 合约中使用的this表示当前合约地址
- 所有的合约对象都可以被转成地址类型,查询当前合约的余额
address和address payablede 转换:
address payable e = address(uint160(c));
地址字面量可以隐式转换成address payable
和address互相显示转换的类型如下:
整形
整形字面量
bytes20
contract types
合约对象包含payable 类型的fallback函数,address(contract)的类型是address payable, 如果合约对象不包含payable 类型的fallback函数,address(contract)的类型是address 。
十六进制整数常量,如果能够通过地址校验,就会被认为是地址类型,例如
0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
就是一个地址类型的常量,如果长度为39~40,没有通过地址校验的十六进制整数常量,会被视为有理数常量,并且会产生一个warning(警告提示)。
校验规则[https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md]
整数字面量和有理数字面量均支持科学计数法。基数可以是小数,指数必须是整数。
例如2e10,-2e10,2e-10,2.5e10
注意:
数字常量表达式一旦其中含有非常量表达式,它就会被转为一个非常量表达式
pragma solidity ^0.5.0;
contract IntegerLiteralConvert{
constructor()public{
}
function literalTest()public{
uint128 a = 1;
//uint128 b = 2.5 + a + 0.5;
//2.5+a不能转换成一个非常量表达式
}
}
字符串常量是用双引号或单引号(“foo”或‘bar’)编写,长度可变。 它们不像C语言那样默认以0结尾; “foo”代表三个不是四个字节。 它们可以隐式转换为bytes1,…,bytes32,如果合适的话,还可以转换成 bytes 以及 string。
pragma solidity ^0.5.0;
contract StringLiteralsTest{
bytes15 public name;
function setName() public{
name = 'lianrenxueyuan';
}
}
十六进制常量,以关键字hex打头,后面紧跟用单或双引号包裹的字符串。如hex"001122ff"。在内部会被表示为二进制流,与字符串的存储形式相同,可以与字符串进行隐式转换。
pragma solidity ^0.5.0;
contract HexLiteral{
string name;
function setName()public{
name = hex"6c69616e6b7561697875657975616e";
}
}
枚举类型是在Solidity中的一种用户自定义类型。
pragma solidity ^0.5.0;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public {
choice = ActionChoices.GoStraight;
// choice = ActionChoices(2);
}
function getChoice() public view returns (ActionChoices) {
return choice;
}
function getDefaultChoice() public pure returns (uint) {
return uint(defaultChoice);
}
}
注意:
- 枚举可以显示的转换与整数进行转换,但不能进行隐式转换。显示的转换会在运行时检查数值范围,如果不匹配,将会引起异常。
- 枚举类型应至少有一名成员。
- 函数的定义
- 函数的调用方式
- 函数的可见性
- pure函数
- constant、view函数
- payable函数
- Fallback函数
- 构造函数
- 函数参数
function关键字声明的,合约中的可执行单元,一个函数的完整定义如下:
function (funcName) (<parameter types>) {public|external|internal|private} [constant|view|payable] [returns (<return types>)]
Solidity封装了两种函数的调用方式internal(内部调用)和external(外部调用)。
注意:
上面所提到的internal和external指的函数调用方式,请不要与后面的函数可见性声明的external,public,internal,private弄混。声明只是意味着这个函数需要使用相对应的调用方式去调用。
internal调用,实现时转为简单的EVM跳转,所以它能直接使用上下文环境中的数据,对于引用传递时将会变得非常高效(不用拷贝数据)。
在当前的代码单元内,如对合约内函数,引入的库函数,以及父类合约中的函数直接使用即是以internal方式的调用。我们来看个简单的例子:
pragma solidity ^0.5.0;
contract Test {
function f()public{}
//以internal的方式调用
function callInternally()public{
f();
}
}
在上述代码中,callInternally()以internal的方式对f()函数进行了调用。
简而言之,internal(内部调用方式)就是直接使用函数名去调用函数。
external调用,实现为合约的外部消息调用。所以在合约初始化时不能external的方式调用自身函数,因为合约还未初始化完成。下面来看一个以external方式调用的例子:
pragma solidity ^0.5.0;
contract A{
function f()public {}
}
contract B{
//以external的方式调用另一合约中的函数
function callExternal(A a)public{
a.f();
}
}
虽然当前合约A和B的代码放在一起,但部署到网络上后,它们是两个完全独立的合约,它们之间的方法调用是通过消息调用。上述代码中,在合约B中的callExternal()以external的方式调用了合约A的f()。
简而言之,external(外部调用方式)就是使用合约实例名.函数名的方式去调用函数。
使用this强制external方式调用:
我们可以在合约的调用函数前加this.来强制以external方式的调用。
pragma solidity ^0.5.0;
contract A{
function f() public{}
function callExternally() public{
this.f();
}
}
Solidity为函数提供了四种可见性,external,public,internal,private。
pragma solidity ^0.5.0;
contract FuntionTest{
function externalFunc() external{}
function callFunc()public {
//以`internal`的方式调用函数报错
//Error: Undeclared identifier.
//externalFunc();
//以`external`的方式调用函数
this.externalFunc();
}
}
声明为external的externalFunc()只能以external的方式进行调用,以internal的方式调用会报Error: Undeclared identifier.。
pragma solidity ^0.5.0;
contract FuntionTest{
//默认是public函数
function publicFunc()public{}
function callFunc()public{
//以`internal`的方式调用函数
publicFunc();
//以`external`的方式调用函数
this.publicFunc();
}
}
我们可以看到声明为public的publicFunc()允许两种调用方式。
在当前的合约或继承的合约中,只允许以internal的方式调用。
pragma solidity ^0.5.0;
contract A{
function internalFunc() internal{}
function callFunc()public{
//以`internal`的方式调用函数
internalFunc();
}
}
contract B is A{
//子合约中调用
function callFunc()public{
internalFunc();
}
}
上述例子中声明为internal的internalFunc()在定义合约,和子合约中均只能以internal的方式可以进行调用。
pragma solidity ^0.5.0;
contract A{
function privateFunc() private{}
function callFunc()public{
//以`internal`的方式调用函数
privateFunc();
}
}
contract B is A{
//不可调用`private`
function callFunc()public{
//privateFunc();
}
}
既不从状态读取数据也不写入数据的函数可以被声明为pure
除了之前修改状态数据的情况外,我们认为以下情况属于从状态读取数据。
pragma solidity ^0.4.24;
contract C {
function f(uint a, uint b) public pure returns (uint) {
return a * (b + 42);
}
}
不改变状态的函数可以被声明为只读函数
以下几种情况被视为修改了状态:
声明了该函数涉及接收以太币操作,如果函数没有声明为payable,并且在调用过程中有以太币通过被调用的函数转入合约,那么EVM虚拟机将会抛出异常,状态回退。
pragma solidity ^0.5.0;
contract AddressExample {
uint public balance;
constructor() public payable{}
function deposit()public payable{
balance += msg.value;
}
}
合约可以有一个未命名的函数。这个函数不能有参数也不能有返回值。
如果在一个到合约的调用中,没有其他函数与给定的函数标识符匹配(或没有提供调用数据),那么这个函数(fallback 函数)会被执行。
每当合约收到以太币(没有任何数据),这个函数就会执行。此外,为了接收以太币,fallback 函数必须标记为 payable。 如果不存在这样的函数,则合约不能通过常规交易接收以太币。
pragma solidity ^0.5.0;
contract Test {
constructor() public{
}
event log(uint);
function() payable external{
emit log(1);
}
}
contract Caller {
constructor()public{
}
function call(address addr) public{
(bool success, bytes memory returnData) = address(addr).call.value(10)("");
}
}
如果涉及支付以太币,即回退函数被声明为payable类型,并且通过send或者transfer被调用,那么回退函数仅有你2300gas可以使用,如果回退函数中的代码执行消耗超过2300gas那么被转入的以太币将会退回,修改过的数据状态回退。
用做接收以太币回退函数内部仅能进行触发事件操作
即使 fallback 函数不能有参数,仍然可以使用 msg.data 来获取随调用提供的任何有效数据。
构造函数是一个用constructor关键字声明的可选函数,它在创建合约时执行。构造函数可以是public,也可以是internal。如果没有构造函数,则该合约将生成默认构造函数:contructor() public {}。
pragma solidity ^0.5.0;
contract A {
uint public a;
constructor(uint _a) internal {
a = _a;
}
}
在版本0.4.22之前,构造函数被定义为与合同名称相同的函数。这个语法现在不推荐使用。
Solidity函数的输入参数的数量是可选的,也可以有任意数量的返回参数。
入参(Input Parameter)与变量的定义方式一致,稍微不同的是,不会用到的参数可以省略变量名称。一种可接受两个整型参数的函数如下:
pragma solidity ^0.5.0;
contract Simple {
function taker(uint _a, uint) public{
// do something with _a.
}
}
出参(Output Paramets)在returns关键字后定义,语法类似变量的定义方式。返回结果的数量需要与定义的一致。如果给定了参数名,则函数可以不适用return关键字返回,如果没有给定参数名则需要函数体重使用return关键字按照顺序返回。
pragma solidity ^0.5.0;
contract Simple {
//return sum and product
function arithmetics(uint _a, uint _b) public pure returns (uint o_sum, uint o_product) {
o_sum = _a + _b;
o_product = _a * _b;
}
}