輸入參數和輸出參數
與在Javascript中一樣,函數可以將參數作爲輸入; 與Javascript和C不同,它們也可以返回任意數量的參數作爲輸出。
輸入參數
輸入參數的聲明方式與變量相同。可以省略未使用參數的名稱。例如,假設我們希望我們的合約接受一種帶有兩個整數的外部調用,我們會寫如下:
pragma solidity >=0.4.16 <0.6.0;
contract Simple {
uint sum;
function taker(uint _a, uint _b) public {
sum = _a + _b;
}
}
輸入參數可以像使用任何其他局部變量一樣使用,也可以分配給它們。
輸出參數
可以在returns
關鍵字後使用相同的語法聲明輸出參數 。例如,假設我們希望返回兩個結果:兩個給定整數的和和乘積,然後我們寫:
pragma solidity >=0.4.16 <0.6.0;
contract Simple {
function arithmetic(uint _a, uint _b)
public
pure
returns (uint o_sum, uint o_product)
{
o_sum = _a + _b;
o_product = _a * _b;
}
}
輸出參數的名稱可以省略。也可以使用return
語句指定輸出值,這些語句也能夠返回多個值。返回參數可以用作任何其他局部變量,並且它們是零初始化的; 如果它們沒有明確設置,它們保持爲零。
控制結構
大括號語言中已知的大多數控制結構都可以在Solidity中找到:
有:if
,else
,while
,do
,for
,break
,continue
,return
,具有選自C或JavaScript已知的常規語義。
條件不能省略括號,但可以在單語句體周圍省略捲曲的brances。
請注意,有從非布爾沒有類型轉換爲Boolean類型,因爲在C和JavaScript,因此是不是有效的密實度。if (1) { ... }
返回多個值
當函數有多個輸出參數時,可以返回多個值。組件數必須與輸出參數的數量相同。return (v0, v1, ..., vn)
函數調用
內部函數調用
當前合約的函數可以直接調用(“內部”),也可以遞歸調用,如這個荒謬的例子所示:
pragma solidity >=0.4.16 <0.6.0;
contract C {
function g(uint a) public pure returns (uint ret) { return a + f(); }
function f() internal pure returns (uint ret) { return g(7) + f(); }
}
這些函數調用轉換爲EVM內部的簡單跳轉。這導致當前存儲器未被清除,即將內存引用傳遞給內部調用的函數非常有效。只能在內部調用同一合同的功能。
您仍應避免過度遞歸,因爲每個內部函數調用至少使用一個堆棧槽,並且最多有1024個可用插槽。
外部函數調用
表達式this.g(8);
和c.g(2);
(其中c
是契約實例)也是有效的函數調用,但這次,函數將通過消息調用“外部”調用,而不是直接通過跳轉調用。請注意,函數調用this
不能在構造函數中使用,因爲尚未創建實際的合約。
其他合約的功能必須在外部調用。對於外部調用,必須將所有函數參數複製到內存中。
注意
從一個合約到另一個合約的函數調用不會創建自己的事務,它是作爲整個事務的一部分的消息調用。
在調用其他合約的功能時,可以使用特殊選項指定通過call和gas發送的Wei數量,.value()
並.gas()
分別:
pragma solidity >=0.4.0 <0.6.0;
contract InfoFeed {
function info() public payable returns (uint ret) { return 42; }
}
contract Consumer {
InfoFeed feed;
function setFeed(InfoFeed addr) public { feed = addr; }
function callFeed() public { feed.info.value(10).gas(800)(); }
}
您需要將修飾符payable
與info
函數一起使用,否則該.value()
選項將不可用。
警告
注意feed.info.value(10).gas(800)
只在本地設置函數調用發送的value
數量和數量,gas
最後的括號執行實際調用。所以在這種情況下,不調用該函數。
如果被調用的合約不存在(在該帳戶不包含代碼的意義上)或者被調用的合約本身拋出異常或沒有gas,則函數調用會導致異常。
警告
與另一份合同的任何互動都會帶來潛在的危險,特別是如果事先不知道合約的源代碼。目前的合約將控制權移交給被call的合約,這可能會對任何事情產生影響。即使被調用的合約繼承自已知的父合約,繼承合約也只需要具有正確的接口。然而,合約的實施可以完全是任意的,因此構成危險。此外,如果它在第一次調用返回之前調用您系統的其他合約或甚至返回到調用合約,請做好準備。這意味着被調用的合約可以通過其函數來改變調用合約的狀態變量。以某種方式編寫函數,例如,
命名調用和匿名函數參數
函數調用參數可以按名稱以任何順序給出,如果它們被包含在中,如下例所示。參數列表必須與函數聲明中的參數列表一致,但可以按任意順序排列。{ }
pragma solidity >=0.4.0 <0.6.0;
contract C {
mapping(uint => uint) data;
function f() public {
set({value: 2, key: 3});
}
function set(uint key, uint value) public {
data[key] = value;
}
}
省略的函數參數名稱
可以省略未使用參數的名稱(尤其是返回參數)。這些參數仍然存在於堆棧中,但它們無法訪問。
pragma solidity >=0.4.16 <0.6.0;
contract C {
// omitted name for parameter
function func(uint k, uint) public pure returns(uint) {
return k;
}
}
通過創建合約new
合約可以使用new
關鍵字創建其他合約。編譯創建合約時必須知道正在創建的合約的完整代碼,因此無法實現遞歸創建依賴性。
pragma solidity >0.4.99 <0.6.0;
contract D {
uint public x;
constructor(uint a) public payable {
x = a;
}
}
contract C {
D d = new D(4); // will be executed as part of C's constructor
function createD(uint arg) public {
D newD = new D(arg);
newD.x();
}
function createAndEndowD(uint arg, uint amount) public payable {
// Send ether along with the creation
D newD = (new D).value(amount)(arg);
newD.x();
}
}
如示例所示,可以在創建D
使用該.value()
選項的實例時發送以太網,但不能限制gas量。如果創建失敗(由於堆棧外,沒有足夠的平衡或其他問題),則拋出異常。
表達式的評價順序
未指定表達式的求值順序(更正式地,未指定表達式樹中一個節點的子節點的計算順序,但它們當然在節點本身之前進行求值)。只保證語句按順序執行,並且完成布爾表達式的短路。有關更多信息,請參閱運算符的優先順序。
解構賦值和返回多個值
Solidity內部允許元組類型,即可能不同類型的對象列表,其編號在編譯時是常量。這些元組可以用於同時返回多個值。然後可以將這些變量分配給新聲明的變量或預先存在的變量(或一般的LValues)。
元組在Solidity中不是正確的類型,它們只能用於形成表達式的句法分組。
pragma solidity >0.4.23 <0.6.0;
contract C {
uint[] data;
function f() public pure returns (uint, bool, uint) {
return (7, true, 2);
}
function g() public {
// Variables declared with type and assigned from the returned tuple,
// not all elements have to be specified (but the number must match).
(uint x, , uint y) = f();
// Common trick to swap values -- does not work for non-value storage types.
(x, y) = (y, x);
// Components can be left out (also for variable declarations).
(data.length, , ) = f(); // Sets the length to 7
}
}
無法混合變量聲明和非聲明賦值,即以下內容無效: (x, uint y) = (1, 2);
注意
在版本0.5.0之前,可以分配給較小尺寸的元組,或者填充在左側或右側(它曾經是空的)。現在不允許這樣做,因此雙方必須擁有相同數量的組件。
警告
在涉及引用類型的同時分配多個變量時要小心,因爲它可能導致意外的複製行爲。
數組和結構的併發症
對於非值類型(如數組和結構),賦值的語義稍微複雜一些。分配給狀態變量始終會創建一個獨立的副本。另一方面,分配給局部變量只爲基本類型創建一個獨立的副本,即適合32個字節的靜態類型。如果將結構或數組(包括bytes
和string
)從狀態變量賦值給局部變量,則局部變量保存對原始狀態變量的引用。對局部變量的第二次賦值不會修改狀態,只會更改引用。對局部變量的成員(或元素)的賦值確實會改變狀態。
範圍和聲明
聲明的變量將具有初始默認值,其字節表示全爲零。變量的“默認值”是任何類型的典型“零狀態”。例如,對於一個的默認值bool
是false
。uint
或int
類型的默認值是0
。對於靜態大小的數組和bytes1
對bytes32
,每個單獨的元件將被初始化爲對應於其類型的默認值。最後,對於動態大小的陣列,bytes
並且string
,默認值是空數組或字符串。
在Solidity中確定範圍遵循C99(以及許多其他語言)的廣泛範圍規則:變量在其聲明之後直到包含聲明的最小塊的結尾處可見。作爲此規則的一個例外,在for循環的初始化部分中聲明的變量只有在for循環結束時纔可見。{ }
在代碼塊之外聲明的變量和其他項,例如函數,合約,用戶定義的類型等,甚至在聲明之前都是可見的。這意味着您可以在聲明它們之前使用狀態變量,並遞歸調用函數。
因此,以下示例將在沒有警告的情況下進行編譯,因爲這兩個變量具有相同的名稱但是不相交的範圍。
pragma solidity >0.4.99 <0.6.0;
contract C {
function minimalScoping() pure public {
{
uint same;
same = 1;
}
{
uint same;
same = 3;
}
}
}
作爲C99範圍規則的一個特例,請注意,在下文中,第一個賦值x
將實際分配外部變量而不是內部變量。在任何情況下,您都會收到有關被遮蔽的外部變量的警告。
pragma solidity >0.4.99 <0.6.0;
// This will report a warning
contract C {
function f() pure public returns (uint) {
uint x = 1;
{
x = 2; // this will assign to the outer variable
uint x;
}
return x; // x has value 2
}
}
警告
在版本0.5.0之前,Solidity遵循與JavaScript相同的作用域規則,也就是說,在函數內任何地方聲明的變量都在整個函數的範圍內,無論它在何處被聲明。以下示例顯示了用於編譯但導致從版本0.5.0開始的錯誤的代碼段。
pragma solidity >0.4.99 <0.6.0;
// This will not compile
contract C {
function f() pure public returns (uint) {
x = 2;
uint x;
return x;
}
}
錯誤處理:斷言,要求,還原和異常
Solidity使用狀態恢復異常來處理錯誤。這樣的異常將撤消對當前調用(及其所有子調用)中的狀態所做的所有更改,並且還向調用者標記錯誤。便利功能assert
和require
可用於檢查的條件,並拋出一個異常,如果條件不滿足。該assert
函數僅應用於測試內部錯誤,並檢查不變量。該require
函數應用於確保滿足有效條件(如輸入或合同狀態變量),或驗證調用外部合約的返回值。如果使用得當,分析工具可以評估您的合同,以確定將導致失敗的條件和函數調用assert
。正常運行的代碼永遠不會達到失敗的斷言語句; 如果發生這種情況,您的合同中有一個錯誤,您應該修復。
還有另外兩種觸發異常的方法:該revert
函數可用於標記錯誤並恢復當前調用。可以提供一個字符串消息,其中包含有關將傳遞迴調用者的錯誤的詳細信息。
注意
曾經有一個被調用的關鍵字throw
具有與revert()
版本0.4.13中不推薦使用並在版本0.5.0中刪除的相同語義。
當子調用中發生異常時,它們會自動“冒泡”(即異常被重新拋出)。此規則的例外是send
和低級函數call
,delegatecall
並且staticcall
- false
在異常的情況下返回作爲它們的第一個返回值而不是“冒泡”。
警告
低級別的功能call
,delegatecall
並staticcall
返回true
他們的第一個返回值,如果被叫帳戶是不存在的,如EVM的設計的一部分。如果需要,必須在調用之前檢查存在性。
捕捉異常尚不可能。
在下面的示例中,您可以看到如何require
使用它來輕鬆檢查輸入條件以及如何assert
用於內部錯誤檢查。請注意,您可以選擇提供消息字符串require
,但不能提供assert
。
pragma solidity >0.4.99 <0.6.0;
contract Sharer {
function sendHalf(address payable addr) public payable returns (uint balance) {
require(msg.value % 2 == 0, "Even value required.");
uint balanceBeforeTransfer = address(this).balance;
addr.transfer(msg.value / 2);
// Since transfer throws an exception on failure and
// cannot call back here, there should be no way for us to
// still have half of the money.
assert(address(this).balance == balanceBeforeTransfer - msg.value / 2);
return address(this).balance;
}
}
一種assert
風格的例外在下列情況下產生的:
- 如果以過大或負的索引(即
x[i]
where 或)訪問數組。i >= x.length
i < 0
- 如果
bytesN
以過大或負的索引訪問固定長度。 - 如果除以零或模數爲零(例如或)。
5 / 0
23 % 0
- 如果你換一個負數。
- 如果將值太大或負值轉換爲枚舉類型。
- 如果調用內部函數類型的零初始化變量。
- 如果
assert
使用計算結果爲false的參數調用。
require
在以下情況下會生成A 風格的異常:
require
使用求值爲的參數調用false
。- 如果你調用通過消息通話功能,但它不能正常完成(即用完gas,沒有匹配的功能,或拋出一個異常本身),當低層次的操作,除了
call
,send
,delegatecall
,callcode
或staticcall
使用。低級操作從不拋出異常,但通過返回指示失敗false
。 - 如果使用
new
關鍵字創建合同但合同創建未正確完成(請參閱上面的“未正確完成”的定義)。 - 如果執行鍼對不包含代碼的合同的外部函數調用。
- 如果您的合約通過公共函數接收Ether而沒有
payable
修飾符(包括構造函數和回退函數)。 - 如果您的合約通過公共getter函數接收Ether。
- 如果
.transfer()
失敗了。
在內部,Solidity 0xfd
對require
a-樣式異常執行恢復操作(指令),並執行無效操作(指令0xfe
)以拋出assert
樣式異常。在這兩種情況下,這都會導致EVM還原對狀態所做的所有更改。恢復的原因是沒有安全的方法來繼續執行,因爲沒有發生預期的影響。因爲我們希望保留事務的原子性,所以最安全的做法是還原所有更改並使整個事務(或至少調用)不起作用。請注意,assert
樣式異常消耗了call可用的所有gas,而 require
樣式異常不會消耗從Metropolis版本開始的任何gas。
以下示例顯示瞭如何將錯誤字符串與revert和require一起使用:
pragma solidity >0.4.99 <0.6.0;
contract VendingMachine {
function buy(uint amount) public payable {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
// Alternative way to do it:
require(
amount <= msg.value / 2 ether,
"Not enough Ether provided."
);
// Perform the purchase.
}
}
提供的字符串將被abi編碼,就好像它是對函數的調用一樣Error(string)
。在上面的示例中,將導致以下十六進制數據設置爲錯誤返回數據:revert("Not enough Ether provided.");
0x08c379a0 // Function selector for Error(string)
0x0000000000000000000000000000000000000000000000000000000000000020 // Data offset
0x000000000000000000000000000000000000000000000000000000000000001a // String length
0x4e6f7420656e6f7567682045746865722070726f76696465642e000000000000 // String data