在以太坊区块链上,智能合约的编写离不开对数据类型的精确理解和运用,以太坊数据类型是 Solidity 编程语言(以太坊最主流的智能合约开发语言)的基础,它们定义了变量可以存储的数据种类、占用的内存或存储空间以及可执行的操作,掌握这些数据类型,对于编写安全、高效且符合逻辑的智能合约至关重要,本文将详细介绍以太坊(Solidity)中的主要数据类型。

以太坊的数据类型主要可以分为两大类:值类型(Value Types)引用类型(Reference Types)

值类型 (Value Types)

值类型的变量在赋值或传递给函数参数时,总是被复制一份,这意味着对副本的修改不会影响原始变量。

  1. 布尔型 (bool)

    • 描述:最基本的数据类型,取值只能是 truefalse
    • 操作:支持 (逻辑非)、&& (逻辑与)、 (逻辑或)、 (等于)、 (不等于) 等运算。
    • 示例bool public isActive = true;
  2. 整数型 (Integers)

    • 描述:用于表示整数,分为有符号整数 (int) 和无符号整数 (uint)。
    • 大小int8int256(8位到256位,有符号),uint8uint256(8位到256位,无符号),默认是 int256uint256
    • 操作:支持所有常见的算术运算(, , , , %, **, <<, >>)和比较运算(, , <, >, <=, >=)。
    • 注意:Solidity 中的整数运算不会溢出/下溢(在 0.8.0 版本之后默认开启溢出检查,之前的版本需要使用 SafeMath 库)。
    • 示例uint256 public supply = 1000000000000000000; // 1e18
  3. 地址型 (address)

    • 描述:存储一个 20 字节的以太坊地址,可以是普通地址或合约地址。
    • 成员
      • balance:获取地址的以太币余额(单位是 wei)。
      • transfer(uint amount):向地址发送指定数量的以太币(会抛出异常,消耗固定 2300 gas)。
      • send(uint amount):向地址发送指定数量的以太币(返回 bool,可能失败,消耗最少 2300 gas)。
      • call(bytes memory data):低级调用,可以发送数据和以太币,返回 (bool, bytes memory),消耗 gas 不固定。
      • delegatecall(bytes memory data):低级委托调用,用于调用另一个合约的代码但使用当前合约的存储上下文。
      • staticcall(bytes memory data):低级静态调用,确保不会修改状态变量。
    • 示例address public owner = msg.sender;
  4. 定长字节数组 (Fixed-size Byte Arrays)

    • 描述:用于存储固定长度的字节序列,长度从 bytes1bytes32
    • 操作:支持位运算(&, , ^)、长度访问(.length)、索引访问(如 bytesData[0])以及类型转换。
    • 示例bytes32 public constant HASH = keccak256("abc");
  5. 枚举 (Enum)

    • 描述:用户定义的类型,由一组命名常量组成,默认情况下,枚举中的第一个映射到 0,第二个映射到 1,依此类推。
    • 用途:提高代码可读性,例如表示状态、选项等。
    • 示例
      enum State { Pending, Shipped, Delivered }
      State public currentState;
  6. 函数类型 (Function Types)

    • 描述:表示函数,函数类型有可见性(public, private, internal, external)、状态可变性(pure, view, payable)以及是否返回值等特性。
    • 注意:函数名称是函数签名的一部分,但函数类型本身不包括名称。
    • 示例function add(uint a, uint b) public pure returns (uint) { return a b; }

引用类型 (Reference Types)

引用类型的变量存储的是数据的内存地址或位置,赋值或传递时传递的是引用,对引用的修改会影响原始数据,使用引用类型时需要特别注意数据位置(Data Location)storage(合约的持久化存储,昂贵)、memory(函数执行时的内存,临时且便宜)、calldata(函数参数的数据,只读,便宜)。

  1. 数组 (Arrays)

    • 描述:存储一系列相同类型的元素,可以是固定大小数组或动态大小数组。
    • 数据位置:可以是 storagememory(或 calldata,用于函数参数)。
    • 成员.length(获取/设置长度,动态数组可设置)、.push()(动态数组添加元素)、.pop()(动态数组移除最后一个元素)。
    • 示例
      uint[] public dynamicArray; // 动态数组,默认 storage
      uint[5] public fixedArray;  // 固定大小数组
      function addToMemory() public pure {
          uint[] memory memoryArray = new uint[](3); // memory 数组
          memoryArray[0] = 1;
      }
  2. 结构体 (Structs)

    • 描述:用户自定义的数据类型,允许将不同类型的数据组合在一起,结构体可以包含值类型和其他结构体(但不能包含自身,但可以包含指向自身的引用)。
    • 数据位置:通常是 storage,也可以是 memory
    • 示例
      struct User {
          uint id;
          string name;
          bool isActive;
      }
      User public user = User(1, "Alice", true);
  3. 映射 (Mappings)

    • 描述:一种键值对(dictionary/hash map)的数据结构,键(key)的类型可以是任何基本类型(除映射外,包括枚举和地址),值(value)的类型可以是任何类型,包括映射(形成嵌套映射)。
    • 数据位置:只能是 storage,不能用于 memorycalldata
    • 注意:映射没有长度概念,也不能直接迭代其键值对(需要额外设计数据结构如数组来记录键)。
    • 示例
      mapping(address => uint) public balances; // 地址到余额的映射
      mapping(uint => string) public idToName;  // ID 到名称的映射

特殊类型和关键字

  1. 字符串 (string)

    • 描述:用于存储 UTF-8 编码的字符串,本质上是一个动态字节数组 (bytes),但提供了更方便的字符串操作。
    • 数据位置:可以是 storagememory
    • 示例string public greeting = "Hello, Ethereum!";
  2. 地址的成员 (address payable)

    • 描述address payableaddress 的子类型,增加了 transfersend 方法(虽然 address 也有,但 payable 更明确表示可以接收以太币),普通地址可以通过 .call() 接收以太币。
  3. 修饰符 (Modifiers)

    • 虽然不是严格的数据类型,但常与数据类型配合使用,如 onlyOwner 检查 msg.sender 是否为特定地址。viewpure 也是修饰符,用于声明函数不修改状态或读取状态。