Solidity入门了解
简介
使用一种称为Solidity的编程语言可以编写供应链、在线市场或其他用例的操作代码。
通过使用Solidity,还可以指定用户操作。通过对网络上允许的这些操作进行编程,你可以创建自己的区块链网络,而这些网络对所有参与者都是安全且透明的。
什么是Solidity
Solidity是一种面向对象的用于编写智能合同的语言。
智能合同是存储在区块链中的程序,它们指定有关数字资产传输的规则和行为。可以使用Solidity为Ethereum区块链平台对智能合同进行编程。
智能合同包含状态和可编程逻辑,通过事务执行函数,因此使用智能合同可以创建业务工作流。
概述
Solidity是Ethereum区块链最常用的编程语言。
Solidity是一种基于其他编程语言(包括 C++、Python和JavaScript)的高级语言,如果你熟悉这些语言中的任何一种,则有助于熟悉Solidity代码。
Solidity是静态类型语言,这意味着类型检查在编译时进行,而不像动态类型语言在运行时进行。
Solidity支持继承,这意味着一个合同中存在的函数、变量和其他属性可以在另一个合同中使用。该语言还支持复杂的用户定义类型(如struct和enum),这使你可以将相关类型的数据组合在一起。
Solidity是一种开源编程语言,协作者社区庞大。若要了解有关 Solidity 项目以及如何参与的详细信息,请参阅GitHub存储库。
什么是 Ethereum?
Ethereum是最受欢迎的区块链平台之一,仅次于比特币。这是一种社区构建的技术,有自己的加密货币Ether(ETH),可以进行购买和销售。
Ethereum的独特之处在于它是“全球可编程区块链”。Solidity是用于在Ethereum平台上开发的主要编程语言,由Ethereum开发人员构建和维护。
Ethereum 虚拟机
Solidity合同在Ethereum虚拟机(简称EVM)的虚拟环境上运行。它是一个完全隔离的沙盒环境。
除了执行的合同外,它不会访问网络上的任何其他内容。
Solidity语言基础知识
所有Solidity合同通常都包括:
- Pragma指令
- 状态变量
- 函数
- 事件
Pragma指令
Pragma是用来指示编译器检查其Solidity版本是否与所需版本匹配的关键字。
如果匹配,则表示源文件可以成功运行。如果不匹配,编译器将发出错误,因此需要始终确保在合同定义中包含Solidity最新版本。
版本pragma指令如下所示:
pragma solidity ^0.7.0;
此行意味着源文件将使用高于0.7.0版本(最高版本为0.7.9)的编译器进行编译。从版本0.8.0开始,可能会引入一些中断性变更,导致源文件无法成功编译。
若要查找Solidity的当前版本,可访问Solidity网站,使用源文件中的最新版本。
状态变量
状态变量是任何Solidity源文件的关键,状态变量永久存储在合同存储中。
pragma solidity >0.7.0 <0.8.0;
contract Marketplace {
uint price; // State variable
备注:合同源文件始终以“contract ContractName”开头。
在本例中,状态变量名为price,类型为“uint”。整数类型uint表示此变量是256位的无符号整数,这意味着它可以存储到范围内的正数。
对于所有变量定义,必须指定类型和变量名。
此外,可以将状态变量的可见性指定为:
- public:合同接口的一部分,可以从其他合同访问。
- internal:仅限从当前合同内部访问。
- private:仅对定义它的合同可见。
internal和private类似,不过如果某个合同继承自其父合同,这个合同即可以访问父合同中定义的“内部”函数。
external与public类似,只不过这些函数只能在合同之外调用 -它们不能被合同内的其他函数调用。
函数
在合同中,可执行代码单元称为函数。
函数描述实现一个任务的单个操作,它们是可重用的,也可以从其他源文件(如库)进行调用。 Solidity中函数的行为类似于其他编程语言中的函数。
pragma solidity >0.7.0 <0.8.0;
contract Marketplace {
function buy() public {
// ...
}
}
这段代码显示了一个名为buy的函数,它具有公共可见性,这意味着它可以由其他合同访问。
函数可以使用以下可见性说明符之一:public、private、internal和external。
函数可以在内部合同进行调用,也可以从外部另一个合同进行调用。
下面是一个函数示例,该函数接受一个参数(一个称为price的整数),并返回一个整数:
pragma solidity >0.7.0 <0.8.0;
contract Marketplace {
function buy(uint price) public returns (uint) {
// ...
}
}
函数修饰符
函数修饰符可用于更改函数的行为。它们的工作原理是在函数执行前检查条件。
例如,函数可以检查只有指定为卖方的用户才能列出要出售的商品。
pragma solidity >0.7.0 <0.8.0;
contract Marketplace {
address public seller;
modifier onlySeller() {
require(
msg.sender == seller,
"Only seller can put an item up for sale."
);
_;
}
function listItem() public view onlySeller {
// ...
}
}
此示例介绍以下各项:
- 类型为address的变量,用于存储卖方用户的20字节Ethereum地址。(地址变量)
- 名为onlySeller的修饰符,用于说明只有卖方才能列出商品。
- 特殊符号_;,表示函数体插入的位置。
- 使用修饰符onlySeller可以定义函数。
可在函数定义中使用的其他函数修饰符包括:
- pure,用于描述不允许修改或不可访问状态的函数。(不能读写)
- view,用于描述不允许修改状态的函数。(只读不写)
- payable,用于描述可以接收Ether的函数。(调用此函数可以附加发送一些ETH)
事件
事件描述了合同中采取的操作。与函数类似,事件具有在调用事件时需要指定的参数。
若要调用事件,必须将关键字“emit”与事件名称及其参数一起使用。
pragma solidity >0.7.0 <0.8.0;
contract Marketplace {
event PurchasedItem(address buyer, uint price);
function buy() public {
// ...
emit PurchasedItem(msg.sender, msg.value);
}
}
调用事件时,事件会被捕获为事务日志中的事务,事务日志是区块链中的一种特殊数据结构。
这些日志与合同的地址相关联,已合并到区块链中,并且始终保持不变。从合同中是无法访问日志及其事件数据,并且无法修改它。
Solidity值类型
值类型通过值传递,并在使用时进行复制。编写合同时将使用的主要值类型包括“整数”、“布尔”、“字符串”、“地址”和“枚举”。
整型
每个Solidity源文件中都使用整数。它们可以表示有符号整数也可以表示无符号整数。存储的整数大小介于8位到256位之间。
- signed:包括负数和正数。可以表示为int。
- unigned:仅包含正数。可以表示为uint。
- 如果未指定位数,则默认值为256位。
以下运算符可应用于整数:
- 比较:<=、<、==、!=、>=、>
- 位运算符:&、| 、^ 、~
- 算术运算符:+ 、- 、* 、/ 、% 、)、**
以下是整数定义的一些示例:
int32 price = 25; // signed 32 bit integer
uint256 balance = 1000; // unsigned 256 bit integer
balance - price; // 975
2 * price; // 50
price % 2; // 1
布尔型
布尔使用关键字bool进行定义。它们的值始终是true或false。
下面提供了定义方法:
bool forSale; //true if an item is for sale
bool purchased; //true if an item has been purchased
布尔值通常用于比较语句中。例如:
if(balance > 0 & balance > price) {
return true;
}
if(price > balance) {
return false;
}
布尔值也可以用在函数参数和返回类型中。
function buy(int price) returns (bool success) {
// ...
}
字符串
大多数合同文件中也使用字符串,它们是用双引号或单引号括起来的字符或字词。
String shipped = "shipped"; // shipped
String delivered = 'delivered'; // delivered
String newItem = "newItem"; // newItem
此外,以下转义字符可以与字符串一起使用:
- <newline> 转义为换行
- \n 换行
- \r 回车
- \t Tab
地址
地址是一种具有20字节值的类型,它表示Ethereum用户帐户。此类型可以是常规“address”,也可以是“address payable”。
两者之间的区别在于,“address payable”类型是Ether发送到的地址,它包含额外的成员transfer和send。
address payable public seller; // account for the seller
address payable public buyer; // account for the user
function transfer(address buyer, uint price) {
buyer.transfer(price); // the transfer member transfers the price of the item
}
枚举
在Solidity中,可使用枚举创建用户定义类型。之所以称之为用户定义,是因为创建合同的人员才能决定要包含哪些值。
枚举可用于显示许多可选择的选项,其中有一项是必需的。
例如,可以使用enum来表示项目的不同状态,将枚举视为代表多项选择答案,其中所有值都是预定义的,你必须选择一个。
可以在合同或库定义中声明枚举。
enum Status {
Pending,
Shipped,
Delivered
}
Status public status;
constructor() public {
status = Status.Pending;
}
Solidity引用类型
在编写合同时,还应了解引用类型。
与总是传递值的独立副本的值类型不同,引用类型为值提供了一个已知位置。三种引用类型为:结构、数组和映射。
数据位置
使用引用类型时,必须显式提供该类型的数据存储位置。以下选项可用于指定存储类型的数据位置:
- memory:(内存,即栈中数据,生存期与调用函数相同)
- 存储函数参数的位置。
- 生存期限制为外部函数调用的生存期。
- storage:(存储,即写入本地区块链副本中,开销最高)
- 存储状态变量的位置。
- 生命期仅限于合同有效期。
- calldata:(函数参数数据,行为类似内存数据,因其不可修改因此赋值时完全不需要数据拷贝,开销最低)
- 存储函数参数的位置。
- 本位置对于外部函数的参数是必需的,但也可用于其他变量。
- 生存期限制为外部函数调用的生存期。
引用类型总是创建数据的独立副本。
下面举例说明如何使用引用类型:
contract C {
uint[] x;
// the data location of values is memory
function buy(uint[] memory values) public {
x = value; // copies array to storage
uint[] storage y = x; //data location of y is storage
g(x); // calls g, handing over reference to x
h(x); // calls h, and creates a temporary copy in memory
}
function g(uint[] storage) internal pure {}
function h(uint[] memory) public pure {}
}
(注意h(x)传入memory参数后要创建一个临时拷贝)
数组
数组是一种在集合数据结构中存储相似数据的方法。
数组可以是固定大小,也可以是动态大小。它们的索引从 0 开始。
若要创建固定大小k和元素类型T的数组,则需要编写T[k]。 对于动态大小数组,应编写T[]。(不指定长度即默认为动态数组,类似vector)
数组元素可以是任何类型。例如,它们可以包含“uint”、“memory”或“bytes”,也可以包含“映射”或“结构”。
以下示例演示如何创建数组:
uint[] itemIds; // Declare a dynamically sized array called itemIds
uint[3] prices = [1, 2, 3]; // initialize a fixed size array called prices, with prices 1, 2, and 3
uint[] prices = [1, 2, 3]; // same as above
(与c类似,如果声明时直接初始化相当于编译器可以直接指定内存,因此即使不给出长度也是定义一个静态数组)
数组成员
以下成员既可以操作数组,又可以获取有关数组的信息:
- length:获取数组的长度。
- push:在数组末尾追加一个元素。
- pop:从数组末尾删除元素。
// Create a dynamic byte array
bytes32[] itemNames;
itemNames.push(bytes32("computer")); // adds "computer" to the array
itemNames.length; // 1
结构
结构是用户可以定义用来表示实际对象的自定义类型,通常用作架构或用于表示记录。
结构声明示例:
struct Items_Schema {
uint256 _id;
uint256 _price;
string _name;
string _description;
}
映射类型
映射是封装或打包在一起的键值对。映射最接近JavaScript中的字典或对象。
通常使用映射来建模实际对象,并执行快速数据查找。这些值可以包括结构等复杂类型,这使得映射类型灵活且可读性强。
下面的代码示例使用结构Items_Schema ,并将Items_Schema表示的项列表保存为字典。映射通过这种方式模拟数据库。
contract Items {
uint256 item_id = 0;
mapping(uint256 => Items_Schema) public items;
struct Items_Schema {
uint256 _id:
uint256 _price:
string _name;
}
function listItem(uint256 memory _price, string memory _name) public {
item_id += 1;
item[vehicle_id] = Items_Schema(item_id, _price, _name);
}
}