一文聊透 Solidity 语法:助你成为智能合约专家(一)

简介: 一文聊透 Solidity 语法:助你成为智能合约专家

关于区块链和智能合约开发者的区别解释


我发现很多人都表述不清楚区块链和智能合约。我认识几位程序员朋友,他们都自称是在做区块链开发,但实际上是在做智能合约的开发。大多数外行分不清楚区块链和智能合约我能理解,但是很多从事智能合约开发的程序员竟然也分不清楚,我不知道是不是表述问题还是理解问题。

区块链是区块链,智能合约是智能合约,两者的关系就像是微信和微信小程序一样,一个是 App 开发,一个是小程序开发,根本不一样,不能混为一谈。

据我了解,区块链的需求没那么多,特别是中国这个环境下。大多数区块链相关的程序员都是在做智能合约开发,而不是真的在开发区块链。

区块链是可以用很多后端语言去开发的,比如用 Go、Node.js、Rust、Java 等。

但是智能合约不可以随便选择编程语言,我这里讲的智能合约是指以太坊智能合约。目前它只能选择 Solidity、Vyper、YUL、YUL+ 和 Fe 这 5 种语言。其中 solidity 最受欢迎,大多数项目和开发者都是选择了 solidity。我们几乎可以说 solidity 是智能合约的首选编程语言。


这篇文章会讲什么?


这篇文章将会介绍我认为使用 Solidity 编写智能合约时 90% 以上的场景中能够用到的语法和特性。

但是 Solidity 是一门完整的编程语言,想要把它彻底学明白,一篇文章肯定是不够的。因为很多语言都被写成了一厚厚地本书。不过通常写编程语言的书都会非常全体、体系化地介绍语言的全部,包括那些平时压根用不到的知识,或者一些已经落伍,语言设计上糟粕的部分。总体来说,通过一本厚厚的书来讲一门编程语言,多少是从研究的角度出发的,如果你只想快速用 Solidity 开发智能合约,不想把这门语言研究的这么透彻,那么本文很适合你。

同时本文会拿 solidity 和一些面向对象的语言做对比,如果你完全不懂其他编程语言,那么本文不适合你。


面向合约


Solidity 的设计理念和面向对象编程语言很相似,不过 Solidity 是面相合约的编程语言,如果你有面向对象编程语言的开发经验,那么学习 Solidity 就没有那么难。

Solidity 语言被设计为编写合约的语言,目前来说也只能写合约,所以它不像其他语言那样可以做很多事情。


合约构成解读


我们先来看一个最简单的合约构成,做一个整体的感受。


// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract HelloWorld {
  address private owner;
  unit public state;
  modifier onlyOwner() {
    require(msg.sender == owner, "only owner");
    _;
  }
  event StateChanged(unit state);
  constructor() public {
    owner = msg.sender;
  }
  function setState(uint _state) external onlyOwner {
    state = state;
    emit StateChanged(_state)
  }
}

我简单解释下这个合约的代码,不会详细介绍。

第 1 行是指定版本许可。

第 2 行是指定使用的语言版本。

第 4 行是声明一个名为 HelloWorld 的合约。

第 5-6 行是状态变量,它们会永久存储在合约中。

第 8 -11 行是函数修改器,它可以用在函数修饰符上,可以改变函数的行为。

第 13 行是声明一个事件,事件可以被触发和监听。

第 15-17 行是构造函数,在部署时会被调用。

第 19-22 行是声明了一个名为 setState 的函数。


版本


solidity 有很多种版本,目前最新的版本是 8.x。

但是在早期比较流行的是 5.x、6.x 这两个版本。

solidity 的版本命名规范采用 。

和其他大多数编程语言不同的是,solidity 的版本是直接写在源代码里的。

在任意一个 sol 文件的最开始,都应该是版本代码。

语法为:


pragma solidity 0.8.0;

如果你用过 npm 的话,那这个版本语言一定不会陌生,因为 solidity 同样使用了 semver 版本规范。


合约


合约的概念有点像面向对象编程语言的类,属于一等公民。

通过关键字 contract 创建。

语法:


contract MyContract {
}

可以通过 new 关键字创建合约。


new MyContract();


继承


面向对象的语言通常会使用 extends 关键字来继承,但是 solidity 没有这样做,它使用 is 来继承合约。


contract MyContract1 {
  uint256 num = 2022;
}
contract MyContract2 is MyContract1 {
}

子合约被部署时,会把所有父合约的代码一起打包,所以对父合约中函数的调用都属于内部调用。

子合约可以隐式转换父合约,合约也可以显式转换为地址。


address addr = address(c);

重写函数使用 override 关键字。父合约中支持重写的函数必须是 virtual 的。


contract Parent {
    function fn() public virtual {}
}
contract Child is Parent {
    function fn() public override {}
}

调用父合约中的方法,使用 super 关键字。


contract Parent {
    function fn() public {}
}
contract Child is Parent {
    function fn2() public {
        super.fn();
    }
}

支持多重继承。


contract Parent1 {
    function fn() public virtual {}
}
contract Parent2 {
    function fn() public virtual {}
}
contract Child is Parent1, Parent2 {
    function fn() public override(Parent1, Parent2) {}
}


变量与基础类型


变量是永久存储在合约中的值,通常用来记录业务信息。

每个变量都需要声明类型,solidity 中的类型有如下几种:

  • string:字符串类型
  • bool:布尔值,true/false。
  • uint:无符号整型,有 uint 和 uint8/16/32/64/128/256 几个类型。uint 是 uint256 的别名。
  • int:有符号整型,规则和 uint 一样。
  • bytes:定长字节数组。从 bytes1 到 bytes32,byte 是 bytes1 的别名。它和数组类似,通过下标获取元素,通过 length 获取成员数量。
  • address:地址类型。保存一个 20 字节的地址。
  • address payable:可支付的地址,有成员函数 transfer 和 send。


contract MyContract {
  string name = ""
}


uint


对于整型变量,我们可以通过 type(x).min 和 type(x).max 来获取某个类型的最大值和最小值。


address


address payable 可以隐式转换到 address,但是 address 必须通过 payable(address) 这种方式显示转换。

address 还可以显示转换为 uint160 和 bytes20。


bytes 和 string


bytes 和 string 都是数组,而不是普通的值类型。

bytes 和 byte[] 非常像,但是它在 calldata 和 memory 中会紧打包。紧打包的意思是将元素连续存储在一起,不会按照每 32 字节为一个单元进行存储。

string 是变长 utf-8 编码的字节数组,和 bytes 不同的是它不可以用索引来访问。

字符串没有操作函数,一般都是通过第三方 string 库来操作字符串。

string 可以转换为 bytes,转换时是创建引用而不是创建拷贝。


function stringToBytes() public pure returns (bytes memory) {
  string memory str = "hello";
  bytes memory bts = bytes(str);
  return bts;
}

由于 bytes 和 string 很相似,所以我们在使用它们时应该有对应的原则。

  • 对于任意长度的原始字节使用 bytes。
  • 对于任意长度的 UTF-8 字符串使用 string。
  • 当需要对字节数组长度进行限制时,应该使用 byte1-byte32 之间的具体类型。

合理使用 bytes 可以节省 Gas 费。


变量修饰符


我们也可以为变量指定访问修饰符。

语法是 类型 访问修饰符(可选) 字段名。

访问修饰符有三种:

  • public:公开,外部可以访问,声明为 public 的话会自动生成 getter 函数。
  • internal:默认,只有合约自身和派生的合约可以访问。
  • private:只有合约自身可以访问。

solidity 中的变量与传统语言的变量有些不同。

  1. 字符串的值默认不可以包含中文。如果要使用除了英文外的其他语言,必须加 unicode 前缀。


string name = unicode"小明";


结构体


使用关键字 struct 创建结构,有点类似 Go/C 的 struct,或者类似 TypeScript 中的 type/interface。


struct User {
  string name;
  string password;
  uint8 age;
  bool state;
}

初始化结构体和调用函数类似,参数的顺序和结构体的顺序保持一致。


User user = User("章三", "123", 12, false);

访问某一个属性使用点号。


user.name;

属性也可以直接赋值。


user.name = "里斯";


数组


和 TypeScript 中的数组语法一致,语法是 type[]。


User[] users;

访问数组元素,使用 array[index] 的方式。


users[0];

访问不存在的下标,会直接报错。

在创建数组时可以声明长度,如果不声明,那就是可以动态调整大小的数组。


uint256[10] nums;

数组具有 pop 和 push 方法,分别用于弹出一个元素和添加一个元素。但是它们不可以用在定长数组中。

push 方法可以不传递参数,这时表示它添加一个该元素类型的零值。


strs.push("1");
strs.pop();


映射


类似于很多语言中的 Map 结构。语法是 mapping(keyType => valueType)。


mapping(address => User) userMapping;

key 的类型只允许是基本类型,不可以是复杂类型,比如合约、枚举、映射和结构体。

value 的类型没有限制。

访问 mapping 元素,使用 mapping[key] 的方式。


userMapping[0x021221]

访问不存在的 key,会返回 value 类型的默认值。

mapping 不可以作为公有函数的参数和返回值,只可以作为变量或者函数内的存储或者库函数的参数。

声明为 public 的 mapping,会自动创建 getter 函数。KeyType 作为参数,ValueType 作为返回值。

mapping 无法被遍历。不过有一些开源库用一种结构来实现了可遍历的 mapping。可以直接拿过来用。



相关文章
|
10月前
|
存储 JSON 监控
Viper,一个Go语言配置管理神器!
Viper 是一个功能强大的 Go 语言配置管理库,支持从多种来源读取配置,包括文件、环境变量、远程配置中心等。本文详细介绍了 Viper 的核心特性和使用方法,包括从本地 YAML 文件和 Consul 远程配置中心读取配置的示例。Viper 的多来源配置、动态配置和轻松集成特性使其成为管理复杂应用配置的理想选择。
359 2
|
JSON 网络协议 Go
golang使用resty库实现模拟请求正方教务
本文主要讲解了如何使用golang模拟请求正方教务
611 0
|
10月前
|
存储 easyexcel Java
SpringBoot+EasyExcel轻松实现300万数据快速导出!
本文介绍了在项目开发中使用Apache POI进行数据导入导出的常见问题及解决方案。首先比较了HSSFWorkbook、XSSFWorkbook和SXSSFWorkbook三种传统POI版本的优缺点,然后根据数据量大小推荐了合适的使用场景。接着重点介绍了如何使用EasyExcel处理超百万数据的导入导出,包括分批查询、分批写入Excel、分批插入数据库等技术细节。通过测试,300万数据的导出用时约2分15秒,导入用时约91秒,展示了高效的数据处理能力。最后总结了公司现有做法的不足,并提出了改进方向。
|
7月前
|
机器学习/深度学习 传感器 监控
机器学习:强化学习中的探索策略全解析
在机器学习的广阔领域中,强化学习(Reinforcement Learning, RL)无疑是一个充满魅力的子领域。它通过智能体与环境的交互,学习如何在特定的任务中做出最优决策。然而,在这个过程中,探索(exploration)和利用(exploitation)的平衡成为了智能体成功的关键。本文将深入探讨强化学习中的探索策略,包括其重要性、常用方法以及代码示例来论证这些策略的效果。
|
8月前
|
运维 算法 Ubuntu
Copilot测评报告——2025如果你需要做运维,强烈推荐你使用Copilot
作为一名开发工程师,我曾参与阿里云Copilot的测评工作。2025年最新版Copilot支持Alinux、CentOS、Ubuntu、Anolis OS等操作系统,并新增了Agent模式,可直接执行命令并返回系统健康度等信息,大幅提升了运维效率。它还具备复杂任务理解能力,能处理定时任务和脚本编写,结合管道符号使用,极大便利了运维工作。强烈推荐给中高级运维工程师使用。
393 22
|
11月前
|
网络协议 索引
Flutter获取手机的IP地址
Flutter获取手机的IP地址
|
存储 缓存 资源调度
Yarn: 安装与使用教程
Yarn: 安装与使用教程
1101 3
|
JavaScript
Vue中给当前页面传递参数并重新加载,vue使用this.$router.push跳转页面,给跳转过去的页面传参不一致时重新加载
Vue中给当前页面传递参数并重新加载,vue使用this.$router.push跳转页面,给跳转过去的页面传参不一致时重新加载
633 0
|
自然语言处理 前端开发 安全
当被问到你使用过iframe吗?有哪些优点和缺点?
当被问到你使用过iframe吗?有哪些优点和缺点?
547 0
|
JavaScript 前端开发 API
使用ort.js的create方法加载onnx模型报错:Fetch API cannot load file…… URL scheme “file“ is not supported.
使用ort.js的create方法加载onnx模型报错:Fetch API cannot load file…… URL scheme “file“ is not supported.
735 0
使用ort.js的create方法加载onnx模型报错:Fetch API cannot load file…… URL scheme “file“ is not supported.