0xAA

Posted on Apr 04, 2022Read on Mirror.xyz

WTF Solidity极简入门:3. 函数类型

我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。

推特@0xAA_Science

WTF Academy社群: 官网 wtf.academy | discord | 微信群申请

所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity

Solidity中的函数

solidity官方文档里把函数归到数值类型,但我觉得差别很大,所以单独分一类。我们先看一下solidity中函数的形式:

function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)]

看着些复杂,咱们从前往后一个一个看(方括号中的是可写可不写的关键字):

1. function:声明函数时的固定用法,想写函数,就要以function关键字开头。

2. ():圆括号里写函数的参数,也就是要输入到函数的变量类型和名字。

3. {internal|external|public|private} :函数可见性说明符,一共4种。没标明函数类型的,默认internal。

  • public: 内部外部均可见,并且自动给stoage变量生成 getter 函数

  • private: 只能从本合约内部访问,继承的合约也不能用。

  • external: 只能从合约外部访问(但是可以用this.f()来调用,f是函数名)

  • internal: 只能从合约内部访问,继承的合约可以用。

4. [pure|view|payable]:决定函数权限/功能的关键字。payable很好理解,带着它的函数,运行的时候可以给合约转入ETH。pure和view的介绍见下一节。

5. [returns ()]:函数返回的变量类型和名称。

到底什么是Pure和View?

我刚开始学solidity的时候,一直不理解pure跟view关键字,因为别的语言没有类似的关键字。solidity加入这两个关键字,我认为是因为gas fee。合约的状态变量存储在链上,gas fee很贵,如果不改写这些变量,就不用付gas。调用pure跟view的函数是不需要付gas的。

我画了一个马里奥插画,帮助大家理解。在插画里,我把合约中的状态变量(存储在链上)比作碧池公主,三种不同的角色代表不同的关键字。

WTH is pure and view in solidity?

pure,中文意思是“纯”,在solidity里理解为“纯纯牛马”。包含pure关键字的函数,不能读取也不能写入存储在链上的状态变量。就像小怪一样,看不到也摸不到碧池公主。

view,“看”,在solidity里理解为“看客”。包含view关键字的函数,能读取但也不能写入状态变量。类似马里奥,能看到碧池,但终究是看客,不能入洞房。

不写pure也不写view,函数既可以读取也可以写入状态变量。类似马里奥里的boss,可以对碧池公主为所欲为🐶。

代码

1. pure v.s. view

我们在合约里定义一个状态变量 number = 5。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract FunctionTypes{
    uint256 public number = 5;

定义一个add() function,每次调用,输出 number + 1。

    // 默认
    function add() external{
        number = number + 1;
    }

如果add()包含了pure关键字,例如 function add() pure external,就会报错。因为pure(纯纯牛马)是不配读取合约里的状态变量的,更不配改写。那pure函数能做些什么?举个例子,你可以给函数传递一个参数 _number,然后让他返回 _number+1。

    // pure: 纯纯牛马
    function addPure(uint256 _number) external pure returns(uint256 new_number){
        new_number = _number+1;
    }

如果add()包含view,比如function add() view external,也会报错。因为view能读取,但不能够改写状态变量。可以稍微改写下方程,让他不改写 _number,而是返回一个新的变量。

    // view: 看客
    function addView() external view returns(uint256 new_number) {
        new_number = number + 1;
    }

2. Internal v.s. External

    // internal: 内部
    function minus() internal {
        number = number - 1;
    }

    // 合约内的函数可以调用内部函数
    function minusCall() external {
        minus();
    }

我们定义一个internal的minus()函数,每次调用使得number变量减1。由于是internal,只能由合约内部调用。我们再定义一个external的minusCall()函数,调用minus()。这样,人们就能通过调用minusCall()来间接调用internal的minus()。

3. Payable

    // payable: 递钱,能给合约支付eth的函数
    function minusPayable() external payable returns(uint256 balance) {
        minus();    
        balance = address(this).balance;
    }

我们定义一个external payable的minusPayable()函数,间接的调用minus(),并且返回合约里的ETH余额(this关键字可以让我们引用合约地址)。我们调用minusPayable()时,往合约里转入1个ETH。

我们可以在返回的信息中看到,合约的余额是1 ETH。

注意:如果想在部署合约的时候往合约里转账,则需要给合约的构造函数声明payable

constructor() payable {}

总结

在第三讲,我们介绍了solidity中的函数类型,比较难理解的是pure和view,在其他语言中没出现过。solidity拥有pure和view两种关键字主要是为了节省gas fee和控制函数权限,这两种方程都是不消耗gas的。下一讲我们会介绍引用和映射两种类型,并介绍更复杂的函数。