solidity第九课——事件与继承
来到基础课程中的难点与重点课,事件与继承。
一,事件
事件是合约区块链通讯的一种机制。应用“监听”某些事件,并作出反应。solidity中的事件具有两个特点:
1.响应,应用程序(ether.js)可以通过RPC接口订阅和监听这些事件,并在前端做出反应。
2.经济,事件是EVM上比较经济的存储数据的方式。
以代币ERC20合约实例一般格式为:
event Transfer(address indexed from, address indexed to, uint256 value);
我们可以看到,Transfer
事件共记录了3个变量from
,to
和value
,分别对应代币的转账地址,接收地址和转账数量。同时from
和to
前面带着indexed
关键字,每个indexed
标记的变量可以理解为检索事件的索引“键”,在以太坊上单独作为一个topic
进行存储和索引,程序可以轻松的筛选出特定转账地址和接收地址的转账事件。每个事件最多有3个带indexed
的变量。每个 indexed
变量的大小为固定的256比特。事件的哈希以及这三个带indexed
的变量在EVM
日志中通常被存储为topic
。其中topic[0]
是此事件的keccak256
哈希,topic[1]
到topic[3]
存储了带indexed
变量的keccak256
哈希。
value
不带 indexed
关键字,会存储在事件的 data
部分中,可以理解为事件的“值”。data
部分的变量不能被直接检索,但可以存储任意大小的数据。因此一般 data
部分可以用来存储复杂的数据结构,例如数组和字符串等等,因为这些数据超过了256比特,即使存储在事件的 topic
部分中,也是以哈希的方式存储。另外,data
部分的变量在存储上消耗的gas相比于 topic
更少。我们可以在函数里释放事件。在下面的例子中,每次用_transfer()
函数进行转账操作的时候,都会释放Transfer
事件,并记录相应的变量。
// 定义_transfer函数,执行转账逻辑
function _transfer(
address from,
address to,
uint256 amount
) external {
_balances[from] = 10000000; // 给转账地址一些初始代币
_balances[from] -= amount; // from地址减去转账数量
_balances[to] += amount; // to地址加上转账数量
// 释放事件
emit Transfer(from, to, amount);
}
二,继承
这里介绍solidity中的继承(inheritance),包括简单继承,多重继承,以及修饰关键词(modifier)和构造函数(constructor)的继承。继承是面向对象编程很重要的组成部分,可以显著减少重复代码。如果把合约看作是对象的话,solidity也是面向对象的语言可以继承。
virtual:父合约里的函数,如果希望子合约重写,需要加上virtual关键字。
override:子合约重写了父合约中的函数,需要加上override关键字。
来看一串代码吧!
contract Doge{
function catchphrase() public returns(String){
return "So Wow cryptoDoge";
}
}
contract BabyDoge is Doge{
function anotherCatchphrase() public returns(String){
return "Such Moon BabyDoge";
}
}
由于BabyDoge是从Doge那里继承来的。意味着当你编译和部署了BabyDoge,它将可以访问catchphrase()和anotherCatchphrase()和其他我们在Doge中的其他公共函数,用继承逻辑表达子类。
solidity中的modifier同样可以继承,用法和函数继承类似,在相应的地方加virtual和override关键字即可。
contract Base1 {
modifier exactDividedBy2And3(uint _a) virtual {
require(_a % 2 == 0 && _a % 3 == 0);
_;
}
}
contract Identifier is Base1 {
//计算一个数分别被2除和被3除的值,但是传入的参数必须是2和3的倍数
function getExactDividedBy2And3(uint _dividend) public exactDividedBy2And3(_dividend) pure returns(uint, uint) {
return getExactDividedBy2And3WithoutModifier(_dividend);
}
//计算一个数分别被2除和被3除的值
function getExactDividedBy2And3WithoutModifier(uint _dividend) public pure returns(uint, uint){
uint div2 = _dividend / 2;
uint div3 = _dividend / 3;
return (div2, div3);
}
}
构造函数的继承:
子合约有两种方法继承父合约的构造函数。举个简单的例子,父合约A
里面有一个状态变量a
,并由构造函数的参数来确定:
// 构造函数的继承
abstract contract A {
uint public a;
constructor(uint _a) {
a = _a;
}
}
1.在继承时声明父构造函数的参数,例如:contract B is A(1)
2.在子合约的构造函数中声明构造函数的参数,例如:
contract C is A {
constructor(uint _c) A(_c * _c) {
}
}
调用父合约的函数
子合约有两种方式调用父合约的函数,直接调用和利用super关键字。
1.直接调用:格式为父合约.函数名()
2.super关键字:子合约可以利用super.函数名()来调用最近的父合约函数。solidity继承关系按声明时从右到左的顺序。
function catchphraseSuper() public{
super.__()
}