美文网首页
SOL02:构造器与函数

SOL02:构造器与函数

作者: 杨强AT南京 | 来源:发表于2020-04-21 08:26 被阅读0次

  区块链2.0的很大特色是引入了智能合约,智能合约的最大好处是可以 把任何交易自动化。当然任何过程的执行都是依赖编程意义上的过程:函数。Solidity语言的函数设计了与众不同的风格。在数据的访问保护还是有特色的,实现的是语法级别的保护。
  这个文章主要介绍两种函数:构造函数与功能函数。


创建合约

合约创建与构造器

  1. 构造器的说明
    • 语法基本上与Javascript与C++一样。
  • 创建合约时,会执行一次构造函数(与合约同名的函数)。

  • 构造函数是可选的。只允许有一个构造函数,这意味着不支持重载。

  • 在内部,构造函数参数在合约代码之后通过 ABI 编码传递。

  • 如果你使用 web3.js 则不必关心这个问题。

  • 构造器语法

    • constructor(参数......) public { ...... }
  • 构造器可以使用public或者internal修饰

    • 如果没有构造器,则产生public的默认构造器。
    • 使用internal修饰的构造器,则意味这个合同是抽象合同。
  1. 合约构造器例子
pragma solidity ^0.5.0;

contract Contrac_Base{
    uint  age;
    constructor(uint age_) public {
        age = age_;
    }

    function  getAge() public view returns (uint){
        // return 99;
        return age;
    }

    function setAge(uint age_) public {
        age = age_;
    }
}

使用web3创建合约

  • web3创建合约传递参数,通过deploy函数来传递,等价于通过 ABI 编码传递。
    var contract_addr ;
    await new web3.eth.Contract(abi)
    .deploy(
        {
            data: '0x' + bytecode, // 需要注意 字节码需要添加 '0x' 不然会有各种错误   
            arguments: [55]          // 构造器参数

        }
    )

实验

  • 不是带构造器的合约,并调用函数查看构造器的结果。

智能合约

  • 文件名:contract2_constructor.sol
pragma solidity ^0.5.0;

contract Contrac_Base{
    uint  age;
    constructor(uint age_) public {
        age = age_;
    }

    function  getAge() public view returns (uint){
        // return 99;
        return age;
    }

    function setAge(uint age_) public {
        age = age_;
    }
}

Web部署、调用与执行

  • 采用了异步执行的方式,并使用await同步。
/**
 * 这个智能合约增加了构造器
 */
const path =  require('path');
const fs = require('fs');
const solc =  require('solc');

const Web3 = require("web3");
const web3 = new Web3();
web3.setProvider(new Web3.providers.HttpProvider("http://localhost:9999"));

const file_name = "contract2_constructor.sol"
const sol_file = ".\\sols\\"+ file_name
const contract_name = "Contrac_Base"
// 获取智能合约的绝对路径
var contract_path = path.resolve(sol_file);
// console.log("合同路径: " + contract_path);
 
// 读取合约内容
var contract_source = fs.readFileSync(contract_path, 'utf-8');
// console.log(contract_source)
// 编译
var contract_json = JSON.stringify({
    language: 'Solidity',
    sources: {
        "contract2_constructor.sol" : {  // 指明编译的文件名
            content: contract_source, // solidity 源代码
        },
    },
    settings: { // 自定义编译输出的格式。以下选择输出全部结果。
        outputSelection: {
            '*': {
                '*': [ '*' ]
            }
        }
    },
});

var contract_compiled = solc.compile(contract_json);
// console.log(contract_compiled);
// 注意: solc的版本。
var contract_obj = JSON.parse(contract_compiled);
var ou = contract_obj.contracts[file_name][contract_name];

var abi = ou.abi;
var bytecode = ou.evm.bytecode.object;
console.log(abi);
console.log(bytecode);

// 部署合约前的准备
web3.eth.getAccounts().then(async function(accouts_){   // 设置函数是异步
    accounts = accouts_;
    // 1. 解锁账号
    // (设置异步后,可以使用await等待解锁成功后(就是then调用后)Promise才继续执行。)
    await web3.eth.personal.unlockAccount(accounts[0], "gaoke2020", 0).then(
        function(result){
            if (result){
                console.log("解锁账号成功!");
            }
            else{
                console.log("解锁账号失败!");
            }
        }
    )
    // 2. 部署合约
    var contract_addr ;
    await new web3.eth.Contract(abi)
    .deploy(
        {
            data: '0x' + bytecode, // 需要注意 字节码需要添加 '0x' 不然会有各种错误   
            arguments: [55]          // 构造器参数

        }
    ).send(
        {
            from: accounts[0],
            gas: '4712388',
            gasPrice: '1000000'
        }
    )
    .on(
        'transactionHash', 
        function (hash) {
            console.log("交易Hash:" + hash)
        }
    )
    .on(
        'receipt', 
        function (receipt) {
            console.log("交易收据:" , receipt);
        }
    )
    // .on(   // 不注释会一直调用
    //     'confirmation', 
    //     function (confirmationNumber, receipt) {
    //         console.log( "交易确认:" + confirmationNumber);
    //     }
    // )
    .on(
        'error', 
        function(error){
            console.log("错误:" + error);
        } 
    )
    .then(
        function(newContractInstance){  // 部署成功,返回一个新的合同实例,包含合同的部署地址
            // console.log("合同地址:" + newContractInstance.options.address) 
            contract_addr = newContractInstance.options.address;
        }
    )
    console.log("合同地址:" + contract_addr) 
    // 3. 执行合同完毕后,查询是否修改成功
    await new web3.eth.Contract(abi, contract_addr)
    .methods
    .getAge()
    .call(
        {
            from: accounts[0]
        }
    )
    .then(
        function(result){
            console.log("查询构造器初始化结果[55]:" , result);
        }
    );


    // 4. 获取刚刚部署好的合同, 并调用合约函数修改数据
    await new web3.eth.Contract(abi, contract_addr)
    .methods
    .setAge(128)
    .send(
        {
            from: accounts[0],
            gas: '4712388',
            gasPrice: '1000000'
        }
    )
    .on(
        'transactionHash', 
        function (hash) {
            console.log("交易Hash:" + hash);
        }
    )
    .on(
        'receipt', 
        function (receipt) {
            console.log("交易收据:" , receipt);
        }
    )
    .on(
        'error', 
        function(error){
            console.log("错误:" + error);
        } 
    )
    .then(
        function(result){
            var keys = Object.getOwnPropertyNames(result);
            console.log(keys);
            for(var k in  result){
                console.log(k + ":" + result[k]);
            }
        }
    );
    
    // 3. 执行合同完毕后,查询是否修改成功
    var age;
    await new web3.eth.Contract(abi, contract_addr)
    .methods
    .getAge()
    .call(
        {
            from: accounts[0]
        }
    )
    .then(
        function(result){
            age = result;
        }
    );
    console.log("返回的数据:", age);  // 看看是否成功
});


  • 后面只使用局部代码来说明

状态变量

状态变量的修饰符

  • 对函数也有一样的修饰符。

  • 状态变量四个修饰符:external ,public ,internal 或者 private

    • 状态变量默认是internal
  1. external :
    • 外部函数作为合约接口的一部分,意味着我们可以从其他合约和交易中调用。 一个外部函数 f 不能从内部调用(即 f 不起作用,但 this.f() 可以)。 当收到大量数据的时候,外部函数有时候会更有效率。
  2. public :
    • public 函数是合约接口的一部分,可以在内部或通过消息调用。对于公共状态变量, 会自动生成一个 getter 函数(见下面)。
  3. internal :
    • 这些函数和状态变量只能是内部访问(即从当前合约内部或从它派生的合约访问),不使用 this 调用。
  4. private :
    • private 函数和状态变量仅在当前定义它们的合约中使用,并且不能被派生合约使用。
  • 注意:
    • 合约中的所有内容对外部观察者都是可见的。设置一些 private 类型只能阻止其他合约访问和修改这些信息, 但是对于区块链外的整个世界它仍然是可见的。

修饰符的使用

  • 可见性标识符的定义位置,对于状态变量来说是在类型后面,对于函数是在参数列表和返回关键字中间。
pragma solidity ^0.4.16;

contract ContractStorage {
    uint public data;
}

public修饰符与getter函数

  • 编译器自动为所有 public 状态变量创建 getter 函数。

    • getter函数没有参数,函数名与状态变量名一样。该函数不会接收任何参数并返回一个状态变量。
    • 可以在声明时完成状态变量的初始化。
  • getter 函数具有外部可见性。

    • 如果在内部访问 getter(即没有 this. ),它被认为一个状态变量。
    • 如果它是外部访问的(即用 this. ),它被认为为一个函数。
  • 备注:

    • this变量也可以使用,与C++的意义一样。

Constant 状态变量

  • 状态变量可以被声明为 constant。

    1. 在这种情况下,只能使用那些在编译时有确定值的表达式来给它们赋值。
    2. 任何通过访问 storage,区块链数据(例如 now, this.balance 或者 block.number)或执行数据( msg.gas ) 或对外部合约的调用来给它们赋值都是不允许的。
    3. 在内存分配上有边界效应(side-effect)的表达式是允许的,但对其他内存对象产生边界效应的表达式则不行。
    4. 内建(built-in)函数 keccak256,sha256,ripemd160,ecrecover,addmod 和 mulmod 是允许的(即使他们确实会调用外部合约)。
  • 注意:

    • 不是所有类型的状态变量都支持用 constant 来修饰,当前支持的仅有值类型字符串
  • 例子

    pragma solidity ^0.5.0;

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

函数

函数基础

函数的基本语法

  • 函数的语法:

    • function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]
  • 函数支持与状态变量一样的修饰符。

函数参数

  • 输入参数的声明方式与变量相同。但是有一个例外,未使用的参数可以省略参数名。

函数的返回值

  1. 输出参数的声明方式在关键词 returns 之后,与输入参数的声明方式相同。

  2. 输出参数名可以被省略。输出值也可以使用 return 语句指定。 return 语句也可以返回多值

  3. 返回的输出参数被初始化为 0;如果它们没有被显式赋值,它们就会一直为 0。

  4. 输入参数和输出参数可以在函数体中用作表达式。因此,它们也可用在等号左边被赋值。

  5. 返回多值:

    • return (v0, v1, ..., vn)
    • 必须与returns的返回值列表个数相同。

函数类型

  • 函数分成三种:
    1. view函数(constant函数)
    2. pure函数
    3. fallback函数

view函数

  • 保证不修改状态的函数需要声明成pure函数。下面的语句被认为是修改状态:

    1. 修改状态变量。
    2. 产生事件。
    3. 创建其它合约。
    4. 使用 selfdestruct。
    5. 通过调用发送以太币。
    6. 调用任何没有标记为 view 或者 pure 的函数。
    7. 使用低级调用。
    8. 使用包含特定操作码的内联汇编。
  • constant 是 view 的别名。

  • Getter 方法被标记为 view。

pure函数

  • pure 表示不读取或修改状态。除了上面解释的状态修改语句列表之外,以下被认为是从状态中读取:

    1. 读取状态变量。
    2. 访问 this.balance 或者 <address>.balance。
    3. 访问 block,tx, msg 中任意成员 (除 msg.sig 和 msg.data 之外)。
    4. 调用任何未标记为 pure 的函数。
    5. 使用包含某些操作码的内联汇编。

fallback函数

  • 合约可以有一个未命名的函数。这个函数不能有参数也不能有返回值。

  • 如果在一个到合约的调用中,没有其他函数与给定的函数标识符匹配(或没有提供调用数据),那么这个函数(fallback 函数)会被执行。

  • 除此之外,每当合约收到以太币(没有任何数据),这个函数就会执行。此外,为了接收以太币,fallback 函数必须标记为 payable。 如果不存在这样的函数,则合约不能通过常规交易接收以太币。

  • 在这样的上下文中,通常只有很少的 gas 可以用来完成这个函数调用(准确地说,是 2300 gas),所以使 fallback 函数的调用尽量廉价很重要。

  • 注意:

    • 调用 fallback 函数的交易(而不是内部调用)所需的 gas 要高得多,因为每次交易都会额外收取 21000 gas 或更多的费用,用于签名检查等操作。
    • 请确保您在部署合约之前彻底测试您的 fallback 函数,以确保执行成本低于 2300 个 gas。
  • 以下操作会消耗比 fallback 函数更多的 gas:

    1. 写入存储
    2. 创建合约
    3. 调用消耗大量 gas 的外部函数
    4. 发送以太币
  • fallback函数必须使用external修饰,而不是public。老版本是public。

  • fallback函数例子


pragma solidity >0.4.99 <0.6.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() external { x = 1; }
    uint x;
}


// This contract keeps all Ether sent to it with no way
// to get it back.
contract Sink {
    function() external payable { }
}

函数重载

  • 合约可以具有多个不同参数的同名函数。这也适用于继承函数。

  • 重载函数也存在于外部接口中。如果两个外部可见函数仅区别于 Solidity 内的类型而不是它们的外部类型则会导致错误。

  • 重载解析和参数匹配

    • 过将当前范围内的函数声明与函数调用中提供的参数相匹配,可以选择重载函数。 如果所有参数都可以隐式地转换为预期类型,则选择函数作为重载候选项。如果一个候选都没有,解析失败。
  • 例子 :

pragma solidity ^0.5.0;

contract A {
    function f(uint8 _in) public pure returns (uint8 out) {
        out = _in;
    }

    function f(uint256 _in) public pure returns (uint256 out) {
        out = _in;
    }
}

//调用 f(50) 会导致类型错误,因为 50 既可以被隐式转换为 uint8 也可以被隐式转换为 uint256。 
// 另一方面,调用 f(256) 则会解析为 f(uint256) 重载,因为 256 不能隐式转换为 uint8。

附录

  • 数据位置的指定非常重要,因为它们影响着赋值行为:
    1. 在 存储storage 和 内存memory 之间两两赋值,或者 存储storage 向状态变量(甚至是从其它状态变量)赋值都会创建一份独立的拷贝。
    2. 然而状态变量向局部变量赋值时仅仅传递一个引用,而且这个引用总是指向状态变量,因此后者改变的同时前者也会发生改变。
    3. 另一方面,从一个 内存memory 存储的引用类型向另一个 内存memory 存储的引用类型赋值并不会创建拷贝。

相关文章

  • SOL02:构造器与函数

      区块链2.0的很大特色是引入了智能合约,智能合约的最大好处是可以 把任何交易自动化。当然任何过程的执行都是依赖...

  • 【Dart】Dart类与对象

    类与对象 类 继承 抽象类 接口 混入 泛型 枚举类类-简介 构造器 (构造函数) 默认构造函数与类同名的函数,在...

  • c++之构造函数

    构造函数 构造函数也叫构造器,在对象创建的时候自动调用,一般用于对象成员的初始化。 构造函数与类名同,没有返回值,...

  • 原型链

    一、原型(是个对象) 自定义构造函数(构造器) (2)访问方式一、构造器中包含prototype,可以通过构造函数...

  • C++: 类(下)

    构造函数 定义:构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作 特点: 函数名与类...

  • C++之构造函数基础篇

    构造函数的概述 构造函数:编译器自动调用,如果用户不提供构造函数,编译器会提供一个空的无参构造函数。 类实例化对象...

  • 第二章 Java与Kotlin的写法比较

    3 Java与Kotlin的写法比较 3.1 构造器、变量、常量和静态数据 3.1.1 构造函数 java中的构造...

  • C# 构造函数总结

    构造函数 构造函数分为:实例构造函数,静态构造函数,私有构造函数。 实例构造函数 1、构造函数的名字与类名相同。 ...

  • java面向对象之构造方法

    构造函数(构造方法、构造器、Constructor) 关于java类的构造函数 1、构造方法语法【修饰符列表】构造...

  • C++语法系列之2

    1 默认构造函数(不带参数的构造函数) 1)如果一个类没有显示定义任何构造函数,那么编译器会默认提供一个空的构造器...

网友评论

      本文标题:SOL02:构造器与函数

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