solidity基础知识
Tim Chen(motion$) Lv5

基础知识目录

  • Solidity数据类型
  • Solidity运算符
  • Solidity控制结构
  • Solidity函数
  • Solidity事件
  • Solidity修饰器

类型和值

  • 所有的数据类型都有默认值,如果没有赋值,就是默认值。也有一定的数值范围。
  • 它们通常还包含增、删、改、查4个操作中的其中一个或者几个。(set/get, push/pop, delete)
  • 数据类型也包含基础类型和复合类型两种。
    • 基础类型:整数,浮点数,布尔值,地址,字节。
    • 复合类型:数组,枚举,结构体,映射。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract dataType{
uint public uint_min = type(uint).min;
uint public uint_max = type(uint).max;
uint public uint_def;

int public int_min = type(int).min;
int public int_max = type(int).max;
int public int_def;

address public addr;
bool public bol;
}
点我查看图片:solidity数据类型大小范围
- 有符号整数,无符号整数
类型 默认值 最小值 最大值
int(默认: int256) 0 -2**255 2**255-1
int128 0 -2**127 2**127-1
int64 0 -2**63 2**63-1
int32 0 -2**31 2**31-1
int16 0 -2**15 2**15-1
int8 0 -2**7 2**7-1
uint(默认: uint256) 0 0 2**256-1
uint128 0 0 2**128-1
uint64 0 0 2**64-1
uint32 0 0 2**32-1
uint16 0 0 2**16-1
uint8 0 0 2**8-1
  • 字节,布尔值
类型 默认值
address 0x0…(total 40 zeros)
|bool false

数组 - array(增、删、改、查)

  • Copilot生成:
  • 在memory中不能定义动态数组,不能使用push()和pop()方法
  • 动态数组只能存在于状态变量中,stableVariable
  • 固定数组可以存在于memory中,也可以存在于状态变量中
  • 固定数组的长度是固定的,不能改变;动态数组的长度是动态的,可以改变
  • 固定数组的长度是在编译时确定的,动态数组的长度是在运行时确定的
  • 固定数组的长度是在定义数组的时候确定的,动态数组的长度是在定义数组的时候确定的,也可以在运行时确定

枚举 - enum(改、查)

  • 枚举:是对一个对象的所有可能取到的值的集合。
  • set的时候,传入的参数是数字0,1,2…,如果不在枚举定义的范围内,会报错。
  • delete会重置默认值,默认值是枚举定义中的第一个值0。

结构体 - struct (增、删、改、查)

  • 结构体:是对一个对象的所有属性的集合。
  • push()方法

映射 - mapping (增、删、改、查)

  • delete map[key],不会删除value,而是把value重置为默认值了

函数返回值 - returns

  • Copilot生成:
  • 函数返回值可以有多个,也可以没有返回值
  • 函数返回值可以是任何类型,包括自定义的类型
  • 函数返回值可以是内部函数,也可以是外部函数
  • 函数返回值可以是可见的,也可以是不可见的
  • 函数返回值可以是常量,也可以是变量
  • 函数返回值可以是单个值,也可以是数组,映射,结构体等复合类型

pure view 关键字

  • view > pure

  • ChatGPT生成:

  • 在 Solidity 中,pureview 关键字用于标识函数的状态修改行为,以便在编译时进行优化和验证。它们之间的主要区别在于函数对合约状态的访问和修改:

    1. pure 函数:
    • pure 函数声明表示函数不访问合约的状态变量,也不修改状态。它是一个纯函数,仅通过输入参数来计算并返回结果。
    • pure 函数不会访问合约的存储区域,也不会调用其他合约或发送以太币。
    • 使用 pure 关键字声明的函数对于调用者和其他合约的状态都是完全透明的,可以保证函数的执行结果不受外部环境的影响。
    1. view 函数:
    • view 函数声明表示函数只读取合约的状态变量,而不修改状态。它也是一个纯函数,因为它不会改变合约的状态。
    • view 函数可以访问合约的存储区域,但不能对其进行修改。它也不能调用其他修改状态的函数。
    • 使用 view 关键字声明的函数可以在不消耗 gas 的情况下被调用,因为它们不会改变区块链的状态。
  • 总的来说,pureview 关键字都用于标识函数的纯度和对状态的访问和修改行为,但pure 函数不允许访问状态变量,而 view 函数允许读取状态变量但不允许修改。

异常报错 - error

  • gas费用:require > assert > error
  • asser():断言,不能带说明信息,如果断言失败,会消耗所有的gas,所有的状态变量都会被重置,所以不要使用断言。一般不会用,通常在调试的时候使用。

修饰器 - modifier

  • 类似于java中的注解,可以在函数前面加修饰器,修饰器可以用来检查函数的输入,比如检查函数的调用者是否是合约的拥有者,是否是合约的管理员,是否是合约的成员等等。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;

contract PauseFuncion{
bool public paused;
uint public num;

function setPause(bool _paused) external {
paused = _paused;
}

modifier whenNotPaused(){
require(!paused, "paused");
_; // _;代表被调用的代码块如果符合条件会在这里继续运行
}


function inc() external whenNotPaused{
// require(!paused, "paused");
num += 1;
}

function dec() external whenNotPaused{
// require(!paused, "paused");
num -= 1;
}
}

payable关键字

  • ChatGPT生成: 在 Solidity 中,payable 是一个关键字,用于标记函数或变量可以接收以太币。它通常用于两个地方:
  1. 函数修饰符: 使用 payable 修饰符标记的函数可以接收以太币。这些函数可以被其他账户发送以太币调用,并且可以执行向其他账户发送以太币的操作。

  2. 合约构造函数: 在合约构造函数中,如果你想要合约在部署时接收以太币,你可以将构造函数标记为 payable

  • 以下是使用 payable 的两个示例:
  1. 函数修饰符:
    1
    2
    3
    4
    5
    6
    7
    8
    pragma solidity ^0.8.0;

    contract MyContract {
    // 接收以太币的函数,标记为 payable
    function receiveEther() public payable {
    // 函数逻辑
    }
    }
  • 在这个示例中,receiveEther 函数被标记为 payable,表示它可以接收以太币。调用这个函数时,可以通过 msg.value 获取发送的以太币数量。
  1. 构造函数:
    1
    2
    3
    4
    5
    6
    7
    8
    pragma solidity ^0.8.0;

    contract MyContract {
    // 合约构造函数,标记为 payable
    constructor() payable {
    // 构造函数逻辑
    }
    }
  • 在这个示例中,合约的构造函数被标记为 payable,这意味着在部署合约时可以发送以太币。在构造函数中,可以通过 msg.value 获取发送的以太币数量。

payable 中transfer(), send(), call()的区别

  • ChatGPT生成: 在 Solidity 中,payable 关键字用于标记合约函数,以表示该函数可以接收以太币。当一个合约需要处理以太币时,通常会使用 transfersendcall 函数之一来执行支付操作。这些函数之间有一些区别,下面是它们的简要比较:
  1. transfer

    • transfer 是 Solidity 中最简单的发送以太币的方法。
    • 它是 address 类型的成员函数,只能发送固定的 2300 gas,并且在失败时会抛出异常(即抛出异常后所有状态改变都会被回滚)。
    • 它被认为是比较安全的发送以太币的方式,因为它不允许发生回调(也就是说,接收方不能执行代码以影响交易的结果)。
  2. send

    • send 函数与 transfer 函数相似,但它返回一个布尔值来指示发送是否成功,而不是抛出异常。
    • send 函数会发送所有可用的 gas(最多 2300 gas),但是在发送失败时不会抛出异常,而是返回 false
    • 这个函数通常用于与外部地址进行简单的支付操作,但不建议在需要确保交易原子性的情况下使用。
  3. call

    • call 函数是最通用的发送以太币的方法,它允许发送任意数量的 gas,并且返回一个布尔值来指示调用是否成功。
    • call 函数可以传递额外的数据和设置 gas 限制,也可以捕获执行过程中的异常。
    • 这个函数通常用于与合约进行交互,或者需要更复杂逻辑的支付操作。
  • 总的来说,transfer 是最简单且最安全的发送以太币的方法,适用于大多数情况。send 函数在需要检查支付是否成功时比较有用,但在复杂场景下不够灵活。call 函数是最通用的方法,可以满足各种需求,但需要更小心地处理异常情况和 gas 的使用。

public internal external private关键字

修饰变量:internal public private

  • 权限开放程度:public > internal > private
  • public:公开的,自动生成getter()函数,可以在合约内部外部访问。
  • private: 私有的,只能在当前合约中访问,不会生成getter()函数,也不能被子合约继承。
  • internal:只能在当前合约或子合约中访问
    • 适用情况 ChatGPT生成:

    • internal 关键字通常用于需要在当前合约及其派生合约中共享和访问的状态变量。它提供了一种在合约内部实现继承和代码重用的方式,并且可以限制对变量的访问权限,以提高合约的安全性和封装性。

      • 以下是一些常见的情况下使用 internal 修饰变量的示例:
      1. 共享状态数据: 如果你希望在当前合约及其派生合约中共享和访问某个状态变量的值,但不希望它被合约外部的调用者直接访问,你可以将该状态变量声明为 internal

      2. 内部状态追踪: 在复杂的合约中,可能会有一些状态需要在多个函数之间共享和更新。使用 internal 修饰的状态变量可以确保这些状态只能在合约内部被访问和修改,从而降低了出错的可能性。

      3. 继承和重用: 在使用继承的合约结构中,internal 修饰的状态变量可以在父合约和子合约之间共享,从而实现代码的重用和扩展。子合约可以直接访问父合约中的 internal 变量,而不需要重新定义相同的变量。

      • 总的来说,使用 internal 修饰变量可以在合约内部提供数据共享和封装的能力,同时限制对变量的访问权限,使得合约更加安全和可靠。

修饰函数: internal external public private

  • 权限开放程度:public > internal > external > private

  • public:公开的,可以在合约内部外部访问。

  • private: 私有的,只能在当前合约中访问,不能被子合约继承。

  • internal:只能在当前合约或子合约中访问.

  • external:外部函数,只能在外部调用,不能在合约内部调用。

  • ChatGPT生成: 在 Solidity 中,internalexternal 是用于修饰函数的访问权限的关键字,它们在修饰函数时有以下区别:

    1. Internal(内部):

      • 使用 internal 关键字修饰的函数只能在当前合约及其派生合约内部调用,无法被合约外部的调用者访问。这种访问权限对于实现合约内部逻辑提供了保护和封装。
      • 示例:
        1
        2
        3
        4
        5
        6
        contract MyContract {
        // 内部函数
        function internalFunction() internal {
        // 函数逻辑
        }
        }
    2. External(外部):

      • 使用 external 关键字修饰的函数只能通过外部调用方式来访问,不能在合约内部被直接调用。这种访问权限通常用于合约的接口函数,供合约外部的调用者调用。
      • 示例:
        1
        2
        3
        4
        5
        6
        contract MyContract {
        // 外部函数
        function externalFunction() external {
        // 函数逻辑
        }
        }
    • 总的来说,internal 修饰的函数适用于合约内部调用和继承,而 external 修饰的函数适用于合约外部调用。这两种修饰符的选择取决于你希望函数被调用的方式以及对函数的访问权限的要求。

msg 关键字

  • ChatGPT生成: 在 Solidity 中,msg 是一个全局预定义变量,用于提供有关当前交易或消息的信息。它是一个包含以下属性的特殊对象:
  1. msg.sender:返回当前交易或消息的发送者(即合约调用者的地址)。
  2. msg.value:返回当前交易发送的以太币数量(以 wei 为单位)。
  3. msg.data:返回当前消息的数据。
  4. msg.gas:返回当前交易还剩余的 gas 数量。
  • 这些属性可以在合约的函数中使用,以便根据当前交易或消息的上下文进行逻辑判断或处理。

  • 以下是一个简单的示例,演示了如何在 Solidity 中使用 msg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
pragma solidity ^0.8.0;

contract MyContract {
address public owner;
uint256 public valueReceived;

// 构造函数,在部署合约时设置合约的拥有者
constructor() {
owner = msg.sender;
}

// 接收以太币的函数
receive() external payable {
// 更新接收到的以太币数量
valueReceived = msg.value;
}

// 提取合约中的以太币的函数
function withdraw() external {
require(msg.sender == owner, "Only the owner can withdraw");

// 向合约拥有者发送合约中的所有以太币
payable(owner).transfer(address(this).balance);
}
}
  • 在这个示例中,我们使用了 msg.sendermsg.value 属性:
  • 在构造函数中,owner 变量被设置为部署合约的账户地址,即 msg.sender
  • receive 函数中,接收到的以太币数量被记录在 valueReceived 变量中,并且该函数被标记为 externalpayable,表示它可以接收以太币。
  • withdraw 函数中,我们检查调用者是否是合约的拥有者(即 msg.sender == owner),然后将合约中的所有以太币发送给拥有者地址。

address(this)

  • ChatGPT生成: 在 Solidity 中,address(this) 是一个特殊的表达式,用于获取当前合约的地址。它通常用于以下场景:
  1. 合约内部调用: 当合约需要获取自身的地址时,可以使用 address(this) 表达式来获取。例如,合约可以在部署时记录自己的地址,或者在与其他合约进行交互时将自己的地址传递给它们。

  2. 支付操作: 当合约需要将自己的地址作为收款地址进行支付操作时,可以使用 address(this) 表达式来获取。例如,在向其他地址发送以太币或者接收以太币时,合约可以使用 address(this) 表达式作为收款地址。

  • 以下是一些示例,演示了 address(this) 的用法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pragma solidity ^0.8.0;

contract MyContract {
address public contractAddress;

// 构造函数,在部署时记录合约地址
constructor() {
contractAddress = address(this);
}

// 向指定地址发送以太币
function sendEther(address payable receiver) external payable {
// 发送以太币给指定地址
receiver.transfer(msg.value);
}
}
  • 在这个示例中,合约在构造函数中使用 address(this) 表达式来获取自身的地址,并将其记录在 contractAddress 变量中。在 sendEther 函数中,合约可以使用 address(this) 表达式作为收款地址来接收以太币。
 评论