前言
本文高效梳理Solidity编程语言基础知识点
类型
1. 值类型
- bool(布尔):
例子: bool public _bool - int or uint(整型):
例子:int public _int or unt public _uint - address(地址):
例子:address public _address - Bytes(定长字节数组):
例子:bytes32 public _byte32 - constant or immutable(常量):
# constant
string public constant _string = "Hello World";
# immutable
uint public immutable _string1;
//需要在构造函数中初始化;
- string (字符串):
string public _string - enum (枚举):
# 例子:
enum ActionWeek {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
ActionWeek action = ActionWeek.Monday
function enumToUint() external view returns(uint){
return uint(action);
}//返回0
2. 引用类型
- Structs(结构体):
// 结构体
struct Student{
uint256 id;
string name;
unit256 age;
}
Student student;
//赋值
//方法1
student(1,"Boykayuri",20)
//方法2
student({id:1,name:"Boykayuri",age:20})
//方法3
Student storage _student = student;
_student.id = 11;
_student.name = "tom";
_student.age= 20;
//方法4
student.id=10;
student.name = "tom";
student.age= 20;
- Array(数组):
# 固定长
uint[5] public arr1;//类型 长度
# 不定长
uint[] public arr1;//类型 长度
# 方法、属性
lenght//长度
push//添加
pop//删除
3. 映射类型
- mapping(映射):
# 通过权限设置的例子
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "hardhat/console.sol";
contract mappingType {
// 声明一个 mapping,键是 address,值是 bool
mapping(address => bool) public hasPermission;
constructor() {
// 初始化时可以设置一些值
hasPermission[msg.sender] = true;
}
// 设置权限
function setPermission(address _address, bool _hasPermission) public {
hasPermission[_address] = _hasPermission;
}
// 检查权限
function checkPermission(address _address) public view returns (bool) {
return hasPermission[_address];
}
// 删除权限
function deletePermission(address _address) public {
delete hasPermission[_address];
}
}
循环和分支
- for(循环):
for (unit i i<10;i++){
console.log(i)
}
- if else(分支)or 三目运算:
# 分支
if(){}else{}
# 或者
条件? 条件为真的表达式:条件为假的表达式
x>=y?x:y
函数和事件
函数
函数形式:
function <function name>([parameter types[, ...]]) {internal|external|public|private} [pure|view|payable] [virtual|override] [<modifiers>] [returns (<return types>)]{ <function body> }
函数说明:
- function name:函数名
- [parameter types[, ...]]:函数的参数,类型 参数
- {internal|external|public|private}:可见性说明符
- [pure|view|payable]:权限/功能的关键字
- [virtual|override]:是否可以被重写,或者是否是重写方法
- modifiers: 自定义的修饰器
- [returns ()]:函数返回的变量类型和名称
可见性修饰符
public:内部和外部均可见。private:只能从本合约内部访问,继承的合约也不能使用。external:只能从合约外部访问(但内部可以通过this.f()来调用,f是函数名)。internal: 只能从合约内部访问,继承的合约可以用。
权限/功能的关键字
pure:既不能读取也不能改写状态变量view:可以读取状态变量,但不能改写payable:可支付的
是否是重写方法
virtual:用在父合约上,标识的方法可以被子合约重写override:用在自合约上,表名方法重写了父合约的方法
完整代码实例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Base {
function foo() public virtual returns (string memory) {
return "Base foo";
}
function bar() public virtual returns (string memory) {
return "Base bar";
}
}
contract Derived is Base {
function foo() public override returns (string memory) {
return "Derived foo";
}
function bar() public override returns (string memory) {
return "Derived bar";
}
}
修饰器
# 定义modifier
modifier onlyOwner {
require(msg.sender == owner); // 检查调用者是否为owner地址
_; // 如果是的话,继续运行函数主体;否则报错并revert交易
}
function changeOwner(address _newOwner) external onlyOwner{
owner = _newOwner; // 只有owner地址运行这个函数,并改变owner
}
# 特性
* 可见性修饰符:public、internal、external 和 private
* 函数修饰符:pure、view、payable 和 virtual
*函数重写和重载:override、overload
Event事件
# 定义事件
event EventName(address indexed _addressParam, uint _uintParam, string _stringParam);
说明:indexed 关键字用于指定该参数为索引参数,允许外部应用程序通过该参数进行快速检索
# 触发事件 传递对应的参数
emit EventName(0x4***,10,"str")
继承、抽象合约与接口
继承
- 基本的合约继承
contract Grandfather {
event Log(string msg); // 定义3个function: hip(), pop(), yeye(),Log值为Yeye。
function hip() public virtual{ emit Log("grandfather"); }
function pop() public virtual{ emit Log("grandfather"); }
function grandfather() public virtual { emit Log("grandfather"); }
}
contract Father is Grandfather{
// 继承两个function: hip()和pop(),输出改为father。
function hip() public virtual override{ emit Log("father"); }
function pop() public virtual override{ emit Log("father"); }
function father() public virtual{ emit Log("father"); } }
- 多重继承
contract Boykayuri is Grandfather, Father{
// 继承两个function: hip()和pop(),输出值为boykayuri。
function hip() public virtual override(Grandfather, Father){ emit Log("boykayuri"); }
function pop() public virtual override(Grandfather, Father) { emit Log("boykayuri"); }
}
- 构造函数的继承
# 使用`super`关键字
contract Base {
address public owner;
constructor() {
owner = msg.sender;
}
}
contract Derived is Base {
constructor() {
super.constructor();
// 其他初始化代码
}
}
# 直接调用父合约的构造函数
contract Derived is Base {
constructor() {
Base.constructor();
// 其他初始化代码
}
}
# 传递参数给父合约的构造函数
contract Derived is Base {
constructor(address _owner) Base(_owner) {
// 其他初始化代码
}
}
抽象合约
-
定义:抽象合约是一种不能被直接实例化的合约,它通常包含一些未实现的方法(抽象方法)。这些方法必须在继承抽象合约的具体合约中被实现。
-
特点:
- 不能直接实例化:抽象合约不能直接创建实例,只能通过继承它的具体合约来实例化。
- 包含抽象方法:抽象合约可以包含一些没有具体实现的方法,这些方法称为抽象方法。抽象方法必须在继承抽象合约的具体合约中被实现。
- 提供接口规范:抽象合约定义了一组方法的接口和规范,但不提供具体实现。这使得抽象合约可以作为其他合约的模板或框架。
-
abstract:帮助开发者定义接口规范、提供通用功能以及确保合约实现特定接口
-
语法
# 使用
abstract contract Verifiable {
function verify(address user) external virtual returns (bool);
}
contract UserContract is Verifiable {
function verify(address user) external override returns (bool) {
// 实现用户验证逻辑
return true;
}
}
接口
- 定义:接口是一种特殊的合约,它只能声明方法的签名,但不能实现这些方法。接口中的所有方法都是抽象的,且不能包含任何状态变量。
- 特点:
- 定义接口规范:接口主要用于定义一组方法的签名,确保不同的合约实现这些方法,从而保证接口的一致性。
- 轻量级:接口不包含任何实现,因此比抽象合约更轻量级,适合用于定义简单的接口规范。
- 互操作性:接口可以用于不同合约之间的互操作,使得合约之间可以调用彼此的方法,而不需要知道具体的实现细节。
- interface:适用于定义轻量级的接口规范,确保不同合约之间的一致性,不能包含实现、状态变量、构造函数和事件
- 语法
# 使用
interface MyInterface {
function myFunction() external;
}
contract MyContract is MyInterface {
function myFunction() external {
// 实现具体的功能
}
}
变量作用域
-
全局变量:
定义:全局变量是Solidity提供的内置变量,它们可以在任何合约中直接使用,无需声明。全局变量通常用于访问区块链的某些基本信息,如区块信息、交易信息等。
常见的全局变量:
block:
block.number:当前区块的编号。block.timestamp:当前区块的时间戳。block.difficulty:当前区块的难度。block.gaslimit:当前区块的气体限制。
tx:
tx.gasprice:交易的气体价格。tx.origin:交易的发起者地址。
msg:
msg.sender:当前消息的发送者地址。msg.value:发送给合约的以太币数量(单位为Wei)。msg.data:发送给合约的数据。msg.sig:调用函数的签名。
abi:
abi.encode:将参数编码为字节数组。abi.encodePacked:将参数编码为紧凑的字节数组。abi.encodeWithSelector:将参数编码为字节数组,并包含函数选择器。abi.decode:将字节数组解码为指定类型的参数。
-
局部变量:
定义:局部变量是在函数内部声明的变量,它们的作用范围仅限于声明它们的函数。局部变量在函数调用时被创建,在函数返回时被销毁。
使用案例
pragma solidity ^0.8.0; contract MyContract { function myFunction() public pure { uint256 localVariable = 42; // 局部变量 // 可以在函数内部使用 localVariable } } -
状态变量:
定义:状态变量是存储在合约中的变量,它们的作用范围是整个合约。状态变量的值被永久存储在区块链上,因此它们在合约的整个生命周期中都存在
使用案例
pragma solidity ^0.8.0; contract MyContract { uint256 public stateVariable = 42; // 状态变量 function myFunction() public { stateVariable = 100; // 修改状态变量 } }
作用域总结
- 全局变量:由Solidity提供,用于访问区块链的基本信息,无需声明。
- 局部变量:在函数内部声明,仅在函数执行期间存在,存储在栈中。
- 状态变量:在合约中声明,存储在区块链上,永久存在,可以通过合约的外部调用来访问和修改。
数据存储
Storage(存储)
特点:
- 持久性:存储在
storage中的数据是永久的,存储在区块链上。 - 成本:访问和修改
storage中的数据成本较高,因为每次读写操作都需要消耗Gas。 - 可变性:存储在
storage中的数据可以被修改。
用法
pragma solidity ^0.8.0;
contract MyContract {
uint256 public stateVariable = 42; // 存储在 storage 中
function updateStateVariable(uint256 newValue) public {
stateVariable = newValue; // 修改 storage 中的数据
}
}
Memory(内存)
特点:
- 临时性:存储在
memory中的数据是临时的,仅在函数调用期间存在。 - 成本:访问和修改
memory中的数据成本较低,因为这些操作不会写入区块链。 - 不可变性:存储在
memory中的数据在函数调用结束后被销毁。
用法
pragma solidity ^0.8.0;
contract MyContract {
function myFunction(uint256 a, uint256 b) public pure returns (uint256) {
uint256 localVariable = a + b; // 存储在 memory 中
return localVariable;
}
}
Calldata(调用数据)
特点:
- 只读性:存储在
calldata中的数据是只读的,不能被修改。 - 临时性:存储在
calldata中的数据是临时的,仅在函数调用期间存在。 - 成本:访问
calldata中的数据成本较低,因为这些数据不会写入区块链。
用法
pragma solidity ^0.8.0;
contract MyContract {
function myFunction(uint256[] calldata values) public pure returns (uint256) {
uint256 sum = 0;
for (uint256 i = 0; i < values.length; i++) {
sum += values[i];
}
return sum;
}
}
Stack(栈)
特点:
- 临时性:存储在
stack中的数据是临时的,仅在当前操作的上下文中存在。 - 成本:访问和修改
stack中的数据成本极低,因为这些操作在内存中完成。 - 不可变性:存储在
stack中的数据在当前操作完成后被销毁。
用法
pragma solidity ^0.8.0;
contract MyContract {
function add(uint256 a, uint256 b) public pure returns (uint256) {
uint256 result = a + b; // a 和 b 存储在 stack 中
return result;
}
}
数据存储总结
- Storage:永久存储在区块链上,成本高,可变。
- Memory:临时存储在合约执行期间的内存中,成本低,不可变。
- Calldata:存储在调用交易数据中的只读数据,成本低,只读。
- Stack:存储在执行栈中的临时数据,成本极低,不可变。
异常处理
- require:用于输入验证和状态检查,失败时返回剩余 gas 并恢复状态。
- assert:用于检测不变量和内部错误,失败时消耗所有剩余 gas 并恢复状态。
- revert:用于显示抛出异常,支持携带错误信息,失败时返回剩余 gas 并恢复状态。
- 自定义错误:从 Solidity 0.8.4 开始引入,使用 error 关键字定义,通过 revert 抛出,节省 gas 并提高代码可读性。
- 事件日志:用于记录错误信息,便于后续查询和调试。
使用方法说明:
- require
# 例子:
require(false,"错误信息描述");//判断条件为false
- assert:
# 例子:
assert(true)//判断条件为true
- 自定义异常
# 例子
# 定义事件
error TransferNotOwner(address sender);
if(true){
revert TransferNotOwner(msg.sender);
}
总结
以上内容对Solidity的基础要点进行了梳理。如需深入了解,可查阅Solidity官方文档。