Type:
整数
int public _int =-1
uint public _uint =1
uint256 public _number =20220330 //256位整数
//地址
address(存储20字节) public _address=0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
address payable public _address1 = payable(_address); // payable address,可以转账、查余额
// 地址类型的成员
uint256 public balance = _address1.balance; // balance of address
定长字节数组(数值类型)
bytes32 public _byte32 ="MiniSolidity";
bytes1 public _byte=_byte32[0]
不定长字节数组(引用类型)
//枚举(enum)
enum ActionSet {Buy,Hold,Sell}
//创建enum action
ActionSet action=ActionSet.Buy;
//枚举可以显式地和uint相互转换
函数权限关键字
pure:既不能读也不能写入链上状态变量
view:可以读但不能写入
internal 和external
可以通过external类型的函数间接调用internal类型的函数
返回值
命名式返回 public pure returns(uint256 _number, bool _bool, uint256[3] memory _array)
读取所有返回值
uint256 _number;
bool _bool;
uint256[3] memory _array;
(_number, _bool, _array) = returnNamed();
部分返回值
(,_bool,)=returnNamed();
数据位置
-
storage:合约里的状态变量默认都是storage,存储在链上。
-
memory:函数里的参数和临时变量一般用memory,存储在内存中,不上链。
-
calldata:和memory类似,存储在内存中,不上链。与memory的不同点在于calldata变量不能修改(immutable),一般用于函数的参数。
-
storage(合约的状态变量)赋值给本地storage(函数里的)时候,会创建引用,改变新变量会影响原变量。
-
其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方
变量的作用域
Solidity中变量按作用域划分有三种,分别是状态变量(state variable),局部变量(local variable)和全局变量(global variable)
状态变量:存储在链上的数据 例如uint public x=1;
局部变量: 局部变量是仅在函数执行过程中有效的变量,函数退出后,变量无效。局部变量的数据存储在内存里,不上链,gas低。局部变量在函数内声明
例如:uint xx=1
全局变量:是全局范围工作的变量,都是solidity预留关键字。他们可以在函数内不声明直接使用:例如:address sender = msg.sender;
以太单位:
Solidity中不存在小数点,以0代替为小数点,来确保交易的精确度,并且防止精度的损失,利用以太单位可以避免误算的问题,方便程序员在合约中处理货币交易。
1 wei=1
1 gwei =1e9=1000000000
1 ether=1e18=10000000000000000000
时间单位:
-
seconds: 1
-
minutes: 60 seconds = 60
-
hours: 60 minutes = 3600
-
days: 24 hours = 86400
-
weeks: 7 days = 604800
数组 array
数组(Array)是solidity常用的一种变量类型,用来存储一组数据(整数,字节,地址等等)。数组分为固定长度数组和可变长度数组两种:
-
固定长度数组:在声明时指定数组的长度。用T[k]的格式声明,其中T是元素的类型,k是长度,例如:
-
固定长度 Array
-
uint[8] array1;
-
bytes1[5] array2;
-
address[100] array3;
可变长度数组(动态数组):在声明时不指定数组的长度。用T[]的格式声明,其中T是元素的类型,例如:
可变长度 Array
uint[] array4;
bytes1[] array5;
address[] array6;
bytes array7;
//例子1
uint[5] arr = [0,1,2,3,4];//创建一个定长的数组
uint[] storageArr;
function a() public {
//通过索引查询时要先初始化
uint[5] memory arr1 = [uint(0),1,2,3,4];//uint8显示的转换为uint256,否则会报类型错误。
uint[] memory memoryArr;
//storageArr[0] = 12;
//memoryArr[0] = 13; //执行会报VM error: invalid opcode.,原因是数组还没有执行初始化。
storageArr = new uint[](5);
memoryArr = new uint[](5);
storageArr[0] = 12;
memoryArr[0] = 13;
}
2、存储位置对赋值的影响:
值类型申明不允许指定storage/memory,值类型之间赋值都是创建拷贝。
下面指引用类型赋值操作:
Storage与memory相互赋值,会创建拷贝。
storage/memory同一存储区域内的引用类型(除状态变量)之间赋值不会创建拷贝,创建引用;所有变量给Storage状态变量赋值,都会创建拷贝。
Storage状态赋值给Storage局部变量:不创建拷贝,仅创建一个引用。
Memory引用类型赋值给Memory引用类型:不创建拷贝,仅创建一个引用。
memory变量不能给storage局部变量赋值,但可以给storage状态变量赋值。
bytes比较特殊是数组,但不用写[],声明单字节数组用bytes[]或bytes但是bytes1更省gas
//memory动态数组
uint[] memory array8 =new uint[](5)
bytes memory array9=new bytes(9)
用new操作符来创建,但是必须声明长度,并且声明后长度不能改变。
数组成员
-
length: 数组有一个包含元素数量的length成员,memory数组的长度在创建后是固定的。
-
push(): 动态数组拥有push()成员,可以在数组最后添加一个0元素,并返回该元素的引用。
-
push(x): 动态数组拥有push(x)成员,可以在数组最后添加一个x元素。
-
pop(): 动态数组拥有pop()成员,可以移除数组最后一个元素。
-
push:
-
1.面对uint[], push可以加一个uint
-
2.面对bytes[],push只可以加256以内的(2的8次方)
-
push返回的数组长度
结构体 struct
Solidity支持通过构造结构体的形式定义新的类型。结构体中的元素可以是原始类型,也可以是引用类型;结构体可以作为数组或映射的元素。创建结构体的方法:
struct Student{
uint256 id;
uint256 score;
}
Student student;//初始一个student结构体
//给结构体赋值的四种方法
在函数中创建一个storage的struct引用
function initStudent1() external{
Student storage _student =student //这里面定义的_student会指向student,更改里面的外面也会修改
_student.id=11;
_student.score=100; }
//方法2:直接俄引用状态变量的struct
function initStudent2( ) external{
student.id=1;
student.sscore=80;z
}
//方法3:构造函数
function initStudent3() external{
student =Student(3,90);
}
//方法4:key value
function initStudent4() external{
student=Student({id :4,score:60});
}
//映射Mapping( 哈希表)将数组的数字下标转化为key下标,映射不能用结构体
mapping(uint =>address) public idToAddress ;//id映射到地址
mapping (address =>address) public swapPair ;//币对的映射,地址到地址
映射实质上就是 代币迁移
在旧币钱包和新币钱包之间建立一个对应关系
-
规则2:映射的存储位置必须是storage,因此可以用于合约的状态变量,函数中的storage变量,和library函数的参数(见例子)。不能用于public函数的参数或返回结果中,因为mapping记录的是一种关系 (key - value pair)。
-
规则3:如果映射声明为public,那么Solidity会自动给你创建一个getter函数,可以通过Key来查询对应的Value。
-
规则4:给映射新增的键值对的语法为_Var[_Key] = _Value,其中_Var是映射变量名,_Key和_Value对应新增的键值对。
映射的原理
-
原理1: 映射不储存任何键(Key)的资讯,也没有length的资讯。
-
原理2: 映射使用keccak256(abi.encodePacked(key, slot))当成offset存取value,其中slot是映射变量定义所在的插槽位置。
-
原理3: 因为Ethereum会定义所有未使用的空间为0,所以未赋值(Value)的键(Key)初始值都是各个type的默认值,如uint的默认值是0。
constant和immutable
只有数值变量可以声明constant和immutable
状态变量声明这个两个关键字之后,不能在合约后更改数值。这样做的好处是提升合约的安全性并节省gas。
//constant变量必须在声明时或构造函数中初始化
uint256 constant CONSTANT_NUM=10;
string constant CONSTANT_STRING = "0xAA";
bytes constant CONSTANT_BYTES = "WTF";
address constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000;
immutable变量可以在声明时或构造函数中初始化,因此更加灵活。
// immutable变量可以在constructor里初始化,之后不能改变
uint256 public immutable IMMUTABLE_NUM = 9999999999;
address public immutable IMMUTABLE_ADDRESS;
uint256 public immutable IMMUTABLE_BLOCK;
uint256 public immutable IMMUTABLE_TEST;
你可以使用全局变量例如address(this),block.number ,或者自定义的函数给immutable变量初始化。在下面这个例子,我们利用了test()函数给IMMUTABLE_TEST初始化为9:
address public immutable immutable_address;
uint256 public immutable immutable_block;
constructor(){
immutablle_address=address(this);
immutable_block =block.number;
immutable_test=test();
}
function test() public pure returns(uint256){
uint256 what=9;
return(what);
}
构造函数
构造函数(constructor)是一种特殊的函数,每个合约可以定义一个,并在部署合约的时候自动运行一次。它可以用来初始化合约的一些参数,例如初始化合约的owner地址:
老写法
constructor(){
}
//新写法
address owner /.地址
function 合约名 () public{
owner =msg.sender
}
修饰器
修饰器(modifier)是solidity特有的语法,类似于面向对象编程中的decorator,声明函数拥有的特性,并减少代码冗余。它就像钢铁侠的智能盔甲,穿上它的函数会带有某些特定的行为。modifier的主要使用场景是运行函数前的检查,例如地址,变量,余额等。
(类似于vue的过滤器,对数据进行筛选
modifier onlyOwner{
require(msg.sender ==owner);//检查调用者是否为owner,
//require作用是如果值为true则执行下面的语句
_;//如果是的话继续运行函数主体;否则报错并revert交易
}
//判断是否以owner用户登录
//带有onlyOwner修饰符的函数只能owner调用
function changeOwner(address _newOwner)external onlyOwner{ //修改owner,类似于修改密码
owner =_newOwner;
}
事件
Solidity中的事件(event)是EVM上日志的抽象,它具有两个特点:
-
响应:应用程序(ethers.js)可以通过RPC接口订阅和监听这些事件,并在前端做响应。
-
经济:事件是EVM上比较经济的存储数据的方式,每个大概消耗2,000 gas;相比之下,链上存储一个新变量至少需要20,000 gas。
声明事件
事件的声明由event关键字开头,接着是事件名称,括号里面写好事件需要记录的变量类型和变量名。以ERC20代币合约的Transfer事件为例:
event Transfer(address indexed from, address indexed to, uint256 value);
我们可以看到,Transfer事件共记录了3个变量from,to和value,分别对应代币的转账地址,接收地址和转账数量,其中from和to前面带有indexed关键字,他们会保存在以太坊虚拟机日志的topics中,方便之后检索。