SOL01:Solidity语言基础


  Solidity是传说中编写智能合约的脚本语言,运行在EVM中;用以解决区块链中的任务执行。一个目前看起来还非常稚嫩的语言。这里做一个结构性介绍。并据此展开详细的说明。


Solidity语言特点

  • Solidity 是一门面向合约的、为实现智能合约而创建的高级编程语言。
  • 这门语言受到了 C++,Python 和 Javascript 语言的影响,设计的目的是能在以太坊虚拟机(EVM)上运行。
  • Solidity 是静态类型语言,支持继承、库和复杂的用户定义类型等特性。
  • 使用Solidity 语言,可以为各种应用创建合约
    • 投票;
    • 众筹;
    • 秘密竞价(盲拍);
    • 多重签名的钱包;
    • 以及其他应用;

Slidity语言结构

源代码文件

  1. 源代码文件与其他语言一样,使用文本文件,扩展名使用sol。

  2. 文件的的组织操作系统的文件一样,使用目录组织;

    • 文件之间使用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
  1. 合约

    • contract 合同名 {}
  2. 注释

    • 与javascript一样,行注释与块注释。
    • //
    • /**/

例子

  1. other.sol
pragma solidity ^0.6.1;

contract Other {
    uint value;
}

  1. solidity.sol
pragma solidity ^0.6.1;

import "./other.sol";
// 行注释
contract MySol is Other {
    /**
    块注释
    */
    uint age;
}

  1. 编译
    • solcjs --abi solidity.sol other.sol
多文件编译

合约contract

  • 在 Solidity 中,合约类似于面向对象编程语言中的类。 每个合约中可以包含

    1. 状态变量;
    2. 函数;
    3. 函数修饰器;
    4. 事件;
    5. 结构类型;
    6. 枚举类型 ;
  • 合约可以继承的

合约定义


    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;

数据类型与字面值

布尔类型与布尔值

  1. 类型关键字:bool
  2. 布尔值:truefalse

整数类型与整数值

  1. 类型关键值:
    • int / uint (有符号与无符号)
    • 整数也分字节大小:单位是位,8位一个字节,根据对齐规则,必须是8的倍数。
      • int8/uint8
      • 。。。
      • int256/uint256 = int/uint
  2. 整数值:
    • 只支持10与16进制
      • 普通法表示:122
        • 不能前缀0。
        • 16进制使用hex前缀转换:hex"001122FF" 或者 0x前缀。
      • 科学记数法表示:1e10
        • 指数必须是整数,不支持小数。

小数类型与小数值

  1. 小数类型关键字:fixed / ufixed

    • 有符号与无符号小数
    • 小数还可以自带精度表示:
      • ufixedMxN / fixedMxN (M表示表示该类型占用的位数,N表示可用的小数位数)
        • M也必须是8的倍数,最大256。
  2. 小数值:

    • 使用小数点表示小数。 与整数一样,分成普通表示与科学表示。
  3. 例子:

    • ufixed32x2 score = 12.45;
  4. 注意:

    • Solidity 还没有完全支持定长浮点型。可以声明定长浮点型的变量,但不能给它们赋值或把它们赋值给其他变量。。

地址类型与地址值

  • 地址类型存储一个 20 字节的值(以太坊地址的大小)。
  • 地址类型也有成员变量,并作为所有合约的基础。
  1. 地址类型关键字:address
  2. 地址值表示:0x开头的16进制表示。
  3. 地址变量包含多个成员(成员属性与成员函数),用来访问地址相关信息:
    • balance :balance 属性来查询一个地址的余额
    • send/transfer :transfer 函数向一个地址发送 以太币Ether (以 wei 为单位):
  • 备注:地址的所有成员:
    1. .balance (uint256):

      • 以Wei为单位的地址类型的余额。
    2. .transfer(uint256 amount):

      • 向地址类型发送数量为amount的Wei,失败时抛出异常,发送 2300 gas 的矿工费,不可调节。
    3. .send(uint256 amount) returns (bool):

      • 向地址类型 发送数量为 amount 的 Wei,失败时返回 false,发送 2300 gas 的矿工费用,不可调节。
    4. .call(...) returns (bool):

      • 发出低级函数 CALL,失败时返回 false,发送所有可用 gas,可调节。
    5. .callcode(...) returns (bool)

      • 发出低级函数 CALLCODE,失败时返回 false,发送所有可用 gas,可调节。
    6. .delegatecall(...) returns (bool):

      • 发出低级函数 DELEGATECALL,失败时返回 false,发送所有可用 gas,可调节。

数组类型与值表示

  1. 数组关键字:类型[]
  2. 数组的创建:new
  3. 例子:

    pragma solidity ^0.4.16;

    contract C {
        function f(uint len) public pure {
            uint[] memory a = new uint[](7);
            a[6] = 8;
        }
    }
  1. 两个特殊的数组:
    • bytes 与 string 等加以 int8[]或者 byte[]
  2. 例子:
    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. 数组字面值

    • [1, 2, 3, 4]
  2. 数组变量赋值的注意事项:长度一致

    • 下面例子无法赋值:
// 这段代码并不能编译。

pragma solidity ^0.4.0;

contract C {
    function f() public {
        // 这一行引发了一个类型错误,因为 unint[3] memory
        // 不能转换成 uint[] memory。
        uint[] x = [uint(1), 3, 4];
    }
}
  1. 数组的成员

    • length属性:获取数组长度,还可以通过这个成员属性修改数组的长度(只对存储有效,为位置在内存的变量无效,参考后面存储位置的说明)
    • push函数:用来向数组末尾添加数据
    • 这length对字符串数组无效。
  2. 多维数组:

    • bool[2][3] m_pairsOfFlags;

字符串类型与值表示

  1. 字符串也是数组,其字面值表示:

    • "foo":3字节字符数组。
    • 字符串数组与bytes数组可以隐式转换。
  2. 字符串支持转移字符

    • 字符串字面常数支持转义字符,例如 \n,\xNN 和 \uNNNN\xNN 表示一个 16 进制值,最终转换成合适的字节, 而 \uNNNN 表示 Unicode 编码值,最终会转换为 UTF-8 的序列。

枚举类型

  1. 定义枚举类型

    • enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
  2. 使用枚举类型

    • 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;
    }


  • 结构体不能包含自己。

存储位置

  • 所有的复杂类型,即数组和结构类型,都有一个额外属性,“数据位置”,

    1. 说明数据是保存在 内存memory 中还是 存储storage 中。
    2. 根据上下文不同,大多数时候数据有默认的位置,但也可以通过在类型名后增加关键字 storage 或 memory 进行修改。
    3. 函数参数(包括返回的参数)的数据位置默认是 memory, 局部变量的数据位置默认是 storage,状态变量的数据位置强制是 storage 。
  • 第三种数据位置calldata

    1. 这是一块只读的,且不会永久存储的位置,用来存储函数参数。
    2. 外部函数的参数(非返回参数)的数据位置被强制指定为 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哈希值,从而便于查询实际的值。

类型转换

  • 类型转换一般分成显式转换与隐式转换:
    • 显式转换 : 类型(值)

运算符与表达式

布尔运算

  1. ! (逻辑非)
  2. && (逻辑与, "and" )
  3. || (逻辑或, "or" )
  4. == (等于)
  5. != (不等于)

整数运算

  1. 比较运算符:
    • <= , < , == , != , >= , > (返回布尔值)
  2. 位运算符:
    • & , | , ^ (异或), ~ (位取反)
  3. 算数运算符:
    • + , - , 一元运算 - , 一元运算 + , * , / , % (取余) , ** (幂), << (左移位) , >> (右移位)

小数运算

  1. 比较运算符:
    • <=, <, ==, !=, >=, > (返回值是布尔型)
  2. 算术运算符:
    • +, -, 一元运算 -, 一元运算 +, *, /, % (取余数)

地址运算

  • 比较运算:
    • <=, <, ==, !=, >= 和 >

数组运算

  1. 比较运算符:
    • <=, <, ==, !=, >=, > (返回布尔型)
  2. 位运算符:
    • &, |, ^ (按位异或), ~ (按位取反), << (左移位), >> (右移位)
  3. 索引访问:
    • 如果 x 是 bytesI 类型,那么 x[k] (其中 0 <= k < I)返回第 k 个字节(只读)。
  4. .length
    • 表示这个字节数组的长度(对定长只读).

流程控制

  1. JavaScript 中的大部分控制结构在 Solidity 中都是可用的,除了 switch 和 goto。

    • if,else,
    • while,
    • do,
    • for,
    • break,continue,return,
    • ? :
  2. 注意:

    • 用于表示条件的括号不可以被省略,单语句体两边的花括号可以被省略。
    • 与C和JavaScript不同, Solidity 中非布尔类型数值不能转换为布尔类型,因此 if (1) { ... } 的写法在 Solidity 中 无效 。

你可能感兴趣的:(SOL01:Solidity语言基础)