美文网首页Dapp开发智能合约
Solidity语法(七)合约

Solidity语法(七)合约

作者: yuyangray | 来源:发表于2018-02-28 14:39 被阅读1545次

    可见性和访问限制符(Visibility And Accessors)

    因为Solidity可以理解两种函数调用(“内部调用”,不创建一个真实的EVM调用(也称为“消息调用”);“外部的调用”-要创建一个真实的EMV调用), 有四种的函数和状态变量的可见性。

    函数可以被定义为external, public, internal or private,缺省是 public。对状态变量而言, external是不可能的,默认是 internal。

    external: 外部函数是合约接口的一部分,这意味着它们可以从其他合约调用, 也可以通过事务调用。外部函数f不能被内部调用(即 f()不执行,但this.f()执行)。外部函数,当他们接收大数组时,更有效。

    public:公共函数是合约接口的一部分,可以通过内部调用或通过消息调用。对公共状态变量而言,会有的自动访问限制符的函数生成(见下文)。

    internal:这些函数和状态变量只能内部访问(即在当前合约或由它派生的合约),而不使用(关键字)this 。

    private:私有函数和状态变量仅仅在定义该合约中可见, 在派生的合约中不可见。

    请注意

    在外部观察者中,合约的内部的各项均可见。用 private 仅仅防止其他合约来访问和修改(该合约中)信息, 但它对blockchain之外的整个世界仍然可见。

    可见性说明符是放在在状态变量的类型之后,(也可以放在)参数列表和函数返回的参数列表之间。

    contract c {
    
        function f(uint a) private returns (uint b) { return a + 1; }
    
        function setData(uint a) internal { data = a; }
    
        uint public data;
    
    }
    

    其他合约可以调用c.data()来检索状态存储中data的值,但不能访问(函数)f。由c派生的合约可以访问(合约中)setData(函数),以便改变data的值(仅仅在它们自己的范围里)。

    访问函数(Getter Functions)

    编译器为自动为所有的public的状态变量创建访问函数。下面的合约例子中,编译器会生成一个名叫data的无参,返回值是uint的类型的值data。状态变量的初始化可以在定义时完成。

    pragma solidity ^0.4.0;
    
    contract C{
        uint public c = 10;
    }
    
    contract D{
        C c = new C();
        
        function getDataUsingAccessor() returns (uint){
            return c.c();
        }
    }
    

    访问函数有外部(external)可见性。如果通过内部(internal)的方式访问,比如直接访问,你可以直接把它当一个变量进行使用,但如果使用外部(external)的方式来访问,如通过this.,那么它必须通过函数的方式来调用。

    pragma solidity ^0.4.0;
    
    contract C{
        uint public c = 10;
        
        function accessInternal() returns (uint){
            return c;
        }
        
        function accessExternal() returns (uint){
            return this.c();
        }
    }
    

    在acessExternal函数中,如果直接返回return this.c;,会出现报错Return argument type function () constant external returns (uint256) is not implicitly convertible to expected type (type of first return variable) uint256.。原因应该是通过外部(external)的方式只能访问到this.c作为函数的对象,所以它认为你是想把一个函数转为uint故而报错。

    函数修改器(Function Modifiers)

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

    pragma solidity ^0.4.0;
    
    contract owned {
        function owned() { owner = msg.sender; }
        address owner;
    
        // This contract only defines a modifier but does not use
        // it - it will be used in derived contracts.
        // The function body is inserted where the special symbol
        // "_;" in the definition of a modifier appears.
        // This means that if the owner calls this function, the
        // function is executed and otherwise, an exception is
        // thrown.
        modifier onlyOwner {
            if (msg.sender != owner)
                throw;
            _;
        }
    }
    
    contract mortal is owned {
        // This contract inherits the "onlyOwner"-modifier from
        // "owned" and applies it to the "close"-function, which
        // causes that calls to "close" only have an effect if
        // they are made by the stored owner.
        function close() onlyOwner {
            selfdestruct(owner);
        }
    }
    
    
    contract priced {
        // Modifiers can receive arguments:
        modifier costs(uint price) {
            if (msg.value >= price) {
                _;
            }
        }
    }
    
    
    contract Register is priced, owned {
        mapping (address => bool) registeredAddresses;
        uint price;
    
        function Register(uint initialPrice) { price = initialPrice; }
    
        // It is important to also provide the
        // "payable" keyword here, otherwise the function will
        // automatically reject all Ether sent to it.
        function register() payable costs(price) {
            registeredAddresses[msg.sender] = true;
        }
    
        function changePrice(uint _price) onlyOwner {
            price = _price;
        }
    }
    

    修改器可以被继承,使用将modifier置于参数后,返回值前即可。

    特殊_表示使用修改符的函数体的替换位置。

    从合约Register可以看出全约可以多继承,通过,号分隔两个被继承的对象。

    修改器也是可以接收参数的,如priced的costs。

    使用修改器实现的一个防重复进入的例子。

    pragma solidity ^0.4.0;
    contract Mutex {
        bool locked;
        modifier noReentrancy() {
            if (locked) throw;
            locked = true;
            _;
            locked = false;
        }
    
        /// This function is protected by a mutex, which means that
        /// reentrant calls from within msg.sender.call cannot call f again.
        /// The `return 7` statement assigns 7 to the return value but still
        /// executes the statement `locked = false` in the modifier.
        function f() noReentrancy returns (uint) {
            if (!msg.sender.call()) throw;
            return 7;
        }
    }
    

    例子中,由于call()方法有可能会调回当前方法,修改器实现了防重入的检查。

    如果同一个函数有多个修改器,他们之间以空格隔开,修饰器会依次检查执行。

    需要注意的是,在Solidity的早期版本中,有修改器的函数,它的return语句的行为有些不同。
    在修改器中和函数体内的显式的return语句,仅仅跳出当前的修改器和函数体。返回的变量会被赋值,但整个执行逻辑会在前一个修改器后面定义的"_"后继续执行。

    修改器的参数可以是任意表达式。在对应的上下文中,所有的函数中引入的符号,在修改器中均可见。但修改器中引入的符号在函数中不可见,因为它们有可能被重写。

    常量(constant state variables)

    状态变量可以被定义为constant,常量。这样的话,它必须在编译期间通过一个表达式赋值。赋值的表达式不允许:1)访问storage;2)区块链数据,如now,this.balance,block.number;3)合约执行的中间数据,如msg.gas;4)向外部合约发起调用。也许会造成内存分配副作用表达式是允许的,但不允许产生其它内存对象的副作用的表达式。内置的函数keccak256,keccak256,ripemd160,ecrecover,addmod,mulmod可以允许调用,即使它们是调用的外部合约。

    允许内存分配,从而带来可能的副作用的原因是因为这将允许构建复杂的对象,比如,查找表。虽然当前的特性尚未完整支持。

    编译器并不会为常量在storage上预留空间,每个使用的常量都会被对应的常量表达式所替换(也许优化器会直接替换为常量表达式的结果值)。

    不是所有的类型都支持常量,当前支持的仅有值类型和字符串。

    pragma solidity ^0.4.0;
    
    contract C {
        uint constant x = 32**22 + 8;
        string constant text = "abc";
        bytes32 constant myHash = keccak256("abc");
    }
    

    常函数(Constant Functions)

    函数也可被声明为常量,这类函数将承诺自己不修改区块链上任何状态。

    pragma solidity ^0.4.0;
    
    contract C {
        function f(uint a, uint b) constant returns (uint) {
            return a * (b + 42);
        }
    }
    

    访问器(Accessor)方法默认被标记为constant。当前编译器并未强制一个constant的方法不能修改状态。但建议大家对于不会修改数据的标记为constant。

    回退函数(fallback function)

    每一个合约有且仅有一个没有名字的函数。这个函数无参数,也无返回值。如果调用合约时,没有匹配上任何一个函数(或者没有传哪怕一点数据),就会调用默认的回退函数。

    此外,当合约收到ether时(没有任何其它数据),这个函数也会被执行。在此时,一般仅有少量的gas剩余,用于执行这个函数(准确的说,还剩2300gas)。所以应该尽量保证回退函数使用少的gas。

    下述提供给回退函数可执行的操作会比常规的花费得多一点。

    • 写入到存储(storage)
    • 创建一个合约
    • 执行一个外部(external)函数调用,会花费非常多的gas
    • 发送ether

    请在部署合约到网络前,保证透彻的测试你的回退函数,来保证函数执行的花费控制在2300gas以内。

    一个没有定义一个回退函数的合约。如果接收ether,会触发异常,并返还ether(solidity v0.4.0开始)。所以合约要接收ether,必须实现回退函数。下面来看个例子:

    pragma solidity ^0.4.0;
    
    contract Test {
        // This function is called for all messages sent to
        // this contract (there is no other function).
        // Sending Ether to this contract will cause an exception,
        // because the fallback function does not have the "payable"
        // modifier.
        function() { x = 1; }
        uint x;
    }
    
    
    // This contract keeps all Ether sent to it with no way
    // to get it back.
    contract Sink {
        function() payable { }
    }
    
    contract Caller {
        function callTest(Test test) {
            test.call(0xabcdef01); // hash does not exist
            // results in test.x becoming == 1.
    
            // The following call will fail, reject the
            // Ether and return false:
            test.send(2 ether);
        }
    }
    

    事件(Event)

    事件

    在介绍事件前,我们先明确事件,日志这两个概念。事件发生后被记录到区块链上成为了日志。总的来说,事件强调功能,一种行为;日志强调存储,内容。

    事件是以太坊EVM提供的一种日志基础设施。事件可以用来做操作记录,存储为日志。也可以用来实现一些交互功能,比如通知UI,返回函数调用结果等。

    当定义的事件触发时,我们可以将事件存储到EVM的交易日志中,日志是区块链中的一种特殊数据结构。日志与合约关联,与合约的存储合并存入区块链中。只要某个区块可以访问,其相关的日志就可以访问。但在合约中,我们不能直接访问日志和事件数据(即便是创建日志的合约)。下面我们来看看,如何在Solidity中实现一个事件:

    pragma solidity ^0.4.0;
    
    contract Transfer{
      event transfer(address indexed _from, address indexed _to, uint indexed value);
    
      function deposit() payable {
        address current = this;
        uint value = msg.value;
        transfer(msg.sender, current, value);
      }
    
    
      function getBanlance() constant returns(uint) {
          return this.balance;
      }
    
      /* fallback function */
      function(){}
    }
    

    从上面的例子中,我们使用event关键字定义一个事件,参数列表中为要记录的日志参数的名称及类型。

    监听事件

    在web3.js中,提供了响应事件的方法,如下:

    var event = myContract.transfer();
    
        // 监听
        event.watch(function(error, result){
        console.log("Event are as following:-------");
        
        for(let key in result){
         console.log(key + " : " + result[key]);
        }
        
        console.log("Event ending-------");
    });
    

    另外一种简便写法是直接加入事件回调,这样就不用再写watch的部分:

    var event = myContract.transfer(function(error, result){
        console.log("Event are as following:-------");
        
        for(let key in result){
         console.log(key + " : " + result[key]);
        }
        
        console.log("Event ending-------");
    });
    

    检索日志

    Indexed属性

    可以在事件参数上增加indexed属性,最多可以对三个参数增加这样的属性。加上这个属性,可以允许你在web3.js中通过对加了这个属性的参数进行值过滤,方式如下:

    var event = myContract.transfer({value: "100"});
    

    上面实现的是对value值为100的日志,过滤后的返回。

    如果你想同时匹配多个值,还可以传入一个要匹配的数组。

    var event = myContract.transfer({value: ["99","100","101"]});
    

    增加了indexed的参数值会存到日志结构的Topic部分,便于快速查找。而未加indexed的参数值会存在data部分,成为原始日志。需要注意的是,如果增加indexed属性的是数组类型(包括stringbytes),那么只会在Topic存储对应的数据的web3.sha3哈希值,将不会再存原始数据。因为Topic是用于快速查找的,不能存任意长度的数据,所以通过Topic实际存的是数组这种非固定长度数据哈希结果。要查找时,是将要查找内容哈希后与Topic内容进行匹配,但我们不能反推哈希结果,从而得不到原始值。

    View

    函数可以被声明为view,在这种情况下,它们承诺不修改状态。

    以下语句被认为是修改状态:

    1. 写入状态变量。
    2. 发射事件。
    3. 创建其他合约。
    4. 使用selfdestruct。
    5. 通过调用发送Ether。
    6. 调用其他函数不被标记view或者pure。
    7. 使用低等级调用。
    8. 使用包含某些操作码的内联汇编。
    contract C {
        function f(uint a, uint b) view returns (uint) {
            return a * (b + 42) + now;
        }
    }
    
    • constant是view的别名。

    • Getter方法被标记为view。

    • 编译器并没有强制执行view方法不修改状态。

    Pure

    函数可以声明为pure,在这种情况下,它们承诺不会从该状态中读取或修改该状态。

    除了上述状态修改语句的列表外,以下是从状态读取的:

    1. 从状态变量读取。
    2. 访问this.balance或.balance。
    3. 访问block,tx,msg的任何成员(除了msg.sig和msg.data)。
    4. 调用任何未标记为pure的功能。
    5. 使用包含某些操作码的内联汇编。
    contract C {
        function f(uint a, uint b) pure returns (uint) {
            return a * (b + 42);
        }
    }
    

    编译器并没有强制执行pure方法不是从状态中读取的。

    继承(Inheritance)

    pragma solidity ^0.4.0;
    
    contract owned {
        function owned() { owner = msg.sender; }
        address owner;
    }
    
    contract mortal is owned {
        function kill() {
            if (msg.sender == owner) selfdestruct(owner);
        }
    }
    

    抽象(Abstract Contracts)

    抽象函数是没有函数体的的函数。如下:

    pragma solidity ^0.4.0;
    
    contract Feline {
        function utterance() returns (bytes32);
    }
    

    这样的合约不能通过编译,即使合约内也包含一些正常的函数。但它们可以做为基合约被继承。

    pragma solidity ^0.4.0;
    
    contract Feline {
        function utterance() returns (bytes32);
        
        function getContractName() returns (string){
            return "Feline";
        }
    }
    
    contract Cat is Feline {
        function utterance() returns (bytes32) { return "miaow"; }
    }
    

    如果一个合约从一个抽象合约里继承,但却没实现所有函数,那么它也是一个抽象合约。

    相关文章

      网友评论

        本文标题:Solidity语法(七)合约

        本文链接:https://www.haomeiwen.com/subject/vflvxftx.html