# 《Solidity 教程》值類型

By [Samumu.eth](https://paragraph.com/@samumu) · 2022-03-01

---

Solidity 是一種靜態類型語言，這意味著每個變數（狀態變數和局部變數）都需要在編譯時指定變數的類型

Solidity 提供了幾種基本類型，並且基本類型可以用來組合出複雜類型。除此之外，類型之間可以在包含運算符號的表達式中進行交互， `undefined` 或 `null` 值的概念在Solidity中不存在，但是新聲明的變數總是有一個 [默認值](https://learnblockchain.cn/docs/solidity/control-structures.html#default-value)，具體的預設值跟類型相關。 要處理任何意外的值，應該使用 [錯誤處理](https://learnblockchain.cn/docs/solidity/control-structures.html#assert-and-require)來恢復整個交易，或者返回一個帶有第二個 `bool` 值的元組表示成功

類型可以說是所有合約都會使用到的基本概念，他非常簡單也經常被使用，各位讀者請確保自己牢記這些簡單的知識點

### 運算子

下列運算子是經常使用到的，每個的用法都必須搞清楚

*   `!`（邏輯非）
    
*   `&&`（邏輯與， "and" ）
    
*   `||`（邏輯或， "or" ）
    
*   比較運算子： `<=`，`<`，`==`，`!=`，`>`，`=>`（ 傳回布林值 ）
    
*   算數運算子： `+`， `-`， `*`， `/`， `%`（ 取餘或叫模運算 ） `**` （指數）
    
*   位運算子： `&`， `|` ，`^`（ 異或）`~` （ 位取反 ）
    
*   移位運算子： `<<`（左移位），`>>`（右移位）
    

而關於Solidity的運算溢出，可以參考此篇解析

*   解析 SafeMath 合約
    

### Booleans

`bool` : 可能的取值為字面常量值 `true` 和 `false`

### Integers

`int` / `uint` ：分別表示有符號和無符號的不同位數的整型變數。 支援關鍵字 `uint8`到 `uint256`，以 `8` 位為遞增

`uint` 和 `int` 分別表示 `uint256` 和 `int256`

`uint256` 有最大值 2^256-1 以及最小值 0

`int256` 有最大值 2^255-1 以及最小值 -2^255

### Function Types

函數類型是一種表示函數的類型。 可以將一個函數賦值給另一個函數類型的變數，也可以將一個函數作為參數進行傳遞，還能在函數調用中返回函數類型變數。 函數類型有兩類：

> 內部（internal） 函數類型外部（external） 函數類型

*   內部函數只能在當前合約內被調用，更具體來說，在當前合約內，包括內部庫函數和繼承的函數中
    
*   外部函數由一個位址和一個函數簽名組成，可以通過外部函數調用傳遞或者返回。
    

函數類型表示成如下的形式

    **function** (<parameter types>) {**internal**|**external**} [**pure**|**view**|**payable**] [**returns** (<**return** types>)]
    

與參數類型相反，返回類型不能為空 —— 如果函數類型不需要返回，則需要刪除整個 `returns (<return types>)` 部分。

**函數類型預設是內部函數，因此不需要聲明** `internal` 關鍵字。

💡 請注意，這僅適用於函數類型，合約中定義的函數明確指定可見性，它們沒有預設值。

如果當函數類型的變數還沒有初始化時就調用它的話會引發一個 [Panic 異常](https://learnblockchain.cn/docs/solidity/control-structures.html#assert-and-require)。 如果在一個函數被`delete`之後調用它也會發生相同的情況。

請注意，當前合約的 public 函數既可以被當作內部函數也可以被當作外部函數使用。 如果想將函式當作內部函數使用，就用`f`呼叫，如果想將其當作外部函數，使用 `this.f`

成員方法：

public（或 external）函數都有下面的成員：

*   `.address` - 返回函數的合約位址。
    
*   `.selector` - 返回 [ABI 函數選擇器](https://learnblockchain.cn/docs/solidity/abi-spec.html#abi-function-selector)
    

> View, Pure, Payable

*   View 函數是只讀函數\*\*\*，不會修改區塊鏈的狀態。\*\*\*換句話說，如果你想從區塊鏈中讀取數據，可以使用 View 函數
    
*   Pure 函數比 View 函數更具限制性，\*\*\*並且不修改狀態，也不讀取區塊鏈的狀態。\*\*\*換句話說，他是純粹幫助運算、執行其他函數的函數
    
*   Payable 函數的話請記住以下幾點
    
    *   在函數或狀態變數中使用payable來發送和接收乙太幣
        
    *   在狀態變數中包含payable，以便從合同中退出
        
    *   在構造函數中包含payable，以便在創建/部署合約時能夠存入合約
        
    *   在函數中包含payable，以允許將存款存入合同
        

> 內部函數使用例子

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    contract Azuki is Ownable, ERC721A, ReentrancyGuard {
        //...
        string private _baseTokenURI;
    
      function _baseURI() internal view virtual override returns (string memory) {
        return _baseTokenURI;
      }
        //...
    }
    

這個函數非常簡單就是將原本的函數 `_baseURI()` 從回傳空值覆寫成回傳 `_baseTokenURI` ，並且因為他被宣告為 `internal` ，所以理論上我們可以在合約找到這個函數被誰使用，在 `ERC721A.sol` 中:

    // SPDX-License-Identifier: MIT
    pragma solidity ^0.8.0;
    
    contract ERC721A is
      Context,
      ERC165,
      IERC721,
      IERC721Metadata,
      IERC721Enumerable
    {
        //...
        function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
      {
        require(
          _exists(tokenId),
          "ERC721Metadata: URI query for nonexistent token"
        );
    
        string memory baseURI = _baseURI();
        return
          bytes(baseURI).length > 0
            ? string(abi.encodePacked(baseURI, tokenId.toString()))
            : "";
      }
    
        function _baseURI() internal view virtual returns (string memory) {
        return "";
      }
        //...
    }
    

首先看到函數 `_baseURI()` 他確實在宣告時帶有 `internal` & `virtual` 兩個性質，也印證了上面的說法，接著，看到函數 `tokenURI()` 他會先確認 `tokenId` 是否存在，如果存在的話就會回傳 `tokenURI` （注意到他帶有 `view` 表示該函數不會修改區塊鏈的狀態），裡面用到的 `abi.encodePacked` 會在後面內容中解釋

> 外部函數使用例子

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    contract Azuki is Ownable, ERC721A, ReentrancyGuard {
        //...
        function allowlistMint() external payable callerIsUser {
        uint256 price = uint256(saleConfig.mintlistPrice);
        require(price != 0, "allowlist sale has not begun yet");
        require(allowlist[msg.sender] > 0, "not eligible for allowlist mint");
        require(totalSupply() + 1 <= collectionSize, "reached max supply");
        allowlist[msg.sender]--;
        _safeMint(msg.sender, 1);
        refundIfOver(price);
      }
        //...
    }
    

外部函數如上述定義所描述，通常不會是合約內部去使用它，而是提供外面的使用者跟合約互動的函數，以函數 `allowlistMint()` 為例，前面三個 `require` 先檢查了使用者使否有資格 mint，然後再去呼叫 `_safeMint()` 來讓使用者鑄造NFT，是很常見的白單公售NFT的函數（這個函數看他的功能就應該能知道他是 `payable` ，因為他需要添加新的鏈上狀態，紀錄這個使用者鑄造了一個NFT）

### Contract Types

每一個[contract](https://learnblockchain.cn/docs/solidity/contracts.html#contracts)定義都有他自己的類型

如果聲明一個合約類型的局部變數（`MyContract c`），則可以調用該合約的函數。

*   注意需要賦相同合約類型的值給它
    

合約特性：

*   可以實例化合約（即新創建一個合約物件），參考 [『使用new創建合約』](https://learnblockchain.cn/docs/solidity/control-structures.html#creating-contracts)
    
*   合約和`address`的數據表示是相同的， 參考 [ABI](https://learnblockchain.cn/docs/solidity/abi-spec.html#abi)
    
*   合約不支援任何運算符
    
*   合約類型的成員是合約的外部函數及 public 的 狀態變數
    
*   對於合約 `C`可以使用`type(C)`取得合約的類型資訊，參考 [類型資訊](https://learnblockchain.cn/docs/solidity/units-and-global-variables.html#meta-type)
    

### Address

位址類型有兩種形式，他們大致相同：

*   address：保存一個20位元組的值（乙太坊位址的大小）
    
*   address payable ：可支付位址，與 `address`相同，不過有成員函數 `transfer`和 `send`
    

**地址類型成員變數**

查看所有的成員，可參考 [地址成員](https://learnblockchain.cn/docs/solidity/units-and-global-variables.html#address-related)

> `balance` : 查詢一個地址的餘額

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    contract Azuki is Ownable, ERC721A, ReentrancyGuard {
        //...
        function withdrawMoney() external onlyOwner nonReentrant {
        (bool success, ) = msg.sender.call{value: address(this).balance}("");
        require(success, "Transfer failed.");
      }
        //...
    }
    

> `transfer` : 向一個可支付位址（payable address）發送乙太幣Ether （以 wei 為單位）

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    contract Azuki is Ownable, ERC721A, ReentrancyGuard {
        //...
        function refundIfOver(uint256 price) private {
        require(msg.value >= price, "Need to send more ETH.");
        if (msg.value > price) {
          payable(msg.sender).transfer(msg.value - price);
        }
      }
        //...
    }
    

💡 如果當前合約的餘額不夠多，則\`transfer\`函數會執行失敗，或者如果乙太轉移被接收帳戶拒絕， \`transfer\`函數同樣會失敗而進行回退

> `call` : 是與其他合約交互的低級函數，當你只是通過調用函數發送乙太幣時要使用的推薦方法，但是，這不是調用現有函數的推薦方法

    // SPDX-License-Identifier: MIT
    
    pragma solidity ^0.8.0;
    
    contract Azuki is Ownable, ERC721A, ReentrancyGuard {
        //...
        function withdrawMoney() external onlyOwner nonReentrant {
        (bool success, ) = msg.sender.call{value: address(this).balance}("");
        require(success, "Transfer failed.");
      }
        //...
    }
    

上述的函數 `withdrawMoney` 中 `msg.sender.call` 就是要領出合約中的ETH，不過他繼承自 `onlyOwner` 與 `nonReentrant` 所以想要呼叫這函數會有限制，之後再作詳盡的解釋

*   `delegatecall` : 使用起來並不容易，錯誤的用法或不正確的理解會導致毀滅性的結果，詳細使用方式可以[參考這篇](https://eip2535diamonds.substack.com/p/understanding-delegatecall-and-how?utm_source=url)
    

💡 不管是讀取狀態還是寫入狀態，最好避免在合約代碼中硬編碼使用的 gas 值。 這可能會引入"陷阱"，而且 gas 的消耗也是可能會改變的

### Fixed-size byte array

種類有以下幾種：`bytes1`， `bytes2`， ...`bytes32`

除了第一部分講到的通用運算子，他能進行索引訪問：

*   索引訪問：如果`x` 是`bytesI` 類型，那麼 `x[k]`（其中`0 <= k < I`）返回第`k`個字節（只讀）
    

成員變數：

*   `.length`
    
    表示這個位元組陣組的長度（只讀）.
    

### **Dynamically-sized byte array**

*   `bytes`: Dynamically-sized \*\*\*\*位元組陣列
    
*   `string`: Dynamically-sized UTF-8 編碼字串類型
    
*   以上可參閱 [陣列](https://learnblockchain.cn/docs/solidity/types.html#arrays) 他們不是值類型
    

### Enums

枚舉是在Solidity中創建使用者定義類型的一種方法。 它們是顯示所有整型相互轉換，但不允許隱式轉換。 從整型顯式轉換枚舉，會在運行時檢查整數時候在枚舉範圍內，否則會導致異常（ [Panic異常](https://learnblockchain.cn/docs/solidity/control-structures.html#assert-and-require) ）。 枚舉需要至少一個成員，預設值是第一個成員，枚舉不能多於256個成員。

    // SPDX-License-Identifier: GPL-3.0
    pragma solidity ^0.8.8;
    
    contract test {
        enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
        ActionChoices choice;
        ActionChoices constant defaultChoice = ActionChoices.GoStraight;
    
        function setGoStraight() public {
            choice = ActionChoices.GoStraight;
        }
    
        // Since enum types are not part of the ABI, the signature of "getChoice"
        // will automatically be changed to "getChoice() returns (uint8)"
        // for all matters external to Solidity.
        function getChoice() public view returns (ActionChoices) {
            return choice;
        }
    
        function getDefaultChoice() public pure returns (uint) {
            return uint(defaultChoice);
        }
    
        function getLargestValue() public pure returns (ActionChoices) {
                    //get max value in enums
            return type(ActionChoices).max;
        }
    
        function getSmallestValue() public pure returns (ActionChoices) {
                    //get max value in enums
            return type(ActionChoices).min;
        }
    }

---

*Originally published on [Samumu.eth](https://paragraph.com/@samumu/solidity-3)*
