1. create contract instance
创建合约的语法:
new 合约名();
示例:
contract A {}
contract B {
function test() public {
A a = new A();
}
}
创建合约实例的时候,默认会调用构造函数,并执行构造函数中的代码。如果没有定义构造函数,EVM编译器会自动生成一个默认的构造函数。如果构造函数指定了参数,那么创建合约实例时候需要传入相同数量的实参。
contract A {
int num;
constructor(int _num) public {
num = _num;
}
}
contract B {
function test() public {
A a = new A(10);
}
}
注意:合约构造函数是可选的。solidity不支持构造函数的重载,也就是一个合约最多只有一个构造函数。
2. contract inherit
一个合约可以继承另外一个合约,也可以同时继承多个合约
contract A {}
contract B is A {}
如果同时继承了多个父合约,最远的派生函数会被调用。
contract C is A, B {}
contract SimpleContract {
function test() public returns(uint) {
C c = new C();
c.a(); // 20
c.f(); // 20
}
}
上面合约C同时继承了A和B,因为B合约比A合约距离C合约远,所以在test
函数中调用父合约状态变量和函数,实际调用的是B合约。
父合约的函数可以被派生合约中具有相同名称、相同参数的函数重载。如果派生合约的重载函数的返回值类型或数量不相同,会导致错误。
如果父合约构造函数带参数,那么可以在派生合约声明时候指定参数,或者也可以在构造函数位置以修饰器方式提供。
contract A {
int num;
constructor(int _num) public {
num = _num;
}
}
contract B is A {}
contract B is A(10) {}
contract B is A {
constructor() public A(10) {}
}
3. abstract contract
如果一个派生合约没有指定所有父合约的构造函数参数,那么该合约是一个抽象合约。
contract A {
uint a;
constructor(uint _a) internal {
a = _a;
}
}
contract B is A {}
contract SimpleContract {
function test() public {
//B a = new B();
}
}
因为合约B继承合约A时候,没有指定构造函数参数,因此合约B是一个抽象合约。抽象合约不能够实例化,因此上面test
函数中注释代码会报错。
另外,如果一个合约包含未实现的函数,那么该合约也是一个抽象合约。
contract A {
function test() public;
}
4. interface
接口主要用于规范合约的实现,其定义格式为:
定义接口使用
interface
关键字。接口里面只能够声明函数,不能定义状态变量和构造函数,也不能够对函数提供实现。
interface InterfaceA {
function f() external;
}
上面接口函数f()
必须要使用external
修饰符。与Java不同,solidity接口之间无法继承。
一个合约可以继承多个接口。
interface InterfaceA {}
interface InterfaceB {}
interface Impl is InterfaceA, InterfaceB {}
如果一个合约没有实现接口里面的所有函数,那么该合约是一个抽象合约。
👉抽象合约与接口的相同点和不同点?
1)抽象合约可以定义状态变量和构造函数,而接口不可以;
2)抽象合约中的函数可以不实现,也可以实现,而接口中只能包含未实现的函数;
3)抽象合约中的函数可以使用任意修饰符,但接口中函数只能使用external修饰符;
4)抽象合约可以继承其他合约或接口,而接口不能;
5)抽象合约和接口都不能实例化;
6)抽象合约和接口都可以声明结构体和枚举;
5. lib合约
库合约不同于一般合约,它更像Java里的工具类,负责为其他合约提供有用的工具函数。
定义语法:
library 库合约 {}
库合约可以定义结构体、枚举,也可以声明状态变量,但是状态变量必须是常量。
像一般函数调用一样,库合约中的成员可以通过库合约.成员
方式访问。
library Utils {
function sum(uint a, uint b) public pure returns(uint) {
return a + b;
}
}
library SimpleContract {
function test() public pure {
uint result = Utils.sum(10, 20);
}
}
如果在合约中包含了库合约,那么部署合约时候,EVM会自动将库合约代码包含进来,无需额外部署库合约。
除了上面调用方式以外,还可以通过using...for
语法访问库合约里面的函数。该语法运行将库合约中的函数附加到任意类型上,从而实现类似原生函数调用的效果。
library Utils {
function get(int[] storage self, uint index) internal view returns(int) {
require(index >= 0);
return self[index];
}
function get(mapping(address => uint128) storage self) internal view returns(uint128) {
require(msg.sender > address(0));
return self[msg.sender];
}
}
contract SimpleContract {
int[] ids;
mapping(address => uint128) balances;
using Utils for int[];
using Utils for mapping(address => uint128);
function test() public view {
int id = ids.get(0);
uint128 bal = balances.get();
}
}
上面库合约定义了两个重载方法。当使用using..for
语法访问库合约时候,第一个参数为对应类型的状态变量。
上面代码通过using...for语法,将库合约绑定到uint[]和mapping(address => uint128)类型。因此,该合约中所有uint[]和mapping(address => uint128)类型的变量都可以库函数,这时候库函数的第一个参数引用了调用该函数的变量。
6.Event
事件使得开发者可以访问EVM的日志系统,帮助开发者了解智能合约运行过程的状态信息。开发者可以在dapp中监听solidity事件,当智能合约中触发了某个事件,EVM的日志系统会反过来调用dapp中定义的事件回调函数,从而在回调函数中将日志信息打印出来。
记录在区块链上的日志信息可以被外部检索出来。solidity规定一个事件里面最多有3个参数可以设置为indexed,表示用户可以通过该参数对搜索结果进行过滤。所有非indexed参数将保存在日志的数据部分,而indexed参数不会被保存。
contract SimpleContract {
event LogSave(
address indexed _addr,
uint _value
);
function saveMoney() public payable {
if (msg.value <= 100 wei) {
emit LogSave(msg.sender, msg.value);
}
}
}
上面合约定义了一个LogSave
事件,其中第一个参数是可索引参数。在saveMoney
函数中,判断如果msg.value
小于等于100 wei,则触发LogSave
事件。