Solidity 数据类型

2022-12-28 Web3 Solidity

# 一、一些 Tips

# 1.1 区分大小写

Solidity 语言是大小写敏感的,下面是两个不同合约的定义。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Test {

}

contract TEST {

}
1
2
3
4
5
6
7
8
9
10

# 1.2 注释

Solidity 有两种类型的注释:

  • 单行注释以 // 开头。
  • 多行注释以 /* 开头,以 */ 结束。

# 1.3 注释注解

  • @dev:声明合约或函数。
  • @param:声明函数入参。
  • @return:声明函数返回类型。

# 1.4 代码块

代码块表示一系列应该按顺序执行的语句,这些语句被封装在 {} 之间。

# 1.5 声明变量

Solidity 一行只能声明一个变量,不支持声明多个变量。

# 1.6 命名变量

  • 必须以字母、_$ 开头;
  • 除第一个字符,其他字符可以是字母、_$ 或数字。

# 1.7 声明变量是必须的

Solidity 要求必须为每个变量声明类型(包括 var 类型),否则会报错。

# 二、简单类型

# 2.1 字符串类型

String

  • 字符串字面量是指由 '" 包围起来的字符串。字符串不同于 C 语言,是不包含结束符的。
  • 字符串类型在 Solidity 语法中支持 bytes、bytesN、string 三种类型。
  • String 是一个动态的 UTF-8 编码字符串,属于引用类型。
  • String 字符串没有提供获取字符串长度、索引取值、push 追加字节的方法。这里有一个解决方案是将 string 转换为 bytes 来进行相关操作。

Bytes

  • bytes(name) 可以将字符串 name 转换为 bytes 类型。
  • bytes 类型可以直接使用获取长度、索引取值赋值、push 追加等操作,如 bytes(name).length
  • string 和 bytes 类型之间可以直接进行类型转换。
  • 对于变长类型的字符串,不支持一般的比较运算符。如需比较,需要遍历单个 bytes 进行比较。

# 2.2 整数类型

整数类型分为有符号或无符号整型。变量支持的步长以 8 递增,支持从 uint8 到 uint256,以及 int8 到 int256,uint 和 int 默认的是 uint256 和 int256。

  • uint8 的存储范围为 02810\sim 2^8-1
  • int8 的存储范围为 27271-2^7\sim 2^7-1

Solidity 支持以下运算符:

  • 比较:<=<==!=>=>,返回值为 bool 类型。
  • 位运算符:& 与,| 或,^ 异或,~ 非。
  • 数学运算:+-*/% 取余,** 乘方,<< 左移(乘 2k2^k),>> 右移(除以 2k2^k)。

Solidity 目前没有支持 double 或 float,如果 7 / 2 会得到 3,即无条件舍去。除以 0 会抛出异常。

# 2.3 布尔类型

  • 布尔类型的取值为常量值 true 和 false。
  • 支持的运算符包括:!&&||==!=

# 2.4 地址类型

  • 以太坊中的地址长度为 20 字节,一共 160 位,因此 address 类型也可以用 uint160 来声明。
  • address 类型支持比较运算符:<=<==!=>=>
  • address 包含成员变量 balance 以及成员方法 send()

# 2.5 枚举类型

枚举类型是在 Solidity 中的一种用户自定义类型,其中至少要有一个元素,如下所示。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Enum {
    // 声明枚举类型
    enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }
    FreshJuiceSize choice;
    FreshJuiceSize constant defaultChoice = FreshJuiceSize.MEDIUM;
    
    // 通过 . 来访问
    function setLarge() public {
        choice = FreshJuiceSize.LARGE;
    }
    
    function getChoice() public view returns (FreshJuiceSize) {
        return choice;
    }
    
    // 可以显示的与整数进行转换,但不能隐式转换
    function getDefaultChoice() public pure returns (uint) {
        return uint(defaultChoice);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 三、复杂类型

# 3.1 结构体类型

  • struct 就是一个实体,包含了特定实体的属性。
  • struct 可以作为映射和数组中的元素,本身也可以包含映射和数组等类型。
  • 不能在声明一个 struct 的同时将这个 struct 作为自身的一个成员,因为数据结构的大小必须有限。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Struct {
    // 结构体定义
    struct Book { 
        string title;
        string author;
        uint bookId;
        
        // 编译错误,不允许递归定义
        // Book book;
    }
    Book book1;
    Book book2;
    
    // 两种结构体初始化方法
    function setBook() public {
        // 默认初始化 Storage 中的状态变量
        book1 = Book("Learn Solidity", "xyz", 1);
        book2 = Book({title:"Learn Solidity", author:"xyz", bookId:1});

        // 指定初始化 memory 中的状态变量
        Book memory book3 = Book("Learn Solidity", "xyz", 1);
        Book memory book4 = Book({title:"Learn Solidity", author:"xyz", bookId:1});
    }
    
    // 通过 . 来访问或修改结构体中的属性
    function getBookId() public view returns (uint) {
        return book1.bookId;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

# 3.3 数组类型

  • 数组可以在声明时指定长度,或者是变长的。
  • 定长数组不能与变长数组相互赋值。
  • 对于 Storage 的数组来说,元素类型可以是任意的;但对于 Memory 的数组来说,元素类型不能是映射类型的。
  • 一个类型为 T,长度为 k 的数组,声明方式为 T[k],而一个变长的数组的声明方式为 T[]
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Array {
    // 数组初始化,两种方式初始化的数组相同
    uint[3] a = [1, 2, 3];

    // 可以省略数组长度
    uint[] b = [1, 2, 3];

    // 访问数组元素
    uint c = b[2];

    function test() public {
        // 变长数组通过 push 添加元素
        b.push(4);

        // 初始化 memory 数组
        // 数组的基本类型是列表上的第一个表达式的类型
        uint[3] memory d = [uint(1), 2, 3];
        uint[] memory e = new uint[](7);

        // 编译错误,memory 数组元素不能是映射类型
        // mapping(uint => string)[] memory f;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# 3.4 映射类型

映射类型,是一种键值对的映射关系存储结构。定义方式为 mapping(KeyType => ValueType),其中:

  • KeyType 可以是任何基本类型,即不包括映射、结构体以及数组(bytes 和 string 除外);
  • ValueType 可以是包括映射类型在内的任何类型。

映射是没有长度的,也没有 key 的集合或 value 的集合的概念。映射也没有排序的概念。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Mapping {
    mapping(address => uint) public balances;
    uint balance;

    function update(uint amount) public returns (uint) {
        // 设置 mapping 中的值
        balances[msg.sender] = amount;
        // 取出 mapping 中的值
        balance = balances[msg.sender];
        
        return balance;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 四、特殊变量

# 4.1 以太币单位

  • 数字后面可以加后缀,表示以太币单位,包括 weigweiether
  • 如果以太币数量后面没有单位,默认为 wei
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Eth {
    function test() public {
        assert(1 wei == 1);
        assert(1 gwei == 1e9);
        assert(1 ether == 1e18);
    }
}
1
2
3
4
5
6
7
8
9
10

# 4.2 时间单位

  • 数字后面可以加后缀,表示时间单位,包括 secondsminuteshoursdaysweeks
  • 如果时间数字后没有单位,默认为 seconds
  • now 是当前时间戳。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Time {
    function test() public {
        assert(1 == 1 seconds);
        assert(1 minutes == 60 seconds);
        assert(1 hours == 60 minutes);
        assert(1 days == 24 hours);
        assert(1 weeks == 7 days);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

这些后缀不能直接用在变量后边。如果想用时间单位(例如 days)来将输入变量换算为时间,可以用如下方式来完成:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract Time {
    function f(uint start, uint daysAfter) public {
        if (block.timestamp >= start + daysAfter * 1 days) {
            // ...
        }
    }
}
1
2
3
4
5
6
7
8
9
10

# 五、参考资料

Last Updated: 2023-01-28 4:31:25