欢迎来到Solidity大神之路之内功修炼第一章!在第扫盲章节中,我们介绍了Solidity的基本概念,本文将带你深入了解Solidity的核心语法与特性,帮助你掌握编写智能合约的基础知识。无论你是初学者还是有一定编程经验的开发者,这篇文章将为你提供清晰、实用的指导
为了快速上手Solidity开发,我们选择 Remix IDE 作为主要开发工具。Remix是一个功能强大、基于浏览器的集成开发环境(IDE),支持Solidity合约的编写、编译、测试和部署。它无需安装本地环境,直接在浏览器中使用,专为Solidity智能合约设计,适合初学者和专业开发者
打开浏览器,访问 Remix
接下来在contracts 文件夹中,新建 HelloWorld.sol 文件,我将在代码中,详细讲解每一行代码
SPDX
// SPDX-License-Identifier: MIT
从Solidity 0.6.8开始,建议在文件开头添加SPDX许可证标识符,表明代码的开源许可类型。最常用的是MIT许可证
如果代码无许可证,使用UNLICENSED
许可证标识符不会影响代码执行,但有助于代码共享和合规性
关于SPDX许可证的详细介绍: https://spdx.org/licenses
pragma
pragma solidity ^0.8.0;
任意一个 Solidity 文件中,pragma 指令必须是在代码的第一行。Pragma 是一个指令,它告诉编译器应该使用哪个
编译器版本将人类可读的 Solidity 代码转换为机器可读的字节码。Solidity 是一门新语言,更新频率很高,所以不
同版本的编译器在编译代码时会产生不同的结果。当使用较新的编译器版本编译时,一些较旧的 solidity 文件会抛出
错误或警告。在较大的项目中,当你使用像 Hardhat 这样的工具时,可能需要指定多个编译器版本,因为导入的
solidity 文件或你依赖的库是为旧版本的 solidity 编写的
Solidity官方版本控制和pragma说明:
https://docs.soliditylang.org/en/latest/layout-of-source-files.html#version-pragma
pragma 指令遵循语义化版本控制 (SemVer),SemVer 是一个系统,其中每个数字表示该版本中包含的更改的类型和范围
如果你想要 SemVer 的实际操作解释,请查看:https://semver.org/
pragma 更加详细的使用
(1) pragma solidity
用于指定Solidity编译器版本。
示例: pragma solidity ^0.8.0;
版本范围说明:
^0.8.0 允许使用0.8.x版本(例如0.8.1、0.8.2),但不允许0.9.0或更高版本
>=0.7.0 <0.9.0 允许使用0.7.0到0.8.x的版本
>0.7.0 <=0.8.5 允许版本高于0.7.0但不超过0.8.5
=0.8.0 等价于精确版本指定 0.8.0
0.8.0 严格要求使用0.8.0版本
~0.8.1 允许0.8.1、0.8.2等,但不允许0.8.0(更低的版本)或 0.9.0(更高的版本)
0.7.6 || ^0.8.0 允许使用0.7.6版本或0.8.x系列版本
0.8.* 等价于 >=0.8.0 <0.9.0,允许0.8.x的任意版本
0.*.* 等价于 >=0.0.0 <1.0.0,允许0.x.x的任意版本
0.8 等价于 >=0.8.0 <0.9.0,允许0.8.x的任意版本
0 等价于 >=0.0.0 <1.0.0,允许0.x.x的任意版本
(2) pragma experimental
用于启用实验性功能,这些功能可能不稳定,仅用于测试或开发。
示例:pragma experimental ABIEncoderV2;
这启用了ABI编码器V2,允许更复杂的数据结构(如动态数组)在函数调用中传递。
注意:实验性功能在生产环境中需谨慎使用。
(3) pragma abicoder
用于指定ABI编码器版本,通常与实验性功能相关。
示例:pragma abicoder v2;
从Solidity 0.8.0开始,abicoder v2 已成为默认设置,取代了旧的 ABIEncoderV2。
(4) 其他 pragma
例如 pragma optimize(用于设置优化参数,但目前较少使用)。
未来可能会有新的 pragma 指令,具体取决于Solidity的发展。
contract
contract HelloWorld {}
关键字 contract 告诉编译器你正在声明一个智能合约。如果你熟悉面向对象编程,
那么你可以将契约视为类。如果你不熟悉 OOP,那么可以将合约视为保存数据的对象
——包括变量和函数。你可以通过智能合约为区块链应用程序提供所需的功能
变量
string public greeting = "Hello, World!";
uint public public_val = 123;
uint internal internal_val = 234;
uint private private_val = 456;
string , uint 是变量的数据类型
public , internal , private 是变量的修饰符
修饰符后面的是变量名称
等号右边的是变量的值
变量作用域
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract HelloWorld {
string public greeting = "Hello, World!";
function public_fun() public {
uint num = 1;
address owner = msg.sender;
}
}
状态变量:通过将值记录在区块链上,在智能合约中存储永久数据(称为持久状态)
位置:状态变量通常位于智能合约内部,但位于函数外部 ,这里对应的就是 greeting
局部变量:这些是暂时性数据,在运行计算时会在短时间内保存信息。这些值不会永久存储在区块链上
位置:局部变量位于函数内部,不能从该函数之外访问,这里对应的是 num
全局变量:这些变量和函数由 Solidity注入到您的代码中,无需专门创建或从任何地方导入它们即可使用。这些提供了代码运行时的区块链环境信息,还包括程序中会用到的功能性函数
位置:全局变量不是由你声明的,是由系统提供的,这里对应的是 msg.sender
constant 、immutable 关键字
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract HelloWorld {
string public greeting = "Hello, World!";
uint constant NUMBER = 123;
bytes constant BYTE_ = "Hello";
string constant TEXT = "abc";
address constant OWNER = 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e;
address immutable OWNER2;
function public_fun() public {
uint num = 1;
address owner = msg.sender;
}
constructor(address _owner) {
OWNER2 = _owner;
}
}
constant
数据修饰符,代表常量,用于声明在编译时就确定值且运行时不可更改的变量
变量名称一般使用大写,是一种约定,使用小写也不会报错
常量的值必须在定义时指定,
且无法在构造函数或运行时修改
值是固定,变量的值在编译时必须是确定的(即硬编码或通过常量表达式计算)。
存储位置:constant 变量不占用区块链存储空间,它们的值直接嵌入到合约的字节码中。
适用类型:通常用于基本类型(如 uint, address, string, bytes 等)或值类型。
Gas 优化:因为值直接嵌入字节码,读取 constant 变量不需要访问存储,Gas 成本极低。
限制:
不能用于引用类型(如动态数组或映射)
不能在运行时或构造函数中初始化。
不能被函数修改
immutable
数据修饰符,代表不可变变量,用于声明在部署时(构造函数中)初始化且之后不可更改的变量
相比 constant,immutable 提供了更大的灵活性,因为它的值可以在合约部署时动态设置
变量名称一般使用大写,是一种约定,使用小写也不会报错
值在部署时确定:immutable 变量的值可以在构造函数中设置,但一旦合约部署完成,值就不可更改
存储位置:immutable 变量的值也嵌入到合约字节码中,不占用存储槽,读取时 Gas 成本低
适用类型:支持大多数类型,包括基本类型和引用类型(如数组、结构体等)
Gas 优化:与 constant 类似,immutable 变量的读取不涉及存储访问,Gas 成本低
限制:
只能在声明时或构造函数中初始化
初始化后不可修改
不支持在函数中动态修改
构造函数(constructor)
constructor(address _owner) {
OWNER2 = _owner;
}
构造函数(constructor) 是一个特殊的函数,用于在智能合约部署时初始化合约的状态
它在合约创建时自动执行一次,且只能执行一次
定义方式:使用 constructor 关键字定义,无返回值
执行时机:仅在合约部署时由以太坊虚拟机(EVM)自动调用,之后无法再次调用
作用:用于初始化合约的状态变量、设置关键参数或执行部署时的逻辑
可选性:构造函数是可选的,如果不定义,Solidity 会使用默认的空构造函数
可见性:在 Solidity 0.7.0 之前,构造函数可以指定可见性(如 public 或 internal),
但从 0.7.0 起,构造函数不再需要(也不允许)显式指定可见性,默认为 public,且只在部署时调用
构造函数被调用时机:
智能合约创建的几个阶段:
1. 编译成字节码,这个阶段称为编译时间
2. 被创建(构造) - 这是构造函数开始运行的时候,这可以称为构造时间
3. 字节码被部署到区块链,这就是部署
4. 部署的智能合约字节码在区块链上运行(执行),这可以被认为是运行时
在 Solidity 中,与其他语言不同,程序(智能合约)仅在构造函数完成其创建
智能合约的工作后才会部署。
有趣的是,在 Solidity 中,最终部署的字节码并不包含构造函数代码。
这是因为在 Solidity 中,构造函数代码是创建代码(构造时间)的一部分,
而不是运行时代码的一部分。它在创建智能合约时用完了,因为它只会调用一次,
在这个阶段过去后,就不需要被调用了,所以不会在最终部署的字节码中
可见性标识符 public、external、internal 和 private
uint public public_val = 123;
uint internal internal_val = 234;
uint private private_val = 456;
function public_fun2() public {}
function external_fun() external {}
function internal_fun() internal {}
function private_fun() private {}
修饰变量:
public:变量可被合约内外部访问,自动生成 getter 函数
internal:变量只能在合约内部及其继承的子合约中访问
private:变量仅在当前合约内访问,子合约无法访问
如果不显式指定可见性,Solidity 0.8.0 及以上版本中,变量默认为 internal
修饰函数:
public:函数可被合约内外部调用
external:函数只能被外部调用(通过交易或外部合约),不能被合约内部直接调用(除非使用 this)
internal:函数只能在合约内部及其子合约中调用
private:函数仅在当前合约内调用,子合约无法调用
函数需要显式声明可见性
function 定义
function public_fun2(
uint num1,
uint num2
) public pure returns (uint, uint) {
return (num1 + 1, num2 + 1);
}
函数是封装单个想法、特定功能、任务等的可执行代码单元。通常我们希望函数一次只做一件事。
尽管函数可以在智能合约代码块之外的文件中声明,当时它们通常还是出现在智能合约中。函数可以接受 0 个或多个参数,也可以返回 0 个或多个值。输入和输出是静态类型,这里的 pure returns ,我们留到下期再讲
总结:我们从一个最简单的智能合约出发, 从头到尾介绍了SPDX-License、solidity 版本申明 pragma,如何创建合约 contract,还有变量,函数,构造函数的定义,变量的作用域,以及变量与函数的修饰符,可见性修饰符..... , 这些内容都做了详细的解说,除了理解以外,还需要多多练习,才能彻底掌握,我会在讲完所有solidity 内容之后,创建一个代码仓库。下一章见,宝子们!