Solidity 的函数输出

· 1503 字 · 3 分钟 · 黄国政

个人的细碎理解 #

今天在对 pureview 两个状态可变性修饰符进行辨析时,进一步理解了智能约合中的结构——用我自己的话来概括,就是包括「对状态变量保持敏感」、「什么时候该用 pureview」,以及「函数参数列表何时填入内容,填入什么」。

首先是一段基础代码。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

contract FunctionTypes{
    // 状态变量
    uint256 public number = 5;

    // 普通函数
    function add() external{
        number = number + 1;
  }

}

在《理解 Solidity 语法的基本结构》的博文里,我已经详细写过了区别,这里想简要复现部分内容以整理知识。

结论先上,对状态变量的敏感有助于我快速判断什么时候该用 pureview 可变性修饰符,以下面的代码为例。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

contract FunctionTypes{
    // 状态变量
    uint256 public number = 5;
    
    // 普通函数
    function add() external pure{
      number = number + 1;
    }

    function add() external view{
      number = number + 1;
    }
}

从代码开头便可以清楚地知道状态变量是 number,数据存储结构是 uint256,因而两个普通函数的写法显然都是错误的——函数体中都直接读取和修改了状态变量 number,但 pure 既无法读取也无法修改,view 则是只能读取但无法修改。

如果要正确改写,可以如下所示。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

contract FunctionTypes{
    // 状态变量
    uint256 public number = 5;

    // 普通函数
    function addPure(uint256 _number) external pure returns(uint256 new_number1){
        new_number1 = _number + 1;
    }
  
    function addView() external view returns(uint256 new_number2) {
        new_number2 = number + 1;
    }
}

这里可以阐述两点,其一是 pureview 的具体非读取与读取功能的表现,其二是我过去一直不理解的——函数的参数列表中啥时候该填入内容,又该填入什么

第一,可以看到含 pure 的函数在函数体中是完全没有涉及状态变量 number,而是给函数 addPure() 传递了一个参数 _number,并规定好数据类型是 uint256,接着再在函数体中创造了一个局部变量 new_number1,并赋值为 _number + 1,最后返回。

这里我们可以理解,如果要在函数体中加入新的参数,不涉及状态变量,可以在函数的参数列表里声明加入的参数名称(如_number),并指定其类型(如 uint256)。

第二,可以看到含 view 的函数在函数体里涉及了状态变量 number,但是并没有改变 number 本身,而是读取了 number,在此基础上创建了局部变量 new_number2,并赋值为 number + 1,最后返回。

这部分没有在函数的参数列表中加入新的参数,而是直接读取状态变量。

函数输出 #

1. 函数返回值 #

Solidity 中的函数输出相关的关键词是 returnsreturn

returns 跟在函数名之后,用于声明返回的变量类型与变量名。 return 则是指定具体的变量值。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

contract ReturnDemo{
    
    function returnDemo() public pure returns(uint256, bool, uint256[3] memory){
        return(1, true, [uint256(1),2,3] );
    }
}

在 Remix IDE 中编译和部署上述代码后,查看左下角的 returnDemo,会显示如下信息:

0:uint256: 1
1:bool: true
2:uint256[3]: 1,2,3

总结起来就是通过 returns 的关键字来声明有多个返回值以及返回值类型的 returnDemo() 函数,然后在函数主体再通过 return() 来确定具体的返回值。uint256[3] 声明了长度为 3 而且类型为 uint256 的数组作为返回值,而由于 [1,2,3] 会被默认为 uint8(3),因此 [uint256(1),2,3] 中的首个元素必须强制转换为 uint256 来声明该数组内的元素都是这种类型。另外,数组类型返回值默认必须用 memory 来进行修饰。

2. 命名式返回 #

在函数返回值一节中已说到 returns 可以声明返回的变量名,如果这么做,Solidity 会初始化这些变量,自动返回变量值而无需再使用 return。上一节的做法是只声明了变量类型,这节我们就同时声明变量名和变量类型,这样在函数主体中为几个变量赋值就可以了。

function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){
    _number = 1;
    _bool = true;
    _array = [uint256(1),2,3];
}

在 Remix IDE 中编译和部署后运行返回结果如下:

0:uint256: _number 1
1:bool: _bool true
2:uint256[3]: _array 1,2,3

但在命名式返回中仍然可以使用 return

// 命名式返回,依然支持return
function returnNamed2() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){
    return(1, true, [uint256(1),2,5]);
}

3. 解构式赋值 #

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;

contract ReturnDemo{

    function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){
    _number = 1;
    _bool = true;
    _array = [uint256(1),2,3];
}

    function readReturn() public pure{
        uint256 _number;
        bool _bool;
        uint256[3] memory _array;
        (_number, _bool, _array) = returnNamed();

        // 读取部分返回值
        (, _bool, ) = returnNamed();

    }

}

其实我没有看懂「解构式赋值」这部分,但不想一直卡在一个地方,等后续碰到相关题目或者代码时再结合具体语境搞清楚。