Solidity 简介
Solidity 是一种用于编写以太坊虚拟机(EVM)智能合约的编程语言。我认为掌握 Solidity 是参与链上项目的必备技能:区块链项目大部分是开源的,如果你能读懂代码,就可以规避很多亏钱项目。
Solidity 具有两个特点:
- “基于对象”:学会
Solidity之后,可以助你在区块链领域找到好工作,挣钱找对象。 - “高级”:不会
Solidity,在币圈会显得很 low。
test
开发工具:Remix
本教程中,我们将使用 Remix 运行 Solidity 合约。Remix 是以太坊官方推荐的智能合约集成开发环境(IDE),适合新手,可以在浏览器中快速开发和部署合约,无需在本地安装任何程序。
在 Remix 中,左侧菜单有三个按钮,分别对应文件(编写代码)、编译(运行代码)和部署(将合约部署到链上)。点击“创建新文件”(Create New File)按钮,即可创建一个空白的 Solidity 合约。

第一个 Solidity 程序
这个简单的程序只有 1 行注释和 3 行代码:
1 | // SPDX-License-Identifier: MIT |
我们拆解程序,学习 Solidity 代码源文件的结构:
第 1 行是注释,说明代码所使用的软件许可(license),这里使用的是 MIT 许可。如果不写许可,编译时会出现警告(warning),但程序仍可运行。Solidity 注释以“//”开头,后面跟注释内容,注释不会被程序执行。
1
// SPDX-License-Identifier: MIT
第 2 行声明源文件所使用的 Solidity 版本,因为不同版本的语法有差异。这行代码表示源文件将不允许小于 0.8.21 版本或大于等于 0.9.0 的编译器编译(第二个条件由
^提供)。Solidity 语句以分号(;)结尾。1
pragma solidity ^0.8.21;
第 3-4 行是合约部分。第 3 行创建合约(contract),并声明合约名为
HelloWeb3。第 4 行是合约内容,声明了一个 string(字符串)变量_string,并赋值为 “Hello Web3!”。1
2
3contract HelloWeb3 {
string public _string = "Hello Web3!";
}
后续我们会更详细地介绍 Solidity 中的变量。
编译并部署代码
在 Remix 编辑代码的页面,按 Ctrl + S 即可编译代码,非常方便。
编译完成后,点击左侧菜单的“部署”按钮,进入部署页面。
[
默认情况下,Remix 会使用 Remix 虚拟机(以前称为 JavaScript 虚拟机)来模拟以太坊链,运行智能合约,类似在浏览器里运行一条测试链。Remix 还会为你分配一些测试账户,每个账户里有 100 ETH(测试代币),随意使用。点击 Deploy(黄色按钮),即可部署我们编写的合约。
[
部署成功后,在下方会看到名为 HelloWeb3 的合约。点击 _string,即可看到 “Hello Web3!”。
总结
本节课程中,我们简要介绍了 Solidity 和 Remix 工具,并完成了第一个 Solidity 程序 —— HelloWeb3。接下来,我们将继续深入学习 Solidity!
中文 Solidity 资料推荐
- Solidity中文文档(官方文档的中文翻译)
- 崔棉大师solidity教程 web3技术教学博主,我看他视频学到了很多
WTF Solidity极简入门: 2. 值类型
Solidity中的变量类型
==主要就是这3种类型==
- ==**值类型(Value Type)**:包括布尔型,整数型等等,这类变量赋值时候直接传递数值。==
- ==引用类型(Reference Type):包括数组和结构体,这类变量占空间大,赋值时候直接传递地址(类似指针)。==
- ==映射类型(Mapping Type): Solidity中存储键值对的数据结构,可以理解为哈希表==
- 函数类型(Function Types) : 代表函数的变量
我们将仅介绍常用类型,不常用的类型不会涉及,本篇将介绍值类型。
值类型
1. 布尔型
布尔型是二值变量,取值为 true 或 false。
1 | // 布尔值 |
布尔值的运算符包括:
!(逻辑非)&&(逻辑与,”and”)||(逻辑或,”or”)==(等于)!=(不等于)
1 | // 布尔运算 |
在上述代码中:变量 _bool 的取值是 true;_bool1 是 _bool 的非,为 false;_bool && _bool1 为 false;_bool || _bool1 为 true;_bool == _bool1 为 false;_bool != _bool1 为 true。
值得注意的是:&& 和 || 运算符遵循短路规则,这意味着,假如存在 f(x) || g(y) 的表达式,如果 f(x) 是 true,g(y) 不会被计算,即使它和 f(x) 的结果是相反的。假如存在f(x) && g(y) 的表达式,如果 f(x) 是 false,g(y) 不会被计算。 所谓“短路规则”,一般出现在逻辑与(&&)和逻辑或(||)中。 当逻辑与(&&)的第一个条件为false时,就不会再去判断第二个条件; 当逻辑或(||)的第一个条件为true时,就不会再去判断第二个条件,这就是短路规则。
2. 整型
整型是 Solidity 中的整数,最常用的包括:
1 | // 整型 |
常用的整型运算符包括:
- 比较运算符(返回布尔值):
<=,<,==,!=,>=,> - 算数运算符:
+,-,*,/,%(取余),**(幂)
1 | // 整数运算 |
大家可以运行一下代码,看看这 4 个变量分别是多少。
3. 地址类型
地址类型(address)有两类:
- ==普通地址(address): 存储一个 20 字节的值(以太坊地址的大小)。==
- ==payable address: 比普通地址多了
transfer和send两个成员方法,用于接收转账。==
我们会在之后的章节更加详细地介绍 payable address。
1 | // 地址 |
4. 定长字节数组
==字节数组分为定长和不定长两种:==
- ==定长字节数组: 属于值类型,数组长度在声明之后不能改变。根据字节数组的长度分为
bytes1,bytes8,bytes32等类型。定长字节数组最多存储 32 bytes 数据,即bytes32。== - 不定长字节数组: 属于==引用类型==(之后的章节介绍),数组长度在声明之后可以改变,包括
bytes等。
1 | // 固定长度的字节数组 |
在上述代码中,MiniSolidity 变量以字节的方式存储进变量 _byte32。如果把它转换成 16 进制,就是:0x4d696e69536f6c69646974790000000000000000000000000000000000000000
_byte 变量的值为 _byte32 的第一个字节,即 0x4d。
==表示一个16进制需要4位,一个bytes1是8位,所以一个bytes1可以表示2个16进制位==
5. 枚举 enum
枚举(enum)是 Solidity 中用户定义的数据类型。它主要用于为 uint 分配名称,使程序易于阅读和维护。它与 C 语言 中的 enum 类似,使用名称来代替从 0 开始的 uint:
1 | // 用enum将uint 0, 1, 2表示为Buy, Hold, Sell |
枚举可以显式地和 uint 相互转换,并会检查转换的正整数是否在枚举的长度内,否则会报错:
1 | // enum可以和uint显式的转换 |
enum 是一个比较冷门的变量,几乎没什么人用。
在 Remix 上运行
- 部署合约后可以查看每个类型的变量的数值:
[
enum和uint转换的示例:
总结
在这一讲,我们介绍了 Solidity 中值类型,包括布尔型、整型、地址、定长字节数组和枚举。在后续章节,我们将继续介绍 Solidity 的其他变量类型,包括引用类型和映射类型。
Solidity极简入门: 3. 函数类型
solidity官方文档里把函数归到数值类型,但我觉得差别很大,所以单独分一类。我们先看一下solidity中函数的形式:
1 | 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种。没标明函数类型的,==默认==public。合约之外的函数,即”自由函数”,始终具有隐含internal可见性。public: 内部外部均可见。private: 只能从本合约内部访问,继承的合约也不能用。external: 只能从合约外部访问(但是可以用this.f()来调用,f是函数名)。 ==这里this后文有解释==internal: 只能从合约内部访问,继承的合约可以用。
Note 1: 没有标明可见性类型的函数,默认为
public。Note 2:
public|private|internal也可用于==修饰状态变量==。 ==public变量会自动生成同名的getter函数,用于查询数值。==Note 3: 没有标明可见性类型的==状态变量,默认为
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,中文意思是“纯”,这 里可以理解为”纯打酱油的”。pure函数既不能读取也不能写入链上的状态变量。就像小怪一样,看不到也摸不到碧琪公主。view,“看”,这里可以理解为“看客”。view函数能读取但也不能写入状态变量。类似马里奥,能看到碧琪公主,但终究是看客,不能入洞房。- 非
pure或view的函数既可以读取也可以写入状态变量。类似马里奥里的boss,可以对碧琪公主为所欲为🐶。
代码
1. pure 和 view
我们在合约里定义一个状态变量 number,初始化为 5。
1 | // SPDX-License-Identifier: MIT |
定义一个 add() 函数,每次调用会让 number 增加 1。
1 | // 默认function |
如果 add() 函数被标记为 pure,比如 function add() external pure,就会报错。因为 pure 是不配读取合约里的状态变量的,更不配改写。那 pure 函数能做些什么?举个例子,你可以给函数传递一个参数 _number,然后让他返回 _number + 1,这个操作不会读取或写入状态变量。
==我的理解就是,参数或者函数内部定义的变量都不是状态变量,函数结束了,这些变量就消失了,也不上链==
1 | // pure: 纯纯牛马 |
[
如果 add() 函数被标记为 view,比如 function add() external view,也会报错。因为 view 能读取,但不能够改写状态变量。我们可以稍微改写下函数,读取但是不改写 number,返回一个新的变量。
1 | // view: 看客 |
==重点来了,view和pure的区别就是view可以读取状态变量,pure不可以读取状态变量,他的输出只能由参数决定。==
2. internal v.s. external
external: 只能从合约外部访问(但是可以用this.f()来调用,f是函数名)。internal: 只能从合约内部访问,继承的合约可以用。
1 | // internal: 内部函数 |
==重点来了,也就是带有函数可见性说明符internal的,在部署完之后,不可以调用,因为我们部署完合约之后,在remix里面进行函数调用,查看变量,传参都相当于在合约外部调用,所以想要调用带有internal的函数,只能通过另外一个external函数调用==
我们定义一个 internal 的 minus() 函数,每次调用使得 number 变量减少 1。由于 internal 函数只能由合约内部调用,我们必须再定义一个 external 的 minusCall() 函数,通过它间接调用内部的 minus() 函数。
3. payable
1 | // payable: 递钱,能给合约支付eth的函数 |
我们定义一个 external payable 的 minusPayable() 函数,间接的调用 minus(),并且返回合约里的 ETH 余额(this 关键字可以让我们引用合约地址)。我们可以在调用 minusPayable() 时往合约里转入1个 ETH。
==this关键字代表合约自身的地址。address(this).balance会返回当前合约地址所持有的以太币(ETH)数量,以wei为单位==
==在Solidity中,this关键字代表当前合约的实例。它允许你在合约内部访问合约自身的成员,比如函数和状态变量。this可以在合约的任何函数内使用,但通常在需要引用合约自身的上下文中使用,比如转账操作时指向合约自己的地址(address(this))。这使得合约能够操作自己的属性和调用自己的方法。==
==在Solidity中,this除了用于引用合约自身的实例和获取合约的地址外(address(this)),还可以用来在合约内部调用当前合约的其他公共和外部函数。例如,如果你有一个公共函数functionA,你可以通过this.functionA()来调用它,尤其在需要显式地表明函数调用是在合约内部发生时。这种方式通常用于需要通过合约地址调用函数的场景,确保调用遵循EVM的调用规则,如通过消息调用(message call)。==
==一言以蔽之:在合约内部使用this调用函数,实际上是模拟外部调用者的行为。==
external: 只能从合约外部访问(但是可以用this.f()来调用,f是函数名)。
1 | pragma solidity ^0.8.0; |
我们可以在返回的信息中看到,合约的余额变为 1 ETH。
总结
在这一讲,我们介绍了 Solidity 中的函数。pure 和 view 关键字比较难理解,在其他语言中没出现过:view 函数可以读取状态变量,但不能改写;pure 函数既不能读取也不能改写状态变量。



