Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
MagicalBridge opened this issue Aug 29, 2024 · 0 comments
Open

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

MagicalBridge opened this issue Aug 29, 2024 · 0 comments

Comments

@MagicalBridge
Copy link
Owner

基本概念:

在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是一个特殊的数据位置, 包含函数调用的输入数据, 它是只读的, 并且不会被复制。
  1. public函数的行为:
  • 当你调用一个public函数时, 无论是从合约内部还是外部调用, 参数都会被复制到内存中。
  • 在我们的例子中,publicFunction使用memory关键字, 意味着data数组会被复制到内存中。
  1. external函数的行为:
  • external函数只能从合约外部调用。
  • 它们可以直接读取calldata中的数据,而不需要将其复制到内存中。
  • 在我们的例子中,externalFunction使用calldata关键字,直接从调用数据中读取data数组。
  1. Gas节省:
  • 复制大型数组或结构体到内存是一个昂贵的操作,会消耗大量gas。
  • 通过直接从calldata读取数据,external函数避免了这个复制步骤,从而节省了gas。
  • 对于大型输入数据,这种节省可能会非常显著。
  1. 使用场景:
  • 如果您知道一个函数只会从合约外部调用,并且它接受大型数组或结构体作为参数,使用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 关键字)。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant