主页 > imtoken钱包官方版软件下载 > 学习Solidity——智能合约开发手册(一)

学习Solidity——智能合约开发手册(一)

目录
    1.这本手册是为谁准备的?
    2.必要的前期知识
    3.什么是 Solidity
    4.什么是智能合约?
    5.如何在 Solidity 中声明变量和函数?
    6.智能合约中的变量作用域
    7.可见性操作符如何工作
    8.什么是构造函数?
    9.接口和抽象合约
    10.智能合约示例 #2
    11.什么是合约状态?
    12.状态可变关键字(修饰符)
    13.数据位置——存储、内存和堆栈
    14.typing如何工作
    15.Solidity数据类型
    16.如何在 Solidity 中声明和初始化数组
    17.什么是函数修饰符?
    18.Solidity 中的错误处理——require, assert, revert
    19.Solidity 中的继承
    20.用构造器参数进行继承
    21.Solidity 中的类型转换和类型构造
    22.如何在Solidity中处理浮点数
    23.哈希、ABI编码和解码
    24.如何调用合约和使用 fallback 函数
    25.如何发送和接收以太币
    26.Solidity
    27.Solidity 中的事件和日志
    28.Solidity 中的时间逻辑
    29.结论和更多资源

这本小册子是为谁准备的?

这本手册是为任何有兴趣探索“Web3”[1] 背后的愿景并获得实现它所必需的技能的人而写的。

不要死记硬背! 阅读它,然后将其用作“桌面参考”伴侣。 当你学习任何一门新语言时,你可能会发现概念、习语和用法有点混乱,或者你的记忆会随着时间的推移而消失。 没关系! 这就是本手册旨在帮助您的目的。

随着时间的推移,我可能会为此添加一些更高级的主题,或者创建一个单独的教程。 但就目前而言,这本剧本将为您提供构建前几个 Solidity dApp 所需的大部分知识。

本手册假设您至少有几个月的编程经验。 通过编程,我的意思是至少你用 JavaScript 或 Python 或一些编译语言编写过(因为 HTML 和 CSS 实际上不是“编程”语言,仅仅知道它们是不够的)。

唯一的其他要求是你要有好奇心、有决心并且不要给自己设定任何截止日期。

只要你有一台笔记本电脑和一个可以连接互联网的浏览器,你就可以运行 Solidity 代码。 您可以在浏览器中使用 Remix 来编写本手册中的代码。 不需要其他 IDE!

必要的先验知识

我还假设您了解区块链技术的基础知识 [2],尤其是您了解以太坊的基础知识以及什么是智能合约 [3](提示:它们是在区块链上运行的程序,因此提供了特殊的便利信任最小化!)。

您不太可能需要他们来理解本手册。 但实际上,拥有像 Metamask[4] 这样的浏览器钱包并了解以太坊合约账户和外部拥有账户[5] 之间的区别将帮助您充分利用这本剧本。

什么是坚固性?

现在,让我们先了解什么是 Solidity。 Solidity 是一种面向对象的编程语言 [6],受 C++、JavaScript 和 Python 的影响。

Solidity 旨在编译(从人类可读代码转换为机器可读代码)为在以太坊虚拟机(EVM)上运行的字节码。 这是 Solidity 代码 [7] 的运行环境,就像你的浏览器是 JavaScript 代码的运行环境一样。

所以,你在 Solidity 中编写智能合约代码,编译器将其转换为字节码。 然后将该字节码部署并存储在以太坊(以及其他 EVM 兼容的区块链)上。

您可以在我制作的这个视频中获得 EVM 和字节码的基本介绍 [8]。

什么是智能合约?

这是一个开箱即用的简单智能合约。 它可能看起来毫无用处,但从这一点上你会学到很多关于 Solidity 的知识! 你可以在这里找到很多关于 Solidity 的信息。

阅读每条评论以了解发生了什么,然后继续学习一些关键内容。

以太坊solidity智能合约开发_siteqq.com 以太坊智能合约_以太坊智能合约是什么

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8.0;

contract HotFudgeSauce {
    uint public qtyCups;

    // Get the current hot fudge quantity
    function get() public view returns (uint) {
        return qtyCups;
    }

    // Increment hot fudge quantity by 1
    function increment() public {
        qtyCups += 1; // same as  qtyCups = qtyCups + 1;
    }

    // Function to decrement count by 1
    function decrement() public {
        qtyCups -= 1; // same as  qtyCups = qtyCups - 1;
        // What happens if qtyCups = 0 when this func is called?
    }
}

我们将很快讨论细节,例如 public 和 view 的含义。

现在,从上面的示例中了解七个关键事项:

1. 第一个注释是机器可读行 (//SPDX-License-Identifier: MIT),它指定了涵盖代码的许可证。 强烈建议使用 SPDX 许可证标识符,尽管您的代码在没有它的情况下也能编译。 在此处阅读更多内容 [9]。 此外,您可以通过在任何行前面添加两个正斜杠“//”来添加注释或“注释掉”它。

2. 这个 pragma 必须是任何 Solidity 文件中的第一行代码。 Pragma 是一个指令,它告诉编译器应该使用哪个版本的编译器将人类可读的 Solidity 代码转换为机器可读的字节码。 Solidity是一门新语言,更新频繁,所以不同版本的编译器在编译代码时会产生不同的结果。 当使用较新的编译器版本编译时,一些较旧的 solidity 文件会抛出错误或警告。 在较大的项目中,当您使用像 Hardhat 这样的工具时,您可能需要指定多个编译器版本,因为导入的 solidity 文件或您依赖的库是为旧版本的 solidity 编写的。 在此处阅读更多关于 Solidity 的 pragma 指令的 [10]。

3. 此 pragma 遵循语义版本控制 (SemVer) - 这是一个系统,其中每个数字表示该版本中包含的更改的类型和范围。 如果您想要 SemVer 的实际操作解释,请查看本教程 - 它非常有助于理解并且如今在开发(尤其是 Web 开发)中被广泛使用。

4. 分号在 Solidity 中是必不可少的。 如果缺少一个,编译器将失败。 混音会提醒你!

5.关键字contract告诉编译器你正在声明一个智能合约。 如果您熟悉面向对象的编程,您可以将契约视为类。 如果您不熟悉 OOP,您可以将契约视为保存数据的对象——包括变量和函数。 您可以结合智能合约为您的区块链应用程序提供所需的功能。

6. 函数是封装单个想法、特定功能、任务等的可执行代码单元。通常我们希望函数一次只做一件事。 函数最常见于智能合约中,尽管它们可以在智能合约代码块之外的文件中声明。 函数可以接受零个或多个参数并返回零个或多个值。 输入和输出是静态类型的,您将在本手册的后面部分了解这一概念。

7. 在上面的例子中,变量 qtyCups 被称为“状态变量”。 它持有合约的状态——这是程序在运行时需要跟踪的数据的技术术语。 与其他程序不同,智能合约应用程序即使在程序未运行时也会保持其状态。 数据与应用程序一起存储在区块链上,这意味着区块链网络中的每个节点都在区块链上维护和同步数据和智能合约的本地副本。 状态变量就像传统应用程序中的数据库“存储”,但由于区块链需要在网络中的所有节点之间同步状态,因此使用存储可能非常昂贵! 稍后会详细介绍。

如何在 Solidity 中声明变量和函数

让我们分解一下 HotFudgeSauce 智能合约,以便我们可以更多地了解每个部分。

在 Solidity 中定义事物的基本结构/语法类似于其他静态类型语言。 我们给函数和变量一个名字。

但是在类型化语言中,我们还需要指定我们创建的数据类型,作为输入传递或作为输出返回。 如果您需要了解什么是类型化数据,可以跳至本手册​​的类型化数据部分。

下面,我们看看声明“状态变量”是什么样子的。 我们还可以看到声明函数的样子。

以太坊智能合约是什么_siteqq.com 以太坊智能合约_以太坊solidity智能合约开发

第一个片段声明了一个名为 qtyCups 的类。 这样只能存储uint类型的值。 “整数”是指零以下(负)和零以上(正)的所有整数。

这些数字被称为有符号整数,因为它们带有 + 或 - 符号。 因此,无符号整数总是正整数(包括零)。

在第二个片段中以太坊solidity智能合约开发,我们在声明函数时也看到了一个熟悉的结构。 最重要的是,我们看到函数必须为函数返回的值指定数据类型。

以太坊智能合约是什么_以太坊solidity智能合约开发_siteqq.com 以太坊智能合约

在这个例子中,由于 get() 返回我们刚刚创建的存储变量的值,我们可以看到返回值必须是一个 uint。

public 是可见性运算符。 稍后会详细介绍。 view 是状态可变修饰符。 下面还有更多!

这里值得注意的是,状态变量也可以是其他类型——常量和不可变。 它们看起来像这样:

string constant TEXT = "abc";
address immutable owner = 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e;

常量和不可变变量的值被赋值一次以太坊solidity智能合约开发,而且只能赋值一次。 在分配了第一个值后,不能再为它们分配另一个值。

因此,如果我们将 qtyCups 状态变量设为常量或不可变,我们将无法再对其调用 increment() 或 decrement()(事实上,代码将无法编译!)。

常量必须在代码本身中硬编码它们的值,而不可变变量可以将它们的值设置一次,通常是通过构造函数中的赋值(我保证,我们很快就会讨论构造函数)。 您可以在此处的文档 [11] 中阅读更多内容。

智能合约中的可变范围

智能合约可以访问三个变量范围:

1.状态变量:通过在区块链上记录值,在智能合约中存储永久数据(称为持久状态)。

2. 局部变量:这些是“瞬态”数据,在计算运行时会在短时间内保存信息。 这些值不会永久存储在区块链上。

3. 全局变量[12]:这些变量和函数由 Solidity“注入”到您的代码中,无需专门创建或从任何地方导入即可使用。 这些提供有关运行代码的区块链环境的信息,还包括程序中通常使用的实用函数。

您可以按如下方式区分范围:

1. 状态变量通常在智能合约内部,但在函数外部。

2. 局部变量位于函数内部,不能从该函数范围外访问。

3. 全局变量不是由您声明的——它们“神奇地”可供您使用。

这是我们的 HotFudgeSauce 示例,稍作修改以显示不同类型的变量。 我们给 qtyCups 一个起始值,并向除我以外的每个人分发几杯软糖酱(因为我正在节食)。

您可能已经注意到函数声明中的一个新关键字 payable 。 这允许调用者将 Eth 发​​送到智能合约,该主题将在本手册后面的单独部分中介绍。

以太坊solidity智能合约开发_siteqq.com 以太坊智能合约_以太坊智能合约是什么

以太坊智能合约是什么_siteqq.com 以太坊智能合约_以太坊solidity智能合约开发

可见性运算符的工作原理

“可见性”这个词的使用有点令人困惑,因为在公共区块链上几乎所有东西都是“可见的”,因为透明度是一个关键特征。 但在这种情况下,可见性意味着一段代码被另一段代码看到和访问的能力。

可见性指定变量、函数或合约可以从定义它的代码区域之外访问的程度。 可以根据软件系统的哪些部分需要访问它来调整可见范围。

如果您是 JavaScript 或 NodeJS 开发人员,您已经熟悉可见性 - 每次导出对象时,您都会使其在声明它的文件之外可见。

可见性类型

Solidity 中有 4 种不同类型的可见性 [13]:公共、外部、内部和私有。

公共函数和变量可以在合约内部、外部、其他智能合约和外部账户(位于您的 Metamask[14] 钱包中的那种)几乎从任何地方访问。 这是最广泛和最宽松的可见性级别。

当一个存储变量被赋予公共可见性时,Solidity 会自动为变量的值创建一个隐式的 getter 函数。

所以在我们的 HotFudgeSauce 智能合约中,我们并不真的需要这个 get() 方法,因为 Solidity 会隐式地为我们提供相同的功能,只需给 qtyCups 一个公共可见性修饰符。

私有函数和变量只能在声明它们的智能合约中访问。 但是它们不能在包含它们的智能合约之外访问。 private 是四个可见性运算符中限制性最强的。

内部可见性类似于私有可见性,因为内部函数和变量只能从声明它们的合约中访问。 但是标记为内部的函数和变量也可以从派生合约(即从声明合约继承的子合约)访问,但不能从合约外部访问。 稍后我们将讨论继承(和派生/分包)。

internal 是存储变量的默认可见性。

以太坊solidity智能合约开发_以太坊智能合约是什么_siteqq.com 以太坊智能合约

4 Solidity 可见性操作符以及它们可以从哪里访问

外部可见性运算符不适用于变量 - 只有函数可以指定为外部。

不能从声明合约或从声明合约继承的合约中调用外部函数。 因此,它们只能从封闭合约之外调用。

这就是它们与公共函数的不同之处——公共函数也可以从声明它们的合约中调用,而外部函数则不能。

什么是构造函数?

构造函数是一种特殊类型的函数。 在 Solidity 中,它是可选的,并且只在创建合约时执行一次。

以太坊solidity智能合约开发_siteqq.com 以太坊智能合约_以太坊智能合约是什么

在下面的示例中,我们有一个将一些数据作为参数的显式构造函数。 您必须在创建智能合约时将此构造函数参数注入到您的智能合约中。

siteqq.com 以太坊智能合约_以太坊智能合约是什么_以太坊solidity智能合约开发

带输入参数的 Solidity 构造函数

要了解何时调用构造函数,请记住智能合约是分几个阶段创建的:

在 Solidity 中,与其他语言不同,程序(智能合约)仅在构造函数完成其创建智能合约的工作后才会部署。

有趣的是,在 Solidity 中,最终部署的字节码不包含构造函数代码。 这是因为在 Solidity 中,构造函数代码是创建代码 [16](构造时间)的一部分,而不是运行时代码的一部分。 它在创建智能合约时被用完,因为它只在这个阶段不需要时被调用,并且被排除在最终部署的字节码之外。

因此,在我们的示例中,构造函数创建(构造)Person 智能合约的实例。 我们的构造函数希望我们向它传递一个名为 _name 的字符串值。

在构建智能合约时,值 _name 将存储在名为 name 的状态变量中(这通常是我们将配置和其他数据传递到智能合约的方式)。 然后当实际部署合约时,状态变量名称将保存我们传递给构造函数的任何字符串值。

明白为什么

您可能想知道为什么我们费心向构造函数中注入值。 为什么不把它们写入合同?

这是因为我们希望合约是可配置的或“参数化的”。 我们想要的不是硬编码值,而是在需要时注入数据所带来的灵活性和可重用性。

在我们的示例中,假设 _name 指的是将部署合约的给定以太坊网络(如 Rinkeby、Goerli、Kovan、Mainnet 等)的名称。

我们如何将这些信息输入到我们的智能合约中? 把这些值都放在那里很浪费。 这也意味着我们需要添加额外的代码来确定合约运行在哪个区块链上。 然后我们必须从我们存储在合约中的硬编码列表中选择正确的网络名称,这在部署时会消耗 gas。

相反,我们可以在将智能合约部署到相关区块链网络时将其注入构造函数。 这就是我们编写可以采用任意数量参数值的合约的方式。

另一个常见的用例是当你的智能合约继承自另一个智能合约并且你需要在创建合约时将值传递给父智能合约。 但是继承是我们后面要讲的。

我提到构造函数是可选的。 在 HotFudgeSauce 中,我们没有编写显式构造函数。 但是 Solidity 支持隐式构造函数。 因此,如果我们不在智能合约中包含构造函数,Solidity 将采用默认构造函数 [17],它看起来像 constructor() {}。

如果你在脑海中评估它,你会发现它什么都不做,这就是为什么它可以被排除(隐式地)并且编译器将使用默认构造函数。

(未完待续)

原版的:

siteqq.com 以太坊智能合约_以太坊智能合约是什么_以太坊solidity智能合约开发

以太坊智能合约是什么_以太坊solidity智能合约开发_siteqq.com 以太坊智能合约

引用链接

[1] “Web3”背后的愿景:

[2] 区块链技术:

[3] 什么是智能合约:

[4] 元掩码:

[5] 以太坊合约账户与外部拥有账户的区别:

[6] 面向对象编程语言:

[7] 运行环境:

[8] 你可以在我制作的这个视频中找到它:

[9] 此处:#spdx-license-identifier

[10] Solidity 的 pragma 指令:

[11] 你可以在这里找到文档:#constant-and-immutable-state-variables

[12] 全局变量:#special-variables-and-functions

[13] 4 种不同类型的可见性:#function-visibility-specifiers

[14] 元掩码:

[15] 在此处阅读有关字节码的更多信息:

[16]构造函数代码为创建代码:

[17] 采用默认构造函数:#constructors