• solidity语法2——类型


    值类型

    以下类型也称为值类型,因为这些类型的变量将始终按值来传递。 也就是说,当这些变量被用作函数参数或者用在赋值语句中时,总会进行值拷贝。

    布尔类型

    bool :可能的取值为字面常数值 true 和 false 。

    运算符:

    • ! (逻辑非)
    • && (逻辑与, "and" )
    • || (逻辑或, "or" )
    • == (等于)
    • != (不等于)

    运算符 || 和 && 都遵循同样的短路( short-circuiting )规则。就是说在表达式 f(x) || g(y) 中, 如果 f(x) 的值为 true ,那么 g(y) 就不会被执行,即使会出现一些副作用。

    整型

    int / uint :分别表示有符号和无符号的不同位数的整型变量。 支持关键字 uint8 到 uint256 (无符号,从 8 位到 256 位)以及 int8 到 int256,以 8 位为步长递增。 uint 和 int 分别是 uint256 和 int256 的别名。

    定长浮点型

    警告

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

    fixed / ufixed:表示各种大小的有符号和无符号的定长浮点型。 在关键字 ufixedMxN 和 fixedMxN 中,M 表示该类型占用的位数,N 表示可用的小数位数。 M 必须能整除 8,即 8 到 256 位。 N 则可以是从 0 到 80 之间的任意数。 ufixed 和 fixed 分别是 ufixed128x19 和 fixed128x19 的别名。

    地址类型

    address:地址类型存储一个 20 字节的值(以太坊地址的大小)。 地址类型也有成员变量,并作为所有合约的基础。

    运算符:

    • <=, <, ==, !=, >= 和 >

    注解

    从 0.5.0 版本开始,合约不会从地址类型派生,但仍然可以显式地转换成地址类型。

    地址类型成员变量

    • balance 和 transfer

    快速参考,请见 地址相关

    可以使用 balance 属性来查询一个地址的余额, 也可以使用 transfer 函数向一个地址发送 以太币Ether (以 wei 为单位):

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

    send 是 transfer 的低级版本。如果执行失败,当前的合约不会因为异常而终止,但 send 会返回 false

    • call, callcode 和 delegatecall

    此外,为了与不符合 应用二进制接口Application Binary Interface(ABI) 的合约交互,于是就有了可以接受任意类型任意数量参数的 call 函数。 这些参数会被打包到以 32 字节为单位的连续区域中存放。 其中一个例外是当第一个参数被编码成正好 4 个字节的情况。 在这种情况下,这个参数后边不会填充后续参数编码,以允许使用函数签名。

    address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2;
    nameReg.call("register", "MyName");
    nameReg.call(bytes4(keccak256("fun(uint256)")), a);
    

    call 返回的布尔值表明了被调用的函数已经执行完毕(true)或者引发了一个 EVM 异常(false)。 无法访问返回的真实数据(为此我们需要事先知道编码和大小)。

    可以使用 .gas() 修饰器modifier 调整提供的 gas 数量

    namReg.call.gas(1000000)("register", "MyName");
    

    类似地,也能控制提供的 以太币Ether 的值

    nameReg.call.value(1 ether)("register", "MyName");
    

    最后一点,这些 修饰器modifier 可以联合使用。每个修改器出现的顺序不重要

    nameReg.call.gas(1000000).value(1 ether)("register", "MyName");

     

     

    定长字节数组

    关键字有:bytes1, bytes2, bytes3, ..., bytes32byte 是 bytes1 的别名。

    运算符:

    • 比较运算符:<=, <, ==, !=, >=, > (返回布尔型)
    • 位运算符: &, |, ^ (按位异或), ~ (按位取反), << (左移位), >> (右移位)
    • 索引访问:如果 x 是 bytesI 类型,那么 x[k] (其中 <= I)返回第 k 个字节(只读)。

    该类型可以和作为右操作数的任何整数类型进行移位运算(但返回结果的类型和左操作数类型相同),右操作数表示需要移动的位数。 进行负数位移运算会引发运行时异常。

    成员变量:

    • .length 表示这个字节数组的长度(只读).

    变长字节数组

    bytes:
    变长字节数组,参见 数组。它并不是值类型。
    string:
    变长 UTF-8 编码字符串类型,参见 数组。并不是值类型。

    地址字面常数(Address Literals)

    比如像 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF 这样的通过了地址校验和测试的十六进制字面常数属于 address 类型。 长度在 39 到 41 个数字的,没有通过校验和测试而产生了一个警告的十六进制字面常数视为正常的有理数字面常数。

    有理数和整数字面常数

    整数字面常数由范围在 0-9 的一串数字组成,表现成十进制。 例如,69 表示数字 69。 Solidity 中是没有八进制的,因此前置 0 是无效的。

    十进制小数字面常数带有一个 .,至少在其一边会有一个数字。 比如:1..1,和 1.3

    科学符号也是支持的,尽管指数必须是整数,但底数可以是小数。 比如:2e10, -2e10, 2e-10, 2.5e1

    数值字面常数表达式本身支持任意精度,除非它们被转换成了非字面常数类型(也就是说,当它们出现在非字面常数表达式中时就会发生转换)。 这意味着在数值常量表达式中, 计算不会溢出而除法也不会截断。

    例如, (2**800 1) 2**800 的结果是字面常数 1 (属于 uint8 类型),尽管计算的中间结果已经超过了 以太坊虚拟机Ethereum Virtual Machine(EVM) 的机器字长度。 此外, .5 8 的结果是整型 4 (尽管有非整型参与了计算)。

    只要操作数是整型,任意整型支持的运算符都可以被运用在数值字面常数表达式中。 如果两个中的任一个数是小数,则不允许进行位运算。如果指数是小数的话,也不支持幂运算(因为这样可能会得到一个无理数)。

    字符串字面常数

    字符串字面常数是指由双引号或单引号引起来的字符串("foo" 或者 'bar')。 不像在 C 语言中那样带有结束符;"foo" 相当于 3 个字节而不是 4 个。 和整数字面常数一样,字符串字面常数的类型也可以发生改变,但它们可以隐式地转换成 bytes1,……,bytes32,如果合适的话,还可以转换成 bytes 以及 string

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

    十六进制字面常数

    十六进制字面常数以关键字 hex 打头,后面紧跟着用单引号或双引号引起来的字符串(例如,hex"001122FF")。 字符串的内容必须是一个十六进制的字符串,它们的值将使用二进制表示。

    十六进制字面常数跟字符串字面常数很类似,具有相同的转换规则。

    枚举类型

    函数类型

    函数类型是一种表示函数的类型。可以将一个函数赋值给另一个函数类型的变量,也可以将一个函数作为参数进行传递,还能在函数调用中返回函数类型变量。 函数类型有两类:- 内部(internal) 函数和 外部(external) 函数:

    内部函数只能在当前合约内被调用(更具体来说,在当前代码块内,包括内部库函数和继承的函数中),因为它们不能在当前合约上下文的外部被执行。 调用一个内部函数是通过跳转到它的入口标签来实现的,就像在当前合约的内部调用一个函数。

    外部函数由一个地址和一个函数签名组成,可以通过外部函数调用传递或者返回。

    函数类型表示成如下的形式

    function (<parameter types>) {internal|external} [pure|constant|view|payable] [returns (<return types>)]
    

    与参数类型相反,返回类型不能为空 —— 如果函数类型不需要返回,则需要删除整个 returns (<return types>) 部分。

    函数类型默认是内部函数,因此不需要声明 internal 关键字。 与此相反的是,合约中的函数本身默认是 public 的,只有当它被当做类型名称时,默认才是内部函数。

    有两种方法可以访问当前合约中的函数:一种是直接使用它的名字,f ,另一种是使用 this.f 。 前者适用于内部函数,后者适用于外部函数。

    批注:这里与js不一样。

    如果当函数类型的变量还没有初始化时就调用它的话会引发一个异常。 如果在一个函数被 delete 之后调用它也会发生相同的情况。

    如果外部函数类型在 Solidity 的上下文环境以外的地方使用,它们会被视为 function 类型。 该类型将函数地址紧跟其函数标识一起编码为一个 bytes24 类型。。

    请注意,当前合约的 public 函数既可以被当作内部函数也可以被当作外部函数使用。 如果想将一个函数当作内部函数使用,就用 f 调用,如果想将其当作外部函数,使用 this.f 。

    除此之外,public(或 external)函数也有一个特殊的成员变量称作 selector,可以返回 ABI 函数选择器:

    pragma solidity ^0.4.16;
    
    contract Selector {
      function f() public view returns (bytes4) {
        return this.f.selector;
      }
    }
    

    如果使用内部函数类型的例子:

    pragma solidity ^0.4.16;
    
    library ArrayUtils {
      // 内部函数可以在内部库函数中使用,
      // 因为它们会成为同一代码上下文的一部分
      function map(uint[] memory self, function (uint) pure returns (uint) f)
        internal
        pure
        returns (uint[] memory r)
      {
        r = new uint[](self.length);
        for (uint i = 0; i < self.length; i++) {
          r[i] = f(self[i]);
        }
      }
      function reduce(
        uint[] memory self,
        function (uint, uint) pure returns (uint) f
      )
        internal
        pure
        returns (uint r)
      {
        r = self[0];
        for (uint i = 1; i < self.length; i++) {
          r = f(r, self[i]);
        }
      }
      function range(uint length) internal pure returns (uint[] memory r) {
        r = new uint[](length);
        for (uint i = 0; i < r.length; i++) {
          r[i] = i;
        }
      }
    }
    
    contract Pyramid {
      using ArrayUtils for *;
      function pyramid(uint l) public pure returns (uint) {
        return ArrayUtils.range(l).map(square).reduce(sum);
      }
      function square(uint x) internal pure returns (uint) {
        return x * x;
      }
      function sum(uint x, uint y) internal pure returns (uint) {
        return x + y;
      }
    }
    

    另外一个使用外部函数类型的例子:

    pragma solidity ^0.4.11;
    
    contract Oracle {
      struct Request {
        bytes data;
        function(bytes memory) external callback;
      }
      Request[] requests;
      event NewRequest(uint);
      function query(bytes data, function(bytes memory) external callback) public {
        requests.push(Request(data, callback));
        NewRequest(requests.length - 1);
      }
      function reply(uint requestID, bytes response) public {
        // 这里要验证 reply 来自可信的源
        requests[requestID].callback(response);
      }
    }
    
    contract OracleUser {
      Oracle constant oracle = Oracle(0x1234567); // 已知的合约
      function buySomething() {
        oracle.query("USD", this.oracleResponse);
      }
      function oracleResponse(bytes response) public {
        require(msg.sender == address(oracle));
        // 使用数据
      }
    }
    
     

    引用类型

    比起之前讨论过的值类型,在处理复杂的类型(即占用的空间超过 256 位的类型)时,我们需要更加谨慎。 由于拷贝这些类型变量的开销相当大,我们不得不考虑它的存储位置,是将它们保存在 ** 内存memory ** (并不是永久存储)中, 还是 ** 存储storage ** (保存状态变量的地方)中。

    数据位置

    所有的复杂类型,即 数组 和 结构 类型,都有一个额外属性,“数据位置”,说明数据是保存在 内存memory 中还是 存储storage 中。 根据上下文不同,大多数时候数据有默认的位置,但也可以通过在类型名后增加关键字 storage 或 memory 进行修改。 函数参数(包括返回的参数)的数据位置默认是 memory, 局部变量的数据位置默认是 storage,状态变量的数据位置强制是 storage (这是显而易见的)。

    也存在第三种数据位置, calldata ,这是一块只读的,且不会永久存储的位置,用来存储函数参数。 外部函数的参数(非返回参数)的数据位置被强制指定为 calldata ,效果跟 memory 差不多。

    数据位置的指定非常重要,因为它们影响着赋值行为: 在 存储storage 和 内存memory 之间两两赋值,或者 存储storage 向状态变量(甚至是从其它状态变量)赋值都会创建一份独立的拷贝。

    然而状态变量向局部变量赋值时仅仅传递一个引用,而且这个引用总是指向状态变量,因此后者改变的同时前者也会发生改变。

    另一方面,从一个 内存memory 存储的引用类型向另一个 内存memory 存储的引用类型赋值并不会创建拷贝。

    数组

    数组可以在声明时指定长度,也可以动态调整大小。 对于 存储storage 的数组来说,元素类型可以是任意的(即元素也可以是数组类型,映射类型或者结构体)。 对于 内存memory 的数组来说,元素类型不能是映射类型,如果作为 public 函数的参数,它只能是 ABI 类型。

    创建内存数组

    可使用 new 关键字在内存中创建变长数组。 与 存储storage 数组相反的是,你 不能 通过修改成员变量 .length 改变 内存memory 数组的大小。

    数组字面常数 / 内联数组

    数组字面常数是写作表达式形式的数组,并且不会立即赋值给变量。

    pragma solidity ^0.4.16;
    
    contract C {
        function f() public pure {
            g([uint(1), 2, 3]);
        }
        function g(uint[3] _data) public pure {
            // ...
        }
    }
    

    数组字面常数是一种定长的 内存memory 数组类型,它的基础类型由其中元素的普通类型决定。 例如,[1, 2, 3] 的类型是 uint8[3] memory,因为其中的每个字面常数的类型都是 uint8。 正因为如此,有必要将上面这个例子中的第一个元素转换成 uint 类型。 目前需要注意的是,定长的 内存memory 数组并不能赋值给变长的 内存memory 数组

    结构体

    Solidity 支持通过构造结构体的形式定义新的类型,以下是一个结构体使用的示例:

     

    映射

    映射类型在声明时的形式为 mapping(_KeyType => _ValueType)。 其中 _KeyType 可以是除了映射、变长数组、合约、枚举以及结构体以外的几乎所有类型。 _ValueType 可以是包括映射类型在内的任何类型。

    映射可以视作 哈希表 <https://en.wikipedia.org/wiki/Hash_table>,它们在实际的初始化过程中创建每个可能的 key, 并将其映射到字节形式全是零的值:一个类型的 默认值。然而下面是映射与哈希表不同的地方: 在映射中,实际上并不存储 key,而是存储它的 keccak256 哈希值,从而便于查询实际的值。

    正因为如此,映射是没有长度的,也没有 key 的集合或 value 的集合的概念。

    只有状态变量(或者在 internal 函数中的对于存储变量的引用)可以使用映射类型。。

    可以将映射声明为 public,然后来让 Solidity 创建一个 getter。 _KeyType 将成为 getter 的必须参数,并且 getter 会返回 _ValueType

    _ValueType 也可以是一个映射。这时在使用 getter 时将将需要递归地传入每个 _KeyType 参数。

     

    删除

    delete a 的结果是将 a 的类型在初始化时的值赋值给 a。即对于整型变量来说,相当于 0, 但 delete 也适用于数组,对于动态数组来说,是将数组的长度设为 0,而对于静态数组来说,是将数组中的所有元素重置。 如果对象是结构体,则将结构体中的所有属性重置。

    delete 对整个映射是无效的(因为映射的键可以是任意的,通常也是未知的)。 因此在你删除一个结构体时,结果将重置所有的非映射属性,这个过程是递归进行的,除非它们是映射。 然而,单个的键及其映射的值是可以被删除的。

    理解 delete a 的效果就像是给 a 赋值很重要,换句话说,这相当于在 a 中存储了一个新的对象。

     

    基本类型之间的转换

    隐式转换

    如果一个运算符用在两个不同类型的变量之间,那么编译器将隐式地将其中一个类型转换为另一个类型(不同类型之间的赋值也是一样)。 一般来说,只要值类型之间的转换在语义上行得通,而且转换的过程中没有信息丢失,那么隐式转换基本都是可以实现的: uint8 可以转换成 uint16int128 转换成 int256,但 int8 不能转换成 uint256 (因为 uint256 不能涵盖某些值,例如,-1)。 更进一步来说,无符号整型可以转换成跟它大小相等或更大的字节类型,但反之不能。 任何可以转换成 uint160 的类型都可以转换成 address 类型。

    显式转换

    如果某些情况下编译器不支持隐式转换,但是你很清楚你要做什么,这种情况可以考虑显式转换。 注意这可能会发生一些无法预料的后果,因此一定要进行测试,确保结果是你想要的! 下面的示例是将一个 int8 类型的负数转换成 uint

    int8 y = -3;
    uint x = uint(y);
    

    这段代码的最后,x 的值将是 0xfffff..fd (64 个 16 进制字符),因为这是 -3 的 256 位补码形式。

    如果一个类型显式转换成更小的类型,相应的高位将被舍弃

    uint32 a = 0x12345678;
    uint16 b = uint16(a); // 此时 b 的值是 0x5678
  • 相关阅读:
    2013年10月17日 搬出来了
    如何与领导相处
    WEB系统开发
    C++ 常用术语(后续补充)
    C++ 构造函数放置默认转换explicit关键字(2)
    工作与生活
    C++类型转化分析(1)
    (一)win7下cocos2d-x 21 + vs2010
    为了生活
    iOS
  • 原文地址:https://www.cnblogs.com/zccst/p/14766300.html
Copyright © 2020-2023  润新知