loading...
solidity入门教程
Published in:2024-04-09 | category: 区块链 | solidity

0x00前言

solidity是一门运行在以太坊虚拟机(EVM)上的编程语言,用来构建智能合约,社区生态在这几年发展中变得非常丰富,许多重要的项目都是solidity,目前也是成为了区块链最流行的编程语言。其实基于区块链的语言不止solidity,不同公链会有不同的编程语言,比如apt网络和sui网络,用的是move编程语言,详细的可以看move还有最近比较火热的solana链,用的是rust。每个语言都有各自的优势,不展开细讲。

顺便介绍一下用来编写solidity的平台,主流的是以太坊推荐使用的remix,是一款集成IDE

0x01经典hello world

万物离不开hello world,用solidity实现下

1
2
3
4
5
6
7
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

contract helloworld{
string public hello = "hello world";

}

解释下上面这段代码

// 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
2
3
4
5
address public _address = xxx; // 20位的以太坊地址
address payable public _address1 = payable(_address);
uint256 public balance = _address1.balance;

//这里对原有地址_address用了payable()修饰,让_address1有了transfer和send方法,还有一个balance成员

4、定长的字节数组

solidity的字节类型相对于传统语言也是byte类型,还多了个bytes

byte、bytes8、bytes32

分为定长,还有一种是不定长

稍微指出一下,这里的定长属于数值类型,而不定长引用类型,两者类型的区别在于定长类型可以存放一些数据,消耗的gas比较少,因为不定长数组在每次动态添加元素的时候是重新分配内存,如果不懂gas,可移步到我之前的文章web3.0入门

1
2
3
uint256[5] fixedArray; // 定长数组,长度为5
address[3] addresses; // 定长数组,长度为3,存储地址类型数据
bytes32[10] hashes; // 定长数组,长度为10,存储32字节的数据哈希值
1
2
3
4
5
6
int256[] dynamicArray; // 不定长数组
address[] addresses; // 不定长数组,存储地址类型数据
bytes32[] hashes; // 不定长数组,存储32字节的数据哈希值

dynamicArray.push(10); // 向不定长数组添加一个新的元素,值为10
uint256 value = dynamicArray[2]; // 访问数组中索引为2的元素,获取其值

两者区别在于是否在[]定义了长度

5、枚举enum

比较冷门的一个类型

1
2
3
enum listset {buy,sell,confrim}
listset set1 = listset.buy;
//一般用来枚举用户身份比较多

6、部分全局变量

1
2
3
msg.sender	// 调用这个函数的地址是什么  address msg.sender
block.timestamp // 区块的时间戳 uint256 block.timestamp
block.number // 当前区块的编号 uint256 block.number

7、默认值

1
2
3
4
5
bool public f // false
uint public num // 0
int public num1 // 0
address public addr // 0x0000000000000000000000000000000000000000 40个0
bytes32 public by32 // 0x0000000000000000000000000000000000000000000000000000000000000000 64个0

8、常量

1
2
uint256 public const NUM // 定义常量,一般为大写命名
// 消耗的gas会低于非常量

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
2
3
4
5
6
7
8
9
10
11
12
13
contract con{
uint256 public number = 1;
function add(uint256 b) public pure returns (uint256 a){
a = b + 1;// 可以
//number += 1; //不可以读也不可以写
// number; 不可以读取,纯纯牛马
}

function add(uint256 b) public view returns (uint256 a){
number;//不报错,可以读取
//number += 1; //不可以写
}
}

pure也就是纯纯牛马的意思,不能更改也不能读取合约状态变量

view就是旁观者,不能修改合约状态变量,而能读取合约状态变量

什么是合约状态变量,就是在合约内部定义的变量,而在函数中定义的变量称为局部变量,是不会添加到链上的

可以看到上述编译器出现了波浪线,也就是提示要严格遵守pure和view的规则,如果不加以修饰,则做什么都可以,修改或者读取合约状态变量

这三者有什么区别?

是solidity特有的修饰符,用来节省gas费和控制这个函数的权限而设计的,当使用pure和view修饰的时候,调用的函数是不消耗gas的,因为不会写到链上,而不加则默认会写到链上。

0x04函数的输出

在solidity中有两个关键字,一个是return,一个是returns

1
2
3
function test() public pure returns (uint256 a){
return a;
}

returns是用来定义函数返回值,也就是在函数名后定义,而return也是定义返回的值,只不过是在函数体内使用。这种方法称为命名式返回,具体可以这样写

1
2
3
4
5
6
7
8
9
function name() public pure returns(uint256 number,bool _bool, uint256[4] memory _array){
number = 1;
_bool = true;
_array = [uint256(2),2,1,1];
}

function name() public pure returns(uint256 number,bool _bool, uint256[4] memory _array){
return(1,true,[uint256(2),2,1,1]);
}

两种方法都是获得相同的结果

这里的memory在下个章节讲到,用于数据的存储位置

还有一种是解构式赋值,也就是可以有选择性的返回对应的变量,如果不需要变量则留空

1
2
3
4
5
6
7
8
9
10
11
12
function test1() public pure returns(uint256 number, bool _bool, uint256[4] memory _array{
number = 1;
_bool = true;
_array = [uint256(2),1,3,4];
}

function test() public pure{
uint256 number;
bool _bool;
uint[4] memory _array;
(number,_bool,_array) = test1();
}

0x05变量存储作用域

solidity有三种存储的位置

storagememorycalldata,这三种用来区分不同的gas成本,其中storage会存储到链上,消耗的gas最多,memory和calldata是临时存在内存中,消耗的gas相对较少

其中memory和calldata的不同为calldata变量是不能修改的,比如这段代码

1
2
3
4
5
function calldata1(uint[] calldata _x) public pure returns(uint[] calldata){
_x[0] = 0;
return(_x);
}
//TypeError: Calldata arrays are read-only.

可以看到直接报类型错误了,不能修改,只能读,注释掉_x[0]就能通过编译

storage定义时,实际上创建了一个新的引用,什么意思,就是改变新的变量会影响原来的变量

1
2
3
4
5
uint[] x = [1,2,3,4,5];
function store111() public {
uint[] storage test = x;
test[0] = 100;
}

0x06条件语句

if

1
2
3
if (条件) {}
else if (条件){}
else {}

solidity可以使用三元运算符

条件 ? 结果一 : 结果二

0x07循环

for

1
for (addition) {}

while

1
while(addition){}

do-while

1
2
do{}
while(addition)

0x08报错控制

在solidit8.0中,有三种错误控制的形式,require、revert、assert

require

1
2
3
4
// require(检查条件,"异常的描述")  gas费随着异常描述的字符串长度增加而增加
function testRequire(uint _i) public pure {
require(_i <= 10,"i > 10");
}

revert

1
2
3
4
5
function testRevert(uint _i) public  pure {
if (_i > 10){
revert("i >10");
}
}

assert

assert翻译过来是断言,一般用于调试使用,与其他两者不同的是,它不会解释抛出异常的原因,与require相比就是少了异常描述字符串参数

在0.8.0之前的版本抛出的是一个panic exception,会把剩余的gas消耗完毕,不会返还

1
2
3
function testAssert(uint _num) public pure{
assert(_num == 123);
}

可以看到三者中require消耗的gas费是最多的

自定义错误(error)

error是在solidity的0.8.4中引入,在这几种中是最省gas费的,用error类型定义的函数可以携带参数并且需要搭配revert回退函数一起使用

1
error TransferNotOwner(address sender); // 自定义的带参数的error

一般常用error,省gas费,又能解释抛出异常的原因

Prev:
常见区块链算法
Next:
Go学习初探
catalog
catalog