【web3】04. Solidity 数据类型(一、值类型)

824 阅读2分钟

在 Solidity 语言中,数据类型分为值类型和引用类型。

其中值类型包括:

  • Boolean 类型(bool)
  • int 相关类型,如 int8 / int16 / ... / int256 或者 uint8 / uint16 / ... / uint256
  • 定点数类型,如 fixed8x8 表示 8 位整数和 8 位小数的定点数,如 12345678.87654321
  • 字面量,如字符串字面量、整形字面量等
  • 枚举类型
  • 固定大小字节数组,比如 bytes1 表示 1 字节的数组
  • 地址类型 address
  • 函数类型
  • contract 类型

引用类型包括:

  • 映射 mapping 类型
  • 结构体 struct 类型
  • 字符串 string 类型
  • 数组类型
  • 动态字节数组类型 bytes

本篇主要介绍值类型。

1. bool 类型

示例:

bool private var;

其使用方法、支持的操作运算符等跟 Java 一样,不能将整型当做 bool 型判断。

2. int 类型

示例:

int8 private var1;
uint32 private var2;
int private var3;

其中,int 是有符号整数,uint 表示无符号整数。int/uint 分别表示 int256 和 uint256,与其他语言不同的是,Solidity 的 int 类型后面可以跟占用位数,比如 uint8 表示 8 位,占 1 字节,uint256 表示一个占用 32 字节的无符号整型。

整型支持的运算符:常见的比如 +、-、*、/、%;位运算 &、|、^、~、<<、>>;比较运算符 >、>=、<、<= 等。

比较特殊的,Solidity 支持幂指数操作:**

比如,5 的 3 次方为:5**3

2.1. int 整除问题

我们看下面一个合约:

 pragma solidity ^0.8.0;

 contract Hello {
     uint8 private a;
     uint8 private b;

     function test1(uint8 _a, uint8 _b) public returns (uint8 _c) {
         a = _a;
         b = _b;
     }

     function test2(uint8 _mod) public view returns (uint8 _c) {
         _c = a/_mod + b/_mod;
     }

     function test3() public pure returns (uint8 _c) {
         _c = 5/2 + 5/2;
     }

     function test4() public pure returns (uint8 _c) {
         uint8 x = 5;
         uint8 y = 5;
         _c = x/2 + y/2;
     }
 }

在我们调用 test1 将 a 和 b 都设置为 5 以后,得到了如下结果:

image.png

可以看到,在 test2 里面将 )_mod 设置为 2,得到结果为 4,test3 里面返回结果为 5,test4 里面返回结果也是 4.

为什么 test3 里面的返回结果会是 5?因为在 test3 方法中,5/2 是整型的「字面量表达式」,而不是整型变量,这个要注意。针对字面量表达式除法不做截断,只有在赋值的时候才截断,而变量除法会截断。所以 test3 返回结果为 5.

请自行实验以下函数会怎么执行:

function test5() public pure returns (uint8 _c) {
    _c = 5/2;
}

实际上,上面的方法会编译报错,原因请自行查阅。

2.2. 整型怎么取最大值

参考如下代码:

_c = type(uint8).max; // 取 uint8 的最大值,即 255

3. fixed 定点数类型

Solidity 的定点数还没实现,暂时不需要关心。

4. 枚举类型

使用方法等与 Java 类似,比如:

enum Level {A,B,C}

function test5() public pure returns (Level _l) {
    return Level.A;
}

运行结果:

image.png

可以看到,枚举类型实际上就是 uint8 类型。

5. 固定大小字节数组

一般情况下,我们理解的数组是引用类型,而 Solidity 中固定大小的字节数组是值类型。

固定大小字节数组顾名思义,就是字节数组,并且大小是固定的,比如:bytes1 / bytes2 / ... / bytes32,最大到 32 个字节,使用方法比如:

bytes1 private var1 = 255; // 用 uint 赋值

看下面程序:

 contract Hello {
     bytes1 private var1 = "A";

     constructor() {
         var1 = "B";
     }

     function test1() public view returns(bytes1 _c) {
         _c = var1;
     }
 }

此时方法 test1 输出 0x42,十六进制的 0x42 正好对应 ASCII 码表的 B。

固定大小字节数组的赋值可以通过上述字符串的方式赋值,此时每个字节对应一个字符,也可以通过十六进制来赋值,比如:

bytes2 private var2 = 0x4242; // 表示字符串 BB

其支持的运算符与数字一致,除了数字的运算符外,还支持索引访问,参考如下例子:

contract Hello {
    bytes5 private var1 = 0x1a1b1c1d1f;

    function test1() public view returns(bytes1 _c) {
        _c = var1[3];
    }
}

此时,test1 方法输出了 0x1d,注意,下标访问只能访问数据而不能修改数据,比如:

var1[3] = 0x1f; // 这是不合法的,会报错

固定大小字节数组可以通过length属性获取其长度,比如:

bytes5 a = 0x01;
uint8 b = a.length; // b 的值为 5

固定大小字节数组可以与string / uint等之间互相转换,不同长度的固定大小字节数组强制赋值也会造成截断等,可以自行尝试,比如:

bytes3 a = 0x1a1b1c;
bytes2 b = bytes2(a); // b=0x1a1b

6. 地址类型

地址类型address也是值类型,比如:

address a = 0x40d534fb053DEABe53697e2224Ce2E8ebF070bb2;

地址指的就是前面介绍过的区块链地址,比如在以太坊中可能是以太坊的合约地址、账户地址。地址类型实际上是一个 20 字节大小的以太坊地址,地址类型具有很多的成员变量,参考以下示例:

pragma solidity ^0.8.0;

contract Hello {
    function testAddress() public view returns (uint256) {
        address addr = 0xDb09923Aa60F7bbB4Ce7850aBdF40b3A6b938cdF; // 声明一个地址
        bytes code = addr.code; // 该地址的代码(比如智能合约的代码,上面地址如果是合约地址的时候会返回)
        bytes32 codehash = addr.codehash; // 该地址的代码 hash
        return addr.balance; // 该地址的余额
    }
}

输出结果:

image.png

可以看出,地址的余额被返回(单位:Wei,1 ether = 10^18 wei)。

除了address类型以外,还有一个address payable类型,该类型与地址类型相似(可以理解为权限范围大一点的address类型),不同的是,address payable类型可以用于支付、转账等,而普通的address类型却不行,请看下面示例:

pragma solidity ^0.8.0;

contract Hello {

    // 如果合约要接收以太币,则构造方法必须添加 payable
    constructor() payable {
    }

    // 该方法为固定写法,用于接收以太币
    receive() external payable {
    }

    function sendTo(address payable addr) public payable {
        addr.transfer(1 ether); // 部署的这个合约向地址 addr 发送 1 个以太币
    }
}

合约部署的时候,从某个账户转账 10 ether 到合约账户:

image.png

部署完成后,再执行 sendTo 方法,填入想转到的以太坊地址,即可将 1 以太币转到该账户,多次点击,当合约地址的以太币用完后就不能再执行转账了。

address payable类型还有个成员变量,即send方法,与transfer区别如下:

  • transfer 方法如果因为 gas 不足等原因导致发送失败,则会终止合约执行;
  • send 方法发送失败不会终止合约执行,而是会返回 false。

地址类型除了上述的特性以外,address payable类型可以转成address使用,反之则不行。address可以进行强制类型转换,或者作为方法使用,比如:

address contractAddress = address(this); // 当前部署的合约地址
uint256 b = contractAddress.balance; // 当前合约余额

除了上述成员变量外,地址类型还提供call,delegatecallstaticcall供底层调用,这部分不常用也不建议使用,想使用可自行了解。

地址类型可以使用诸如大于、小于、等于这些比较运算符,最常用的是==!=,用来比较地址。

7. 合约类型

合约类型,顾名思义就是合约,类比 Java 的类类型,实际上在以太坊上,所谓合约、地址、账户等,都是地址类型的变种,有点类似于 Java 的「引用」,只不过这里是对地址的引用,合约、地址等都是一串 hash 值。

合约类型可以转换为address类型,比如:address(x)就是将合约类型 x 转换为地址类型,如果该合约内有receive方法或者payable的回调等方法的话,address(x)实际上是转换成了address payable类型,否则就还是转换成了address类型,当然也可以强制转换为address payable类型:payable(address(x))

关于合约类型的详细说明,后面再讲。

8. 函数类型

关于函数类型,后面有需要再讲解。