最近各种前端已死的言论甚嚣尘上,既然web2走不通了,那就去web3这条赛道看看?
不知道你是否曾经听说过区块链?如果你对这个概念感到陌生,那么你可能也没听说过 Solidity。
Solidity 是一种基于以太坊的智能合约语言,用于编写去中心化应用程序(DApp)的代码。
如果你曾经对区块链或智能合约感到好奇,那么你一定听说过 Solidity。在这篇文章中,我不向你讲区块链,不讲 币 ,更不割韭菜;而是介绍一下 Solidity 的基础知识,以及如何从零到一的去学习和构建一个完整的去中心化应用
Solidity:是一门面向合约的、为实现智能合约而创建的高级编程语言。
智能合约:是管理以太坊状态里账户行为的程序。
Solidity 是静态类型语言,支持继承、库和复杂的用户定义类型等特性。
从编辑器开始
万事开头难,我们先拿编辑器开个头
编写Solidity有很多种方式,我们先用浏览器的在线编辑器,比较简单,访问这个在线编辑器网站 Remix 编辑器
第一步就是先把其他的文件给删除掉,
第二步:新建自己的contracts文件夹 ,然后在里面新建一个以 .sol
结尾的文件
(智能合约的文件都是以.sol结尾的)
运行起来
代码要先指明 solidity 使用的版本
pragma solidity 0.8.7; // 只是用这个版本
solidity版本也有几种不同的声明方式
pragma solidity 0.8.7; // 只是用这个版本
pragma solidity ^0.8.7; // 比0.8.7这个版本更新的都适用
pragma solidity >0.8.7; // 大于这个版本
pragma solidity >=0.8.7 <0.9.0; // 大于等于这个版本适用 0.9以上的不适用
重点: 每行代码都要使用
;
结尾
你以为 pragma solidity 0.8.7;
就是web3的第一行代码了吗?
no ! no ! no ! no ! no !
在开始写程序之前 需要先声明一下,否则在有的编辑环境下有可能会报错
它是定义一个代码规则和分享规则
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
在编辑器里,你写的代码就是这个样式
然后进行编译,点击Compile ...
是进行编译;没有语法错误编译成功时,1处就会有个绿色的对号
到这 已经把 Solidity
项目运行起来了,虽然好像什么都没有,但是确实 实实在在的编译成功了
接下来咱们继续,开始深入
合约内容
俗话说:万事开头难,然后中间难;现在开始学习智能合约,开始填充合约内容
合约是使用 contract
进行声明的
它就像一个java语言的class一样,声明一个合约名
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract HelloWorld{
// 合约内容
}
数据类型
在Solidity中的变量类型大概分为这几类
-
数值类型(Value Type) :包括布尔型,整数型等等,这类变量赋值时候直接传递数值。
-
引用类型(Reference Type) :包括数组和结构体,这类变量占空间大,赋值时候直接传递地址(类似指针)。
-
映射类型(Mapping Type) :
Solidity
里的哈希表。
数值类型
数值类型 最基础的有四种数据类型分别是 boolean , uint , int , address 等等
boolean // 布尔值类型 默认值为false
int // 可表示正数和负数 默认值为0
uint // uint是无符号整数 表示这个数字不是可正可负的,就只能表示正数 默认值为0
string // 表示一个单词,字符串,需要用 “”
address // 表示地址 比如数字钱包的地址 默认值为0的地址: 0x0000000000000000000000000000000000000000
enum //枚举
boolean
// SPDX-License-Identifier: MIT
pragma solidity 0.8.8;
contract HelloWorld {
// 定义一个变量名为 hasFavoriteNumber 的布尔值
bool hasFavoriteNumber = true;
}
uint
uint是无符号整数 表示这个数字是正整数
而且 solidity里也没有小数
// SPDX-License-Identifier: MIT
pragma solidity 0.8.8;
contract HelloWorld {
// 定义一个变量名为 favoriteNumber 的正数
uint favoriteNumber; // 初始化值为0
uint favoriteNumber1 = 666;
// 我们可以给unit分配空间 uint8表示他有8个bit,uint就相当于uint256
uint8 favoriteNumber = 255;
uint favoriteNumber = 666; // 默认分配的空间大小就是256
uint256 favoriteNumber = 666;
}
但是如果我给 unit8 赋值一个比较大的数字,那么就会报错
因为赋值的数字已经超过了unit8
可以存储的范围
enum 枚举
枚举(enum
)是solidity
中用户定义的数据类型。它主要用于为uint
分配名称,使程序易于阅读和维护,使用名称来代替从0
开始的uint
:
// 用enum将uint 0, 1, 2表示为Buy, Hold, Sell
enum ActionSet { Buy, Hold, Sell }
// 创建enum变量 action
ActionSet action = ActionSet.Buy;
int
可表示正数和负数 默认值为0
// SPDX-License-Identifier: MIT
pragma solidity 0.8.8;
contract HelloWorld {
// int
int favoriteInt = -5;
int256 favoriteNumber = 666;
}
string
表示一个字符串
// SPDX-License-Identifier: MIT
pragma solidity 0.8.8;
contract HelloWorld {
// string类型
string favoriteText = "dong";
}
address
表示地址 比如数字钱包的地址
// SPDX-License-Identifier: MIT
pragma solidity 0.8.8;
contract HelloWorld {
// address类型
address myAddress = 0xb2028c070948d7F42FB1e0465dcB70F0671A155d;
}
bytes
// SPDX-License-Identifier: MIT
pragma solidity 0.8.8;
contract HelloWorld {
// bytes类型 也可以制定大小 它通常存储以 0x开头后面跟一些随机数字和字母
bytes32 favoriteBytes = 0x3f89
// bytes 赋值字符串类型 会自动转化为bytes 32是它允许分配的最多的空间
bytes32 favoriteBytes = "cat"
}
定义一个常量
常量: 在变量名前面 加上 constant 标识 , 常量名用大写字母表示
// SPDX-License-Identifier: MIT
pragma solidity 0.8.8;
contract HelloWorld {
address public constant MY_ADDRESS;
}
细心的同学可以发现,定义变量的时候在变量名前面有一个 public
public :表示这个变量公开了,任何与合约交互的人 都可以看到 变量中存储的值
我们定义两个变量,一个带有public,一个不带,接下来我们把它部署一下
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
// 第一个合约
contract SimpleStorage {
uint myNumber = 111;
uint public favoriteNumber = 666;
}
编译好后,我们点击 1 这个时候就进入编译的页面,按钮2:"Deploy" 就是部署
点击 Deploy 按钮 就会发现部署好后的操作区域,可以看到有public的显示在页面上,我们能看到,没有public标识的就没有公开
接下来点击一下它就能看到它的值
引用类型
引用类型(Reference Type) :包括数组和结构体,这类变量占空间大,赋值时候直接传递地址(类似指针)。
数组
数组: 如果你想建立一个集合,可以用 数组
这样的数据类型. Solidity 支持两种数组: 静态数组和 动态数组:
// 固定长度为2的 uint类型的静态数组:
uint[2] fixedArray;
// 固定长度为5的 string类型的 静态数组:
string[5] stringArray;
// 动态数组,长度不固定,可以动态添加元素:
uint[] dynamicArray;
结构体
struct
结构体:支持通过构造结构体的形式定义新的类型;声明一个自定义的类型
// 结构体
struct Student{
uint256 id;
uint256 score;
}
Student student; // 初始一个student结构体
以下是对数组和结构体的总结
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
contract MyContract{
// 定义一个类型,类型名为People
struct People{
uint256 favoriteNumber;
string name;
}
//
People public people=People({favoriteNumber:6,name:'dong'});
People public people1=People({favoriteNumber:61,name:'dong1'});
// 定义一个数组
People[] public peopleList;
// 向数组内添加数据
function addPerson(string memory _name,uint256 _favoriteNumber) public{
// 添加数据方式一
People memory newPerson = People({favoriteNumber:_favoriteNumber, name:_name});
peopleList.push(newPerson);
// 方式二
peopleList.push(People({favoriteNumber:_favoriteNumber, name:_name}));
}
}
映射类型
mapping
映射(mapping) ,可以通过键(Key)来查询对应的值(Value), 它可以更容易跟快地访问。 它就像一个字典,每个键返回它关联的某个值;(和js中的map数据结构非常相似)
mapping(uint => address) public idToAddress; // id映射到地址
mapping(address => address) public swapPair; // 币对的映射,地址到地址
//对于金融应用程序,将用户的余额保存在一个 uint类型的变量中:
mapping (address => uint) public accountBalance;
//或者可以用来通过userId 存储/查找的用户名
mapping (uint => string) userIdToName;
映射的key只能选择solidity默认的类型,比如uint,address等,不能用自定义的结构体。而value可以使用自定义的类型
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
contract MyContract{
uint256 favoriteNumber;
// 定义一个类型,类型名为People
struct People{
uint256 favoriteNumber;
string name;
}
// mapping 创建一个映射,起一个名字并把它公开
mapping(string=>uint256) public nameToFavoriteNumber;
// 定义一个数组
People[] public peopleList;
// 向数组内添加数据
function addPerson(string memory _name,uint256 _favoriteNumber) public{
peopleList.push(People({favoriteNumber:_favoriteNumber, name:_name}));
// 存储映射的内容
nameToFavoriteNumber[_name] = _favoriteNumber;
}
}
函数
Solidity
里的函数我觉得和JS的函数非常相似,他也是用function关键字声明的,但是多了一些修饰符
- function 函数名(参数)修饰符 {}
参数也需要声明类型
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
contract SimpleStorage {
uint256 public favoriteNumber; // 以0初始化变量
// 定义一个 store的函数 传入一个形参
function store(uint256 _favoriteNumber) public {
// 进行赋值
favoriteNumber = _favoriteNumber;
}
}
点击黄色的 Deploy进行部署合约,就会出现以下信息
部署合约就 是发送一个交易;区块链上做任何事情,修改任何状态 就是在发一个交易, 我们可以在 store 里输入一个数字,然后点击 store按钮,就会调用一次合约会消耗一些gas
在变量名favoriteNumber
前面添加一个 public 表示这个变量公开了,可以在外部访问到它,我们看看能不能赋值成功
点击favoriteNumber 可以看到已经赋值成功
函数和变量有 4 种可见度的标识符:
-
public
表示公开,任何与合约交互的人 都可以看到 变量中存储的值(public会创建变量的getter函数,返回函数值) -
private
表示只对合约内部可见;只能从本合约内部访问,继承的合约也不能用(也可用于修饰状态变量)。
uint[] numbers;
function _addToArray(uint _number) private {
numbers.push(_number);
}
-
external
表示只对合约外部可见;只能从合约外部访问(但是可以用this.fn()
来调用,fn
是函数名) -
internal
表示只有这个合约或者继承它的合约可以读取;只能从合约内部访问,继承的合约可以用(也可用于修饰状态变量)。
函数权限/功能 关键字
-
payable
(可支付的)很好理解,带着它的函数,运行的时候可以给合约转入ETH
-
view
表示这个函数只会读取这个合约的状态,view函数里面不允许修改任何状态 -
pure
也不允许修改状态,而且pure也不允许读取区块链数据,不能读取变量;通常用于某个不需要读取数据的算法
合约里面的操作和计算越多,消耗的gas也越多
函数输出
Solidity
的函数输出需要有两个关键字:return
和returns
returns
加在函数名后面,用于声明返回的变量类型及变量名;return
用于函数主体中,返回指定的变量。
// 返回多个变量
function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){
return(1, true, [uint256(1),2,5]);
}
命名式返回
在 returns
里表明返回的变量名称,solidity
会自动初始化这些变量,并返回这些函数的值;并且不需要使用 return
// 命名式返回
function returnNamed() public pure returns(uint256 _number, bool _bool,
uint256[3] memory _array){
_number = 2;
_bool = false;
_array = [uint256(3),2,1];
}
构造函数
每个合约可以定义一个构造函数(constructor
),在部署合约的时候自动运行一次。它可以用来初始化合约的一些参数。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
contract SimpleStorage {
uint256 public favoriteNumber; // 以0初始化变量
address owner; // 定义owner变量
// 构造函数
constructor() {
owner = msg.sender; // 在部署合约的时候,将owner设置为部署者的地址
}
}
数据存储位置
数组(array
),结构体(struct
)和映射(mapping
),这类变量占空间大,赋值时候直接传递地址(类似指针)。由于这类变量比较复杂,占用存储空间大,我们在使用时必须要声明数据存储的位置。
solidity数据存储位置有三类:storage
,memory
和calldata
。不同存储位置的gas
成本不同。storage
类型的数据存在链上,类似计算机的硬盘,消耗gas
多;memory
和calldata
类型的临时存在内存里,消耗gas
少。大致用法:
storage
:合约里的状态变量默认都是storage
,存储在链上。memory
:函数里的参数和临时变量一般用memory
,存储在内存中,不上链。calldata
:和memory
类似,存储在内存中,不上链。与memory
的不同点在于calldata
变量不能修改(immutable
),一般用于函数的参数。
赋值规则
不同存储类型进行赋值时候,有的修改新变量不会影响原变量,有的修改新变量会影响原变量。规则如下:
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
,会创建独立的副本,修改其中一个不会影响另一个;反之亦然.
uint[] x = [1,2,3]; // 状态变量:数组 x
function fMemory() public view{
//声明一个Memory的变量xMemory,复制x。修改xMemory不会影响x
uint[] memory xMemory = x;
xMemory[0] = 100;
xMemory[1] = 200;
uint[] memory xMemory2 = x;
xMemory2[0] = 300;
}
memory
赋值给memory
,会创建引用,改变新变量会影响原变量。- 其他情况,变量赋值给
storage
,会创建独立的副本,修改其中一个不会影响另一个。
英语
英文 | 翻译 |
---|---|
Compile | 编绎;编译文件;编译 |
contract | 合同;合约;契约; |
private | 私有的;私人的;私用的; |
external | 外部的;外面的;外界的; |
internal | 里面的;体内的; |