智慧合約基礎語言(三)——Solidity變數型別:值型別
一、目錄
☛值型別和引用型別的區別
☛布林型別(bool)
☛整型(int、uint)
☛定點型小數(fixed、ufixed)
☛地址型別(address)
☛定長位元組陣列(bytes1,bytes2,bytes3,...,bytes32)
☛有理數和整數字面量
☛地址字面量
☛字串字面量
☛十六進位制字面量(hex)
☛列舉型別(enum)
二、值型別和引用型別的區別
Solidity變數型別分為兩大類——值型別、引用型別
值型別:變數的儲存空間存的是變數的資料 引用型別:變數的儲存空間存的是變數資料所在的儲存空間的地址
注意:值傳遞和引用傳遞。值型別的變數,賦值是簡單的值傳遞,即兩個變數佔有獨立的儲存區域。引用型別賦值傳遞的是變數的引用,即兩個變數指向同一儲存區域
三、值型別——布林(bool)
bool: 只有兩種值true和false(預設false)。
3.1 支援的運算子:
▪ ! 邏輯非
▪ && 邏輯與
▪ || 邏輯或
▪ == 等於
▪ != 不等於
3.2 例項
bool a = true; bool b = !a; // a == b -> false // a != b -> true // a || b -> true // a && b -> false
邏輯與(&&)和邏輯或(||)都遵循短路原則,即如果根據前一個表示式可以得到運算結果,則不會執行後面的表示式。
四、值型別——整型(int/uint)
▪ int(m):有符號整數
▪ uint(m):無符號整數
▪ m關鍵字取值為8~256步幅是8 ,表示在記憶體中2進位制的位數,控制了整數的取值範圍,不寫預設為256。
▪ uint和int分別是uint256和int256的別名。
▪ m一定要是8的整數倍
4.1 操作
比較:<=,<,==,!=,>=,>(結果為bool) 位操作符:&,|,^(按位異或),~(按位取反) 算術運算子:+, - ,一元 - ,一元 +,*,/,%(取餘),**(冪),<<(左移),>>(右移)
注意:
▪ 除法總是截斷,但如果兩個運算子都是常量(或常量表達式),則它不會截斷。
▪ 除零和取餘有零引發異常。
▪ 左移幾位和右移幾位相當於乘以或者除以2的幾次方,如果引數為負數的話會引發異常。
▪ 在Solidity中不支援八進位制。
五、值型別——定點數
5.1 定點小數
到現在為止還沒有被solidity完全支援,可以被宣告,不能被賦值也不能用定點數賦值fixed/ufixed 各種大小的有符號和無符號定點小數,ufixedMxN and fixedMxN關鍵字M代表定點數佔用的二進位制位數,N代表定點數能表示多少位小數,M必須是8-256之間的,以8為步幅的整數,N必須是0-80之間的整數,ufixed 和fixed 預設為ufixed128x18和fixed128x18。
▪ 比較運算: <=, <, ==, !=, >=, > (結果為bool)
▪ 算數運算: +, -, 一元-, 一元 +, *, /, % 取餘
六、值型別——地址型別(address)
在瞭解地址之前需要知道一個名詞---ABI協議:
Application Binary Interface 應用二進位制介面,是從區塊鏈外部與合約進行互動以及合約與合約間進行互動的一種標準方式。ABI是以太坊的一種合約間呼叫時的一個訊息格式。類似Webservice裡的SOAP(Simple Object Access Protocol簡單物件訪問協議)協議一樣。
常見格式:
[ { "constant": false, "inputs": [ { "name": "index", "type": "uint8" }, { "name": "s", "type": "string" } ], "name": "removeItem", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" } ]
地址:儲存一個20位元組值,使用40位16進位制數表示。地址型別也有成員,並作為所有合約的基礎。
6.1 地址成員
▪ 以wei位單位返回該地址的餘額
<address>.balance(uint256)
▪ 從當前合約地址中給呼叫函式的地址賬戶轉入amounts數量(以wei為單位)的金額,即從當前合約轉賬到某賬戶地址。如果執行失敗,將丟擲異常,需要支付2300gas的費用,不可以調整。
<address>.transfer(uint256 amount)
其中address就是要把合約中的幣轉到哪個賬號地址。
範例:
pragma solidity ^0.4.24; contract AddressExample { //定義一個接受以太幣的函式往合約裡充值 function
AddressExample
()
payable
{} //函式的引數分別是劃轉到某賬號地址以及轉賬數量 function
giveEthersTo
(address _toAccount,uint amount){ if (this.balance >=amount){ _toAccount.transfer(amount); } } function
getBalance
()
view returns
(uint){ return this.balance; } //定義一個匿名的回退函式接收異常情況下退回的以太幣 function()
payable
{} }
▪ 如果合約地址呼叫transfer,那麼需要合約地址有payable型別的回退函式,並且會隨著轉賬一塊執行回退函式中的程式碼,如果因為回退函式中的程式碼執行把gas消耗光了,EVM會丟擲異常,轉賬也會被回退。
▪ send是低階對等的轉賬。執行失敗,不會丟擲異常,會返回false,需要支付2300gas的費用,不可以調整。推薦使用transfer而不使用send。
<address>.send(uint256 amount) returns (bool)
▪ call(), callcode() 和 delegatecall() 函式。
<address>.call(...) returns (bool) <address>.callcode(...) returns (bool) <address>.calldelegate(...) returns (bool)
為了和非ABI協議;也就是定義操作函式簽名,引數編碼,返回結果編碼等的合約進行互動,可以使用call() 函式, 它用來向另一個合約傳送原始資料,支援任何型別任意數量的引數,每個引數會按規則(ABI協議)打包成32位元組並一一拼接到一起。一個例外是:如果第一個引數恰好4個位元組,在這種情況下,會被認為根據ABI協議定義的函式器指定的函式簽名而直接使用。如果僅想傳送訊息體,需要避免第一個引數是4個位元組。如下面的例子:
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2; nameReg.call("register", "MyName"); nameReg.call(bytes4(keccak256("fun(uint256)")), a);
call()
是一個底層的介面,用來向一個合約傳送訊息,也就是說如果你想實現自己的訊息傳遞,可以使用這個函式。函式支援傳入任意型別的任意引數,並將引數打包成32位元組,相互拼接後向合約傳送這段資料。 call函式返回一個bool值,以表明執行成功與否。正常結束返回true,異常終止返回false。但無法獲取到結果資料。還可以提供.gas()修飾器進行呼叫:
namReg.call.gas(1000000)("register", "MyName");
類似還可以提供附帶以太幣:
nameReg.call.value(1 ether)("register", "MyName");
修飾器可以混合使用,修飾器呼叫順序無所謂。
nameReg.call.gas(1000000).value(1 ether)("register", "MyName");
delegatecall()
call與delegatecall的功能類似,區別僅在於後者僅使用給定地址的程式碼,僅僅是程式碼會執行,其它資訊則使用當前合約(如儲存,餘額等等)。
函式的設計目的是為了使用儲存在另一個合約的庫程式碼。
所以開發者在提供這樣的庫時,就要如何安排儲存來達到這樣的目的。
delegatecall()方法的目的是用來執行另一個合約中的庫程式碼。所以開發者需要保證兩個合約中的儲存變數能相容,來保證delegatecall()能順利執行。
callcode()
未提供對msg.sender,msg.value的訪問許可權。
call和callcode以及delegateCall的區別:
三者的區別主要體現在三個方面,作用域(上下文)、msg.sender、以及this。
以下是比較示例:
contract D { uint public n; address public sender; function callSetN(address _e, uint _n) { _e.call(bytes4(sha3("setN(uint256)")), _n); // 執行結果:改變了E中的變數值, 而當前的同名變數並沒改變。 } function callcodeSetN(address _e, uint _n) { _e.callcode(bytes4(sha3("setN(uint256)")), _n); // 執行結果:當前狀態變數被改變,而E的並沒改變。 } function delegatecallSetN(address _e, uint _n) { _e.delegatecall(bytes4(sha3("setN(uint256)")), _n); //執行結果同callcode } } contract E { uint public n; address public sender; function setN(uint _n) { n = _n; sender = msg.sender; // 如果D用call的方式呼叫,則msg.sender是D的合約地址 //如果D用callcode的方式呼叫,則msg.sender不會被獲取 //如果D用delegateCall的方式呼叫,msg.sender也不能被獲取 // 如果是C中的函式foo()間接呼叫了. msg.sender則是C // 如果D用callcode或者delegateCall呼叫,則this指向D } } contract C { function foo(D _d, E _e, uint _n) { _d.delegatecallSetN(_e, _n); } }
上面的這三個方法call(),delegatecall(),callcode()都是底層的訊息傳遞呼叫,最好僅在萬不得已才進行使用,因為他們破壞了Solidity的型別安全。 .gas() 在call(), callcode() 和 delegatecall() 函式下都可以使用, delegatecall()不支援.value()。
pragma solidity ^0.4.24; contract Person{ uint age = 10; function
increaseAge
(string name, uint num) returns (uint){ return ++age; } function
getAge
()
returns
(uint){ return age; } } contract CallTest{ function
callByFun
(address addr)
returns
(bool){ bytes4 methodId = bytes4(keccak256("increaseAge(string,uint256)")); return addr.call(methodId,"jack", 1); } }
注意:
合約中使用的this表示當前合約地址;
所有的合約物件都可以被轉成地址型別,查詢當前合約的餘額。
address(this).balance this.balance
七、值型別——定長位元組陣列
bytes1, ... ,bytes32,允許值以步長1遞增。byte預設表示bytes1。
7.1 操作
▪ 比較:<=,<,==,!=,>=,>(評估為bool)
▪ 位運算子:&,|,^(按位異或),~(按位取反),<<(左移),>>(右移)
▪ 索引訪問:如果x的型別為bytesI,則0 <= k <I的x [k]返回第k個位元組(只讀)。
7.2 成員
.length產生位元組陣列的固定長度(只讀)。
八、值型別——有理數和整數字面量
整數常量和有理數常量均支援科學計數法。基數可以是小數,指數必須是整數。 例如2e10,-2e10,2e-10,2.5e10。
8.1 有理字面量(特指小數字面量)
▪ 有理數字面量帶一個.,在.的兩邊至少要有一個數字,有效的表示如下1.,.1,1.2
▪ 不允許位運算,小數不能用作指數
▪ 有理數本身支援任意精度,任何運算不會發生溢位或除法截斷,當被轉換成對應的其他型別,或者與其他型別運算時,不再保證精度。
8.2 整數字面量
▪ 由一系列0-9的數字組成的10進位制數,存在十六進位制表示形式“0x”開頭,不存在以“0”八進位制的表示形式。
//十六進位制表示 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a
▪ 整數常量表達式的除法運算,不做截斷處理,結果是有理數。
注意:
數字字面量表達式一旦其中含有非字面量表達式,它就會被轉為一個非字面量表達式。
pragma solidity ^0.4.24; contract IntegerLiteralConvert{ function literalTest(){ uint128 a = 1; //uint128 b = 2.5 + a + 0.5; //2.5+a不能轉換成一個非字面量表達式 } }
九、值型別——地址字面量
地址字面量表現形式其實就是十六進位制整數字面量,如果能夠通過地址校驗,就會被認為是地址型別,如果不通過則它表示的是一個整數。例如:
0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
就是一個地址型別的字面量,如果長度為39~40,沒有通過地址校驗的十六進位制整數字面量,會被視為有理數常量,並且會產生一個warning(警告提示)。
十、值型別——字串常量
字串字面量是用雙引號或單引號(“foo”或‘bar’)編寫,長度可變。 它們不像C語言那樣預設以0結尾; “foo”代表三個不是四個位元組。 它們可以隱式轉換為bytes1,…,bytes32。
pragma solidity ^0.4.24; contract StringLiteralsTest{ bytes15 public name; function setName(){ name = 'liankuaixueyuan'; } }
十一、值型別——十六進位制字面量
十六進位制字面量,以關鍵字hex打頭,後面緊跟用單或雙引號包裹的字串。如hex"001122ff"。在內部會被表示為二進位制流,與字串的儲存形式相同,可以與字串進行隱式轉換,通過下面的例子來理解下是什麼意思:
pragma solidity ^0.4.24; contract HexLiteral{ string name; bytes nameBytes; function setName()public{ name = hex"6c69616e6b7561697875657975616e"; nameBytes = hex"6c69616e6b7561697875657975616e"; //nameBytes = hex"a"; //由於一個位元組是8位,所以一個hex是由兩個[0-9a-z]字元組成的。所以var b = hex“A”;不是成雙的,轉字串是會報錯的 } function getName() public view returns (bytes,string){ return (nameBytes,name); } }
十二、值型別——列舉
列舉型別是在Solidity中的一種使用者自定義型別。
pragma solidity ^0.4.24; contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } ActionChoices choice; ActionChoices constant defaultChoice = ActionChoices.GoStraight; function setGoStraight() public { choice = ActionChoices.GoStraight; // 這裡通過訪問下標的形式得到相同的結果 // choice = ActionChoices(2); } function getChoice() public view returns (ActionChoices) { return choice; } function getDefaultChoice() public view returns (uint) { return uint(defaultChoice); } }
注意:
▪ 列舉可以顯式的轉換與整數進行轉換,但不能進行隱式轉換。顯式的轉換會在執行時檢查數值範圍,如果不匹配,將會引起異常。
▪ 列舉型別應至少有一名成員。
本文完,獲取更多資訊,敬請關注區塊鏈工程師。