solidity语法(二)

继续学习solidity语法。

2.1 函数类型

 

以下是在Solidity中声明函数的方式。

function sampleFunc(string name, uint amount) {}

上面声明的是一个空体函数,它有两个参数:一个字符串和一个 uint。

可以这样调用此函数:

sampleFunc("Shashank", 10000);

谈到函数,Solidity还提供函数修饰符。

 

2.2 函数四种访问权限

 

函数声明有public、private、internal和external四种访问权限

  • 1.函数默认声明为public,即可以以internal方式调用,也可以通过external方式调用。可以理解为能够被内部合约访问和外部合约访问。

  • 2.Internal声明的只允许通过internal方式调用,不能被外部合约。而external能够被外部合约访问。

  • 3.private和internal类似,都不能被外部合约访问,唯一的不同是private函数不能被子类调用,而internal可以。

contract FunctionTest{        function publicFunc() {}    function callFunc(){        //以`internal`的方式调用函数        publicFunc();                //以`external`的方式调用函数        this.publicFunc();    }        function internalFunc() internal{}        function externalFunc() external{}        }contract FunctionTest1 {    function externalCall(FuntionTest ft){        //调用另一个合约的外部函数        ft.publicFunc();        ft.externalFunc();       //ft.internalFunc();调用失败,无法调用internal函数    }}


 

2.3 pure、view、constant函数返回值定义类型

 

当函数有返回值时,可以添加这三种定义,用这三种方式定义的函数都只执行读操作,不会进行编译执行。即用了这三种方式定义的函数,不会执行函数里的逻辑,只会执行一个返回的读操作。所以执行这些函数不需要消耗gas费用。

  • pure区别是用于返回非变量,如returns 10;

  • 而view和constant用于返回全局变量,两者的区别为新旧版本

  • 但是此功能4.x版本可用,5.x版本废弃

  • 声明才能返回

contract HelloWorld4{        uint public a = 1;      //由于被constant声明的函数执行读操作,所以a无法被修改    //执行为f(),a依然为1    function f() constant{       a = 3;    }}

2.4 函数修饰符

 

函数修饰符看起来跟函数没什么不同,不过关键字modifier 告诉编译器,这是个modifier(修饰符),而不是个function(函数)。它不能像函数那样被直接调用,只能被添加到函数定义的末尾,用以改变函数的行为。

示例:如果要仅通过函数的所有者或创建者调用kill contract函数。

/** * @dev 调用者不是‘主人’,就会抛出异常 */modifier onlyOwner() {  require(msg.sender == owner);  _;}onlyOwner 函数修饰符是这么用的:onlyOwner,它会限制陌生人的访问,将访问某些函数的权限锁定在 owner 上contract MyContract is Ownable {  event LaughManiacally(string laughter);  //注意!`onlyOwner`上场 :  function likeABoss() external onlyOwner {    LaughManiacally("Muahahahaha");  }}

修改器(Modifiers)可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)。

 

 

2.5 继承

 

Solidity通过复制包含多态的代码来支持多重继承。

contract Owned {    address Owner ;    function owned() {       owner = msg.sender;    }}contract Mortal is Owned {  // 'is' keyword is used for inheritance     function kill(){         self-destruct(owner);        }}contract User is Owned, Mortal{ //Multiple inheritance(多重继承)     string public UserName;     function User(string _name){         UserName = _name;     }}

 

2.6 事件

 

在Solidity 代码中,使用event 关键字来定义一个事件,如:

event EventName(address bidder, uint amount);

这个用法和定义函数式一样的,并且事件在合约中同样可以被继承。触发一个事件使用emit(说明,之前的版本里并不需要使用emit),如:

emit EventName(msg.sender, msg.value);

触发事件可以在任何函数中调用,如:

function testEvent() public {    // 触发一个事件     emit EventName(msg.sender, msg.value); }

 

2.7 异常处理

 

Solidity使用状态恢复来处理异常,就是说当抛出异常时将恢复到调用(包括自调用)前的状态。

抛出异常的方式有assert,require,revert,throw。

 

 assert函数,用于条件检查,只能测试内部错误和检查常量。

//检查内部计算是否会整型溢出function add(uint256 a, uint256 b) internal constant returns (uint256) {    uint256 c = a + b;    assert(c >= a);    return c;  }

 require函数,也是用于条件检查,用于测试调用的输入或者合约状态变量。

function sendHalf(address addr) payable returns (uint balance) {        require(msg.value % 2 == 0); // 只允许偶数        .....    }

revert 函数用于标记错误并恢复当前调用。

function buy(uint amount) payable {        if (amount > msg.value / 2 ether)            revert("Not enough Ether provided.");    }

throw 和revert一样,但是throw在0.4.13被弃用,将来会被淘汰。

 

2.8 状态变量storage和局部变量memory

 

两者区别很容易理解,memory可以理解为临时变量,不会记录在链上,而storage是永久存储的。

变量定义时默认为storage,而作为函数参数时,默认为memory

contract HelloWorld{        //等价于 string storage public a;    string public a;    //参数等价于string memory _a    function changeNum(string _a){    }        }

当函数参数为memory类型时,相当于值传递,storage才是指针传递

contract HelloWorld2{        string public a;        function HelloWorld2(){        a = "abc";    }            function f(){        changeNum(a);    }        function changeNum(string _a){        bytes(_a)[0] = "d";      //由于_a默认为memory,所以_a只是值传递,所以此时修改a的值是不成功的,输出还是abc      //需要把函数参数修改为string storage _a,才能输出dbc    }}

将变量赋值给一个新变量时,新变量的类型由赋值给它的类型决定。

function changeNum(string _a){        //_a默认为memory类型,所以b也为memory        string b = _a;        bytes(_a)[0] = "d";    }

 

2.9 接口

 

如果我们的合约需要和区块链上的其他的合约会话,则需先定义一个 interface (接口)。

假设在区块链上有这么一个合约:

contract LuckyNumber {  mapping(address => uint) numbers;  function setNum(uint _num) public {    numbers[msg.sender] = _num;  }  function getNum(address _myAddress) public view returns (uint) {    return numbers[_myAddress];  }}

现在假设我们有一个外部合约,使用 getNum 函数可读取其中的数据。

首先,我们定义 LuckyNumber 合约的 interface :

contract NumberInterface {  function getNum(address _myAddress) public view returns (uint);}

在我们的 app 代码中使用这个接口,合约就知道其他合约的函数是怎样的,应该如何调用,以及可期待什么类型的返回值。

上面的接口,我们可以在合约中这样使用:

contract MyContract {  address NumberInterfaceAddress = 0xab38...;  // ^ 这是FavoriteNumber合约在以太坊上的地址  NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);  // 现在变量 `numberContract` 指向另一个合约对象  function someFunction() public {    // 现在我们可以调用在那个合约中声明的 `getNum`函数:    uint num = numberContract.getNum(msg.sender);    // ...在这儿使用 `num`变量做些什么  }}

通过这种方式,只要将您合约的可见性设置为public(公共)或external(外部),它们就可以与以太坊区块链上的任何其他合约进行交互。

处理多返回值

function multipleReturns() internal returns(uint a, uint b, uint c) {  return (1, 2, 3);}function processMultipleReturns() external {  uint a;  uint b;  uint c;  // 这样来做批量赋值:  (a, b, c) = multipleReturns();}// 或者如果我们只想返回其中一个变量:function getLastReturnValue() external {  uint c;  // 可以对其他字段留空:  (,,c) = multipleReturns();}


3.0 payable 修饰符

 

payable 方法是让 Solidity 和以太坊变得如此酷的一部分 —— 它们是一种可以接收以太的特殊函数。

在以太坊中, 因为钱 (以太), 数据 (事务负载), 以及合约代码本身都存在于以太坊。你可以在同时调用函数 并付钱给另外一个合约。

contract OnlineStore {  function buySomething() external payable {    // 检查以确定0.001以太发送出去来运行函数:    require(msg.value == 0.001 ether);    // 如果为真,一些用来向函数调用者发送数字内容的逻辑    transferThing(msg.sender);  }}

在这里,msg.value 是一种可以查看向合约发送了多少以太的方法,另外 ether 是一个內建单元。

这里发生的事是,一些人会从 web3.js 调用这个函数 (从DApp的前端), 像这样 :

 

// 假设 OnlineStore 在以太坊上指向你的合约:OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))

注意这个 value 字段, JavaScript

调用来指定发送多少(0.001)以太。如果把事务想象成一个信封,你发送到函数的参数就是信的内容。添加一个 value 很像在信封里面放钱

—— 信件内容和钱同时发送给了接收者。


 

3.1 基本类型之间的转换

 

隐式转换

 

  如果一个运算符用在两个不同类型的变量之间,编译器将会隐式地将其中一个类型转换成另一个类型。一般来说,只要值类型之间的转换在语义上能行,而且转换过程没有信息丢失,基本上都是可行的。

  如uint8转换成uint16,uint128转换成uint256,但是uint8不能转换成uint256(因为uint256不能涵盖某些值如-1)。通常无符号整型内转换成与它大小相等或者更大的字节类型,反之不能。任何可以转换成uint160的类型都能转换成地址类型。

 

显式转换

 

  某些情况编译器不支持隐式转换,但是用户清楚他在做什么,这时候可以使用显式转换。一定要先进行测试,保证结果可控。

示例:

int8 y = -3;uint x = uint(y);

  x值将为 0xfff...fd(63个f),这是-3的256位补码形式

如果一个显式转换成更小的类型,相应的高位会被抛弃:

uint32 a = 0x12345678;uint16 b = uint16(a); // b的值为 0x5678

 

3.2 内置单位

 

1、货币单位

weifinneyszaboether若不加后缀,则默认都为wei

2、时间单位

1==1 seconds1 minutes == 60 seconds ……特别注意,使用这些单位时要特别小心,因为一年并不总有365天;同时因为有闰秒的存在,一天也并不总是24小时。为了保证日历库的精确性,最好由外部供应商定期更新。

3、区块和交易属性 

block.blockhash(uint blockNumber) returns(bytes32):获取特定区块的散列值,只对不包括当前区块的256个最近的区块有效。block.coinbase:类型为address,表示当前区块“矿工”的帐号地址block.difficulty:类型为uint,表示当前区块的挖矿难度block.gaslimit:类型为uint,表示当前区块的Gas限制block.number:类型为uint,表示当前区块编号block.timestamp:类型为uint,以UNIX时间戳的形式表示当前区块的产生时间msg.data:类型为bytes,表示完整的调用数据msg.gas:类型为uint,表示剩余的Gasmsg.sender:类型为address,表示当前消息的发送者地址msg.sig:类型为bytes4,调用数据的前4字节,函数标识符msg.value:类型为uint,表示该消息转账的以太币数额,单位为weinow:类型为uint,表示当前时间,是block.timestamp的别名。tx.gasprice:类型为uint,表示当前交易Gas价格tx.origin:类型为address,表示完整调用链的发起者。

5、数学和加密函数

 

addmod(uint x,uint y,uint k) returns(uint):计算(x+y)%k,加法支持任意精度,但不超过2的256次方mulmod(uint x,uint y,uint k) returns(uint):计算(x*y)%k,乘法支持任意精度,但不超过2的256次方keccak256(...) returns(bytes32):计算Ethereum-SHA-3散列值sha3(...) returns(bytes32):上面的别名,跟上面功能一样sha256(...) returns(bytes20):计算RIPEMD-160散列值ecrecover(bytes32 hash,uint8 v,bytes32 r,bytes32 s) returns (address):根据公钥,使用ECDSA算法对地址进行解密,返回解密后的地址,如果发生错误,则返回0

6、与合约相关的变量和函数

this:指代当前的合约,可以转换为地址类型selfdestruct(address recipient):销毁当前合约,并且将全部的以太币余额转账到该地址。suicide(address recipient):同上 

 

上一篇:Solidity入门学习笔记1-引用类型-数据位置


下一篇:与Solidity相比,用ink!写智能合约的体验更丝滑