0x00前言
solidity是一门运行在以太坊虚拟机(EVM)上的编程语言,用来构建智能合约,社区生态在这几年发展中变得非常丰富,许多重要的项目都是solidity,目前也是成为了区块链最流行的编程语言。其实基于区块链的语言不止solidity,不同公链会有不同的编程语言,比如apt网络和sui网络,用的是move编程语言,详细的可以看move还有最近比较火热的solana链,用的是rust。每个语言都有各自的优势,不展开细讲。
顺便介绍一下用来编写solidity的平台,主流的是以太坊推荐使用的remix,是一款集成IDE
0x01经典hello world
万物离不开hello world,用solidity实现下
1 | // SPDX-License-Identifier: MIT |
解释下上面这段代码
// SPDX-License-Identifier: MIT 这个是一段注释,在solidity8后不加注释会警告
pragma是声明solodity的版本,其中^代表的是在此版本以上均可使用,但是注意不能超过0.8,也就是0.8.7到0.9.0以下都可以
solidity以分号**;**为结尾
contract是合约的关键词,用于声明一个合约对象,可以当做一个class类
在类中定义了一个hello的变量,一般格式为 [type] [public/private/external/internal] [name] = [value];
这里讲一下**[public/private/external/internal]**分别是什么意思
public:所有合约文件都能访问到这个变量
private:只能从本合约内部访问,继承的合约也不能用
external:只能从合约外部访问,可以用this.function() 调用,function是函数名
internal:继承合约可以用,只能从合约内部访问
这里在函数部分还会再讲一次,因为在solidity中函数被归为数值类型,与普通变量定义的形式一样。
0x02数值类型
1、布尔型(bool)
! 逻辑非
&& 逻辑与
|| 逻辑或
== 等于
!= 不等于
solidity中同样支持短路规则
2、整型
用的最多也是最常见的
int 整数,有符号,负数
uint 正整数,无符号
uint256 256位正整数
3、地址类型
地址类型是用来存储20位的以太坊地址大小
有个修饰符得讲一讲,payable,可以接收转账
在地址类型中使用payable(),则此地址会比普通地址多两个成员,一个是transfer,一个是send
send执行失败不会影响当前合约的执行,但是会返回false值。balance和transfer()可以用来查询eth的余额,transfer()内含安全转账(也就是内置了执行失败的一个处理)
1 | address public _address = xxx; // 20位的以太坊地址 |
4、定长的字节数组
solidity的字节类型相对于传统语言也是byte类型,还多了个bytes
byte、bytes8、bytes32
分为定长,还有一种是不定长
稍微指出一下,这里的定长属于数值类型,而不定长是引用类型,两者类型的区别在于定长类型可以存放一些数据,消耗的gas比较少,因为不定长数组在每次动态添加元素的时候是重新分配内存,如果不懂gas,可移步到我之前的文章web3.0入门
1 | uint256[5] fixedArray; // 定长数组,长度为5 |
1 | int256[] dynamicArray; // 不定长数组 |
两者区别在于是否在[]定义了长度
5、枚举enum
比较冷门的一个类型
1 | enum listset {buy,sell,confrim} |
6、部分全局变量
1 | msg.sender // 调用这个函数的地址是什么 address msg.sender |
7、默认值
1 | bool public f // false |
8、常量
1 | uint256 public const NUM // 定义常量,一般为大写命名 |
0x03函数
在solidity中,函数的表达式为
function [name] (params type) {public|private|internal|external} [pure|view|payable] [return value type]
分析下这条表达式写了什么
首先定义了一个函数,之后接着函数名,函数中可以携带参数和参数的类型(params type),之后有一个关键字,用来修饰这个函数的可见性,在0x01部分已经讲过这个可见性的作用,同样适用于函数。
这里特别说一下pure、view、payable
pure翻译过来是纯净
view翻译过来就是视图
payable刚才说明了是一个修饰可以用来支付的关键字
可以抽象来比喻下pure和view
1 | contract con{ |
pure也就是纯纯牛马的意思,不能更改也不能读取合约状态变量
view就是旁观者,不能修改合约状态变量,而能读取合约状态变量
什么是合约状态变量,就是在合约内部定义的变量,而在函数中定义的变量称为局部变量,是不会添加到链上的
可以看到上述编译器出现了波浪线,也就是提示要严格遵守pure和view的规则,如果不加以修饰,则做什么都可以,修改或者读取合约状态变量
这三者有什么区别?
是solidity特有的修饰符,用来节省gas费和控制这个函数的权限而设计的,当使用pure和view修饰的时候,调用的函数是不消耗gas的,因为不会写到链上,而不加则默认会写到链上。
0x04函数的输出
在solidity中有两个关键字,一个是return,一个是returns
1 | function test() public pure returns (uint256 a){ |
returns是用来定义函数返回值,也就是在函数名后定义,而return也是定义返回的值,只不过是在函数体内使用。这种方法称为命名式返回,具体可以这样写
1 | function name() public pure returns(uint256 number,bool _bool, uint256[4] memory _array){ |
两种方法都是获得相同的结果
这里的memory在下个章节讲到,用于数据的存储位置
还有一种是解构式赋值,也就是可以有选择性的返回对应的变量,如果不需要变量则留空
1 | function test1() public pure returns(uint256 number, bool _bool, uint256[4] memory _array{ |
0x05变量存储作用域
solidity有三种存储的位置
storage、memory、calldata,这三种用来区分不同的gas成本,其中storage会存储到链上,消耗的gas最多,memory和calldata是临时存在内存中,消耗的gas相对较少
其中memory和calldata的不同为calldata变量是不能修改的,比如这段代码
1 | function calldata1(uint[] calldata _x) public pure returns(uint[] calldata){ |
可以看到直接报类型错误了,不能修改,只能读,注释掉_x[0]就能通过编译
storage定义时,实际上创建了一个新的引用,什么意思,就是改变新的变量会影响原来的变量
1 | uint[] x = [1,2,3,4,5]; |
0x06条件语句
if
1 | if (条件) {} |
solidity可以使用三元运算符
条件 ? 结果一 : 结果二
0x07循环
for
1 | for (addition) {} |
while
1 | while(addition){} |
do-while
1 | do{} |
0x08报错控制
在solidit8.0中,有三种错误控制的形式,require、revert、assert
require
1 | // require(检查条件,"异常的描述") gas费随着异常描述的字符串长度增加而增加 |
revert
1 | function testRevert(uint _i) public pure { |
assert
assert翻译过来是断言,一般用于调试使用,与其他两者不同的是,它不会解释抛出异常的原因,与require相比就是少了异常描述字符串参数
在0.8.0之前的版本抛出的是一个panic exception,会把剩余的gas消耗完毕,不会返还
1 | function testAssert(uint _num) public pure{ |
可以看到三者中require消耗的gas费是最多的
自定义错误(error)
error是在solidity的0.8.4中引入,在这几种中是最省gas费的,用error类型定义的函数可以携带参数并且需要搭配revert回退函数一起使用
1 | error TransferNotOwner(address sender); // 自定义的带参数的error |
一般常用error,省gas费,又能解释抛出异常的原因