目录
Solidity中的变量类型
-
数值类型(Value Type):包括布尔型,整数型等等,这类变量赋值时候直接传递数值。
-
引用类型(Reference Type):包括数组和结构体,这类变量占空间大,赋值时候直接传递地址(类似指针)。
-
映射类型(Mapping Type):
Solidity
里的哈希表。 -
函数类型(Function Type):
Solidity
文档里把函数归到数值类型,但我觉得他跟其他类型差别很大,所以单独分一类。
数值类型
1. 布尔型
布尔型是二值变量,取值为true
或false
。
// 布尔值
bool public _bool = true;
布尔值的运算符,包括:
- ! (逻辑非)
- && (逻辑与, "and" )
- || (逻辑或, "or" )
- == (等于)
- != (不等于)
代码:
// 布尔运算
bool public _bool1 = !_bool; //取非
bool public _bool2 = _bool && _bool1; //与
bool public _bool3 = _bool || _bool1; //或
bool public _bool4 = _bool == _bool1; //相等
bool public _bool5 = _bool != _bool1; //不相等
2. 整型
整型是solidity
中的整数,最常用的包括
// 整型
int public _int = -1; // 整数,包括负数
uint public _uint = 1; // 正整数
uint256 public _number = 20220330; // 256位正整数
常用的整型运算符包括:
- 比较运算符(返回布尔值):
<=
,<
,==
,!=
,>=
,>
- 算数运算符:
+
,-
, 一元运算-
,+
,*
,/
,%
(取余),**
(幂)
代码:
// 整数运算
uint256 public _number1 = _number + 1; // +,-,*,/
uint256 public _number2 = 2**2; // 指数
uint256 public _number3 = 7 % 2; // 取余数
bool public _numberbool = _number2 > _number3; // 比大小
3. 地址类型
地址类型(address)存储一个 20 字节的值(以太坊地址的大小)。地址类型也有成员变量,并作为所有合约的基础。有普通的地址和可以转账ETH
的地址(payable
)。payable
的地址拥有balance
和transfer()
两个成员,方便查询ETH
余额以及转账。
// 地址
address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;
address payable public _address1 = payable(_address); // payable address,可以转账、查余额
// 地址类型的成员
uint256 public balance = _address1.balance; // balance of address
4. 定长字节数组
字节数组bytes
分两种,一种定长(byte
, bytes8
, bytes32
),另一种不定长。定长的属于数值类型,不定长的是引用类型(之后讲)。 定长bytes
可以存一些数据,消耗gas
比较少。
代码:
// 固定长度的字节数组
bytes32 public _byte32 = "MiniSolidity";
bytes1 public _byte = _byte32[0];
MiniSolidity
变量以字节的方式存储进变量_byte32
,转换成16进制
为:0x4d696e69536f6c69646974790000000000000000000000000000000000000000
_byte
变量存储_byte32
的第一个字节,为0x4d
。
5. 枚举 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;
它可以显式的和uint
相互转换,并会检查转换的正整数是否在枚举的长度内,不然会报错
// enum可以和uint显式的转换
function enumToUint() external view returns(uint){
return uint(action);
}
enum
的一个比较冷门的变量,几乎没什么人用。
引用类型
1. 数组 array
数组(Array
)是solidity
常用的一种变量类型,用来存储一组数据(整数,字节,地址等等)。数组分为固定长度数组和可变长度数组两种:
- 固定长度数组:在声明时指定数组的长度。用
T[k]
的格式声明,其中T
是元素的类型,k
是长度,例如:
数组(Array)是solidity常用的一种变量类型,用来存储一组数据(整数,字节,地址等等)。数组分为固定长度数组和可变长度数组两种:
固定长度数组:在声明时指定数组的长度。用T[k]的格式声明,其中T是元素的类型,k是长度,例如:
- 可变长度数组(动态数组):在声明时不指定数组的长度。用
T[]
的格式声明,其中T
是元素的类型,例如(bytes
比较特殊,是数组,但是不用加[]
):
// 可变长度 Array
uint[] array4;
bytes1[] array5;
address[] array6;
bytes array7;
创建数组的规则
在solidity里,创建数组有一些规则:
- 对于
memory
修饰的动态数组
,可以用new
操作符来创建,但是必须声明长度,并且声明后长度不能改变。例子:
// memory动态数组
uint[] memory array8 = new uint[](5);
bytes memory array9 = new bytes(9);
- 数组字面常数(Array Literals)是写作表达式形式的数组,用方括号包着来初始化array的一种方式,并且里面每一个元素的type是以第一个元素为准的,例如
[1,2,3]
里面所有的元素都是uint8类型,因为在solidity中如果一个值没有指定type的话,默认就是最小单位的该type,这里int的默认最小单位类型就是uint8。而[uint(1),2,3]
里面的元素都是uint类型,因为第一个元素指定了是uint类型了,我们都以第一个元素为准。下面的合约中,对于f函数里面的调用,如果我们没有显式对第一个元素进行uint强转的话,是会报错的,因为如上所述我们其实是传入了uint8类型的array,可是g函数需要的却是uint类型的array,就会报错了。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;
contract C {
function f() public pure {
g([uint(1), 2, 3]);
}
function g(uint[3] memory) public pure {
// ...
}
}
- 如果创建的是动态数组,你需要一个一个元素的赋值。
uint[] memory x = new uint[](3);
x[0] = 1;
x[1] = 3;
x[2] = 4;
数组成员
length
: 数组有一个包含元素数量的length
成员,memory
数组的长度在创建后是固定的。push()
:动态数组
和bytes
拥有push()
成员,可以在数组最后添加一个0
元素。push(x)
:动态数组
和bytes
拥有push(x)
成员,可以在数组最后添加一个x
元素。pop()
:动态数组
和bytes
拥有pop()
成员,可以移除数组最后一个元素。
2. 结构体 struct
Solidity
支持通过构造结构体的形式定义新的类型。创建结构体的方法:
// 结构体
struct Student{
uint256 id;
uint256 score;
}
Student student; // 初始一个student结构体
给结构体赋值的两种方法:
// 给结构体赋值
// 方法1:在函数中创建一个storage的struct引用
function initStudent1() external{
Student storage _student = student; // assign a copy of student
_student.id = 11;
_student.score = 100;
}
// 方法2:直接引用状态变量的struct
function initStudent2() external{
student.id = 1;
student.score = 80;
}
映射类型
映射Mapping
在映射中,人们可以通过键(Key
)来查询对应的值(Value
),比如:通过一个人的id
来查询他的钱包地址。
声明映射的格式为mapping(_KeyType => _ValueType)
,其中_KeyType
和_ValueType
分别是Key
和Value
的变量类型。例子:
mapping(uint => address) public idToAddress; // id映射到地址
mapping(address => address) public swapPair; // 币对的映射,地址到地址
映射的规则
- 规则1:映射的
_KeyType
只能选择solidity
默认的类型,比如uint
,address
等,不能用自定义的结构体。而_ValueType
可以使用自定义的类型。下面这个例子会报错,因为_KeyType
使用了我们自定义的结构体:
// 我们定义一个结构体 Struct
struct Student{
uint256 id;
uint256 score;
}
mapping(Student => uint) public testVar;
-
规则2:映射的存储位置必须是
storage
,因此可以用于合约的状态变量,函数中的storage
变量,和library函数的参数(见例子)。不能用于public
函数的参数或返回结果中,因为mapping
记录的是一种关系 (key - value pair)。 -
规则3:如果映射声明为
public
,那么solidity
会自动给你创建一个getter
函数,可以通过Key
来查询对应的Value
。 -
规则4:给映射新增的键值对的语法为
_Var[_Key] = _Value
,其中_Var
是映射变量名,_Key
和_Value
对应新增的键值对。例子:
function writeMap (uint _Key, address _Value) public{
idToAddress[_Key] = _Value;
}
映射的原理
-
原理1: 映射不储存任何键(
Key
)的资讯,也没有length的资讯。 -
原理2: 映射使用
keccak256(key)
当成offset存取value。 -
原理3: 因为Ethereum会定义所有未使用的空间为0,所以未赋值(
Value
)的键(Key
)初始值都是0。
函数类型
solidity官方文档里把函数归到数值类型,但我觉得差别很大,所以单独分一类。我们先看一下solidity中函数的形式:
function <function name> (<parameter types>) {internal|external|public|private} [pure|view|payable] [returns (<return types>)]
看着些复杂,咱们从前往后一个一个看(方括号中的是可写可不写的关键字):
-
function
:声明函数时的固定用法,想写函数,就要以function关键字开头。 -
<function name>
:函数名。 -
(<parameter types>)
:圆括号里写函数的参数,也就是要输入到函数的变量类型和名字。 -
{internal|external|public|private}
:函数可见性说明符,一共4种。没标明函数类型的,默认internal
。public
: 内部外部均可见。(也可用于修饰状态变量,public变量会自动生成getter
函数,用于查询数值).private
: 只能从本合约内部访问,继承的合约也不能用(也可用于修饰状态变量)。external
: 只能从合约外部访问(但是可以用this.f()
来调用,f
是函数名)internal
: 只能从合约内部访问,继承的合约可以用(也可用于修饰状态变量)。
-
[pure|view|payable]
:决定函数权限/功能的关键字。payable
(可支付的)很好理解,带着它的函数,运行的时候可以给合约转入ETH
。pure
和view
的介绍见下一节。 -
[returns ()]
:函数返回的变量类型和名称。
到底什么是Pure
和View
?
我刚开始学solidity
的时候,一直不理解pure
跟view
关键字,因为别的语言没有类似的关键字。solidity
加入这两个关键字,我认为是因为gas fee
。合约的状态变量存储在链上,gas fee
很贵,如果不改变链上状态,就不用付gas
。包含pure
跟view
关键字的函数是不改写链上状态的,因此用户直接调用他们是不需要付gas的(合约中非pure
/view
函数调用它们则会改写链上状态,需要付gas)。
在以太坊中,以下语句被视为修改链上状态:
- 写入状态变量。
- 释放事件。
- 创建其他合约。
- 使用
selfdestruct
. - 通过调用发送以太币。
- 调用任何未标记
view
或pure
的函数。 - 使用低级调用(low-level calls)。
- 使用包含某些操作码的内联汇编。
pure:在solidity中不能读取也不能写入存储在链上的状态变量。
view: "看",在solidity中能读取但不能写入状态变量。
不写pure也不写view,在solidity中既可以读取也可以写入状态变量。
1. pure v.s. view
我们在合约里定义一个状态变量 number = 5
。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
contract FunctionTypes{
uint256 public number = 5;
定义一个add()
函数,每次调用,每次给number + 1
// 默认
function add() external{
number = number + 1;
}
如果add()
包含了pure
关键字,例如 function add() pure external
,就会报错。因为pure
是不配读取合约里的状态变量的,更不配改写。那pure
函数能做些什么?举个例子,你可以给函数传递一个参数 _number
,然后让他返回 _number+1
。
// pure
function addPure(uint256 _number) external pure returns(uint256 new_number){
new_number = _number+1;
}
如果add()
包含view
,比如function add() view external
,也会报错。因为view
能读取,但不能够改写状态变量。可以稍微改写下方程,让他不改写number
,而是返回一个新的变量。
// view
function addView() external view returns(uint256 new_number) {
new_number = number + 1;
}
2. internal v.s. external
// internal: 内部
function minus() internal {
number = number - 1;
}
// 合约内的函数可以调用内部函数
function minusCall() external {
minus();
}
我们定义一个internal
的minus()
函数,每次调用使得number
变量减1。由于是internal
,只能由合约内部调用,而外部不能。因此,我们必须再定义一个external
的minusCall()
函数,来间接调用内部的minus()
。 Example:
3. payable
// payable: 递钱,能给合约支付eth的函数
function minusPayable() external payable returns(uint256 balance) {
minus();
balance = address(this).balance;
}
我们定义一个external payable
的minusPayable()
函数,间接的调用minus()
,并且返回合约里的ETH
余额(this
关键字可以让我们引用合约地址)。 我们可以在调用minusPayable()
时,往合约里转入1个ETH
。
我们可以在返回的信息中看到,合约的余额是1 ETH。
到这里solidity基础第一节就结束了。(不太懂的小伙伴可以参考这里)