1.值类型
- 值类型(Value Type): 包括布尔、整数等,这类变量赋值时候直接传递数值
- 引用类型(Reference Type): 包括数组、结构体,这类变量占空间大,赋值时直接传递地址(类似指针)
- 映射类型(Mapping Type): Solidity中存储键值对的数据结构,可以理解为哈希表
布尔 bool是二值变量,取值true、false
bool public _bool = true;
整型 整型是Solidity中的整数
int public _int =-1; //整数,包括负数
uint public _uint = 1; //无符号整数
uint256 public _uint256 = 2022030; //256位无符号整数
地址类型 Solidity中特有的类型
+ 普通地址(address): 存储一个20字节的值(ETH地址的大小)
+ payable address: 比普通地址多了`transfer` `send`两个成员方法,用于接收转账
// 地址
address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
address payable public _address1 = payable(_address); // payable address,可以转账、查余额
// 地址类型的成员
uint256 public balance = _address1.balance; // balance of address
定长字节数组 字节数组分为定长和不定长两种:
- 定长字节数组: 属于值类型,数组长度在声明之后不能改变。根据字节数组的长度分为
bytes1
,bytes8
,bytes32
等类型。定长字节数组最多存储 32 bytes 数据,即bytes32
。
- 定长字节数组: 属于值类型,数组长度在声明之后不能改变。根据字节数组的长度分为
- 不定长字节数组: 属于引用类型(之后的章节介绍),数组长度在声明之后可以改变,包括
bytes
等。
// 固定长度的字节数组
bytes32 public _byte32 = "MiniSolidity";
bytes1 public _byte = _byte32[0];
枚举enum
- 枚举(
enum
)是 Solidity 中用户定义的数据类型。它主要用于为uint
分配名称,使程序易于阅读和维护。它与C 语言
中的enum
类似,使用名称来代替从0
开始的uint
:
// 用enum将uint 0, 1, 2表示为Buy, Hold, Sell
enum ActionSet { Buy, Hold, Sell }
// 创建enum变量 action
ActionSet action = ActionSet.Buy;
2. 函数
- Solidity中函数非常灵活,可以进行各种复杂操作,以下为函数表现形式
function <function name>([parameter types[, ...]]) {internal|external|public|private} [pure|view|payable] [virtual|override] [<modifiers>]
[returns (<return types>)]{ <function body> }
以下为函数部分关键字
internal|external|public|private
函数的可见说明符- public : 内部和外部都可见
- private: 本合约内部可见、继承的合约也不可见
- external: 只能从合约外部访问(但内部可通过this.functhion_name()调用)
- internal: 只能合约内部访问,继承的合约可用
**注意 1**:合约中定义的函数需要明确指定可见性,它们没有默认值。
**注意 2**:`public|private|internal` 也可用于修饰状态变量(定义可参考[WTF Solidity 第5讲的相关内容]
`public`变量会自动生成同名的`getter`函数,用于查询数值。未标明可见性类型的状态变量,默认为`internal`。
``
[pure|view|payable]
:决定函数权限/功能的关键字。payable
(可支付的)很好理解,带着它的函数,运行的时候可以给合约转入 ETH。
pure和view的引入,主要是ETH交易需要支付gas fee,合约的状态变量存储在链上,gas fee很贵,而如果不需要修改链上状态,就可以不用支付gas fee。包含 pure、view的函数是不修改链上状态的,因此用户直接调用不需要支付gas fee(注意:合约中非pure、view的函数调研pure或view需要支付gas fee)
ETH中视为修改链上状态的是:
1.写入状态变量
2.释放事件
3.创建其它合约
4.使用 selfdestruct
5.通过调用发送ETH
6.调用任意未标注pure\view的函数
7.使用低级调用(low-level calls)
8.使用包含某些操作码的内联汇编
pure函数即不可读取也不可写入链上的状态变量
view可以读取但不能写入链上状态变量
[virtual|override]
: 方法是否可以被重写,或者是否是重写方法。virtual
用在父合约上,标识的方法可以被子合约重写。override
用在自合约上,表名方法重写了父合约的方法。<modifiers>
: 自定义的修饰器,可以有0个或多个修饰器。
函数输出
- 函数输出包含: 返回多种变量、命名式返回、结构式返回读取全部或部分返回值
return 和 returns
- returns 跟在函数名后,用于声明返回变量类型以及变量名
- return 用于函数主体中,返回指定变量
返回多种变量
// 返回多个变量
function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){
return(1, true, [uint256(1),2,5]);
}
returns 声明返回分别是uint256、bool、uint256长度为3的数组
return返回数组中 1,2,5默认是uint8长度,所以首个元素显示强转uint256
命名式返回
// 命名式返回 f
unction returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){
_number = 2;
_bool = false;
_array = [uint256(3),2,1];
}
该方法我们通过给返回的变量_number、_bool、_array赋值,即可自动返回,当然你可以用return来返回
return(2,false,[uin(3),2,1]);
解构式返回
- Solidity 支持使用解构式赋值规则来读取函数的全部或部分返回值。
uint256 _number;
bool _bool;
uint256[3] memory _array;
(_number, _bool, _array) = returnNamed();
- 读取部分返回值,不读取的留空
(, _bool2, ) = returnNamed();
变量数据存储
- 引用类型(Reference Type):包括数组
array
和结构体struct
,这类变量比较复杂,占用空间大,所以我们要声明数据存储的位置
数据位置
- Solidity数据存储有3类:
storage
,memory
,calldata
。不同存储位置需要的gas 成本不同storage
存储在链上,类似计算机硬盘,消耗gas多。合约状态变量默认存储storagememory
存储在临时内存上,消耗gas少,函数中参数,临时变量一般用memory,不上链,尤其是返回数据类型是变长的情况下,必须加memory,例如string,bytes,array,和自定义结构calldata
和memory类似,不同点是calldata变量不能修改,一般用于函数参数
- 整体gas消耗是:storge > memory > calldata
数据位置和赋值规则
在不同存储类型相互赋值时候,有时会产生独立的副本(修改新变量不会影响原变量),有时会产生引用(修改新变量会影响原变量)。规则如下:
- 赋值本质上是创建引用指向本体,因此修改本体或者是引用,变化可以被同步:
memory
赋值给memory
,会创建引用,改变新变量会影响原变量。storage
(合约的状态变量)赋值给本地storage
(函数里的)时候,会创建引用,改变新变量会影响原变量。例子:
uint[] x = [1,2,3]; // 状态变量:数组 x
function fStorage() public{
//声明一个storage的变量 xStorage,指向x。修改xStorage也会影响
x uint[] storage xStorage = x;
xStorage[0] = 100;
}
- 其他情况下,赋值创建的是本体的副本,即对二者之一的修改,并不会同步到另一方。这有时会涉及到开发中的问题,比如从
storage
中读取数据,赋值给memory
,然后修改memory
的数据,但如果没有将memory
的数据赋值回storage
,那么storage
的数据是不会改变的。
变量的作用域
Solidity
中变量按作用域划分有三种,分别是状态变量(state variable),局部变量(local variable)和全局变量(global variable)
1 .状态变量
状态变量是数据存储在链上的变量,所有合约内函数都可以访问,gas
消耗高。状态变量在合约内、函数外声明:
contract Variables {
uint public x = 1;
uint public y;
string public z;
}
我们可以在函数里更改状态变量的值:
function foo() external{
// 可以在函数里更改状态变量的值
x = 5;
y = 2;
z = "0xAA";
}
2. 局部变量
局部变量是仅在函数执行过程中有效的变量,函数退出后,变量无效。局部变量的数据存储在内存里,不上链,gas
低。局部变量在函数内声明:
function bar() external pure returns(uint){
uint xx = 1;
uint yy = 3;
uint zz = xx + yy;
return(zz);
}
3.全局变量
全局变量是全局范围工作的变量,都是solidity
预留关键字。他们可以在函数内不声明直接使用:
function global() external view returns(address, uint, bytes memory){
address sender = msg.sender;
uint blockNum = block.number;
bytes memory data = msg.data;
return(sender, blockNum, data);
}
在上面例子里,我们使用了3个常用的全局变量:msg.sender
,block.number
和msg.data
,他们分别代表请求发起地址,当前区块高度,和请求数据。下面是一些常用的全局变量,更完整的列表请看这个链接:
blockhash(uint blockNumber)
: (bytes32
) 给定区块的哈希值 – 只适用于最近的256个区块, 不包含当前区块。block.coinbase
: (address payable
) 当前区块矿工的地址block.gaslimit
: (uint
) 当前区块的gaslimitblock.number
: (uint
) 当前区块的numberblock.timestamp
: (uint
) 当前区块的时间戳,为unix纪元以来的秒gasleft()
: (uint256
) 剩余 gasmsg.data
: (bytes calldata
) 完整call datamsg.sender
: (address payable
) 消息发送者 (当前 caller)msg.sig
: (bytes4
) calldata的前四个字节 (function identifier)msg.value
: (uint
) 当前交易发送的wei
值block.blobbasefee
: (uint
) 当前区块的blob基础费用。这是Cancun升级新增的全局变量。blobhash(uint index)
: (bytes32
) 返回跟当前交易关联的第index
个blob的版本化哈希(第一个字节为版本号,当前为0x01
,后面接KZG承诺的SHA256哈希的最后31个字节)。若当前交易不包含blob,则返回空字节。这是Cancun升级新增的全局变量。
4. 全局变量-以太单位与时间单位
以太单位
Solidity
中不存在小数点,以0
代替为小数点,来确保交易的精确度,并且防止精度的损失,利用以太单位可以避免误算的问题,方便程序员在合约中处理货币交易。
wei
: 1gwei
: 1e9 = 1000000000ether
: 1e18 = 1000000000000000000
时间单位
可以在合约中规定一个操作必须在一周内完成,或者某个事件在一个月后发生。这样就能让合约的执行可以更加精确,不会因为技术上的误差而影响合约的结果。因此,时间单位在Solidity
中是一个重要的概念,有助于提高合约的可读性和可维护性
seconds
: 1minutes
: 60 seconds = 60hours
: 60 minutes = 3600days
: 24 hours = 86400weeks
: 7 days = 604800