在solidity中,函数的四种可见性区别和联系

  • Louis
  • 更新于 2024-08-28 15:52
  • 阅读 558

基本概念:在Solidity中,函数的可见性(Visibility)定义了谁可以访问该函数。主要有四种可见性修饰符:private(私有)、internal(内部)、public(公共)和external(外部)。

基本概念:

在Solidity中,函数的可见性(Visibility)定义了谁可以访问该函数。主要有四种可见性修饰符:private(私有)、internal(内部)、public(公共)和external(外部)。它们的区别如下:

private(私有):

  • 只能在定义该函数的合约内部调用,不能被外部调用
  • 继承该合约的子合约无法访问private函数。
contract MyContract {
    uint private data;

    function privateFunction() private {
        // 只能在这个合约内部调用
    }
}

internal(内部):

  • 可以在定义该函数的合约内部调用,和private修饰符一样,也是不能外部调用的。
  • 继承该合约的子合约也可以访问internal函数。
  • internal是默认的函数可见性修饰符。
contract MyContract {
    uint internal data;

    function internalFunction() internal {
        // 可以在这个合约和子合约中调用
    }
}

public(公共):

  • 可以在合约内部、继承合约的子合约中、以及外部(例如通过交易或外部合约调用)访问。
  • 当你从合约外部(例如通过交易或其他合约)调用public函数时,Solidity会生成一个与该public函数相对应的外部接口,使得它能够被外部调用。
  • 编译器会自动为public状态变量创建getter函数
contract MyContract {
    uint public data; // 自动生成一个 getter 函数

    function publicFunction() public {
        // 任何人都可以调用
    }
}

external(外部):

  • 只能从合约外部调用。不能从合约的内部调用该函数(但可以使用this.functionName()调用)。
  • public函数更节省 gas,因为参数不需要从内存复制到calldata
contract MyContract {
    uint public data;

    function externalFunction() external {
        // 只能通过外部调用
    }
}

为什么external public 函数更加节省gas?

让我们首先看一个示例:

contract GasComparison {
    // Public function
    function publicFunction(uint[] memory data) public pure returns (uint) {
        return data.length;
    }

    // External function
    function externalFunction(uint[] calldata data) external pure returns (uint) {
        return data.length;
    }
}

// 调用示例
contract Caller {
    GasComparison public gc = new GasComparison();

    function callPublic(uint[] memory data) public {
        gc.publicFunction(data);
    }

    function callExternal(uint[] memory data) public {
        gc.externalFunction(data);
    }
}

这涉及到Solidity中的内存管理和gas优化,是一个比较深入的话题。

  1. 内存(memory) vs 调用数据(calldata):

    • memory是一个临时的存储区域, 用于在函数执行期间存储数据。
    • calldata是一个特殊的数据位置, 包含函数调用的输入数据, 它是只读的, 并且不会被复制。
  2. public函数的行为:

    • 当你调用一个public函数时, 无论是从合约内部还是外部调用, 参数都会被复制到内存中。
    • 在我们的例子中,publicFunction使用memory关键字, 意味着data数组会被复制到内存中。
  3. external函数的行为:

    • external函数只能从合约外部调用。
    • 它们可以直接读取calldata中的数据,而不需要将其复制到内存中。
    • 在我们的例子中,externalFunction使用calldata关键字,直接从调用数据中读取data数组。
  4. Gas节省:

    • 复制大型数组或结构体到内存是一个昂贵的操作,会消耗大量gas。
    • 通过直接从calldata读取数据,external函数避免了这个复制步骤,从而节省了gas。
    • 对于大型输入数据,这种节省可能会非常显著。
  5. 使用场景:

    • 如果您知道一个函数只会从合约外部调用,并且它接受大型数组或结构体作为参数,使用external可能会更有效率。
    • 但是, 如果函数需要修改输入数据或在内部被多次调用,public函数可能更合适。

需要注意的是,这种gas节省主要适用于大型数据结构。对于简单的值类型(如uint, bool等),差异可能不明显。 这就是为什么在某些情况下,external函数比public函数更节省gas的原因。它避免了不必要的数据复制,直接利用了以太坊虚拟机(EVM)的底层机制。

什么时候使用memory修饰符,什么时候使用calldata修饰符?

在Solidity中,memorycalldata是两种用于指定数据存储位置的关键字,它们在函数参数和局部变量中的使用有所不同。理解何时使用memorycalldata对优化gas消耗和确保正确的数据处理非常重要。

memory 修饰符

  • 临时数据存储memory表示数据只在函数执行期间临时存储。函数执行完毕后,这些数据会被自动销毁。
  • 可读可写memory中的数据是可变的(即可以修改)。如果你需要在函数中对数据进行修改,通常使用memory
  • 常用于内部处理:当你在函数内部处理数据并且需要修改这些数据(例如复制、排序、变换),你会将数据存储在memory中。
使用场景
  1. 需要修改参数数据:如果你需要在函数内部修改传入的参数数据,使用memory
  2. 创建 局部变量:当你需要创建一个临时的数组、结构体或其他复杂数据类型用于内部计算时,使用memory
function modifyData(uint[] memory data) public {
    // 这里 data 是可变的,可以被修改
    data[0] = 42;
}

calldata 修饰符

  • 只读 数据存储calldata表示数据直接从调用者的输入中读取(即从事务的输入数据中读取),它是只读的,无法修改。
  • 节省gas:因为calldata中的数据不需要在内存中进行复制,且是只读的,所以在处理较大数据结构(如数组)时,使用calldata可以节省gas。
  • 只能用于外部函数的参数calldata只能用于外部函数(external函数)的参数,因为它直接映射到事务的输入数据。
使用场景
  1. 外部函数的 只读 参数:如果函数参数只会被读取,而不会被修改,且这个函数是external的,使用calldata
  2. 优化gas消耗:当处理大数据结构时,使用calldata可以避免不必要的内存复制,从而节省gas。
function processData(uint[] calldata data) external {
    // 这里 data 是只读的,无法修改
    uint value = data[0];
}

memory vs calldata 总结

使用memory

  • 当你需要在函数内部修改传入的数据。
  • 当你需要在内部创建和使用复杂的数据结构(如数组、结构体)时。
  • 当函数是publicinternal,而非external

使用calldata

  • 当数据不需要被修改,仅用于读取。
  • 当你希望在外部函数(external)中处理大数据结构且需要优化gas消耗时。

举例对比

contract Example {
    // 使用 memory:数据可以被修改
    function modifyMemory(uint[] memory data) public {
        data[0] = 1; // 修改了传入的数据
    }

    // 使用 calldata:数据不可修改,但节省gas
    function readCalldata(uint[] calldata data) external {
        uint value = data[0]; // 只能读取,不能修改
    }
}

通过合理使用memorycalldata,你可以在优化gas消耗的同时确保函数的正确性和效率。

总结

  • private:只能在合约内部调用,子合约不能访问。
  • internal:只能在合约内部或继承的子合约中调用。
  • public:可以被任何人、包括合约内外部调用。
  • external:只能从合约外部调用,不能在合约内部直接调用(除非通过 this 关键字)。
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。
该文章收录于 Solidity从入门到进阶
5 订阅 22 篇文章

0 条评论

请先 登录 后评论
Louis
Louis
web3 developer,技术交流或者有工作机会可加VX: magicalLouis