# 《Solidity 教程》值類型 **Published by:** [Samumu.eth](https://paragraph.com/@samumu/) **Published on:** 2022-03-01 **URL:** https://paragraph.com/@samumu/solidity-3 ## Content Solidity 是一種靜態類型語言,這意味著每個變數(狀態變數和局部變數)都需要在編譯時指定變數的類型 Solidity 提供了幾種基本類型,並且基本類型可以用來組合出複雜類型。除此之外,類型之間可以在包含運算符號的表達式中進行交互, undefined 或 null 值的概念在Solidity中不存在,但是新聲明的變數總是有一個 默認值,具體的預設值跟類型相關。 要處理任何意外的值,應該使用 錯誤處理來恢復整個交易,或者返回一個帶有第二個 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 () 部分。 函數類型預設是內部函數,因此不需要聲明 internal 關鍵字。 💡 請注意,這僅適用於函數類型,合約中定義的函數明確指定可見性,它們沒有預設值。 如果當函數類型的變數還沒有初始化時就調用它的話會引發一個 Panic 異常。 如果在一個函數被delete之後調用它也會發生相同的情況。 請注意,當前合約的 public 函數既可以被當作內部函數也可以被當作外部函數使用。 如果想將函式當作內部函數使用,就用f呼叫,如果想將其當作外部函數,使用 this.f 成員方法: public(或 external)函數都有下面的成員: .address - 返回函數的合約位址。 .selector - 返回 ABI 函數選擇器 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定義都有他自己的類型 如果聲明一個合約類型的局部變數(MyContract c),則可以調用該合約的函數。 注意需要賦相同合約類型的值給它 合約特性: 可以實例化合約(即新創建一個合約物件),參考 『使用new創建合約』 合約和address的數據表示是相同的, 參考 ABI 合約不支援任何運算符 合約類型的成員是合約的外部函數及 public 的 狀態變數 對於合約 C可以使用type(C)取得合約的類型資訊,參考 類型資訊 Address 位址類型有兩種形式,他們大致相同: address:保存一個20位元組的值(乙太坊位址的大小) address payable :可支付位址,與 address相同,不過有成員函數 transfer和 send 地址類型成員變數 查看所有的成員,可參考 地址成員 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 : 使用起來並不容易,錯誤的用法或不正確的理解會導致毀滅性的結果,詳細使用方式可以參考這篇 💡 不管是讀取狀態還是寫入狀態,最好避免在合約代碼中硬編碼使用的 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 編碼字串類型 以上可參閱 陣列 他們不是值類型 Enums 枚舉是在Solidity中創建使用者定義類型的一種方法。 它們是顯示所有整型相互轉換,但不允許隱式轉換。 從整型顯式轉換枚舉,會在運行時檢查整數時候在枚舉範圍內,否則會導致異常( Panic異常 )。 枚舉需要至少一個成員,預設值是第一個成員,枚舉不能多於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; } } ## Publication Information - [Samumu.eth](https://paragraph.com/@samumu/): Publication homepage - [All Posts](https://paragraph.com/@samumu/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@samumu): Subscribe to updates - [Twitter](https://twitter.com/SamumuClan): Follow on Twitter