Solidity 值类型详解
学习目标
- 掌握 Solidity 的整型、布尔、地址、定长字节数组、枚举等值类型
- 理解 EVM 256 位机器架构对类型设计的影响
- 掌握类型转换规则(隐式 vs 显式)
- 了解溢出问题的历史与解决方案
- 理解 Solidity 中所有类型的默认值机制
一、整型(int / uint)
1.1 基本概念
Solidity 提供有符号整型 int 和无符号整型 uint,从 8 位到 256 位,以 8 位为步长:
| 无符号 | 有符号 | 位宽 | 范围 |
|---|---|---|---|
uint8 |
int8 |
8 位 | 0~255 / -128~127 |
uint16 |
int16 |
16 位 | 0~65535 / -32768~32767 |
uint32 |
int32 |
32 位 | 0~4,294,967,295 |
| ... | ... | ... | ... |
uint256 |
int256 |
256 位 | 0~2^256-1 |
uint是uint256的别名int是int256的别名
1.2 为什么是 256 位?
EVM(以太坊虚拟机)是一台 256 位的栈式虚拟机,其内部寄存器和栈槽位宽度均为 256 位。因此 uint256 / int256 是 EVM 的原生操作类型,使用其他位宽的类型在某些情况下反而可能消耗更多 Gas(因为需要额外的掩码操作)。
1.3 溢出问题
整型溢出是智能合约安全的重大隐患:
低版本(< 0.8.0):取模行为
uint8 x = 255;
x = x + 1; // 结果为 0,发生溢出但不报错!
这种静默溢出导致了大量安全事故(如 2018 年的 BEC Token 漏洞)。
高版本(>= 0.8.0):自动检查,溢出抛异常
uint8 x = 255;
x = x + 1; // 交易 revert,自动检测到溢出
SafeMath 库(历史方案)
在 0.8.0 之前,开发者需要使用 OpenZeppelin 的 SafeMath 库来手动防止溢出。0.8.0 之后编译器内置了溢出检查,SafeMath 不再需要。
如果你确实需要溢出取模行为(极少数场景),可以使用
unchecked块:solidity
unchecked { x = x + 1; }
1.4 获取类型边界
使用 type(T).min 和 type(T).max 获取类型的最小值和最大值:
type(uint8).min // 0
type(uint8).max // 255
type(int8).min // -128
type(int8).max // 127
type(uint256).max // 2^256 - 1(一个极大的数)
1.5 类型转换
隐式转换(窄 → 宽):安全,自动进行
uint8 x = 8;
uint16 y = x; // OK:uint8 → uint16,范围变宽,不丢失数据
显式转换(强制):可能丢失数据,需手动声明
uint16 y = 256;
uint8 x = uint8(y); // 强制截断:x = 0(256 超出 uint8 范围)
1.6 完整示例
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract BasicType {
function testInt() public pure returns (uint) {
uint256 max = type(uint256).max;
uint8 x = 8;
uint16 y = 9;
y = x; // 隐式转换:y比x表达范围宽
x = uint8(y); // 显式强制类型转换
return max;
}
// 枚举类型
enum OrderState {
layorder,
payment
}
function enumTest() public pure returns (OrderState) {
OrderState state = OrderState.payment;
return state;
}
}
二、布尔类型(bool)
布尔类型只有两个值:true 和 false。
bool isActive = true;
bool isExpired = false;
虽然逻辑上只需要 1 位,但在 EVM 中布尔值实际占用 8 位(1 字节) 的存储空间。支持的运算符:
| 运算符 | 说明 |
|---|---|
! |
逻辑非 |
&& |
逻辑与(短路求值) |
\|\| |
逻辑或(短路求值) |
== |
等于 |
!= |
不等于 |
注意:Solidity 不支持将布尔值与整型互相转换(不像 C 语言中 0 为 false、非 0 为 true)。
三、地址类型(address)
3.1 基本概念
地址类型是 Solidity 中最重要的类型之一,长度为 20 字节(160 位):
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
3.2 address 与 address payable
Solidity 区分两种地址类型:
| 类型 | 说明 | 特有方法 |
|---|---|---|
address |
普通地址 | balance, code, codehash, call, delegatecall, staticcall |
address payable |
可支付地址 | 在 address 基础上增加 transfer 和 send |
address payable recipient = payable(0x123...);
recipient.transfer(1 ether); // 发送 ETH,失败自动 revert
bool success = recipient.send(1 ether); // 发送 ETH,失败返回 false
3.3 类型转换
地址类型可以与 uint160 和 bytes20 互相转换:
address addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
uint160 num = uint160(addr); // address → uint160
bytes20 data = bytes20(addr); // address → bytes20
address back = address(num); // uint160 → address
3.4 合约地址 vs EOA 地址
以太坊有两种账户类型,它们的地址格式完全相同:
| 类型 | 说明 | 判断方式 |
|---|---|---|
| EOA(外部账户) | 由私钥控制,可以发起交易 | addr.code.length == 0 |
| 合约账户 | 由代码控制,被交易触发执行 | addr.code.length > 0 |
3.5 常用成员
addr.balance // 获取地址的 ETH 余额(单位 wei)
addr.code // 获取地址的合约字节码
addr.call(data) // 底层调用,返回 (bool, bytes)
addr.delegatecall(data) // 委托调用
addr.staticcall(data) // 静态调用(只读)
3.6 示例
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.2 <0.9.0;
contract ComplexValueType {
function testAddress() public view returns (address) {
address addr = msg.sender; // 函数调用者的账号地址
return addr;
}
function testMyAddress() public view returns (address) {
address addr = address(this); // 当前合约地址
return addr;
}
// 合约类型
function testContract() public view {
ComplexValueType mycontract = this;
}
// 定长字节数组:只读不可写
function testFixedByteArray() public pure returns (bytes3) {
bytes3 data = 0x111111;
bytes1 first = data[0]; // 下标访问
return first;
}
}
四、合约类型(Contract Type)
4.1 基本概念
每个 contract 定义都会生成一个独立的数据类型。合约类型的变量本质上持有该合约的地址引用。
contract MyToken { ... }
// 在其他合约中
MyToken token = MyToken(0x123...); // 用地址实例化合约引用
token.transfer(to, amount); // 调用合约函数
4.2 核心特性
- 隐式转换为父合约(多态):如果
Child继承Parent,则Child类型可隐式转为Parent类型 - 显式转换为 address:
address(myContract)获取合约地址 - 不支持运算符:合约类型不能做加减比较等运算
- 用
new部署新合约:
contract Factory {
function createToken() public returns (MyToken) {
MyToken token = new MyToken(); // 部署新合约并返回引用
return token;
}
}
五、定长字节数组(bytes1 ~ bytes32)
5.1 基本概念
Solidity 提供从 bytes1 到 bytes32 共 32 种定长字节数组类型。
重要区分:定长字节数组(如
bytes32)是值类型,而动态字节数组(bytes)和一般数组是引用类型。
5.2 特性
| 特性 | 说明 |
|---|---|
| 下标访问 | data[0] 获取第一个字节,返回 bytes1 |
length 属性 |
返回字节数(编译时确定,不可变) |
| 只读不可写 | data[0] = 0xff 是非法的 |
5.3 应用场景
应将定长字节数组视为一个整体来使用,而不是当作可变数组操作:
bytes32 hash = keccak256(abi.encodePacked("hello")); // 哈希值
bytes4 selector = bytes4(keccak256("transfer(address,uint256)")); // 函数选择器
bytes20 addrBytes = bytes20(msg.sender); // 地址的字节表示
六、枚举类型(enum)
6.1 基本概念
枚举是 Solidity 中的用户自定义类型之一(另外两个是 contract 和 struct)。它用于定义一组有限的命名常量。
6.2 规则
- 最少 1 个成员,最多 256 个成员(因为底层用
uint8存储) - 默认值为第一个成员(序号 0)
- 与整型的转换必须用显式转换
enum Color { Red, Green, Blue }
uint8 idx = uint8(Color.Green); // 1
Color c = Color(0); // Color.Red
6.3 完整示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
contract Enum {
enum Status {
None, Pending, Shipped, Completed, Rejected, Canceled
}
Status public status;
struct Order {
address buyer;
Status status;
}
Order public orders;
function get() external view returns (Status) {
return status;
}
function set(Status _status) external {
status = _status;
}
function ship() external {
status = Status.Shipped;
}
function reset() external {
delete status; // 重置为默认值(第一个定义的值)
}
}
七、默认值
Solidity 中没有 undefined 或 null 的概念。所有变量在声明时如果未赋值,都会自动初始化为该类型的默认值:
| 类型 | 默认值 |
|---|---|
bool |
false |
uint / int |
0 |
address |
0x0000000000000000000000000000000000000000(零地址) |
bytes1 ~ bytes32 |
全零字节 |
enum |
第一个成员(序号 0) |
string |
空字符串 "" |
bytes(动态) |
空字节数组 |
使用
delete关键字可以将变量重置为默认值:solidity
uint x = 42;
delete x; // x 变回 0
八、综合示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
contract ValueTypes {
bool b = true;
uint public u = 123;
int public i = -123;
int public minInt = type(int).min;
int public maxInt = type(int).max;
address public addr = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
bytes32 public b32 = keccak256(abi.encodePacked("This is a test value"));
}
部署此合约后,你可以通过 Remix 直接点击各个 public 变量名来查看它们的值:
u→ 123i→ -123minInt→ -57896044618658097711785492504343953926634992332820282019728792003956564819968maxInt→ 57896044618658097711785492504343953926634992332820282019728792003956564819967addr→ 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4b32→ 一个 32 字节的 keccak256 哈希值
小结
本节我们详细学习了 Solidity 的值类型系统。值类型的核心特征是:赋值和传参时会完整拷贝一份数据(而非引用)。掌握这些基础类型是编写安全、高效智能合约的前提。下一节我们将学习引用类型(数组、结构体、映射)以及数据存储位置(storage / memory / calldata)。
主题测试文章,只做测试使用。发布者:Walker,转转请注明出处:https://walker-learn.xyz/archives/7488