高级 Solidity 主题
在新生课程中,我们研究了一些基本的 Solidity 语法。我们涵盖了变量、数据类型、函数、循环、条件流和数组。
然而,Solidity 还有一些东西,这些东西在大二及以后的编码作业中会很重要。在本教程中,我们将介绍一些更重要的 Solidity 主题。
更喜欢视频?
如果您想从视频中学习,我们在 YouTube 上提供了本教程的录音。它被分成两部分,并有时间戳。通过点击下面的截图来观看视频,或者继续阅读该教程!
第一部分
第二部 分
索引
- 映射
- 枚举
- 结构
- 视图和纯函数
- 函数修改器
- 事件
- 构造函数
- 继承性
- 转移 ETH
- 调用外部合约
- 导入语句
- Solidity 库
映射
Solidity 中的映射就像其他编程语言中的哈希图或字典。它们被用来存储键值对中的数据。
映射是通过语法 mapping (keyType => valueType) 创建的。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract Mapping {
// Mapping from address to uint
mapping(address => uint) public myMap;
function get(address _addr) public view returns (uint) {
// Mapping always returns a value.
// If the value was never set, it will return the default value.
// The default value for uint is 0
return myMap[_addr];
}
function set(address _addr, uint _i) public {
// Update the value at this address
myMap[_addr] = _i;
}
function remove(address _addr) public {
// Reset the value to the default value.
delete myMap[_addr];
}
}
我们还可以创建嵌套映射,其中的键指向第二个嵌套映射。要做到这一点,我们将 valueType 设置为映射本身。
contract NestedMappings {
// 从地址映射 => (从 uint 到 bool 的映射)
mapping(address => mapping(uint => bool)) public nestedMap;
function get(addr1, uint _i) public view returns (bool) {
// 你可以从一个嵌套的映射中获取值
// 即使它没有被初始化
// bool类型的默认值是false
return nestedMap[_addr1][_i]。
}
函数 set(
地址 _addr1,
uint _i,
bool _boo
) 公共 {
nestedMap[_addr1][_i] = _boo;
}
function remove(addr1, uint _i) public {
删除nestedMap[_addr1][_i]。
}
}
枚举
Enum 这个词代表了 Enumerable。它们是用户定义的类型,包含一组常量的可读名称,称为成员。它们通常被用来限制一个变量只能有几个预定义的值中的一个。由于它们只是人类可读常量的抽象,实际上,它们在内部被表示为 uints。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract Enum {
// Enum 代表不同的可能运输状态
enum Status {
待定。
已发货。
已接受。
拒绝。
取消了
}
// 声明一个类型为Status的变量。
// 它只能包含一个预定义的值
状态 public status;
// 因为枚举在内部是用uint表示的
// 这个函数将总是返回一个uint值
// 待处理 = 0
// 已发送 = 1
// 已接受 = 2
// 被拒绝 = 3
// 取消 = 4
// 高于4的值不能被返回
function get() public view returns (Status) {
返回状态。
}
// 传递一个uint作为输入来更新值
function set(Status _status) public {
status = _status;
}
// 更新值到一个特定的枚举成员
function cancel() public {
status = Status.Canceled; // 将设置 status = 4
}
}
结构
结构的概念存在于许多高级编程语言中。它们被用来定义你自己的数据类型,将相关数据组合在一起。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract TodoList {
// Declare a struct which groups together two data types
struct TodoItem {
string text;
bool completed;
}
// Create an array of TodoItem structs
TodoItem[] public todos;
function createTodo(string memory _text) public {
// There are multiple ways to initialize structs
// Method 1 - Call it like a function
todos.push(TodoItem(_text, false));
// Method 2 - Explicitly set its keys
todos.push(TodoItem({ text: _text, completed: false }));
// Method 3 - Initialize an empty struct, then set individual properties
TodoItem memory todo;
todo.text = _text;
todo.completed = false;
todos.push(todo);
}
// Update a struct value
function update(uint _index, string memory _text) public {
todos[_index].text = _text;
}
// Update completed
function toggleCompleted(uint _index) public {
todos[_index].completed = !todos[_index].completed;
}
}
视图和纯函数
你可能已经注意到,我们所写的一些函数在函数头中指定了一个视图或纯函数的关键字。这些是特殊的关键字,表示函数的特定行为。
Getter 函数(那些返回值的函数)可以被声明为 view 或 pure。
- View。不改变任何状态值的函数
- Prue。不改变任何状态值的函数,但也不读取任何状态值
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract ViewAndPure {
// Declare a state variable
uint public x = 1;
// Promise not to modify the state (but can read state)
function addToX(uint y) public view returns (uint) {
return x + y;
}
// Promise not to modify or read from state
function add(uint i, uint j) public pure returns (uint) {
return i + j;
}
}
函数修改器
修改器是可以在一个函数调用之前和/或之后运行的代码。它们通常用于限制对某些函数的访问,验证输入参数,防止某些类型的攻击,等等。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract Modifiers {
address public owner;
constructor() {
// Set the contract deployer as the owner of the contract
owner = msg.sender;
}
// Create a modifier that only allows a function to be called by the owner
modifier onlyOwner() {
require(msg.sender == owner, "You are not the owner");
// Underscore is a special character used inside modifiers
// Which tells Solidity to execute the function the modifier is used on
// at this point
// Therefore, this modifier will first perform the above check
// Then run the rest of the code
_;
}
// Create a function and apply the onlyOwner modifier on it
function changeOwner(address _newOwner) public onlyOwner {
// We will only reach this point if the modifier succeeds with its checks
// So the caller of this transaction must be the current owner
owner = _newOwner;
}
}
事件
事件允许合约在以太坊区块链上执行日志记录。例如,一个给定合约的日志可以在以后被解析,以便在前台界面上执行更新。它们通常被用来让前端界面监听特定的事件并更新用户界面,或者被用作一种廉价的存储形式。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract Events {
// Declare an event which logs an address and a string
event TestCalled(address sender, string message);
function test() public {
// Log an event
emit TestCalled(msg.sender, "Someone called test()!");
}
}
构造函数
构造函数是一个可选的函数,在合约首次部署时执行。你也可以向构造函数传递参数。
P.S.--如果你还记得,我们在大一的加密货币和 NFT 教程中实际上使用了构造函数!
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
contract X {
string public name;
// You will need to provide a string argument when deploying the contract
constructor(string memory _name) {
// This will be set immediately when the contract is deployed
name = _name;
}
}
继承
继承是一个合约可以继承另一个合约的属性和方法的程序。Solidity 支持多重继承。契约可以通过使用 is 关键字来继承其他契约。
注意:我们实际上也在新生轨道加密货币和 NFT 教程中做了继承--我们分别从 ERC20 和 ERC721 合约中继承。
一个父合约如果有一个可以被子合约覆盖的函数,必须被声明为一个虚拟函数。
一个要覆盖父函数的子合约必须使用 override 关键字。
如果父合约共享同名的方法或属性,继承的顺序就很重要。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
/* Graph of inheritance
A
/ \
B C
|\ /|
| \/ |
| /\ |
D E
*/
contract A {
// Declare a virtual function foo() which can be overridden by children
function foo() public pure virtual returns (string memory) {
return "A";
}
}
contract B is A {
// Override A.foo();
// But also allow this function to be overridden by further children
// So we specify both keywords - virtual and override
function foo() public pure virtual override returns (string memory) {
return "B";
}
}
contract C is A {
// Similar to contract B above
function foo() public pure virtual override returns (string memory) {
return "C";
}
}
// When inheriting from multiple contracts, if a function is defined multiple times, the right-most parent contract's function is used.
contract D is B, C {
// D.foo() returns "C"
// since C is the right-most parent with function foo();
// override (B,C) means we want to override a method that exists in two parents
function foo() public pure override (B, C) returns (string memory) {
// super is a special keyword that is used to call functions
// in the parent contract
return super.foo();
}
}
contract E is C, B {
// E.foo() returns "B"
// since B is the right-most parent with function foo();
function foo() public pure override (C, B) returns (string memory) {
return super.foo();
}
}