solidity(基础特性)—学习总结

学习solidity(基本特性)

本文主要是自身学习总结的solidity的一些经验,可以帮助新手小白或已有其他语言基础的开发者快速入门solidity的特性

数组的一些注意事项

返回非固定长度数组使用的修饰符

csharp 复制代码
 function getArr() public view returns (uint256[] memory) {
     return arr;
 }
  • 如果我们在返回时线程的数组我们需要手写一个数组,如果对长度有要求还要设置对应的长度
  • arr 是一个状态变量,存储在 storage 中。
  • 当函数返回 arr 时,Solidity 需要将 arr 的数据复制到一个新的位置,以便将其返回给调用者。
  • memory 关键字指定了这个新的位置是内存。这意味着函数会在内存中创建一个 arr 的副本,并将这个副本返回。

如果不使用 memory 会发生什么?

如果不使用 memory,编译器会报错,因为它不知道将数组存储在哪里。你需要明确指定返回的数组应该存储在 memory 中。

为什么 memory 是必要的:

  • 避免修改原始数据: 通过在 memory 中创建副本,函数可以返回数组的内容,而不会修改存储在 storage 中的原始数组。这对于保持合约状态的完整性非常重要。
  • 临时存储: memory 中的数据是临时的,只在函数调用期间存在。这避免了在合约的 storage 中创建不必要的永久存储,从而节省了 gas 费用。
  • 符合 EVM 规范: 以太坊虚拟机(EVM)要求函数返回的数据必须存储在 memory 中。

总结:

当函数返回动态数组时,必须使用 memory 关键字来指定返回的数组存储在内存中。这确保了函数返回的是原始数组的副本,而不是直接返回对 storage 中数据的引用,从而避免了意外修改合约状态的可能性,并符合EVM规范。

补充说明: 固定大小的数组直接使用unit返回

对于固定大小的数组,例如 uint256[10],在函数返回时通常不需要 memory 关键字,因为它们的大小是固定的,可以直接在栈上处理。但是,对于动态数组(例如 uint256[]),memory 是必不可少的。

数组的pop和push方法

1. push() 方法:

push() 方法用于在数组的末尾添加一个新元素。它有两种用法:

  • push() 如果不带参数调用 push(),则会在数组末尾添加一个零值元素(对于 uint 数组,就是 0;对于 address 数组,就是 address(0),以此类推)。此方法返回数组的新长度。
  • push(x) 如果带一个参数 x 调用 push(x),则会在数组末尾添加元素 x。此方法返回数组的新长度。

2. pop() 方法:

pop() 方法用于移除数组的最后一个元素。它会修改原数组,并返回被移除的元素。

重要注意事项:

  • 只能用于 storage 数组: push()pop() 方法只能用于存储在 storage 中的动态数组。你不能直接对 memory 中的数组使用这些方法。如果你需要在函数内部操作数组,可以使用 memory 数组,但你需要预先分配好数组的大小,并且不能使用 push()pop() 动态调整大小。
  • Gas 消耗: push()pop() 操作会消耗 gas。尤其是在 storage 中操作数组时,gas 消耗会比较高。因此,在设计智能合约时,需要仔细考虑数组的使用,以优化 gas 消耗。
  • 空数组的 pop() 尝试对空数组执行 pop() 操作会导致 revert,因此在执行 pop() 之前,务必检查数组的长度是否大于 0。

总结:

push()pop() 是操作动态数组的有用工具。push() 用于在数组末尾添加元素,pop() 用于移除数组末尾的元素。请记住,这些方法只能用于 storage 数组,并且需要注意 gas 消耗和空数组的情况。

如何在 Solidity 中方法于内存(memory)中创建一个固定大小的数组

scss 复制代码
 function examples() external pure {
     // create array in memory, only fixed size can be created
     uint256[] memory a = new uint256[](5);
 }
  1. function examples() external pure { :

    • function examples(): 这是函数声明,函数名为 examples
    • external: 这个关键字表示该函数只能从合约外部调用,不能从合约内部调用。
    • pure: 这个关键字表示该函数既不读取合约的状态变量,也不修改合约的状态变量。它只使用传入的参数进行计算,并返回结果。这意味着该函数执行不会消耗 gas 来读取或写入存储。
  2. uint256[] memory a = new uint256[](5); :

    • uint256[] memory a: 这部分声明了一个名为 a 的变量,其类型是 uint256 类型的动态数组,并且存储位置是 memorymemory 关键字至关重要,因为它指定了数组存储在内存中,而不是持久存储在合约的存储(storage)中。
    • = new uint256[](5);: 这部分是数组的初始化。new uint256[](5) 创建了一个新的 uint256 类型的数组,其大小(长度)为 5。这意味着该数组可以存储 5 个 uint256 类型的元素。new 关键字用于在内存中分配空间。

关键点和解释:

  • 内存数组的特点: 内存数组是临时的,它们只在函数执行期间存在。当函数执行完毕后,内存中的数据会被释放。
  • 只能创建固定大小的内存数组: 在 Solidity 中,你只能 使用 new 关键字创建固定大小的内存数组。你不能使用 new 创建动态大小的内存数组,然后通过 push() 或其他方法动态调整其大小。如果需要可变大小的数组,你需要使用 storage 数组。
  • pure 函数和内存数组: 由于 pure 函数不能访问或修改状态变量,因此在 pure 函数中使用 memory 数组是很常见的。因为函数本身不与链上存储交互,所以所有操作都在本地的内存中完成。
  • storage 数组的区别: storage 数组是持久的,它们存储在合约的状态变量中,并在合约的多次调用之间保持不变。memory 数组是临时的,它们只在函数执行期间存在。
  • gas 消耗: 在内存中创建数组的 gas 消耗相对较低,因为不需要写入链上存储。

示例说明:

假设你想在函数内部进行一些数组操作,而不想修改合约的永久状态。你可以使用以下模式:

solidity 复制代码
 pragma solidity ^0.8.0;
 ​
 contract ArrayExample {
 ​
     function processData(uint256[] memory input) external pure returns (uint256) {
         uint256[] memory processed = new uint256[](input.length);
         for (uint256 i = 0; i < input.length; i++) {
             processed[i] = input[i] * 2; // 对输入数据进行处理
         }
         uint256 sum = 0;
         for (uint256 i = 0; i < processed.length; i++) {
             sum += processed[i];
         }
         return sum;
     }
 }

在这个例子中,processData 函数接收一个输入数组 input,在内存中创建一个新的数组 processed,对输入数据进行处理,然后返回处理后的结果之和。所有这些操作都在内存中完成,不会影响合约的存储状态。

总而言之,uint256[] memory a = new uint256[](5); 这行代码在内存中创建了一个可以容纳 5 个 uint256 元素的固定大小数组,并且该数组只在函数执行期间存在。这在需要临时存储和处理数据的场景中非常有用,尤其是在 pureview 函数中。

new关键字的用法

我们前面已经使用new关键字在合约中定义对应长度的数组

那我们还有哪些常用的方法呢?

new 关键字在 Solidity 中主要用于创建新的合约实例。这意味着它会在区块链上部署一个新的合约,并返回新合约的地址。除了你已经了解的在内存中创建数组之外,new 关键字在合约中最重要的用法就是创建合约实例。

以下是 new 关键字在合约中的用法和作用的详细解释:

1. 创建合约实例:

这是 new 关键字最常见的用法。它允许一个合约(称为"创建者合约")创建另一个合约(称为"被创建合约")的新实例。

语法:

solidity 复制代码
 address newContractAddress = new ContractName{value: amount}(constructorArguments);
  • ContractName:被创建合约的名称。
  • {value: amount}:可选部分,用于在创建合约时发送以太币。amount 是要发送的以太币数量(以 wei 为单位)。
  • (constructorArguments):可选部分,用于传递给被创建合约构造函数的参数。

示例:

solidity 复制代码
 pragma solidity ^0.8.0;
 ​
 contract ContractA {
     uint256 public value;
 ​
     constructor(uint256 _value) {
         value = _value;
     }
 }
 ​
 contract ContractB {
     ContractA public contractA;
 ​
     function createContractA(uint256 _value) public {
         contractA = new ContractA{value: 1 ether}(_value); // 创建 ContractA 的新实例,并发送 1 ether
     }
 ​
     function getContractAValue() public view returns (uint256) {
         return contractA.value();
     }
 }

在这个例子中:

  • ContractBcreateContractA 函数使用 new ContractA{value: 1 ether}(_value) 创建了 ContractA 的一个新实例。
  • {value: 1 ether} 表示在创建 ContractA 的实例时发送 1 个以太币。
  • (_value)_value 参数传递给 ContractA 的构造函数。
  • 创建的新实例的地址被存储在 ContractBcontractA 状态变量中。

2. 重要注意事项:

  • 构造函数: 被创建合约必须有一个构造函数(constructor),用于初始化合约的状态变量。传递给 new 关键字的参数会传递给被创建合约的构造函数。
  • 以太币发送: 可以使用 {value: amount} 在创建合约时发送以太币。这对于需要初始资金才能运行的合约非常有用。
  • gas 消耗: 使用 new 关键字创建合约会消耗大量的 gas,因为需要在区块链上部署新的合约代码。
  • 错误处理: 如果合约创建失败(例如,由于 gas 不足、堆栈溢出或构造函数抛出异常),整个交易会回滚。
  • 合约代码必须已知: 使用new创建合约时,被创建合约的完整代码在创建合约的合约编译时必须已知,因此不可能存在递归创建依赖。

3. 与直接赋值的区别:

不要将 new 关键字与直接赋值混淆。例如:

ini 复制代码
 ContractA public contractA = ContractA(someAddress); // 错误!这是类型转换,而不是创建新实例

这行代码不是 创建 ContractA 的新实例。它只是将 contractA 变量指向 someAddress 处的现有合约。如果 someAddress 处没有部署 ContractA 的合约,或者部署的合约代码不兼容,则会导致错误。

总结:

new 关键字是 Solidity 中用于创建新合约实例的关键工具。它允许合约动态地创建其他合约,并与其进行交互。使用 new 关键字时,请务必注意构造函数、以太币发送、gas 消耗和错误处理。

除了创建合约实例,在较新的Solidity版本中,new 关键字也用于在memory中创建数组,但这个数组的大小必须在创建时就指定,不能动态调整大小(不能使用push或pop)。这是它和storage数组的重要区别。

合约地址的理解

以下是对合约中地址的详细解释,包括合约自身地址、所有者地址和其他地址:

1. 地址类型 (address):

address 类型可以存储任何以太坊账户或合约的地址。它可以用于:

  • 发送以太币。
  • 调用其他合约的函数。
  • 检查地址的余额。

从 Solidity 0.5.0 版本开始,address 分为两种类型:

  • address:普通地址,不能直接发送以太币。
  • address payable:可支付地址,可以接收和发送以太币。

你需要使用类型转换将 address 转换为 address payable 才能发送以太币,例如:payable(someAddress).transfer(amount)

2. 合约自身的地址 (address(this)):

address(this) 返回当前合约的地址。this 是一个特殊的关键字,指代当前合约实例。

示例:

solidity 复制代码
 pragma solidity ^0.8.0;
 ​
 contract MyContract {
     function getContractAddress() public view returns (address) {
         return address(this);
     }
 ​
     function sendEtherToSelf() public payable {
         // 将合约自身余额发送到另一个地址
         payable(0x...).transfer(address(this).balance);
     }
 }

在这个例子中,getContractAddress() 函数返回 MyContract 合约的地址。

3. 所有者的地址:

合约通常有一个所有者,拥有特殊的权限,例如升级合约、提取资金等。所有者的地址通常存储在合约的一个状态变量中。

示例:

solidity 复制代码
 pragma solidity ^0.8.0;
 ​
 contract OwnedContract {
     address public owner;
 ​
     constructor() {
         owner = msg.sender; // 将部署合约的人设置为所有者
     }
 ​
     modifier onlyOwner() {
         require(msg.sender == owner, "Only owner can call this function.");
         _;
     }
 ​
     function withdraw() public onlyOwner {
         payable(owner).transfer(address(this).balance);
     }
 }

在这个例子中:

  • owner 是一个状态变量,存储合约所有者的地址。
  • 构造函数将部署合约的人(msg.sender)设置为所有者。
  • onlyOwner 修饰器用于限制只有所有者才能调用 withdraw() 函数。

4. 其他地址:

合约可以与其他合约或外部账户进行交互。这些交互需要使用其他合约或账户的地址。

示例:

solidity 复制代码
pragma solidity ^0.8.0;

interface OtherContract {
    function getValue() external view returns (uint256);
}

contract MyContract {
    OtherContract public otherContract;

    constructor(address _otherContractAddress) {
        otherContract = OtherContract(_otherContractAddress);
    }

    function getOtherContractValue() public view returns (uint256) {
        return otherContract.getValue();
    }

    function sendEtherToOther(address payable _to, uint256 _amount) public {
        _to.transfer(_amount);
    }
}

在这个例子中:

  • MyContractOtherContract 进行交互。OtherContract 的地址在构造函数中传递给 MyContract
  • sendEtherToOther 函数向指定的地址发送以太币。

5. msg.sender

msg.sender 是一个全局变量 ,表示当前消息的发送者。在合约的上下文中,msg.sender 通常是调用合约函数的外部账户或另一个合约的地址。

总结:

  • address 类型用于表示以太坊地址。
  • address(this) 返回当前合约的地址。
  • 所有者地址用于控制合约的权限。
  • 合约可以与其他合约或外部账户通过地址进行交互。
  • msg.sender 表示当前消息的发送者。

理解地址的概念对于编写安全的智能合约至关重要。你需要正确处理地址,以避免潜在的安全漏洞,例如未经授权的访问或资金损失。

payable修饰符的使用

payable 关键字在 Solidity 中用于指定函数或地址可以接收以太币(Ether)。这是处理以太币转移的关键概念,理解 payable 的使用对于编写涉及价值转移的智能合约至关重要。

1. payable 修饰符用于函数:

当一个函数被声明为 payable 时,它表示该函数可以接收随交易发送的以太币。如果没有 payable 修饰符,函数就不能接收以太币,任何试图向该函数发送以太币的交易都会被拒绝。

示例:

solidity 复制代码
pragma solidity ^0.8.0;

contract PayableExample {
    address payable public owner; // 可支付的地址

    constructor() {
        owner = payable(msg.sender); // 将部署者设置为所有者,并转换为可支付地址
    }

    // 可接收以太币的函数
    function deposit() public payable {
        // msg.value 包含随交易发送的以太币数量(以 wei 为单位)
        // 这里可以将收到的以太币记录下来,例如增加一个余额变量
    }

    function getBalance() public view returns (uint256) {
        return address(this).balance; // 获取合约的以太币余额
    }

    function withdraw(uint256 _amount) public {
        require(msg.sender == owner, "Only owner can withdraw.");
        require(address(this).balance >= _amount, "Insufficient balance.");
        payable(owner).transfer(_amount); // 将以太币转移给所有者
    }
}

在这个例子中:

  • deposit() 函数使用 payable 修饰符声明,因此可以接收以太币。
  • msg.value 是一个全局变量,表示随交易发送的以太币数量(以 wei 为单位)。在 deposit() 函数中,你可以使用 msg.value 来处理接收到的以太币,例如更新合约的余额。
  • withdraw函数演示了如何将合约中的以太币转出。注意payable(owner).transfer(_amount)的用法,只有payable类型的地址才能调用transfer方法。
  • address(this).balance 用于获取合约当前的以太币余额。

2. payable 类型用于地址:

在 Solidity 0.5.0 之后,地址类型被分为 addressaddress payable 两种。

  • address:普通地址,不能直接接收或发送以太币。
  • address payable:可支付地址,可以接收和发送以太币。

你需要使用类型转换将 address 转换为 address payable 才能发送以太币。

我们也可以在声明address变量时直接加上对应的payable修饰符

示例:

solidity 复制代码
pragma solidity ^0.8.0;

contract PayableExample {
    address public someAddress;
    address payable public payableAddress;

    function setAddress(address _addr) public {
        someAddress = _addr;
        payableAddress = payable(_addr); // 将 address 转换为 address payable
    }

    function sendEther() public payable{
        payableAddress.transfer(1 ether);
    }
}

在这个例子中,payable(_addr)address 类型的 _addr 转换为 address payable 类型,然后赋值给 payableAddress。只有payableAddress才能调用transfer方法发送以太币。

3. 使用 transfer()send()call() 发送以太币:

只有 address payable 类型的地址才能使用以下方法发送以太币:

  • transfer(uint256 amount) 将指定数量的以太币发送到地址。如果发送失败(例如,由于 gas 不足或接收地址不存在),则会抛出异常(revert)。这是推荐的发送以太币的方式,因为它会进行 gas 安全检查。
  • send(uint256 amount) 将指定数量的以太币发送到地址。如果发送失败,则返回 false,而不是抛出异常。你需要手动检查返回值以确保发送成功。不推荐使用,因为它不会进行gas安全检查,容易导致重入攻击。
  • call{value: uint256}(bytes memory) 执行低级调用,可以用于发送以太币并执行其他操作。这提供了最大的灵活性,但也需要更谨慎地处理 gas 和错误。

示例:

solidity 复制代码
pragma solidity ^0.8.0;

contract PayableExample {
    address payable public recipient;

    function sendEtherWithTransfer() public payable {
        recipient.transfer(msg.value); // 使用 transfer 发送以太币
    }

    function sendEtherWithSend() public payable returns (bool) {
        bool success = recipient.send(msg.value); // 使用 send 发送以太币
        require(success, "Send failed."); // 手动检查发送是否成功
        return success;
    }

    function sendEtherWithCall() public payable returns (bool) {
        (bool success, ) = recipient.call{value: msg.value}(""); // 使用 call 发送以太币
        return success;
    }
}

总结:

  • payable 关键字用于指定函数或地址可以接收以太币。
  • msg.value 用于获取随交易发送的以太币数量。
  • 你需要将 address 转换为 address payable 才能发送以太币。
  • 推荐使用 transfer() 方法发送以太币,因为它会进行 gas 安全检查。

理解 payable 的使用对于编写安全的智能合约至关重要。你需要正确处理以太币的转移,以避免潜在的安全漏洞,例如重入攻击或资金损失。

自定义结构体类型

声明结构体数组:

接下来,你需要在合约中声明一个结构体数组,用于存储 Person 类型的多个实例。

示例:

solidity 复制代码
pragma solidity ^0.8.0;

contract StructArrayExample {
    struct Person {
        string name;
        uint256 age;
        address payable wallet;
    }

    Person[] public people; // 声明一个公共的 Person 类型的动态数组
    // ...
}

Person[] public people; 声明了一个名为 people 的公共动态数组。public 关键字会自动生成一个 getter 函数,允许外部调用者访问数组中的元素。

结构体数组的应用场景:

  • 存储用户信息: 如上面的例子所示,可以使用结构体数组来存储用户的信息,例如姓名、年龄、地址等。
  • 管理产品信息: 可以使用结构体数组来存储产品的各种属性,例如名称、价格、描述等。
  • 记录事件日志: 可以使用结构体数组来记录合约中发生的各种事件,例如交易记录、状态变更等。
  • 实现投票系统: 可以使用结构体数组来存储候选人信息和投票结果。

7. 存储类型:

结构体数组可以存储在 storage(持久化存储在区块链上)或 memory(临时存储在内存中)。

  • storage 适合存储需要永久保存的数据。例如 Person[] public people;
  • memory 适合在函数内部临时使用。例如 string[] memory names = new string[](people.length);

总结:

结构体数组是 Solidity 中一种强大的数据组织方式,可以有效地管理复杂的数据集合。通过结合结构体和数组,你可以创建更灵活、更强大的智能合约。在使用结构体数组时,请注意边界检查、存储位置(storagememory)以及 gas 消耗等因素。

修饰符的使用

date location

1. Storage(存储)

  • 用途: 用于持久化存储合约的状态变量。存储在 storage 中的数据会永久保存在区块链上,合约的每次状态变更都会产生交易并消耗 gas。

  • 特点:

    • 持久性:数据在合约的整个生命周期内都存在。
    • 成本高:读写 storage 的 gas 成本最高。
    • 合约状态:用于存储合约的核心数据,例如账户余额、用户数据、合约参数等。
  • 使用场景:

    • 需要长期保存的数据。
    • 合约的核心状态变量。
    • 需要在不同函数调用之间共享的数据。
  • 声明方式: 在合约内部直接声明的变量,默认存储在 storage 中。例如:uint256 public myNumber;

2. Memory(内存)

  • 用途: 用于存储函数执行期间的临时变量。函数执行完毕后,memory 中分配的内存会被自动释放。

  • 特点:

    • 临时性:数据只在函数执行期间存在。
    • 成本较低:读写 memory 的 gas 成本比 storage 低。
    • 局部变量:用于存储函数内部的临时数据,例如循环计数器、中间计算结果等。
  • 使用场景:

    • 函数内部的局部变量。
    • 函数参数(引用类型,如 string、数组、结构体)。
    • 在函数内部创建的临时数据结构。
  • 声明方式: 在函数内部声明变量时,使用 memory 关键字显式声明。例如:string memory myString;

3. Calldata(调用数据) (只能读取不能修改)

  • 用途: 用于存储函数调用的输入数据。它是一个只读的数据区域,用于接收外部调用传递的参数。

  • 特点:

    • 只读性:只能读取 calldata 中的数据,不能修改。
    • 成本最低:读取 calldata 的 gas 成本最低。
    • 外部输入:用于存储函数调用时传递的参数。
  • 使用场景:

    • 函数参数(引用类型,如 string、数组、结构体),特别是外部函数和接口函数的参数。
    • 避免不必要的复制,提高 gas 效率。
  • 声明方式: 在函数参数列表中,使用 calldata 关键字显式声明。例如:function myFunction(string calldata myString) public;

特性 Storage Memory Calldata
持久性 持久 临时 只读(临时)
成本 较低 最低
用途 存储合约状态 存储临时变量 存储函数输入
生命周期 合约生命周期 函数执行期间 函数调用期间
可修改性 可修改 可修改 不可修改
默认位置 合约状态变量 函数内部局部变量 函数参数(外部)

何时使用 storagememorycalldata

  • 需要永久保存的数据: 使用 storage。例如,存储用户的账户余额、代币数量、合约拥有的数据等。
  • 函数内部的临时变量: 使用 memory。例如,在循环中使用的计数器、函数内部的中间计算结果、需要修改的函数参数副本等。
  • 函数参数(特别是外部调用): 优先使用 calldata。这可以节省 gas 成本,因为 calldata 是只读的,不需要复制到 memory 中。

重要注意事项:

  • 尽量减少Storage的读写次数以节省Gas。

  • 在复杂数据操作中,优先考虑Memory

  • 静态数据类型如固定大小的数组或基本类型不需要指定数据位置。

  • 在函数内部,如果需要修改引用类型的参数,应该先将其复制到 memory 中,然后再进行修改。例如:

    solidity 复制代码
    function myFunction(string calldata _myString) public returns (string memory) {
        string memory myStringCopy = _myString; // 将 calldata 复制到 memory
        // 现在可以修改 myStringCopy
        myStringCopy = string(abi.encodePacked(myStringCopy, " added"));
        return myStringCopy;
    }
  • 对于数组和结构体等复杂类型,storagememory 之间复制数据会消耗大量的 gas。因此,应尽量减少不必要的复制。

  • Solidity 0.8.0 版本之后,函数参数的默认数据位置不再是 storage,而是根据参数类型推断。对于引用类型(如 string、数组、结构体),如果函数是 external 的,则默认为 calldata;否则默认为 memory

通过理解 storagememorycalldata 之间的区别,你可以编写更高效、更安全的 Solidity 智能合约。合理地选择数据存储位置,可以有效地降低 gas 消耗,并提高合约的性能。

可见修饰符(关于外部调用合约之后在接口提到)

这些修饰符决定了函数可以从哪里被调用。

  • public

    • 任何地方都可以调用该函数,包括合约内部、外部账户以及其他合约。
    • 对于状态变量,声明为 public 会自动生成一个 getter 函数,允许外部访问该变量
    • 这是最常用的可见性修饰符。
  • private

    • 只能在当前合约的内部调用该函数。派生合约(继承的合约)无法访问 private 函数。
    • 通常用于实现合约内部的辅助函数或逻辑。
  • internal

    • 可以在当前合约及其派生合约内部调用该函数。外部账户或其他合约无法直接调用 internal 函数。
    • 用于实现合约及其子合约共享的逻辑
    • 我们有时候在构造函数中会调用用internal修饰的函数,使其在函数部署之前执行
  • external

    • 只能从合约外部调用该函数。合约内部调用 external 函数需要使用 this.functionName()
    • external 函数的参数数据存储在 calldata 中,而不是 memory 中,这可以节省 gas
    • 通常用于处理大量数据或需要节省 gas 的场景。
    • 我们一些函数只推荐使用该修饰符,如receive函数主要接受来自外部的函数,而内部不需要要调用

总结表格:

可见性修饰符 当前合约内部 派生合约内部 外部账户/合约
public
private
internal
external this.f()

状态修饰符

这些修饰符描述了函数如何与区块链状态进行交互。

  • view

    • 函数可以读取合约的状态变量,但不能修改它们。
    • 调用 view 函数不会消耗 gas(从外部调用时),因为它不会产生状态变更的交易。
    • 用于查询合约状态的函数。
  • pure

    • 函数既不能读取也不能修改合约的状态变量。它只能使用函数参数进行计算。
    • 调用 pure 函数也不会消耗 gas(从外部调用时)。
    • 用于执行与合约状态无关的纯计算。

3. 其他修饰符:

  • payable

    • 允许函数接收以太币。调用 payable 函数时,可以附加 msg.value 来发送以太币。
    • 用于处理涉及货币转移的函数,例如支付、充值等。
  • 自定义修饰符:

    • 可以使用 modifier 关键字定义自己的修饰符,用于在函数执行前后添加一些通用的代码逻辑,例如权限检查、输入验证等。

示例:

Solidity

javascript 复制代码
pragma solidity ^0.8.0;

contract ModifierExample {
    address public owner;
    uint256 public counter;

    constructor() {
        owner = msg.sender;
        counter = 0;
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "Only owner can call this function.");
        _; // 下划线表示函数体将在此处执行
    }

    function increment() public {
        counter++;
    }

    function decrement() public view returns (uint256) {
        return counter - 1;
    }

    function reset() public onlyOwner {
        counter = 0;
    }

    function externalFunc(uint256 a) external pure returns(uint256){
        return a*2;
    }

    function internalFunc(uint256 a) internal pure returns(uint256){
        return a*2;
    }

    function testInternal(uint a) public returns(uint){
        return internalFunc(a);
    }

    function payMe() public payable returns (uint256) {
        require(msg.value > 0, "Please send some Ether.");
        return msg.value;
    }
}

在这个例子中:

  • onlyOwner 是一个自定义修饰符,用于检查调用者是否是合约的所有者。
  • increment 是一个 public 函数,可以被任何地方调用。
  • decrement 是一个 view 函数,可以读取 counter 的值,但不能修改它。
  • reset 是一个 public 函数,使用了 onlyOwner 修饰符,只有合约所有者才能调用。
  • payMe 是一个 payable 函数,可以接收以太币。
  • externalFunc 是一个 external 函数,只能从外部调用。
  • internalFunc 是一个 internal 函数,只能从合约内部调用。
  • testInternal 是一个 public 函数,演示了如何在合约内部调用 internalFunc

总结:

理解这些 Solidity 的函数修饰符对于编写高质量的智能合约至关重要。通过合理地使用这些修饰符,可以控制函数的访问权限、状态修改能力,并提高合约的效率和安全性。希望以上解释能够帮助你更好地理解各种函数修饰符的区别。

相关推荐
我是前端小学生1 个月前
单个solidity合约的文件结构
solidity
天涯学馆1 个月前
从零到英雄:Solidity 智能合约开发全攻略
后端·智能合约·solidity
MavenTalk1 个月前
Move开发语言在区块链的开发与应用
开发语言·python·rust·区块链·solidity·move
jc0803kevin2 个月前
solidity call使用
区块链·solidity
jc0803kevin2 个月前
solidity selfdestruct合约销毁
区块链·solidity
jc0803kevin2 个月前
solidity的struct对象,web3j java解析输出参数
java·web3·solidity
Keegan小钢2 个月前
智能合约开发工具Remix
web3·智能合约·solidity
yoona10203 个月前
盲拍合约:让竞拍更公平与神秘的创新解决方案
区块链·学习方法·solidity·remix·盲拍合约
0x派大星3 个月前
Solidity 设计模式:实现灵活与可扩展的智能合约架构
设计模式·架构·web3·区块链·智能合约·solidity