在以太坊乃至整個(gè)編程世界中,“空”(Null)是一個(gè)基礎(chǔ)但又至關(guān)重要的概念,對(duì)于剛接觸區(qū)塊鏈的開(kāi)發(fā)者或用戶來(lái)說(shuō),這個(gè)詞可能會(huì)帶來(lái)一些困惑,它究竟代表著什么?為什么在以太坊的智能合約和交易中,我們需要頻繁地與它打交道?本文將用通俗易懂的方式,為您徹底揭開(kāi)以太坊中“空”的神秘面紗。

“空”的通俗理解:一個(gè)“空盒子”

想象一下,你有一個(gè)盒子,這個(gè)盒子可以裝東西,比如一個(gè)蘋(píng)果、一本書(shū),或者一個(gè)U盤(pán),但在某些情況下,這個(gè)盒子也可能是空的,我們可以說(shuō)這個(gè)盒子“里面什么都沒(méi)有”。

在編程中,“空”(Null)就類似于這個(gè)“里面什么都沒(méi)有”的空盒子,它是一個(gè)特殊的值,用來(lái)明確地表示“不存在”、“無(wú)值”或“未定義”,它不是一個(gè)數(shù)字(比如0),也不是一個(gè)空字符串(,而是一個(gè)純粹的“無(wú)”的狀態(tài)。

關(guān)鍵區(qū)別:

  • 0 (數(shù)字零): 它是一個(gè)有效的數(shù)值,代表“沒(méi)有數(shù)量”。
  • (空字符串):** 它是一個(gè)有效的字符串,只是長(zhǎng)度為0。
  • false (布爾值假): 它是一個(gè)有效的布爾狀態(tài),代表“不成立”。
  • null (空): 它代表“這個(gè)值目前不存在”。

在以太坊智能合約中,“空”無(wú)處不在

以太坊的智能合約本質(zhì)上是在以太坊虛擬機(jī)上運(yùn)行的程序,它也遵循編程的基本規(guī)則,“空”在合約的編寫(xiě)和執(zhí)行中扮演著重要角色,以下是幾個(gè)最常見(jiàn)的應(yīng)用場(chǎng)景:

未初始化的狀態(tài)變量

在Solidity(以太坊最主流的智能合約語(yǔ)言)中,當(dāng)你聲明一個(gè)狀態(tài)變量但沒(méi)有給它賦予初始值時(shí),它會(huì)被自動(dòng)初始化為“空”。

pragma solidity ^0.8.0;
contract MyContract {
    address public owner; // 這個(gè)變量在創(chuàng)建合約時(shí)默認(rèn)為 "空"
    uint256 public myNumber; // 這個(gè)變量也默認(rèn)為 "空"
}

當(dāng)合約被部署后,owner變量的值就是address(0),這是以太坊中代表“空地址”的特殊地址。myNumber的值則是uint256類型的“零”,雖然數(shù)值上是0,但在概念上,它代表的是“尚未被賦值”的狀態(tài)。

函數(shù)的返回值

函數(shù)可以返回“空”來(lái)表示某個(gè)操作沒(méi)有找到結(jié)果或未成功。

function findUser(address _userAddress) public view returns (string memory) {
    // 假設(shè)我們?cè)谝粋€(gè)數(shù)組中查找用戶名
    for (uint i = 0; i < userAddresses.length; i++) {
        if (userAddresses[i] == _userAddress) {
            return userNames[i]; // 找到則返回用戶名
        }
    }
    // 如果循環(huán)結(jié)束仍未找到,則返回 "空"
    return null;
}

在上面的例子中,如果找不到對(duì)應(yīng)的用戶,函數(shù)就會(huì)返回null,調(diào)用者就知道這個(gè)用戶不存在。

函數(shù)的輸入?yún)?shù)

調(diào)用函數(shù)時(shí),如果某個(gè)參數(shù)是可選的,你可以不傳值,或者顯式地傳入null。

function updateProfile(string memory _newName, address _newReferrer) public {
    // ...
}
// 調(diào)用方式1:不推薦,因?yàn)镾olidity要求參數(shù)必須匹配
// updateProfile("Alice", null); // 在Solidity中直接寫(xiě)null會(huì)報(bào)錯(cuò)
// 調(diào)用方式2:使用Solidity的特殊關(guān)鍵字 "address(0)" 來(lái)表示空地址
updateProfile("Alice", address(0)); // 表示推薦人不存在

注意:在Solidity中,你不能直接寫(xiě)null,對(duì)于地址類型,使用address(0)(全零地址)來(lái)表示“空”;對(duì)于其他類型,則使用type(X).init,例如uint256(0)。

事件中的空值

在觸發(fā)事件時(shí),某些字段可能暫時(shí)沒(méi)有值,此時(shí)可以設(shè)置為“空”。

event LogPayment(address from, address to, uint256 amount, string memo);
function pay(address _to, uint256 _amount) public {
    emit LogPayment(msg.sender, _to, _amount, ""); // 備注信息為空
}

與“空”相關(guān)的核心概念:address(0)

在以太坊的世界里,有一個(gè)“空”的概念尤其重要,那就是address(0),即全零地址 (0x0000000000000000000000000000000000000000)。

它被廣泛用作“空地址”或“無(wú)效地址”的占位符,常見(jiàn)于以下場(chǎng)景:

  • 初始所有者: 很多合約在部署時(shí),將owner變量設(shè)置為address(0),等待后續(xù)的初始化邏輯(如所有權(quán)初始化函數(shù))來(lái)設(shè)置真正的所有者。
  • 取消操作: 在某些投票或委托機(jī)制中,將代表票數(shù)或委托關(guān)系的地址設(shè)置為address(0),可以表示“取消投票”或“取消委托”。
  • 安全漏洞: address(0)也是一個(gè)著名的安全風(fēng)險(xiǎn),如果合約錯(cuò)誤地向address(0)發(fā)送以太幣(在退款邏輯中),這些ETH將永遠(yuǎn)無(wú)法被找回,相當(dāng)于“燃燒”掉了,同樣,如果將address(0)誤當(dāng)作一個(gè)有效的地址來(lái)調(diào)用其函數(shù),交易會(huì)失敗。

處理“空”的重要性:requirechecks-effects-interactions 模式

與“空”打交道時(shí),必須進(jìn)行嚴(yán)格的檢查,否則可能導(dǎo)致嚴(yán)重錯(cuò)誤,最常用的工具就是require語(yǔ)句。

function withdraw() public {
    // 檢查調(diào)用者是否是所有者,并且所有者地址不是 "空"
    // require(msg.sender == owner, "You are not the owner!");
    // 更嚴(yán)格的檢查:
    require(msg.sender != address(0), "Invalid address: cannot be zero address");
    require(msg.sender == owner, "You are not the owner!");
    // 執(zhí)行轉(zhuǎn)賬邏輯...
}

通過(guò)require,我們可以在程序執(zhí)行早期就攔截掉無(wú)效的“空”值,防止后續(xù)代碼出錯(cuò)。

遵循checks-effects-interactions模式(檢查-效果-交互)是編寫(xiě)安全合約的關(guān)鍵原則,其中一個(gè)重要方面就是:在與其他合約進(jìn)行交互(尤其是調(diào)用外部地址)之前,確保所有狀態(tài)變量的更新都已經(jīng)完成隨機(jī)配圖