暂时的一事无成也代表将来万事皆有可能!
目录
一、对Solidity文件的理解
二、Solidity的导sol文件(库、合约)
三、Solidity的继承
1.继承的分类
2.继承的可见性
3.父合约构造函数的传参
4.调用父合约成员
5.重写
四、Solidity的抽象合约
五、Solidity接口合约
实现接口
六、Solidity的库
库合约的存在形式
七、Solidity的对象
Solidity文件是Solidity语言文件,里面包含Solidity语言代码,文件名以.sol结尾。
一个sol文件可以创建(含有)许多的contract(合约),不单单只有一个
那同一个sol文件中的各个合约如何实现交互呢?
不同的sol文件的各个合约又如何进行交互呢?
答案是:继承和对象
接下来就要讲到交互的方式
通过
关键字:import sol文件名.sol (文件的相对位置)
可以进行导入外部sol文件,导的sol文件和本身的sol文件相当于变成同一个sol合约
具体操作
import关键字使用:导入其他源文件
1. imporot "solidity文件名" :导入该文件全局作用域到当前全局作用域中
2.import * as 别名 "solidity文件名" : 导入该文件全局作用域到当前全局作用域中
并起一个文件别名,通过别名可创建导入文件下某一合约的对象
3.import "solidity文件名"as 别名 :和2效果一样,书写比2简单,经常用
4.imoport {合约名 as 别名,合约名} from "solidity文件夹名" :导入导入文件夹名
下的某几个合约到该文件中
像java类的继承一样,在solidity中,那就是合约的继承
Solidity 语言是一种面向对象的编程语言,提供了对合约继承的支持,继承是扩展合约功能的一种方式。
Solidity 语言的合约继承通过关键字 is 来实现。
继承的关键字:is
继承的本质:继承的实现方案是代码拷贝,所以合约继承后,部署到网络时,将变成一个合约,代码从父类拷贝到子类中。
被继承的合约我们称之为父合约(基类合约),继承了的合约称之为子合约(派生合约)
Solidity中合约继承的重要特点
- 派生合约可以访问父合约所有非私有private的成员,包括内部方法和状态变量。但是不允许使用this(代表需要使用创建对象的形式来访问external的东西)
- 如果函数签名保持不变,则允许函数重写,如果派生合约与父合约函数对应输出参数不同,编译将失败
- 使用super关键字或父合约名调用父合约函数,或者直接调用
- 在多重继承的情况下,使用super的父合约函数调用,优先选择被最多继承的合约
单继承:
一个合约继承一个合约
例如:
contract X {}
contract A is X{}
多继承
一个合约被多个合约继承
例如
contract X {}
contract A is X{}
contract B is X{}
多层继承
但可以有爷爷,祖先,而这就是多重继承
合约被合约继承,继承的合约又被另一个合约继承
注意事项:
当多重继承合约时,这些父合约中不允许出现相同的状态变量名
当多重继承合约时,这些父合约中允许出现相同的函数名,事件名,修改器名
例如
contract X {}
contract A is X{}
contract B is A{}
多重继承示例
Solidity 语言提供了对合约继承的支持,而且支持多重继承。
Solidity 语言的多重继承采用线性继承方式。继承顺序很重要,判断顺序的一个简单规则是按照“最类似基类”到“最多派生”的顺序指定基类。
第一种情况:基类 X,Y 没有继承关系,派生类 Z 继承了 X,Y。
X Y / \ \ / Z多重继承格式
子合约 is 父合约1,父合约2····{
}
例
contract X {}
contract A {}
contract B is X,A{}
子合约不能访问父合约的private修饰的私有成员
子合约可以直接访问访问父合约的所有非私有(public、internal)成员
子合约不能直接访问父合约的external修饰的外部成员,因为子合约不能对父合约的东西使用this,想要访问到就必须创建父合约对象的方式来调用间接访问
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract A{
uint stateVar;//状态变量默认是internal权限
function somePublicFun() public{}
function someInternalFun() internal{}
function somePrivateFun() private{}
function someExternalFun() external{}
}
contract B is A{
function call() public {
//访问父类的`public`方法
somePublicFun();
//访问父类的状态变量(状态变量默认是internal权限)
stateVar = 10;
//访问父类的`internal`方法
someInternalFun();
//不能访问`private`
//somePrivateFun();
A a = new A();
a.someExternalFun();
}
}
派生的合约初始化前需要调用所有父合约的构造函数,并且还需要提供所有父合约所需的参数
提供父合约构造函数需要的所有参数,有两种提供方式
第一种称
在声明继承的时候
派生合约名 is 父合约名(参数) { }
此方式对于构造函数是常量情况的时候比较方便,可以直接说明合约的行为
第二种称
在派生合约的构造器中
constructor(参数列表) 父合约名(参数列表){
}
注意:如果是多重继承,那么同理
派生合约名 is 父合约名(参数),·····{ }
constructor(参数列表) 父合约名(参数列表),·······{
}
当一个合约从多个合约继承时,在区块链上只有一个合约被创建,所有基类合约的代码被编译到创建的合约中。这意味着对基类合约函数的所有内部调用也只是使用内部函数调用
1.直接调用和使用(适用于单继承,或者其他继承但名不冲突的情况下)
2. 利用父合约名(这样更好,适用多种类型的继承)
3.super关键字 (使用super可以访问父合约的函数,但不能访问状态变量,如果是多层和多重继承,那么它将调用所有的父类的该名的方法)
派生合约不可以再声明已经是基类合约中可见的状态变量具有相同的名称。即状态变量不允许重写
但是函数名可以是相同的,假如函数名相同参数列表不同,则定为是函数重载
若函数名相同,参数类型也相同,则定为是函数重写
在子合约中允许重写函数,但不允许重写返回参数,即返回值类型和个数不能修改
重写步骤
1.父合约要被重写的函数必须加上能被重写修饰符 virtual
2.子合约重写的函数上面必须加上重写修饰符 override
温馨提示:
midifier函数修改器(后面讲)也会随着父合约被继承从而被继承,被继承后,在子合约中我们还可以对父合约中的修改器进行重写和使用
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Person{
string public name;
uint public age;
function getSalary() external pure virtual returns(uint){
return "unkown";
}
}
contract Employee is Person{
function getSalary() external pure override returns(uint){
return 3000;
}
}
抽象合约和其他语言的抽象类相似,它是一个合约中没有函数体(具体函数实现)的函数(直接以;结尾, 没有函数体标识符{})的合约
关键字: abstract
使用格式:
abstract contract 合约名 {
状态变量;
未实现的函数;
·················
}
抽象合约的作用:
抽象合约 abstract 的作用是将函数定义和具体实现分离,从而实现解耦、可拓展性,其使用规则为
简单来说:抽象合约定义后,是专门用来继承用的,它相比于其他的继承做了一种强迫的事情,就是子合约必须重写抽象合约里面的所有函数
abstract还可以避免子合约不对父合约进行构造函数传参的问题
注意事项
- 当合约中有未实现的函数时,则合约必须修饰为abstract;
- 当合约继承的基合约中有构造函数,但是当前合约并没有对其进行传参时,则必须修饰为abstract;
- abstract合约中未实现的函数必须在子合约中实现,即所有在abstract中定义的函数都必须有实现;
- abstract合约不能单独部署,必须被继承后才能部署;
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;
abstract contract Animal {
string public species;
constructor(string memory _base) {
species = _base;
}
}
abstract contract Feline {
uint public num;
function utterance() public pure virtual returns (bytes32);
function base(uint _num) public returns(uint, string memory) {
num = _num;
return (num, "hello world!");
}
}
// 由于Animal中的构造函数没有进行初始化,所以必须修饰为abstract
abstract contract Cat1 is Feline, Animal {
function utterance() public pure override returns (bytes32) { return "miaow"; }
}
contract Cat2 is Feline, Animal("Animal") {
function utterance() public pure override returns (bytes32) { return "miaow"; }
}
接口和抽象合约类似,也是只定义函数,不实现函数,由实现接口的合约来实现,不同的是,同时还有以下限制
不能继承其他合约或者接口
不能定义构造器
不能定义变量
不能定义结构体
不能定义枚举类
相当于单单定义函数
还有一个相对于抽象合约的特点就是,定义函数的时候它不需要写virtual,且函数修饰符都是external
接口关键字:interface
定义格式:
interface 合约名 {
定义接口函数1;
·········;
定义接口函数n;
}
实现接口和继承一样,使用关键字is
实现接口函数用override修饰符
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;
interface Bank{
function outputMoney(uint _money) external ;
function intputMoney(uint _money) external ;
}
contract own is Bank {
uint public money;
function outputMoney(uint _money) external override {
money+=_money;
}
function intputMoney(uint _money) external override {
money-=_money;
}
}
Solidity 智能合约中通用的代码可以提取到库 library,以提高代码的复用性和可维护性。
库 library 是智能合约的精简版,就像智能合约一样,位于区块链上,包含可以被其他合约使用的代码。
库合约一般定义成public和internal权限,定义成 external 毫无意义,因为库合约函数只在内部使用,不独立运行。同样,定义成 private 也不行,因为其它合约无法使用。
对比普通合约来说,库有以下限制
- 无状态变量
- 不能继承和被继承
- 不能接收以太币
- 不能销毁一个库
代表只能有函数
关键字:library
格式:
library 库名{
库方法
·····
}
使用库 library 的合约,可以将库合约视为隐式的父合约,当然它们不会显式的出现在继承关系中。也就是不用写 is 来继承,直接可以在合约中使用。
库的使用
如果库和使用库的合约在同一个sol文件,那么即可直接用
第一种方式
库名.库合约函数(参数值1,参数值2····) 的方式直接调用库里面的函数
第二种方式
再使用using 库合约名 for 数据类型
使用 using for 语法附着的数据类型,在使用的时候,可以直接用
. 的形式调用,而且省略代表自己的第一个参数。 格式:
using 库名 for 使用的参数类型
然后使用
格式: 使用的参数类型对应的变量.函数名(参数值2 ) //该变量默认为第一个参数
如果库的使用库的合约不在同一个sol文件,那么需要
先导包import
库 library 有两种存在形式:
- 内嵌(embedded):当库中所有的方法都是internal时,此时会将库代码内嵌在调用合约中,不会单独部署库合约;
- 链接(linked):当库中含有external或public方法时,此时会单独将库合约部署,并在调用合约部署时链接link到库合约。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
library Math {
function find(uint[] storage arr, uint val) internal view returns(uint){
for (uint i=0; i
Solidity的对象可以帮助我们访问到非继承合约里面的函数,但不能访问到里面的状态变量,它好比于是两个合约之间的媒人
对象的创建方式
合约名 对象名 = new 合约名()
这就完成了该合约对象的创建,创建完该对象后,那么创建该对象的合约就可以使用这个对象去调用对象对应合约的函数
常用于
子合约访问父合约external的状态变量和函数
使用非继承关系合约的函数
具体操作
1. solidty文件夹名 对象名= new solidity文件夹名()
2.文件别名 对象名=new 文件别名()
3.文别名.合约名 对象名 =new 文别名.合约名()
4. 合约别名 对象名 =new 合约别名()
5. 合约名 对象名 =new 合约名()
当然也可以不起别名,那就用solidity文件夹名,或合名
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Math {
int[2] arr;
function aa() public view returns(int[2] memory){
return arr;
}
}
contract MathTest {
//创建Math合约对象
Math m = new Math();
function get() public view returns(int[2] memory){
//调用Math合约的东西
return m.aa();
}
}