SOL01:Solidity语言基础
2020-04-14 本文已影响0人
杨强AT南京
Solidity是传说中编写智能合约的脚本语言,运行在EVM中;用以解决区块链中的任务执行。一个目前看起来还非常稚嫩的语言。这里做一个结构性介绍。并据此展开详细的说明。
Solidity语言特点
- Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言。
- 这门语言受到了 C++,Python 和 Javascript 语言的影响,设计的目的是能在以太坊虚拟机(EVM)上运行。
- Solidity 是静态类型语言,支持继承、库和复杂的用户定义类型等特性。
- 使用Solidity 语言,可以为各种应用创建合约
- 投票;
- 众筹;
- 秘密竞价(盲拍);
- 多重签名的钱包;
- 以及其他应用;
Slidity语言结构
源代码文件
-
源代码文件与其他语言一样,使用文本文件,扩展名使用sol。
-
文件的的组织操作系统的文件一样,使用目录组织;
- 文件之间使用import引用,引用可以指定目录名。这个与ES6语法类似。
-
import "filename";
- 此语句将从 “filename” 中导入所有的全局符号到当前全局作用域中。
-
import * as symbolName from "filename";
- 创建一个新的全局符号 symbolName,其成员均来自 "filename" 中全局符号。
-
import {symbol1 as alias, symbol2} from "filename";
- 创建新的全局符号 alias 和 symbol2,分别从 "filename" 引用 symbol1 和 symbol2 。
-
import "filename" as symbolName;
- 条语句等同于 import * as symbolName from "filename";。
目录
- 支持"."与".."
- ".":当前目录
- "..":上级目录
- 在编译器支持目录重映射:
import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol" as it_mapping;
- 编译:
solc github.com/ethereum/dapp-bin/=/usr/local/dapp-bin/ source.sol
文件结构
-
头
- 版本申明
- import
-
合约
contract 合同名 {}
-
注释
- 与javascript一样,行注释与块注释。
//
/**/
例子
- other.sol
pragma solidity ^0.6.1;
contract Other {
uint value;
}
- solidity.sol
pragma solidity ^0.6.1;
import "./other.sol";
// 行注释
contract MySol is Other {
/**
块注释
*/
uint age;
}
- 编译
solcjs --abi solidity.sol other.sol
合约contract
-
在 Solidity 中,合约类似于面向对象编程语言中的类。 每个合约中可以包含
- 状态变量;
- 函数;
- 函数修饰器;
- 事件;
- 结构类型;
- 枚举类型 ;
-
合约可以继承的
合约定义
contract 合约名 [is] 父合约{
// 1. 状态变量;
// 2. 函数;
// 3. 事件;
// 4. 结构类型;
// 5. 枚举类型;
}
状态变量
contract MyContract {
uint varState; // 状态变量
// ...
}
- 状态变量:
- 类型 存储名;
- 类型见后面说明;
函数与函数修饰
- 合约中可执行单元
contract Purchase {
address public seller;
modifier onlySeller() { // 修饰器
require(
msg.sender == seller,
"Only seller can call this."
);
_;
}
function abort() public onlySeller { // 函数与修饰器使用
// ...
}
}
事件
- 事件是能方便地调用以太坊虚拟机日志功能的接口。
contract SimpleAuction {
event HighestBidIncreased(address bidder, uint amount); // 事件
function bid() public payable {
// ...
emit HighestBidIncreased(msg.sender, msg.value); // 触发事件
}
}
结构类型
- 结构是可以将几个变量分组的自定义类型
- 用户自定义复合类型
contract Ballot {
struct Voter { // 结构
uint weight;
bool voted;
address delegate;
uint vote;
}
// .....
}
枚举类型
- 举可用来创建由一定数量的“常量值”构成的自定义类型.
contract Purchase {
enum State { Created, Locked, Inactive } // 枚举
}
合同继承
- 使用is关键字实现。
pragma solidity ^0.4.16;
contract owned {
function owned() { owner = msg.sender; }
address owner;
}
// 使用 is 从另一个合约派生。派生合约可以访问所有非私有成员,包括内部函数和状态变量,
// 但无法通过 this 来外部访问。
contract mortal is owned {
function kill() {
if (msg.sender == owner) selfdestruct(owner);
}
}
- 主要:
- 继承包含抽象类与接口的定义。
抽象合约
- 语法没有什么差异,主要在函数的实现上:
- 包含抽象函数 // 没有实现函数体的函数就是抽象函数
- 抽象合约:
- 主要是只有抽象函数的合约就是接口合约。
- 实现函数与抽象函数混杂的就是抽象合约。
contract Feline { // 可以包含实现的就是抽象合约,这里只有一个抽象函数,实际也是接口合约。
function utterance() public returns (bytes32);
}
contract Cat is Feline {
function utterance() public returns (bytes32) { return "miaow"; }
}
库
- 使用libarary定义库,定义好的库可以在合约中使用,下面是官方的例子:
- 库的语法与合约差不多,合约可以使用库中数据与函数。
pragma solidity ^0.4.16;
library Set {
// 我们定义了一个新的结构体数据类型,用于在调用合约中保存数据。
struct Data { mapping(uint => bool) flags; }
// 注意第一个参数是“storage reference”类型,因此在调用中参数传递的只是它的存储地址而不是内容。
// 这是库函数的一个特性。如果该函数可以被视为对象的方法,则习惯称第一个参数为 `self` 。
function insert(Data storage self, uint value)
public
returns (bool)
{
if (self.flags[value])
return false; // 已经存在
self.flags[value] = true;
return true;
}
function remove(Data storage self, uint value)
public
returns (bool)
{
if (!self.flags[value])
return false; // 不存在
self.flags[value] = false;
return true;
}
function contains(Data storage self, uint value)
public
view
returns (bool)
{
return self.flags[value];
}
}
contract C {
Set.Data knownValues;
function register(uint value) public {
// 不需要库的特定实例就可以调用库函数,
// 因为当前合约就是“instance”。
require(Set.insert(knownValues, value));
}
// 如果我们愿意,我们也可以在这个合约中直接访问 knownValues.flags。
}
数据类型与数据
数据的定义
-
语法:
类型 变量名 = 初始值
- 变量名遵循Javascript的命名规则。
-
删除变量
- delete 变量名;
-
常量:
- constant:
int constant a = 2000;
数据类型与字面值
布尔类型与布尔值
- 类型关键字:
bool
- 布尔值:
true
与false
整数类型与整数值
- 类型关键值:
- int / uint (有符号与无符号)
- 整数也分字节大小:单位是位,8位一个字节,根据对齐规则,必须是8的倍数。
- int8/uint8
- 。。。
- int256/uint256 = int/uint
- 整数值:
- 只支持10与16进制
- 普通法表示:122
- 不能前缀0。
- 16进制使用hex前缀转换:hex"001122FF" 或者 0x前缀。
- 科学记数法表示:1e10
- 指数必须是整数,不支持小数。
- 普通法表示:122
- 只支持10与16进制
小数类型与小数值
-
小数类型关键字:fixed / ufixed
- 有符号与无符号小数
- 小数还可以自带精度表示:
- ufixedMxN / fixedMxN (M表示表示该类型占用的位数,N表示可用的小数位数)
- M也必须是8的倍数,最大256。
- ufixedMxN / fixedMxN (M表示表示该类型占用的位数,N表示可用的小数位数)
-
小数值:
- 使用小数点表示小数。 与整数一样,分成普通表示与科学表示。
-
例子:
- ufixed32x2 score = 12.45;
-
注意:
- Solidity 还没有完全支持定长浮点型。可以声明定长浮点型的变量,但不能给它们赋值或把它们赋值给其他变量。。
地址类型与地址值
- 地址类型存储一个 20 字节的值(以太坊地址的大小)。
- 地址类型也有成员变量,并作为所有合约的基础。
- 地址类型关键字:address
- 地址值表示:0x开头的16进制表示。
- 地址变量包含多个成员(成员属性与成员函数),用来访问地址相关信息:
- balance :balance 属性来查询一个地址的余额
- send/transfer :transfer 函数向一个地址发送 以太币Ether (以 wei 为单位):
- 备注:地址的所有成员:
-
<address>.balance (uint256)
:- 以Wei为单位的地址类型的余额。
-
<address>.transfer(uint256 amount)
:- 向地址类型发送数量为amount的Wei,失败时抛出异常,发送 2300 gas 的矿工费,不可调节。
-
<address>.send(uint256 amount) returns (bool)
:- 向地址类型 发送数量为 amount 的 Wei,失败时返回 false,发送 2300 gas 的矿工费用,不可调节。
-
<address>.call(...) returns (bool)
:- 发出低级函数 CALL,失败时返回 false,发送所有可用 gas,可调节。
-
<address>.callcode(...) returns (bool)
:- 发出低级函数 CALLCODE,失败时返回 false,发送所有可用 gas,可调节。
-
<address>.delegatecall(...) returns (bool)
:- 发出低级函数 DELEGATECALL,失败时返回 false,发送所有可用 gas,可调节。
-
数组类型与值表示
- 数组关键字:
类型[]
- 数组的创建:
new
- 例子:
pragma solidity ^0.4.16;
contract C {
function f(uint len) public pure {
uint[] memory a = new uint[](7);
a[6] = 8;
}
}
- 两个特殊的数组:
- bytes 与 string 等加以 int8[]或者 byte[]
- 例子:
pragma solidity ^0.4.16;
contract C {
function f(uint len) public pure {
uint[] a = new uint[](7);
bytes b = new bytes(len); // string b= new string(len)
// 这里我们有 a.length == 7 以及 b.length == len
a[6] = 8;
}
}
-
数组字面值
[1, 2, 3, 4]
-
数组变量赋值的注意事项:长度一致
- 下面例子无法赋值:
// 这段代码并不能编译。
pragma solidity ^0.4.0;
contract C {
function f() public {
// 这一行引发了一个类型错误,因为 unint[3] memory
// 不能转换成 uint[] memory。
uint[] x = [uint(1), 3, 4];
}
}
-
数组的成员
- length属性:获取数组长度,还可以通过这个成员属性修改数组的长度(只对存储有效,为位置在内存的变量无效,参考后面存储位置的说明)
- push函数:用来向数组末尾添加数据
- 这length对字符串数组无效。
-
多维数组:
bool[2][3] m_pairsOfFlags;
字符串类型与值表示
-
字符串也是数组,其字面值表示:
-
"foo"
:3字节字符数组。 - 字符串数组与bytes数组可以隐式转换。
-
-
字符串支持转移字符
- 字符串字面常数支持转义字符,例如
\n,\xNN 和 \uNNNN
。\xNN
表示一个 16 进制值,最终转换成合适的字节, 而\uNNNN
表示 Unicode 编码值,最终会转换为 UTF-8 的序列。
- 字符串字面常数支持转义字符,例如
枚举类型
-
定义枚举类型
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
-
使用枚举类型
ActionChoices defaultChoice = ActionChoices.GoStraight;
pragma solidity ^0.4.16;
contract test {
enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
ActionChoices choice;
ActionChoices constant defaultChoice = ActionChoices.GoStraight;
function setGoStraight() public {
choice = ActionChoices.GoStraight;
}
结构体
- 结构体是定义新的类型,语法如下:
struct Funder {
address addr;
uint amount;
}
- 结构体不能包含自己。
存储位置
-
所有的复杂类型,即数组和结构类型,都有一个额外属性,“数据位置”,
- 说明数据是保存在 内存memory 中还是 存储storage 中。
- 根据上下文不同,大多数时候数据有默认的位置,但也可以通过在类型名后增加关键字 storage 或 memory 进行修改。
- 函数参数(包括返回的参数)的数据位置默认是 memory, 局部变量的数据位置默认是 storage,状态变量的数据位置强制是 storage 。
-
第三种数据位置calldata
- 这是一块只读的,且不会永久存储的位置,用来存储函数参数。
- 外部函数的参数(非返回参数)的数据位置被强制指定为 calldata ,效果跟 memory 差不多。
-
例子:
pragma solidity ^0.4.0;
contract C {
uint[] x; // x 的数据存储位置是 storage
// memoryArray 的数据存储位置是 memory
function f(uint[] memoryArray) public {
x = memoryArray; // 将整个数组拷贝到 storage 中,可行
var y = x; // 分配一个指针(其中 y 的数据存储位置是 storage),可行
y[7]; // 返回第 8 个元素,可行
y.length = 2; // 通过 y 修改 x,可行
delete x; // 清除数组,同时修改 y,可行
// 下面的就不可行了;需要在 storage 中创建新的未命名的临时数组, /
// 但 storage 是“静态”分配的:
// y = memoryArray;
// 下面这一行也不可行,因为这会“重置”指针,
// 但并没有可以让它指向的合适的存储位置。
// delete y;
g(x); // 调用 g 函数,同时移交对 x 的引用
h(x); // 调用 h 函数,同时在 memory 中创建一个独立的临时拷贝
}
function g(uint[] storage storageArray) internal {}
function h(uint[] memoryArray) public {}
}
映射(key - value)
-
映射类型在声明语法为
-
mapping(_KeyType => _ValueType)
。 - 其中
_KeyType
可以是除了映射、变长数组、合约、枚举以及结构体以外的几乎所有类型。 -
_ValueType
可以是包括映射类型在内的任何类型。
-
-
映射可以视作哈希表
- 它们在实际的初始化过程中创建每个可能的 key, 并将其映射到字节形式全是零的值:一个类型的 默认值。
- 然而下面是映射与哈希表不同的地方:
- 在映射中,实际上并不存储 key,而是存储它的keccak256哈希值,从而便于查询实际的值。
类型转换
- 类型转换一般分成显式转换与隐式转换:
- 显式转换 : 类型(值)
运算符与表达式
布尔运算
! (逻辑非)
&& (逻辑与, "and" )
|| (逻辑或, "or" )
== (等于)
!= (不等于)
整数运算
- 比较运算符:
<= , < , == , != , >= , > (返回布尔值)
- 位运算符:
& , | , ^ (异或), ~ (位取反)
- 算数运算符:
+ , - , 一元运算 - , 一元运算 + , * , / , % (取余) , ** (幂), << (左移位) , >> (右移位)
小数运算
- 比较运算符:
<=, <, ==, !=, >=, > (返回值是布尔型)
- 算术运算符:
+, -, 一元运算 -, 一元运算 +, *, /, % (取余数)
地址运算
- 比较运算:
<=, <, ==, !=, >= 和 >
数组运算
- 比较运算符:
<=, <, ==, !=, >=, > (返回布尔型)
- 位运算符:
&, |, ^ (按位异或), ~ (按位取反), << (左移位), >> (右移位)
- 索引访问:
如果 x 是 bytesI 类型,那么 x[k] (其中 0 <= k < I)返回第 k 个字节(只读)。
- .length
表示这个字节数组的长度(对定长只读).
流程控制
-
JavaScript 中的大部分控制结构在 Solidity 中都是可用的,除了 switch 和 goto。
- if,else,
- while,
- do,
- for,
- break,continue,return,
- ? :
-
注意:
- 用于表示条件的括号不可以被省略,单语句体两边的花括号可以被省略。
- 与C和JavaScript不同, Solidity 中非布尔类型数值不能转换为布尔类型,因此 if (1) { ... } 的写法在 Solidity 中 无效 。