Solidity - 快速指南


Solidity - 概述

Solidity 是一种面向合约的高级编程语言,用于实现智能合约。Solidity 深受 C++、Python 和 JavaScript 的影响,旨在针对以太坊虚拟机 (EVM)。

Solidity 是静态类型的,支持继承、库和复杂的用户定义类型编程语言。

您可以使用 Solidity 创建用于投票、众筹、盲拍和多重签名钱包等用途的合约。

什么是以太坊?

以太坊是一个去中心化的即。运行智能合约的区块链平台,即完全按照编程运行的应用程序,没有任何停机、审查、欺诈或第三方干扰的可能性。

以太坊虚拟机(EVM)

以太坊虚拟机,也称为EVM,是以太坊中智能合约的运行环境。以太坊虚拟机专注于为世界各地的计算机提供安全性并执行不受信任的代码。

EVM 专门用于防止拒绝服务攻击,并确保程序无法访问彼此的状态,从而确保可以在没有任何潜在干扰的情况下建立通信。

以太坊虚拟机旨在作为基于以太坊的智能合约的运行时环境。

什么是智能合约?

智能合约是一种计算机协议,旨在以数字方式促进、验证或强制执行合同的谈判或履行。智能合约允许在没有第三方的情况下进行可信交易。这些交易是可追踪且不可逆转的。

智能合约的概念最早由 Nick Szabo 于 1994 年提出。Szabo 是一位法律学者和密码学家,因为数字货币奠定基础而闻名。

如果您现在不了解智能合约也没关系,我们稍后会详细介绍。

Solidity - 环境设置

本章解释了如何在 CentOS 机器上设置 Solidity 编译器。如果您没有 Linux 机器,那么您可以使用我们的在线编译器来编写小型合同并快速学习 Solidity。

方法 1 - npm / Node.js

这是在 CentoS 机器上安装 Solidity 编译器的最快方法。我们有以下步骤来安装 Solidity 编译器 -

安装 Node.js

首先确保您的 CentOS 计算机上有可用的 Node.js。如果它不可用,则使用以下命令安装它 -

# First install epel-release
$sudo yum install epel-release

# Now install nodejs
$sudo yum install nodejs

# Next install npm (Nodejs Package Manager )
$sudo yum install npm

# Finally verify installation
$npm --version

如果一切都已安装,那么您将看到类似这样的输出 -

3.10.10

安装solc

安装 Node.js 包管理器后,您可以继续安装 Solidity 编译器,如下所示 -

$sudonpm install -g solc

上面的命令将安装 solcjs 程序并使其在整个系统中全局可用。现在您可以通过发出以下命令来测试您的 Solidity 编译器 -

$solcjs-version

如果一切顺利,那么这将打印如下内容 -

0.5.2+commit.1df8f40c.Emscripten.clang

现在您可以使用 solcjs,它的功能比标准 Solidity 编译器少,但它会给您一个良好的起点。

方法2-Docker镜像

您可以拉取 Docker 映像并开始使用它来开始 Solidity 编程。以下是简单的步骤。以下是提取 Solidity Docker 映像的命令。

$docker pull ethereum/solc:stable

下载 Docker 镜像后,我们可以使用以下命令对其进行验证。

$docker run ethereum/solc:stable-version

这将打印如下内容 -

$ docker run ethereum/solc:stable -version

solc, the solidity compiler commandlineinterfaceVersion: 0.5.2+commit.1df8f40c.Linux.g++

方法三:二进制包安装

如果您愿意在 Linux 机器上安装完整的编译器,请查看官方网站安装 Solidity 编译器。

Solidity - 基本语法

Solidity 源文件可以包含任意数量的合约定义、导入指令和 pragma 指令。

让我们从一个简单的 Solidity 源文件开始。以下是 Solidity 文件的示例 -

pragma solidity >=0.4.0 <0.6.0;
contract SimpleStorage {
   uint storedData;
   function set(uint x) public {
      storedData = x;
   }
   function get() public view returns (uint) {
      return storedData;
   }
}

杂注

第一行是一个 pragma 指令,它告诉源代码是为 Solidity 版本 0.4.0 或任何更新版本编写的,不会破坏 0.6.0 版本(但不包括)的功能。

pragma 指令始终位于源文件本地,如果导入另一个文件,则该文件中的 pragma 将不会自动应用于导入文件。

因此,无法编译早于 0.4.0 版本且也无法在从 0.5.0 版本开始的编译器上运行的文件的编译指示将编写如下 -

pragma solidity ^0.4.0;

这里使用 ^ 添加第二个条件。

合同

Solidity 合约是驻留在以太坊区块链上特定地址的代码(其功能)和数据(其状态)的集合。

uintstoredData 行声明了一个 uint 类型的名为 storageData 的状态变量,并且函数 set 和 get 可用于修改或检索该变量的值。

导入文件

虽然上面的示例没有 import 语句,但 Solidity 支持与 JavaScript 中可用的 import 语句非常相似的 import 语句。

以下语句从“filename”导入所有全局符号。

import "filename";

以下示例创建一个新的全局符号symbolName,其成员是“filename”中的所有全局符号。

import * as symbolName from "filename";

要从与当前文件相同的目录导入文件 x,请使用 import "./x" as x;。如果使用 import "x" as x; 相反,可以在全局“包含目录”中引用不同的文件。

保留关键字

以下是 Solidity 中的保留关键字 -

抽象的 别名 申请
汽车 案件 抓住 备份
默认 定义 最终的 不可变的
实施 排队
匹配 可变的 无效的
覆盖 部分的 承诺
参考 可重新定位的 密封 大小
静止的 支持 转变 尝试
类型定义 类型 未经检查的

Solidity - 首次应用

我们使用Remix IDE来编译和运行我们的 Solidity 代码库。

步骤 1 - 复制 Remix IDE 代码部分中的给定代码。

例子

pragma solidity ^0.5.0;
contract SolidityTest {
   constructor() public{
   }
   function getResult() public view returns(uint){
      uint a = 1;
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

步骤 2 - 在“编译”选项卡下,单击“开始编译”按钮。

步骤 3 - 在“运行”选项卡下,单击“部署”按钮。

步骤 4 - 在“运行”选项卡下,在下拉列表中选择“SolidityTest at 0x...” 。

步骤 5 - 单击getResult按钮显示结果。

输出

0: uint256: 3

Solidity - 评论

Solidity 支持 C 风格和 C++ 风格注释,因此 -

  • // 和行尾之间的任何文本都被视为注释,并被 Solidity 编译器忽略。

  • 字符 /* 和 */ 之间的任何文本都被视为注释。这可能跨越多行。

例子

以下示例展示了如何在 Solidity 中使用注释。

function getResult() public view returns(uint){
   // This is a comment. It is similar to comments in C++

   /*
      * This is a multi-line comment in solidity
      * It is very similar to comments in C Programming
   */
   uint a = 1;
   uint b = 2;
   uint result = a + b;
   return result;
}

坚固性 - 类型

在用任何语言编写程序时,都需要使用各种变量来存储各种信息。变量只不过是用于存储值的保留内存位置。这意味着当您创建变量时,您会在内存中保留一些空间。

您可能喜欢存储各种数据类型的信息,如字符、宽字符、整数、浮点、双浮点、布尔值等。操作系统根据变量的数据类型分配内存并决定可以存储的内容保留内存。

值类型

Solidity 为程序员提供了丰富的内置数据类型以及用户定义的数据类型。下表列出了七种基本的 C++ 数据类型 -

类型 关键词 价值观
布尔值 布尔值 真假
整数 整数/单位 不同大小的有符号和无符号整数。
整数 int8 到 int256 从 8 位到 256 位的有符号 int。int256 与 int 相同。
整数 uint8 到 uint256 从 8 位到 256 位的无符号整数。uint256 与 uint 相同。
定点数 固定/不固定 不同大小的有符号和无符号定点数。
定点数 固定/不固定 不同大小的有符号和无符号定点数。
定点数 固定MxN 有符号定点数,其中 M 表示类型所占的位数,N 表示小数点。M 应能被 8 整除,范围为 8 到 256。N 可以为 0 到 80。fixed 与fixed128x18 相同。
定点数 固定MxN 无符号定点数,其中 M 表示类型所占的位数,N 表示小数点。M 应能被 8 整除,范围为 8 到 256。N 可以为 0 到 80。ufixed 与 ufixed128x18 相同。

注意:您还可以将有符号和无符号定点数表示为fixedMxN/ufixedMxN,其中M表示类型所占用的位数,N表示小数点。M 应能被 8 整除,范围为 8 到 256。N 可以为 0 到 80。

地址

地址保存表示以太坊地址大小的 20 字节值。一个地址可以使用 .balance 方法获取余额,也可以使用 .transfer 方法将余额转移到另一个地址。

address x = 0x212;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);

Solidity - 变量

Solidity 支持三种类型的变量。

  • 状态变量- 其值永久存储在合约存储中的变量。

  • 局部变量- 其值在函数执行之前一直存在的变量。

  • 全局变量- 全局命名空间中存在特殊变量,用于获取有关区块链的信息。

Solidity 是一种静态类型语言,这意味着需要在声明时指定状态或局部变量类型。每个声明的变量始终具有基于其类型的默认值。没有“未定义”或“空”的概念。

状态变量

其值永久存储在合约存储中的变量。

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData;      // State variable
   constructor() public {
      storedData = 10;   // Using State variable
   }
}

局部变数

其值仅在定义它的函数内可用的变量。函数参数始终是该函数的本地参数。

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData; // State variable
   constructor() public {
      storedData = 10;   
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return result; //access the local variable
   }
}

例子

pragma solidity ^0.5.0;
contract SolidityTest {
   uint storedData; // State variable
   constructor() public {
      storedData = 10;   
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return storedData; //access the state variable
   }
}

使用Solidity First Application章节中提供的步骤运行上述程序。

输出

0: uint256: 10

全局变量

这些是存在于全局工作区中的特殊变量,提供有关区块链和交易属性的信息。

姓名 退货
blockhash(uint blockNumber) 返回 (bytes32) 给定区块的哈希值 - 仅适用于 256 个最新区块(不包括当前区块)
block.coinbase(应付地址) 当前区块矿工地址
区块难度 (uint) 当前区块难度
区块.gaslimit (uint) 当前区块gaslimit
块号(uint) 当前区块号
区块时间戳 (uint) 当前块时间戳(自 Unix 纪元以来的秒数)
Gasleft() 返回 (uint256) 剩余气体
msg.data(调用数据字节) 完整的通话数据
msg.sender(应付地址) 消息发送者(当前呼叫者)
msg.sig(字节4) calldata 的前四个字节(函数标识符)
消息值(uint) 随消息一起发送的 wei 数量
现在(单位) 当前区块时间戳
tx.gasprice (uint) 交易的 Gas 价格
tx.origin(应付地址) 交易发送者

Solidity 变量名称

在 Solidity 中命名变量时,请记住以下规则。

  • 您不应使用任何 Solidity 保留关键字作为变量名称。这些关键字将在下一节中提到。例如,中断或布尔变量名称无效。

  • Solidity 变量名称不应以数字 (0-9) 开头。它们必须以字母或下划线字符开头。例如,123test 是无效变量名称,但 _123test 是有效变量名称。

  • Solidity 变量名称区分大小写。例如,Name 和 name 是两个不同的变量。

Solidity - 可变范围

局部变量的作用域仅限于定义它们的函数,但状态变量可以具有三种类型的作用域。

  • Public - 公共状态变量可以在内部以及通过消息访问。对于公共状态变量,会生成一个自动 getter 函数。

  • 内部- 内部状态变量只能从当前合约或从中派生的合约内部访问,而不使用它。

  • Private - 私有状态变量只能从当前合约内部访问,它们不是在派生合约中定义的。

例子

pragma solidity ^0.5.0;
contract C {
   uint public data = 30;
   uint internal iData= 10;
   
   function x() public returns (uint) {
      data = 3; // internal access
      return data;
   }
}
contract Caller {
   C c = new C();
   function f() public view returns (uint) {
      return c.data(); //external access
   }
}
contract D is C {
   function y() public returns (uint) {
      iData = 3; // internal access
      return iData;
   }
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return storedData; //access the state variable
   }
}

Solidity - 运算符

什么是操作员?

让我们看一个简单的表达式4 + 5 等于 9。这里 4 和 5 称为操作数,“+”称为运算符。Solidity 支持以下类型的运算符。

  • 算术运算符
  • 比较运算符
  • 逻辑(或关系)运算符
  • 赋值运算符
  • 条件(或三元)运算符

让我们一一看看所有的运营商。

算术运算符

Solidity 支持以下算术运算符 -

假设变量 A 为 10,变量 B 为 20,则 -

显示示例

先生。 运算符及描述
1

+(加法)

添加两个操作数

例如: A + B 将给出 30

2

-(减法)

从第一个操作数中减去第二个操作数

例如: A - B 将给出 -10

3

*(乘法)

将两个操作数相乘

例如: A * B 将给出 200

4

/ (分配)

将分子除以分母

例如: B / A 将给出 2

5

%(模数)

输出整数除法的余数

例如: B % A 将给出 0

6

++(增量)

将整数值加一

例如: A++ 会给出 11

7

--(递减)

将整数值减一

例如: A-- 将给出 9

比较运算符

Solidity 支持以下比较运算符 -

假设变量 A 为 10,变量 B 为 20,则 -

显示示例

先生。 运算符及描述
1

= =(等于)

检查两个操作数的值是否相等,如果相等,则条件成立。

例如: (A == B) 不正确。

2

!=(不等于)

检查两个操作数的值是否相等,如果值不相等,则条件成立。

例如: (A != B) 为真。

3

>(大于)

检查左操作数的值是否大于右操作数的值,如果是,则条件成立。

例如: (A > B) 不正确。

4

<(小于)

检查左操作数的值是否小于右操作数的值,如果是,则条件成立。

例如: (A < B) 为真。

5

>=(大于或等于)

检查左操作数的值是否大于或等于右操作数的值,如果是,则条件成立。

例如: (A >= B) 不正确。

6

<=(小于或等于)

检查左操作数的值是否小于或等于右操作数的值,如果是,则条件成立。

例如: (A <= B) 为真。

逻辑运算符

Solidity 支持以下逻辑运算符 -

假设变量 A 为 10,变量 B 为 20,则 -

显示示例

先生。 运算符及描述
1

&&(逻辑与)

如果两个操作数均非零,则条件为真。

例如: (A && B) 是正确的。

2

|| (逻辑或)

如果两个操作数中的任何一个非零,则条件为真。

例如: (A || B) 为真。

3

!(逻辑非)

反转其操作数的逻辑状态。如果条件为真,则逻辑 NOT 运算符会将其设为假。

例如:!(A && B) 是错误的。

按位运算符

Solidity 支持以下按位运算符 -

假设变量 A 为 2,变量 B 为 3,则 -

显示示例

先生。 运算符及描述
1

&(按位与)

它对其整数参数的每一位执行布尔 AND 运算。

例如: (A & B) 是 2。

2

| (按位或)

它对其整数参数的每一位执行布尔 OR 运算。

例如: (A | B) 为 3。

3

^(按位异或)

它对其整数参数的每一位执行布尔异或运算。异或意味着操作数一为真或操作数二为真,但不能两者都为真。

例如: (A ^ B) 为 1。

4

~(按位非)

它是一个一元运算符,通过反转操作数中的所有位来进行运算。

例如: (~B) 为 -4。

5

<<(左移)

它将第一个操作数中的所有位向左移动第二个操作数中指定的位数。新的位用零填充。将值左移一位相当于乘以 2,左移两位相当于乘以 4,依此类推。

例如: (A << 1) 是 4。

6

>>(右移)

二进制右移运算符。左操作数的值向右移动右操作数指定的位数。

例如: (A >> 1) 是 1。

7

>>>(右移零)

该运算符与 >> 运算符类似,只是左侧移入的位始终为零。

例如: (A >>> 1) 是 1。

赋值运算符

Solidity 支持以下赋值运算符 -

显示示例

先生。 运算符及描述
1

=(简单赋值)

将右侧操作数中的值分配给左侧操作数

例如: C = A + B 将把 A + B 的值赋给 C

2

+=(添加和赋值)

它将右操作数添加到左操作数,并将结果分配给左操作数。

例如: C += A 相当于 C = C + A

3

−=(减法和赋值)

它从左操作数中减去右操作数,并将结果赋给左操作数。

例如: C -= A 相当于 C = C - A

4

*=(乘法和赋值)

它将右操作数与左操作数相乘,并将结果赋给左操作数。

例如: C *= A 相当于 C = C * A

5

/=(除法和赋值)

它将左操作数与右操作数相除,并将结果赋给左操作数。

例如: C /= A 相当于 C = C / A

6

%=(模块和分配)

它使用两个操作数取模并将结果分配给左侧操作数。

例如: C %= A 相当于 C = C % A

注意- 相同的逻辑适用于按位运算符,因此它们将变得像 <<=、>>=、>>=、&=、|= 和 ^=。

条件运算符 (?:)

条件运算符首先计算表达式的真值或假值,然后根据计算结果执行两个给定语句之一。

显示示例

先生。 操作符及说明
1

?:(有条件)

如果条件为真?然后值 X :否则值 Y

坚固性 - 循环

在撰写合同时,您可能会遇到需要一遍又一遍地执行某项操作的情况。在这种情况下,您需要编写循环语句以减少行数。

Solidity 支持所有必要的循环,以减轻编程的压力。

先生编号 循环和描述
1

While 循环

Solidity 中最基本的循环是本章将讨论的 while 循环。

2

do...while 循环

do...while 循环与 while 循环类似,只是条件检查发生在循环末尾。

3

For循环

for 循环是最紧凑的循环形式。它包括以下三个重要部分。

4

循环控制

Solidity 提供了处理循环和 switch 语句的完全控制。

Solidity - 决策

在编写程序时,可能会出现需要采用一组给定路径中的一个的情况。在这种情况下,您需要使用条件语句,使您的程序能够做出正确的决策并执行正确的操作。

Solidity 支持条件语句,用于根据不同的条件执行不同的操作。这里我们将解释if..else语句。

if-else 流程图

下面的流程图显示了 if-else 语句的工作原理。

决策

Solidity 支持以下形式的if..else语句 -

先生编号 声明和说明
1

if 语句

if 语句是基本的控制语句,它允许 Solidity 做出决策并有条件地执行语句。

2

if...else 语句

“if...else”语句是控制语句的下一种形式,它允许 Solidity 以更受控的方式执行语句。

3

if...else if... 语句。

if...else if... 语句是 if...else 的高级形式,它允许 Solidity 从多个条件中做出正确的决定。

坚固性 - 弦乐

Solidity 支持使用双引号 (") 和单引号 (') 的字符串文字。它提供字符串作为数据类型来声明 String 类型的变量。

pragma solidity ^0.5.0;

contract SolidityTest {
   string data = "test";
}

在上面的示例中,“test”是一个字符串文字,而 data 是一个字符串变量。更优选的方法是使用字节类型而不是字符串,因为与字节操作相比,字符串操作需要更多的气体。Solidity 提供字节到字符串之间的内置转换,反之亦然。在 Solidity 中,我们可以轻松地将字符串文字分配给 byte32 类型变量。Solidity 将其视为 byte32 文字。

pragma solidity ^0.5.0;

contract SolidityTest {
   bytes32 data = "test";
}

逃脱角色

先生。 特征及描述
1

\n

开始新的一行。

2

\\

反斜杠

3

\'

单引号

4

\"

双引号

5

\b

退格键

6

\F

换页

7

\r

回车

8

\t

标签

9

\v

垂直制表符

10

\xNN

表示十六进制值并插入适当的字节。

11

\uNNNN

表示 Unicode 值并插入 UTF-8 序列。

字节到字符串的转换

可以使用 string() 构造函数将字节转换为字符串。

bytes memory bstr = new bytes(10);
string message = string(bstr);   

例子

尝试以下代码来了解字符串在 Solidity 中的工作原理。

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);
   }
}

使用Solidity First Application章节中提供的步骤运行上述程序。

输出

0: string: 3

Solidity - 数组

数组是一种数据结构,它存储相同类型元素的固定大小的顺序集合。数组用于存储数据的集合,但将数组视为相同类型的变量的集合通常更有用。

您可以声明一个数组变量(例如numbers)并使用numbers[0]、numbers[1]和...、numbers[99]来表示,而不是声明单个变量(例如number0、number1、...和number99)个体变量。数组中的特定元素通过索引来访问。

在 Solidity 中,数组可以是编译时固定大小或动态大小。对于存储数组来说,它也可以有不同类型的元素。对于内存数组,元素类型不能映射,如果要用作函数参数,则元素类型应该是 ABI 类型。

所有数组都由连续的内存位置组成。最低地址对应于第一个元素,最高地址对应于最后一个元素。

声明数组

要在 Solidity 中声明固定大小的数组,程序员需要指定数组所需的元素类型和元素数量,如下所示 -

type arrayName [ arraySize ];

这称为一维数组。arraySize必须是大于零的整数常量,类型可以是任何有效的 Solidity 数据类型例如,要声明一个名为 Balance 的 uint 类型的 10 元素数组,请使用以下语句 -

uint balance[10];

要在 Solidity 中声明动态大小的数组,程序员指定元素的类型,如下所示 -

type[] arrayName;

初始化数组

您可以逐一初始化 Solidity 数组元素或使用单个语句,如下所示 -

uint balance[3] = [1, 2, 3];

大括号 [ ] 之间的值的数量不能大于我们为方括号 [ ] 之间的数组声明的元素数量。以下是分配数组的单个元素的示例 -

如果省略数组的大小,则会创建一个足以容纳初始化的数组。因此,如果你写 -

uint balance[] = [1, 2, 3];

您将创建与上一个示例中完全相同的数组。

balance[2] = 5;

上述语句将数组中第 3个元素的值指定为 5。

创建动态内存数组

动态内存数组是使用 new 关键字创建的。

uint size = 3;
uint balance[] = new uint[](size);

访问数组元素

通过索引数组名称来访问元素。这是通过将元素的索引放在数组名称后面的方括号内来完成的。例如 -

uint salary = balance[2];

上面的语句将从数组中取出第三个元素并将值分配给工资变量。以下是一个示例,它将使用所有上述三个概念,即。声明、赋值和访问数组 -

会员

  • length - length 返回数组的大小。length 可用于通过设置来更改动态数组的大小。

  • Push - Push 允许将一个元素追加到动态存储数组的末尾。它返回数组的新长度。

例子

尝试以下代码来了解数组在 Solidity 中的工作原理。

pragma solidity ^0.5.0;

contract test {
   function testArray() public pure{
      uint len = 7; 
      
      //dynamic array
      uint[] memory a = new uint[](7);
      
      //bytes is same as byte[]
      bytes memory b = new bytes(len);
      
      assert(a.length == 7);
      assert(b.length == len);
      
      //access array variable
      a[6] = 8;
      
      //test array variable
      assert(a[6] == 8);
      
      //static array
      uint[3] memory c = [uint(1) , 2, 3];
      assert(c.length == 3);
   }
}

Solidity - 枚举

枚举将变量限制为仅具有几个预定义值之一。该枚举列表中的值称为枚举。

使用枚举可以减少代码中的错误数量。

例如,如果我们考虑鲜榨果汁店的应用,则可以将玻璃杯尺寸限制为小、中和大。这将确保任何人都不会订购除小号、中号或大号以外的任何尺寸。

例子

尝试以下代码来了解枚举在 Solidity 中的工作原理。

pragma solidity ^0.5.0;

contract test {
   enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }
   FreshJuiceSize choice;
   FreshJuiceSize constant defaultChoice = FreshJuiceSize.MEDIUM;

   function setLarge() public {
      choice = FreshJuiceSize.LARGE;
   }
   function getChoice() public view returns (FreshJuiceSize) {
      return choice;
   }
   function getDefaultChoice() public pure returns (uint) {
      return uint(defaultChoice);
   }
}

使用Solidity First Application章节中提供的步骤运行上述程序。

首先单击setLarge按钮将值设置为 LARGE,然后单击getChoice获取所选选项。

输出

uint8: 2

单击getDefaultChoice按钮获取默认选择。

输出

uint256: 1

Solidity - 结构

结构类型用于表示记录。假设您想跟踪图书馆中的书籍。您可能想要跟踪每本书的以下属性 -

  • 标题
  • 作者
  • 主题
  • 图书编号

定义结构体

要定义结构,必须使用struct关键字。struct 关键字定义了一种新的数据类型,具有多个成员。struct 语句的格式如下 -

struct struct_name { 
   type1 type_name_1;
   type2 type_name_2;
   type3 type_name_3;
}

例子

struct Book { 
   string title;
   string author;
   uint book_id;
}

访问结构及其变量

要访问结构体的任何成员,我们使用成员访问运算符 (.)。成员访问运算符被编码为结构变量名称和我们希望访问的结构成员之间的句点。您可以使用 struct 来定义结构类型的变量。以下示例显示如何在程序中使用结构体。

例子

尝试以下代码来了解结构在 Solidity 中的工作原理。

pragma solidity ^0.5.0;

contract test {
   struct Book { 
      string title;
      string author;
      uint book_id;
   }
   Book book;

   function setBook() public {
      book = Book('Learn Java', 'TP', 1);
   }
   function getBookId() public view returns (uint) {
      return book.book_id;
   }
}

使用Solidity First Application章节中提供的步骤运行上述程序。

首先单击setBook按钮将值设置为 LARGE,然后单击getBookId获取所选书籍 id。

输出

uint256: 1

Solidity - 映射

映射是数组和结构体的引用类型。以下是声明映射类型的语法。

mapping(_KeyType => _ValueType)

在哪里

  • _KeyType - 可以是任何内置类型加上字节和字符串。不允许引用类型或复杂对象。

  • _ValueType - 可以是任何类型。

注意事项

  • 映射只能有存储类型,一般用于状态变量。

  • 映射可以标记为公开。Solidity 会自动为其创建 getter。

例子

尝试以下代码来了解映射类型在 Solidity 中的工作原理。

pragma solidity ^0.5.0;

contract LedgerBalance {
   mapping(address => uint) public balances;

   function updateBalance(uint newBalance) public {
      balances[msg.sender] = newBalance;
   }
}
contract Updater {
   function updateBalance() public returns (uint) {
      LedgerBalance ledgerBalance = new LedgerBalance();
      ledgerBalance.updateBalance(10);
      return ledgerBalance.balances(address(this));
   }
}

使用Solidity First Application章节中提供的步骤运行上述程序。

首先单击updateBalance按钮将值设置为 10,然后查看日志,其中将显示解码输出为 -

输出

{
   "0": "uint256: 10"
}

坚固性 - 转换

Solidity 允许隐式和显式转换。Solidity 编译器允许两种数据类型之间的隐式转换,前提是不可能进行隐式转换并且不会丢失信息。例如,uint8 可转换为 uint16,但 int8 可转换为 uint256,因为 int8 可以包含 uint256 不允许的负值。

显式转换

我们可以使用构造函数语法显式地将一种数据类型转换为另一种数据类型。

int8 y = -3;
uint x = uint(y);
//Now x = 0xfffff..fd == two complement representation of -3 in 256 bit format.

转换为较小的类型需要更高的位。

uint32 a = 0x12345678;
uint16 b = uint16(a); // b = 0x5678

转换为更高类型会在左侧添加填充位。

uint16 a = 0x1234;
uint32 b = uint32(a); // b = 0x00001234 

转换为更小的字节需要更高阶的数据。

bytes2 a = 0x1234;
bytes1 b = bytes1(a); // b = 0x12

转换为较大字节时,会在右侧添加填充位。

bytes2 a = 0x1234;
bytes4 b = bytes4(a); // b = 0x12340000

仅当固定大小字节和 int 大小相同时,才可以在固定大小字节和 int 之间进行转换。

bytes2 a = 0x1234;
uint32 b = uint16(a); // b = 0x00001234
uint32 c = uint32(bytes4(a)); // c = 0x12340000
uint8 d = uint8(uint16(a)); // d = 0x34
uint8 e = uint8(bytes1(a)); // e = 0x12

如果不需要截断,则可以将十六进制数分配给任何整数类型。

uint8 a = 12; // no error
uint32 b = 1234; // no error
uint16 c = 0x123456; // error, as truncation required to 0x3456

Solidity - 以太币单位

在 Solidity 中,我们可以使用 wei、finney、szabo 或 ether 作为文字的后缀,用于转换各种基于以太的面额。最小单位是 wei,1e12 代表 1 x 10 12

assert(1 wei == 1);
assert(1 szabo == 1e12);
assert(1 finney == 1e15);
assert(1 ether == 1e18);
assert(2 ether == 2000 fenny);

时间单位

与货币类似,Solidity 也有时间单位,最小单位是秒,我们可以使用秒、分钟、小时、天和周作为后缀来表示时间。

assert(1 seconds == 1);
assert(1 minutes == 60 seconds);
assert(1 hours == 60 minutes);
assert(1 day == 24 hours);
assert(1 week == 7 days);

Solidity - 特殊变量

特殊变量是全局可用的变量,提供有关区块链的信息。以下是特殊变量的列表 -

先生。 特殊变量和描述
1

blockhash(uint blockNumber) 返回 (bytes32)

给定区块的哈希值 - 仅适用于 256 个最新区块(不包括当前区块)。

2

block.coinbase(应付地址)

当前区块矿工的地址。

3

区块难度 (uint)

当前区块难度。

4

区块.gaslimit (uint)

当前区块的gaslimit。

5

块号(uint)

当前区块号。

6

区块时间戳

当前块时间戳,以 UNIX 纪元以来的秒数表示。

7

Gasleft() 返回 (uint256)

剩余气体。

8

msg.data(调用数据字节)

完整的通话数据。

9

msg.sender(应付地址)

消息的发送者(当前呼叫)。

10

msg.sig(字节4)

calldata 的前四个字节(即函数标识符)

11

消息值(uint)

随消息发送的 wei 数量。

12

现在(单位)

当前区块时间戳(block.timestamp 的别名)。

13

tx.gasprice (uint)

交易的 Gas 价格。

14

tx.origin(应付地址)

交易的发送者(完整调用链)。

例子

尝试以下代码来查看 msg 的使用,msg 是 Solidity 中用于获取发送者地址的特殊变量。

pragma solidity ^0.5.0;

contract LedgerBalance {
   mapping(address => uint) public balances;

   function updateBalance(uint newBalance) public {
      balances[msg.sender] = newBalance;
   }
}
contract Updater {
   function updateBalance() public returns (uint) {
      LedgerBalance ledgerBalance = new LedgerBalance();
      ledgerBalance.updateBalance(10);
      return ledgerBalance.balances(address(this));
   }
}

使用Solidity First Application章节中提供的步骤运行上述程序。

首先单击updateBalance按钮将值设置为 10,然后查看日志,其中将显示解码输出为 -

输出

{
   "0": "uint256: 10"
}

Solidity - 风格指南

样式指南有助于保持代码布局一致并使代码更具可读性。以下是使用 Solidity 编写合约时的最佳实践。

代码布局

  • 缩进- 使用 4 个空格而不是制表符来保持缩进级别。避免将空格与制表符混合。

  • 两个空行规则- 在两个合同定义之间使用 2 个空行。

pragma solidity ^0.5.0;

contract LedgerBalance {
   //...
}
contract Updater {
   //...
}
  • 一个空行规则- 在两个函数之间使用 1 个空行。如果只是声明,则不需要空行。

pragma solidity ^0.5.0;

contract A {
   function balance() public pure;
   function account() public pure;
}
contract B is A {
   function balance() public pure {
      // ...
   }
   function account() public pure {
      // ...
   }
}
  • 最大行长度- 单行不应超过 79 个字符,以便读者可以轻松解析代码。

  • 包装规则- 第一个参数位于新行中,不带左括号。每个参数使用单缩进。终止元素);应该是最后一个。

function_with_a_long_name(
   longArgument1,
   longArgument2,
   longArgument3
);
variable = function_with_a_long_name(
   longArgument1,
   longArgument2,
   longArgument3
);
event multipleArguments(
   address sender,
   address recipient,
   uint256 publicKey,
   uint256 amount,
   bytes32[] options
);
MultipleArguments(
   sender,
   recipient,
   publicKey,
   amount,
   options
);
  • 源代码编码- 最好使用 UTF-8 或 ASCII 编码。

  • 导入- 导入语句应放置在文件顶部,紧接在 pragma 声明之后。

  • 函数顺序- 函数应根据其可见性进行分组。

pragma solidity ^0.5.0;

contract A {
   constructor() public {
      // ...
   }
   function() external {
      // ...
   }

   // External functions
   // ...

   // External view functions
   // ...

   // External pure functions 
   // ...

   // Public functions
   // ...

   // Internal functions
   // ...

   // Private functions
   // ...
}
  • 避免额外的空格- 避免紧邻括号、方括号或大括号内的空格。

  • 控制结构- 大括号应与声明在同一行打开。关闭在自己的行上,保持相同的缩进。使用带左大括号的空格。

pragma solidity ^0.5.0;

contract Coin {
   struct Bank {
      address owner;
      uint balance;
   }
}
if (x < 3) {
   x += 1;
} else if (x > 7) {
   x -= 1;
} else {
   x = 5;
}
if (x < 3)
   x += 1;
else
   x -= 1;
  • 函数声明- 对大括号使用上述规则。始终添加可见性标签。可见性标签应首先出现在任何自定义修饰符之前。

function kill() public onlyowner {
   selfdestruct(owner);
}
  • 映射- 声明映射变量时避免空格。

mapping(uint => uint) map;
mapping(address => bool) registeredAddresses;
mapping(uint => mapping(bool => Data[])) public data;
mapping(uint => mapping(uint => s)) data;
  • 变量声明- 声明数组变量时避免空格。

uint[] x;  // not unit [] x;
  • 字符串声明- 使用双引号而不是单引号来声明字符串。

str = "foo";
str = "Hamlet says, 'To be or not to be...'";

布局顺序

元素应按以下顺序布局。

  • 杂注语句

  • 进口声明

  • 接口

  • 图书馆

  • 合约

在接口、库或合约中,顺序应为 -

  • 类型声明

  • 状态变量

  • 活动

  • 功能

命名约定

  • 合同和库应使用 CapWords 样式命名。例如,智能合约、所有者等。

  • 合同和库名称应与其文件名匹配。

  • 如果一个文件中有多个合约/库,请使用核心合约/库的名称。

拥有.sol

pragma solidity ^0.5.0;

// Owned.sol
contract Owned {
   address public owner;
   constructor() public {
      owner = msg.sender;
   }
   modifier onlyOwner {
      //....
   }
   function transferOwnership(address newOwner) public onlyOwner {
      //...
   }
}

国会.sol

pragma solidity ^0.5.0;

// Congress.sol
import "./Owned.sol";

contract Congress is Owned, TokenRecipient {
   //...
}
  • 结构名称

    - 使用像 SmartCoin 这样的 CapWords 风格。

  • 事件名称

    - 使用 CapWords 风格,如 Deposit、AfterTransfer。

  • 函数名称

    - 使用混合大小写样式,如initiateSupply。

  • 局部变量和状态变量

    - 使用混合大小写样式,如creatorAddress、supply。

  • 常数

    - 使用所有大写字母和下划线来分隔单词,例如 MAX_BLOCKS。

  • 修饰符名称

    - 使用 mixCase 样式,如 onlyAfter。

  • 枚举名称

    - 使用像 TokenGroup 这样的 CapWords 样式。

Solidity - 函数

函数是一组可重用代码,可以在程序中的任何位置调用。这消除了一次又一次编写相同代码的需要。它可以帮助程序员编写模块化代码。函数允许程序员将大程序划分为许多小的且可管理的函数。

与任何其他高级编程语言一样,Solidity 还支持使用函数编写模块化代码所需的所有功能。本节介绍如何在 Solidity 中编写自己的函数。

功能定义

在使用函数之前,我们需要定义它。在 Solidity 中定义函数的最常见方法是使用function关键字,后跟唯一的函数名称、参数列表(可能为空)和用大括号括起来的语句块。

句法

基本语法如下所示。

function function-name(parameter-list) scope returns() {
   //statements
}

例子

尝试以下示例。它定义了一个名为 getResult 的函数,该函数不带参数 -

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint){
      uint a = 1; // local variable
      uint b = 2;
      uint result = a + b;
      return result;
   }
}

调用函数

要稍后在合约中的某个位置调用函数,您只需编写该函数的名称,如以下代码所示。

尝试以下代码来了解字符串在 Solidity 中的工作原理。

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);//access local variable
   }
}

使用Solidity First Application章节中提供的步骤运行上述程序。

输出

0: string: 3

功能参数

到目前为止,我们已经看到了没有参数的函数。但是有一种方法可以在调用函数时传递不同的参数。这些传递的参数可以在函数内部捕获,并且可以对这些参数进行任何操作。一个函数可以采用多个参数,并用逗号分隔。

例子

尝试以下示例。我们在这里使用了uint2str函数。它需要一个参数。

pragma solidity ^0.5.0;

contract SolidityTest {   
   constructor() public{       
   }
   function getResult() public view returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      
      while (j != 0) {
         len++;
         j /= 10;
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      
      while (_i != 0) {
         bstr[k--] = byte(uint8(48 + _i % 10));
         _i /= 10;
      }
      return string(bstr);//access local variable
   }
}

使用Solidity First Application章节中提供的步骤运行上述程序。

输出

0: string: 3

退货声明

Solidity 函数可以有一个可选的return语句。如果您想从函数返回值,则这是必需的。该语句应该是函数中的最后一条语句。

如上面的示例所示,我们使用 uint2str 函数返回一个字符串。

在 Solidity 中,一个函数也可以返回多个值。请参阅下面的示例 -

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint product, uint sum){
      uint a = 1; // local variable
      uint b = 2;
      product = a * b;
      sum = a + b;
  
      //alternative return statement to return 
      //multiple values
      //return(a*b, a+b);
   }
}

使用Solidity First Application章节中提供的步骤运行上述程序。

输出

0: uint256: product 2
1: uint256: sum 3

Solidity - 函数修饰符

函数修饰符用于修改函数的Behave。例如,为函数添加先决条件。

首先我们创建一个带或不带参数的修饰符。

contract Owner {
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}

函数体插入特殊符号“_;”的位置 出现在修饰符的定义中。因此,如果调用该函数时满足修饰符的条件,则执行该函数,否则将引发异常。

请参阅下面的示例 -

pragma solidity ^0.5.0;

contract Owner {
   address owner;
   constructor() public {
      owner = msg.sender;
   }
   modifier onlyOwner {
      require(msg.sender == owner);
      _;
   }
   modifier costs(uint price) {
      if (msg.value >= price) {
         _;
      }
   }
}
contract Register is Owner {
   mapping (address => bool) registeredAddresses;
   uint price;
   constructor(uint initialPrice) public { price = initialPrice; }
   
   function register() public payable costs(price) {
      registeredAddresses[msg.sender] = true;
   }
   function changePrice(uint _price) public onlyOwner {
      price = _price;
   }
}

Solidity - 查看函数

视图函数确保它们不会修改状态。函数可以声明为view。如果函数中存在以下语句,则视为修改状态,在这种情况下编译器将引发警告。

  • 修改状态变量。

  • 发出事件。

  • 创建其他合同。

  • 使用自毁。

  • 通过调用发送以太币。

  • 调用任何未标记为 view 或 pure 的函数。

  • 使用低级调用。

  • 使用包含某些操作码的内联汇编。

Getter 方法默认是视图函数。

请参阅下面使用视图函数的示例。

例子

pragma solidity ^0.5.0;

contract Test {
   function getResult() public view returns(uint product, uint sum){
      uint a = 1; // local variable
      uint b = 2;
      product = a * b;
      sum = a + b; 
   }
}

使用Solidity First Application章节中提供的步骤运行上述程序。

输出

0: uint256: product 2
1: uint256: sum 3

Solidity - 纯函数

纯函数确保它们不会读取或修改状态。函数可以声明为pure。如果函数中存在以下语句,则视为读取状态,在这种情况下编译器将引发警告。

  • 读取状态变量。

  • 访问地址(this).balance 或<address>.balance。

  • 访问block、tx、msg的任意特殊变量(可以读取msg.sig和msg.data)。

  • 调用任何未标记为 pure 的函数。

  • 使用包含某些操作码的内联汇编。

如果发生错误,纯函数可以使用 revert() 和 require() 函数恢复潜在的状态更改。

请参阅下面使用视图函数的示例。

例子

pragma solidity ^0.5.0;

contract Test {
   function getResult() public pure returns(uint product, uint sum){
      uint a = 1; 
      uint b = 2;