本文主要內容來自:https://solidity-cn.readthedocs.io/zh/latest/
五、運算式和控制結構
5.1 輸入引數和輸出引數
與 Javascript 一樣,函式可能需要引數作為輸入; 而與 Javascript 和 C 不同的是,它們可能回傳任意數量的引數作為輸出。
輸入引數
輸入引數的宣告方式與變數相同。但是有一個例外,未使用的引數可以省略引數名。 例如,如果我們希望合約接受有兩個整數形參的函式的外部呼叫,我們會像下面這樣寫
pragma solidity ^0.4.16;
contract Simple {
function taker(uint _a, uint _b) public pure {
// 用 _a 和 _b 實作相關功能.
}
}
輸出引數
輸出引數的宣告方式在關鍵詞 returns 之后,與輸入引數的宣告方式相同。 例如,如果我們需要回傳兩個結果:兩個給定整數的和與積,我們應該寫作
pragma solidity ^0.4.16;
contract Simple {
function arithmetics(uint _a, uint _b)
public
pure
returns (uint o_sum, uint o_product)
{
o_sum = _a + _b;
o_product = _a * _b;
}
}
輸出引數名可以被省略。輸出值也可以使用 return 陳述句指定。 return 陳述句也可以回傳多值,參閱:ref:multi-return。 回傳的輸出引數被初始化為 0;如果它們沒有被顯式賦值,它們就會一直為 0。
輸入引數和輸出引數可以在函式體中用作運算式。因此,它們也可用在等號左邊被賦值。
5.2 控制結構
JavaScript 中的大部分控制結構在 Solidity 中都是可用的,除了 switch 和 goto。 因此 Solidity 中有 if,else,while,do,for,break,continue,return,? : 這些與在 C 或者 JavaScript 中表達相同語意的關鍵詞。
用于表示條件的括號 不可以 被省略,單陳述句體兩邊的花括號可以被省略。
注意,與 C 和 JavaScript 不同, Solidity 中非布爾型別數值不能轉換為布爾型別,因此 if (1) { ... } 的寫法在 Solidity 中 無效 。
5.3 賦值
解構賦值和回傳多值
Solidity 內部允許元組 (tuple) 型別,也就是一個在編譯時元素數量固定的物件串列,串列中的元素可以是不同型別的物件。這些元組可以用來同時回傳多個數值,也可以用它們來同時給多個新宣告的變數或者既存的變數(或通常的 LValues):
pragma solidity >0.4.23 <0.5.0;
contract C {
uint[] data;
function f() public pure returns (uint, bool, uint) {
return (7, true, 2);
}
function g() public {
//基于回傳的元組來宣告變數并賦值
(uint x, bool b, uint y) = f();
//交換兩個值的通用竅門——但不適用于非值型別的存盤 (storage) 變數。
(x, y) = (y, x);
//元組的末尾元素可以省略(這也適用于變數宣告)。
(data.length,,) = f(); // 將長度設定為 7
//省略元組中末尾元素的寫法,僅可以在賦值操作的左側使用,除了這個例外:
(x,) = (1,);
//(1,) 是指定單元素元組的唯一方法,因為 (1)
//相當于 1。
}
}
直到 0.4.24 版本,給具有更少的元素數的元組賦值都可以可能的,無論是在左邊還是右邊(比如在最后空出若干元素)。現在,這已經不推薦了,賦值操作的兩邊應該具有相同個數的組成元素。
5.4 作用域和宣告
變數宣告后將有默認初始值,其初始值位元組表示全部為零。任何型別變數的“默認值”是其對應型別的典型“零狀態”。例如, bool 型別的默認值是 false 。 uint 或 int 型別的默認值是 0 。對于靜態大小的陣列和 bytes1 到 bytes32 ,每個單獨的元素將被初始化為與其型別相對應的默認值。 最后,對于動態大小的陣列, bytes 和 string 型別,其默認預設值是一個空陣列或字串。
Solidity 中的作用域規則遵循了 C99(與其他很多語言一樣):變數將會從它們被宣告之后可見,直到一對 { } 塊的結束。作為一個例外,在 for 回圈陳述句中初始化的變數,其可見性僅維持到 for 回圈的結束。
那些定義在代碼塊之外的變數,比如函式、合約、自定義型別等等,并不會影響它們的作用域特性。這意味著你可以在實際宣告狀態變數的陳述句之前就使用它們,并且遞回地呼叫函式。
基于以上的規則,下邊的例子不會出現編譯警告,因為那兩個變數雖然名字一樣,但卻在不同的作用域里。
pragma solidity >0.4.24;
contract C {
function minimalScoping() pure public {
{
uint same2 = 0;
}
{
uint same2 = 0;
}
}
}
作為 C99 作用域規則的特例,請注意在下邊的例子里,第一次對 x 的賦值會改變上一層中宣告的變數值。如果外層宣告的變數被“影子化”(就是說被在內部作用域中由一個同名變數所替代)你會得到一個警告。
pragma solidity >0.4.24;
contract C {
function f() pure public returns (uint) {
uint x = 1;
{
x = 2; // 這個賦值會影響在外層宣告的變數
uint x;
}
return x; // x has value 2
}
}
在 Solidity 0.5.0 之前的版本,作用域規則都沿用了 Javascript 的規則,即一個變數可以宣告在函式的任意位置,都可以使他在整個函式范圍內可見。而這種規則會從 0.5.0 版本起被打破。從 0.5.0 版本開始,下面例子中的代碼段會導致編譯錯誤。
// 這將無法編譯通過
pragma solidity >0.4.24;
contract C {
function f() pure public returns (uint) {
x = 2;
uint x;
return x;
}
}
5.5錯誤處理:Assert, Require, Revert and Exceptions
Solidity 使用狀態恢復例外來處理錯誤。這種例外將撤消對當前呼叫(及其所有子呼叫)中的狀態所做的所有更改,并且還向呼叫者標記錯誤。
便利函式 assert 和 require 可用于檢查條件并在條件不滿足時拋出例外。assert 函式只能用于測驗內部錯誤,并檢查非變數。 require 函式用于確認條件有效性,例如輸入變數,或合約狀態變數是否滿足條件,或驗證外部合約呼叫回傳的值。
如果使用得當,分析工具可以評估你的合約,并標示出那些會使 assert 失敗的條件和函式呼叫。 正常作業的代碼不會導致一個 assert 陳述句的失敗;如果這發生了,那就說明出現了一個需要你修復的 bug。
還有另外兩種觸發例外的方法:revert 函式可以用來標記錯誤并恢復當前的呼叫。 revert 呼叫中包含有關錯誤的詳細資訊是可能的,這個訊息會被回傳給呼叫者。已經不推薦的關鍵字 throw 也可以用來替代 revert() (但無法回傳錯誤訊息)。
從 0.4.13 版本開始,throw 這個關鍵字被棄用,并且將來會被逐漸淘汰。
當子呼叫發生例外時,它們會自動“冒泡”(即重新拋出例外)。這個規則的例外是 send 和低級函式 call , delegatecall 和 callcode --如果這些函式發生例外,將回傳 false ,而不是“冒泡”。
作為 EVM 設計的一部分,如果被呼叫合約帳戶不存在,則低級函式 call , delegatecall 和 callcode 將回傳 success。因此如果需要使用低級函式時,必須在呼叫之前檢查被呼叫合約是否存在。
例外捕獲還未實作
在下例中,你可以看到如何輕松使用``require``檢查輸入條件以及如何使用``assert``檢查內部錯誤,注意,你可以給 require 提供一個訊息字串,而 assert 不行。
pragma solidity ^0.4.22;
contract Sharer {
function sendHalf(address addr) public payable returns (uint balance) {
require(msg.value % 2 == 0, "Even value required.");
uint balanceBeforeTransfer = this.balance;
addr.transfer(msg.value / 2);
//由于轉移函式在失敗時拋出例外并且不能在這里回呼,因此我們應該沒有辦法仍然有一半的錢。
assert(this.balance == balanceBeforeTransfer - msg.value / 2);
return this.balance;
}
}
下列情況將會產生一個 assert 式例外:
如果你訪問陣列的索引太大或為負數(例如 x[i] 其中 i >= x.length 或 i < 0)。
如果你訪問固定長度 bytesN 的索引太大或為負數。
如果你用零當除數做除法或模運算(例如 5 / 0 或 23 % 0 )。
如果你移位負數位。
如果你將一個太大或負數值轉換為一個列舉型別。
如果你呼叫內部函式型別的零初始化變數。
如果你呼叫 assert 的引數(運算式)最終結算為 false。
下列情況將會產生一個 require 式例外:
呼叫 throw 。
如果你呼叫 require 的引數(運算式)最終結算為 false 。
如果你通過訊息呼叫呼叫某個函式,但該函式沒有正確結束(它耗盡了 gas,沒有匹配函式,或者本身拋出一個例外),上述函式不包括低級別的操作 call , send , delegatecall 或者 callcode 。低級操作不會拋出例外,而通過回傳 false 來指示失敗。
如果你使用 new 關鍵字創建合約,但合約沒有正確創建(請參閱上條有關”未正確完成“的定義)。
如果你對不包含代碼的合約執行外部函式呼叫。
如果你的合約通過一個沒有 payable 修飾符的公有函式(包括建構式和 fallback 函式)接收 Ether。
如果你的合約通過公有 getter 函式接收 Ether 。
如果 .transfer() 失敗。
在內部, Solidity 對一個 require 式的例外執行回退操作(指令 0xfd )并執行一個無效操作(指令 0xfe )來引發 assert 式例外。 在這兩種情況下,都會導致 EVM 回退對狀態所做的所有更改。回退的原因是不能繼續安全地執行,因為沒有實作預期的效果。
因為我們想保留交易的原子性,所以最安全的做法是回退所有更改并使整個交易(或至少是呼叫)不產生效果。 請注意, assert 式例外消耗了所有可用的呼叫 gas ,而從 Metropolis 版本起 require 式的例外不會消耗任何 gas。
下邊的例子展示了如何在 revert 和 require 中使用錯誤字串:
pragma solidity ^0.4.22;
contract VendingMachine {
function buy(uint amount) payable {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
// 下邊是等價的方法來做同樣的檢查:
require(
amount <= msg.value / 2 ether,
"Not enough Ether provided."
);
// 執行購買操作
}
}
六、合約
Solidity 合約類似于面向物件語言中的類。合約中有用于資料持久化的狀態變數,和可以修改狀態變數的函式。 呼叫另一個合約實體的函式時,會執行一個 EVM 函式呼叫,這個操作會切換執行時的背景關系,這樣,前一個合約的狀態變數就不能訪問了。
6.1 創建合約
可以通過以太坊交易“從外部”或從 Solidity 合約內部創建合約。
一些集成開發環境,例如 Remix, 通過使用一些用戶界面元素使創建程序更加流暢。 在以太坊上編程創建合約最好使用 JavaScript API web3.js。 現在,我們已經有了一個叫做 web3.eth.Contract 的方法能夠更容易的創建合約。
創建合約時,會執行一次建構式(與合約同名的函式)。建構式是可選的。只允許有一個建構式,這意味著不支持多載。
在內部,建構式引數在合約代碼之后通過 ABI 編碼 傳遞,但是如果你使用 web3.js 則不必關心這個問題。
如果一個合約想要創建另一個合約,那么創建者必須知曉被創建合約的源代碼(和二進制代碼)。 這意味著不可能回圈創建依賴項。
pragma solidity ^0.4.16;
contract OwnedToken {
// TokenCreator 是如下定義的合約型別.
// 不創建新合約的話,也可以參考它。
TokenCreator creator;
address owner;
bytes32 name;
// 這是注冊 creator 和設定名稱的建構式。
function OwnedToken(bytes32 _name) public {
// 狀態變數通過其名稱訪問,而不是通過例如 this.owner 的方式訪問。
// 這也適用于函式,特別是在建構式中,你只能像這樣(“內部地”)呼叫它們,
// 因為合約本身還不存在。
owner = msg.sender;
// 從 `address` 到 `TokenCreator` ,是做顯式的型別轉換
// 并且假定呼叫合約的型別是 TokenCreator,沒有真正的方法來檢查這一點。
creator = TokenCreator(msg.sender);
name = _name;
}
function changeName(bytes32 newName) public {
// 只有 creator (即創建當前合約的合約)能夠更改名稱 —— 因為合約是隱式轉換為地址的,
// 所以這里的比較是可行的。
if (msg.sender == address(creator))
name = newName;
}
function transfer(address newOwner) public {
// 只有當前所有者才能發送 token。
if (msg.sender != owner) return;
// 我們也想詢問 creator 是否可以發送。
// 請注意,這里呼叫了一個下面定義的合約中的函式。
// 如果呼叫失敗(比如,由于 gas 不足),會立即停止執行。
if (creator.isTokenTransferOK(owner, newOwner))
owner = newOwner;
}
}
contract TokenCreator {
function createToken(bytes32 name)
public
returns (OwnedToken tokenAddress)
{
// 創建一個新的 Token 合約并且回傳它的地址。
// 從 JavaScript 方面來說,回傳型別是簡單的 `address` 型別,因為
// 這是在 ABI 中可用的最接近的型別。
return new OwnedToken(name);
}
function changeName(OwnedToken tokenAddress, bytes32 name) public {
// 同樣,`tokenAddress` 的外部型別也是 `address` 。
tokenAddress.changeName(name);
}
function isTokenTransferOK(address currentOwner, address newOwner)
public
view
returns (bool ok)
{
// 檢查一些任意的情況。
address tokenAddress = msg.sender;
return (keccak256(newOwner) & 0xff) == (bytes20(tokenAddress) & 0xff);
}
}
6.2 可見性和 getter 函式
由于 Solidity 有兩種函式呼叫(內部呼叫不會產生實際的 EVM 呼叫或稱為“訊息呼叫”,而外部呼叫則會產生一個 EVM 呼叫), 函式和狀態變數有四種可見性型別。
函式可以指定為 external,public ,internal 或者 private,默認情況下函式型別為 public。 對于狀態變數,不能設定為 external ,默認是 internal 。
external :
外部函式作為合約介面的一部分,意味著我們可以從其他合約和交易中呼叫。
一個外部函式 f不能從內部呼叫(即 f 不起作用,但 this.f() 可以)。
當收到大量資料的時候,外部函式有時候會更有效率。
public :
public 函式是合約介面的一部分,可以在內部或通過訊息呼叫。對于公共狀態變數, 會自動生成一個 getter 函式(見下面)。
internal :
這些函式和狀態變數只能是內部訪問(即從當前合約內部或從它派生的合約訪問),不使用 this 呼叫。
private :
private 函式和狀態變數僅在當前定義它們的合約中使用,并且不能被派生合約使用。
合約中的所有內容對外部觀察者都是可見的。設定一些 private 型別只能阻止其他合約訪問和修改這些資訊, 但是對于區塊鏈外的整個世界它仍然是可見的。
可見性識別符號的定義位置,對于狀態變數來說是在型別后面,對于函式是在引數串列和回傳關鍵字中間。
pragma solidity ^0.4.16;
contract C {
function f(uint a) private pure returns (uint b) { return a + 1; }
function setData(uint a) internal { data = a; }
uint public data;
}
在下面的例子中,D 可以呼叫 c.getData() 來獲取狀態存盤中 data 的值,但不能呼叫 f 。 合約 E 繼承自 C ,因此可以呼叫 compute。
// 下面代碼編譯錯誤
pragma solidity ^0.4.0;
contract C {
uint private data;
function f(uint a) private returns(uint b) { return a + 1; }
function setData(uint a) public { data = a; }
function getData() public returns(uint) { return data; }
function compute(uint a, uint b) internal returns (uint) { return a+b; }
}
contract D {
function readData() public {
C c = new C();
uint local = c.f(7); // 錯誤:成員 `f` 不可見
c.setData(3);
local = c.getData();
local = c.compute(3, 5); // 錯誤:成員 `compute` 不可見
}
}
contract E is C {
function g() public {
C c = new C();
uint val = compute(3, 5); // 訪問內部成員(從繼承合約訪問父合約成員)
}
}
6.3 Getter 函式
編譯器自動為所有 public 狀態變數創建 getter 函式。對于下面給出的合約,編譯器會生成一個名為 data 的函式, 該函式不會接收任何引數并回傳一個 uint ,即狀態變數 data 的值。可以在宣告時完成狀態變數的初始化。
pragma solidity ^0.4.0;
contract C {
uint public data = 42;
}
contract Caller {
C c = new C();
function f() public {
uint local = c.data();
}
}
getter 函式具有外部可見性。如果在內部訪問 getter(即沒有 this. ),它被認為一個狀態變數。
如果它是外部訪問的(即用 this. ),它被認為為一個函式。
pragma solidity ^0.4.0;
contract C {
uint public data;
function x() public {
data = 3; // 內部訪問
uint val = this.data(); // 外部訪問
}
}
下一個例子稍微復雜一些:
pragma solidity ^0.4.0;
contract Complex {
struct Data {
uint a;
bytes3 b;
mapping (uint => uint) map;
}
mapping (uint => mapping(bool => Data[])) public data;
}
這將會生成以下形式的函式
function data
uj5u.com熱心網友回復:
感覺自己不會的東西太多了,不知道如何下手?轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/28556.html
標籤:區塊鏈技術
上一篇:對接數字貨幣交易所需要哪些條件
下一篇:請問大神這個網站的地址是什么
