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