Solidity是一種靜態類型語言,須要再編譯期間指定每一個變量(靜態和局部)的類型。Solidity提供了幾種基本類型,能夠經過基本類型組合成複雜類型。另外,在帶有操做符的表達式中,類型之間會相互影響。數組
下面介紹數值類型,爲何叫數據類型,由於這些變量類型都須要傳入一個值,例如:在函數變量或是賦值,數值類型都會進行拷貝。安全
bool: 值爲 true 或是 false微信
操做:oracle
! 邏輯非app
&& 邏輯與ide
|| 邏輯或函數
== 相等佈局
!= 不等測試
|| 和 && 具備簡化規則,好比 f(x) || g(x) ,若是 f(x)爲true,將不會計算g(x)的值,即便g(x)有可能爲falseui
int/uint : 有符號整型和無符號整型 有多種長度。 unit8 到 uint258 相差8個(從8到258),int8到int256同樣。unit 和int的別名分別爲:unit256 和 int256.
操做:
比較:<= , < , == , != , >= , >
位操做: &(與) , |(或) , ^ (異或), ~ (非)
算術操做:+ , - , * , /, % , **(乘方,求冪) ,<<(左移) , >>(右移)
除法操做常常會被截斷(在EVM中會被編譯成DIV操做碼),可是若是相除的兩個數都是 數字常量,結果不會被截斷。若是除數爲0,將會拋出運行時異常。
移位操做的結果是左操做數,x << y 和 x * 2 ** y 相同,x >> y 和 x / 2**y 相同。這就意味着,若是對一個負數進行移位操做,會擴展到符號位。若是一個數被 移位了 負數次,會拋出運行時異常。
address: 20 字節長度的值(以太坊的地址),地址類型有不少成員變量,是全部合約的基礎。
操做: <= , < , == , != , >= , >
能夠經過地址的balance屬性來查看一個地址的餘額,發送以太幣(單位爲:wei)到一個地址可使用 transfer方法
address x = 0x123; address myAddress = this; if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
注意:若是x是一個合約地址,它的代碼(若是存在的話,更明確是爲 fallback 函數)將會和 transfer 調用一塊兒被執行(這是EVM的限制,是不可阻止的)。若是執行過程當中gas不夠或是失敗,當前合約會終止拋出異常
send
send方法和transfer很類似,可是比transfer更低級。若是send失敗,當前的合約不會中斷,也不會拋出異常,會返回一個false。
注意:使用send有一些風險:若是調用棧深度超過1024或是gas不夠,全部的轉讓操做都會失敗,爲了更安全的以太幣轉移,若是用send就必須每次都要檢查返回值,使用transfer方法會更好;
並且,call方法能夠和沒有依附於ABI上的合約進行交互,它提供了任意多個任意類型的參數。這些參數填充成32字節,並鏈接起來。有狀況若是第一個參數被加密成4個字節,就會拋出異常,這種狀況下,是不容許使用這個方法。
address nameReg = 0x72ba7d8e73fe8eb666ea66babc8116a41bfb10e2; nameReg.call("register", "MyName"); nameReg.call(bytes4(keccak256("fun(uint256)")), a);
call 返回一個boolean值,被調用函數終止返回true或是有EVM異常就返回false。不會返回訪問的具體數據(對此,咱們須要預先知道編碼方式和大小)。
一樣的方式,delegatecall也可使用,和call的惟一區別是 delegatecall會使用給定地址的代碼。全部其餘方面(存儲,餘額等)都是從當前合約中獲取。delegatecall的做用是使用其餘合約裏的library代碼。用戶須要確保兩個合約的存儲佈局適用於 deletegatecall的使用。
call, delegatecall和call都是很低層次的方法,只有在實在沒辦法的時候才使用,這三個方法會破壞Solidity裏的類型安全。
三個方法均可以使用 .gas(),而 .value() 不適合deletegatecall.
注意:全部的合約都繼承了地址相關成員方法。因此可使用 this.balance 來查詢當前合約的餘額
⚠️ 這些方法都是低層數的方法,使用的時候必定要當心。若是使用不當,任何未知的合約均可能別破壞。 你應該移交控制到能夠經過回調到你本身合約的那個合約裏,這樣經過返回值就能夠更新你本身的state變量
bytes1, bytes2, bytes3 ... bytes32。 byte和byte1同樣。
操做:
比較: <= , <, == , != , >= , >
位操做:& , | , ^ , ~, << , >>
下標訪問:若是x是 byteI類型,那麼 x[k] (0<= k < I),返回第k個byte (只讀)
移位操做能夠做爲右操做使用在任何整型上(返回的是一個左操做的類型),若是移位的位數是個負數,會拋出運行時異常。
成員: .length 獲得byte數組的固定長度
bytes: 動態大小byte數組,不是一個值類型
string:動態大小UTF-8編碼string,不是一個值類型
做爲一個經驗法則,對於任意長度的raw數據,使用bytes。對於任意長度的string(UTF-8)數據,使用string。若是能夠限定bytes到必定長度,優先使用byte1到byte32,這32個byte類型開銷更小。
十六進制常量並經過地址的checksum的驗證。好比 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF 是一個address類型。十六進制常量是39位到41位數字長度。若是沒有經過checksum的檢查,會產生一個警告,可是仍是會做爲一個合理的數字常量。
整數常量由0到9的數字組成的一列數字。這些數字序列會被解釋成小數。好比,69是表示數字 69。在Solidity不存在八進制,以0開頭的是無效的。
小數常量是由 . 組成,而且 . 的一邊必需要有數字,好比: 1. , .1 或是 1.3 。科學計數法也是一樣支持,基數能夠有小數部分,可是指數部分不能夠。好比:2e10, -2e10, 2e-10, 2.5e1 。 數字常量表達式保持任意精度,直到他們能夠轉換成一個很是量類型。這意味着 計數指令不會溢出,分割部分在數字常量表達式中不會被截斷。例如:(2**800 + 1) - 2**800 雖然中間結果是不符合機器字大小的,可是值仍是常量1(類型爲uint8)。此外,.5 * 8 結果是整型4(雖然在表達式中使用了非整型)。
若是結果不是一個整型,會根據小數部分的bits大小來使用ufixed仍是fixed類型。在var x = 1/4; 中,x的類型爲 ufixed0x8,在 var x = 1/3 ,x的類型爲ufixed0x256,由於 1/3 的結果在二進制中是不能用有限來表示的。只要操做數是整型,任何應用於整型的操做均可以用於數字常量表達式中。若是兩個操做數中公有一個是小數,位操做就不容許。一樣的,若是指數是小數,乘方操做也不容許。
字符串常量能夠寫成用雙引號或是單引號("foo"
, 'bar'
)。Solidity裏的字符串常量不像C語言中,後面有個0結尾。"foo"是三個bytes長度,而不是四個。在整型常量中,類型能夠改變,他們能夠轉換成 bytes1
, ..., bytes32 ,若是他們符合類型,能夠從bytes或是string。字符串常量支持轉義字符,例如
\n
, \xNN,
\uNNNN 。
十六進制是以 關鍵字 hex開頭的,後面的數字用雙引號或是單引號引發(hex"001122FF")。引號內的內容必須是十六進制的字符串。十六進制常量的行爲和字符串常量同樣,而且有一樣的限制。
枚舉
枚舉是一種用戶定義類型。他們能夠顯示轉換成全部的整型類型,可是不容許隱式轉換。在運行時會檢查顯示轉換的值範圍,若是轉換失敗,就會拋出異常。枚舉至少要有一個成員
pragma solidity ^0.4.0; contract test { enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill } ActionChoices choice; ActionChoices constant defaultChoice = ActionChoices.GoStraight; function setGoStraight() { 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. The integer type used is just // large enough to hold all enum values, i.e. if you have more values, // `uint16` will be used and so on. function getChoice() returns (ActionChoices) { return choice; } function getDefaultChoice() returns (uint) { return uint(defaultChoice); } }
函數類型是方法類型,相似於函數指針。方法能夠賦值給函數類型的變量,函數類型的方法參數可用於傳遞方法或是返回方法。函數類型有兩種形式:內部方法和外部方法。內部方法只能用於當前合約中,由於他們不可在當前合約的上下文以外執行。調用一個內部就跳轉到這個方法的人口處,就想調用一個當前合約的內部方法同樣。外部方法由一個地址和一個函數簽名組成,能夠經過一個外部方法傳值或是返回。
函數類型聲明以下:
function (<parameter types>) {internal|external} [constant] [payable] [returns (<return types>)]
和參數類型相比,返回類型不能爲空。若是函數類型不須要返回任何值,整個returns (<return types>) 都會被省略。默認狀況下,函數內類是內部的。因此 internal 會被省略。在當前的合約中,有兩種方式能夠訪問一個方法:直接使用方法名 f, 或使用 this.f ,前面結果是一個內部方法,然後面一種是一種外部方法。
調用一個沒有初始化的方法類型變量會拋出異常,一樣的若是你若是調用一個已經調用過delete以後的方法類型變量也會拋出異常。若是一個外部方法類型使用在Solidity的上下文以外,他們被當作是一個 函數類型,它的方法標識符和它的地址會用一個bytes24類型 編碼在一塊兒。注意當前合約的public方法能夠即做爲內部和外部方法。直接使用 f 就做爲一個內部方法,若是你想使用外部方法形式,就使用 this.f
以下例子展現瞭如何使用內部函數類型
pragma solidity ^0.4.5; library ArrayUtils { // internal functions can be used in internal library functions because // they will be part of the same code context function map(uint[] memory self, function (uint) returns (uint) f) internal returns (uint[] memory r) { r = new uint[](self.length); for (uint i = 0; i < self.length; i++) { r[i] = f(self[i]); } } function reduce( uint[] memory self, function (uint, uint) returns (uint) f ) internal returns (uint) { r = self[0]; for (uint i = 1; i < self.length; i++) { r = f(r, self[i]); } } function range(uint length) internal returns (uint[] memory r) { r = new uint[](length); for (uint i = 0; i < r.length; i++) { r[i] = i; } } } contract Pyramid { using ArrayUtils for *; function pyramid(uint l) returns (uint) { return ArrayUtils.range(l).map(square).reduce(sum); } function square(uint x) internal returns (uint) { return x * x; } function sum(uint x, uint y) internal returns (uint) { return x + y; } }
另一個使用外部函數類型例子:
pragma solidity ^0.4.11; contract Oracle { struct Request { bytes data; function(bytes memory) external callback; } Request[] requests; event NewRequest(uint); function query(bytes data, function(bytes memory) external callback) { requests.push(Request(data, callback)); NewRequest(requests.length - 1); } function reply(uint requestID, bytes response) { // Here goes the check that the reply comes from a trusted source requests[requestID].callback(response); } } contract OracleUser { Oracle constant oracle = Oracle(0x1234567); // known contract function buySomething() { oracle.query("USD", this.oracleResponse); } function oracleResponse(bytes response) { require(msg.sender == address(oracle)); // Use the data } }
複雜類型,好比相比於咱們常見的數值類型, 咱們要更當心的處理那些不必定老是會符合256bits的類型。因爲拷貝操做的代價很高昂,咱們須要考慮是否要把他們存在內存中仍是存在存儲空間中。
每個複雜類型,好比 array或是structs 都有一個額外的註解,數據位置,用於標識數據是在存在內存仍是存儲空間中。根據上下文,老是有一個默認值, 可是能夠經過重寫來指定是存儲空間仍是內存。默認的方法形參包括返回參數都是存儲在內存中,局部變量的默認存儲是在存儲空間,靜態變量智能存儲在存儲空間中。
還有一種是第三數據位置,calldata, 是一個不可變,非持久區域,方法實參存儲在這個區域。外部方法的形參不是返回參數智能存儲在 calldata中,存儲在calldata裏的數據行爲和存在內存裏同樣。
數據位置很是重要,它決定了如何分配。存儲空間,內存和一個靜態變量的分配老是建立一個獨立的拷貝空間。而一個局部變量只是分配一個引用,這個引用老是 指向一個靜態變量,即便靜態變量同時改變了。另外一方面,從一個內存存儲的引用到一個存儲空間存儲的引用不會建立一個拷貝。
pragma solidity ^0.4.0; contract C { uint[] x; // the data location of x is storage // the data location of memoryArray is memory function f(uint[] memoryArray) { x = memoryArray; // works, copies the whole array to storage var y = x; // works, assigns a pointer, data location of y is storage y[7]; // fine, returns the 8th element y.length = 2; // fine, modifies x through y delete x; // fine, clears the array, also modifies y // The following does not work; it would need to create a new temporary / // unnamed array in storage, but storage is "statically" allocated: // y = memoryArray; // This does not work either, since it would "reset" the pointer, but there // is no sensible location it could point to. // delete y; g(x); // calls g, handing over a reference to x h(x); // calls h and creates an independent, temporary copy in memory } function g(uint[] storage storageArray) internal {} function h(uint[] memoryArray) {} }
指定地址位置
默認地址位置
數組在編譯期間固定大小或是聲明爲一個動態數組, 對於存儲數組,數組裏的元素類型是能夠爲任意的(能夠是其餘數組,mapping或是structs).對於內存數組,若是是一個public方法的實參,元素不能是mapping,並且必須是ABI類型。
若是數組的長度爲x, 元素類型爲T,則能夠寫成 T[x],一個動態長度的數組,能夠寫成 T[]。例如,一個數組爲5的動態數組,類型爲uint,能夠寫成 uint[][5]。訪問第二個uint中的第三個元素,可使用 T[2][1] (數組下標從0開始)。bytes和string是特殊的數組,bytes相似於byte[],可是它在calldata裏是緊湊存放。string等於bytes,可是不容許根據長度或下標訪問。因此從廉價程度來講,相對於byte[],更優先使用bytes。
在內存裏建立一個可變長度數組可使用new關鍵字,和存儲空間數組相反,它不能經過 .length 從新設置長度。
pragma solidity ^0.4.0; contract C { function f(uint len) { uint[] memory a = new uint[](7); bytes memory b = new bytes(len); // Here we have a.length == 7 and b.length == len a[6] = 8; } }
常量數組能夠寫成一種表達式,而且不能夠賦值給一個變量。
pragma solidity ^0.4.0; contract C { function f() { g([uint(1), 2, 3]); } function g(uint[3] _data) { // ... } }
常量數組是一個固定長度的內存數組,它的基本類型是給定元素的類型。[1,2,3]的類型爲 uint8[3] memory ,由於包含的每一個元素類型都爲 uint8。 因此有必要把前面例子的第一個元素類型改爲 uint。須要注意:固定長度的內存數組是不能夠賦值給 動態長度的內存數組。下面的例子是錯誤的。
pragma solidity ^0.4.0; contract C { function f() { // The next line creates a type error because uint[3] memory // cannot be converted to uint[] memory. uint[] x = [uint(1), 3, 4]; }
這個限制現有有計劃會在後續版本里去掉。
數組有個length成員來表示數組元素的個數。動態數組能夠在存儲空間(不是內存)裏經過改變 length值來從新設置長度。
動態存儲數組 和 bytes(不是string)可使用push來增長一個元素到數組的尾部。這個方法將返回一個新的長度。
pragma solidity ^0.4.0; contract ArrayContract { uint[2**20] m_aLotOfIntegers; // Note that the following is not a pair of dynamic arrays but a // dynamic array of pairs (i.e. of fixed size arrays of length two). bool[2][] m_pairsOfFlags; // newPairs is stored in memory - the default for function arguments function setAllFlagPairs(bool[2][] newPairs) { // assignment to a storage array replaces the complete array m_pairsOfFlags = newPairs; } function setFlagPair(uint index, bool flagA, bool flagB) { // access to a non-existing index will throw an exception m_pairsOfFlags[index][0] = flagA; m_pairsOfFlags[index][1] = flagB; } function changeFlagArraySize(uint newSize) { // if the new size is smaller, removed array elements will be cleared m_pairsOfFlags.length = newSize; } function clear() { // these clear the arrays completely delete m_pairsOfFlags; delete m_aLotOfIntegers; // identical effect here m_pairsOfFlags.length = 0; } bytes m_byteData; function byteArrays(bytes data) { // byte arrays ("bytes") are different as they are stored without padding, // but can be treated identical to "uint8[]" m_byteData = data; m_byteData.length += 7; m_byteData[3] = 8; delete m_byteData[2]; } function addFlag(bool[2] flag) returns (uint) { return m_pairsOfFlags.push(flag); } function createMemoryArray(uint size) returns (bytes) { // Dynamic memory arrays are created using `new`: uint[2][] memory arrayOfPairs = new uint[2][](size); // Create a dynamic byte array: bytes memory b = new bytes(200); for (uint i = 0; i < b.length; i++) b[i] = byte(i); return b; } }
Solidity提供了一種用戶自定義格式的新類型。例如:
pragma solidity ^0.4.11; contract CrowdFunding { // Defines a new type with two fields. struct Funder { address addr; uint amount; } struct Campaign { address beneficiary; uint fundingGoal; uint numFunders; uint amount; mapping (uint => Funder) funders; } uint numCampaigns; mapping (uint => Campaign) campaigns; function newCampaign(address beneficiary, uint goal) returns (uint campaignID) { campaignID = numCampaigns++; // campaignID is return variable // Creates new struct and saves in storage. We leave out the mapping type. campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0); } function contribute(uint campaignID) payable { Campaign c = campaigns[campaignID]; // Creates a new temporary memory struct, initialised with the given values // and copies it over to storage. // Note that you can also use Funder(msg.sender, msg.value) to initialise. c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value}); c.amount += msg.value; } function checkGoalReached(uint campaignID) returns (bool reached) { Campaign c = campaigns[campaignID]; if (c.amount < c.fundingGoal) return false; uint amount = c.amount; c.amount = 0; c.beneficiary.transfer(amount); return true; } }
結構能夠用在mapping和數組的內部,固然結構自己也能夠包含mapping或是數組。雖然結構自己能夠做爲mapping的成員值,可是結構不能夠包含它自己類型。這個約束是有必要的,是爲了防止出現結構套結構,無限的循環套用。注意:結構是賦值給一個局部變量(默認爲存儲空間數據位置),不是拷貝結構,而是存儲一個引用指向局部變量。固然,不用賦值給局部變量,也能夠直接訪問結構體的成員 campaigns[campaignID].amount = 0
Mappings類型定義爲 mapping(_KeyType => _ValueType),這裏 _KeyType能夠是除了mapping外的其餘任意類型。 _ValueType 能夠是任意類型,包括mapping。Mapping能夠當作是一個哈希表,初始化每一個存在的key,對應的value的值會初始化爲全部的字節都爲0。mapping的key事實上不是存在mapping中,只有key的keccak256 哈希才用於查找值。因此,mapping是沒有長度和設置key和value的概念。mapping只容許靜態變量或是內部方法中的存儲空間引用類型。
pragma solidity ^0.4.0; contract MappingExample { mapping(address => uint) public balances; function update(uint newBalance) { balances[msg.sender] = newBalance; } } contract MappingUser { function f() returns (uint) { return MappingExample(<address>).balances(this); } }
若是a是一個LValue(一個變量,或是能夠被賦值的),提供了一下操做:
a+= e 等於 a = a + e;一樣 -=
, *=
, /=
, %=
, a |=
, &=
和 ^= , a++ , a--, ++a, --a。
delete a 爲a賦值一個初始值。好比 對於 整型,a = 0; 這個也能夠用於數組,把一個動態數組長度設置爲0 或是靜態數組中的全部元素重設爲初始值。對於結構體來講,也是重設結構體裏的全部元素。delete對於mapping是無效的。因此delete 一個結構體,會遞歸重置全部元素,除了mapping。可是對於mapping的單獨的key或是key指定的元素值能夠被 delete。
delete a是表示重設a的值。
pragma solidity ^0.4.0; contract DeleteExample { uint data; uint[] dataArray; function f() { uint x = data; delete x; // sets x to 0, does not affect data delete data; // sets data to 0, does not affect x which still holds a copy uint[] y = dataArray; delete dataArray; // this sets dataArray.length to zero, but as uint[] is a complex object, also // y is affected which is an alias to the storage object // On the other hand: "delete y" is not valid, as assignments to local variables // referencing storage objects can only be made from existing storage objects. } }
若是操做兩個不一樣類型的數據,編譯器會嘗試隱式轉換其中的一個操做數類型到另一個操做類型。一般來講,若是語法是有意義而且不會出現信息丟失狀況下,值之間的隱式轉換是能夠的:uint8轉換到 uint16, uint 128到 uint 256。可是 uint8不能夠轉換到 uint256,由於uint256不能包含uint8的全部數字,好比 -1。無符號整型能夠轉換成一樣大小的 bytes,可是反過來不行。全部能夠轉換成uint160的均可以轉換成 address。
若是編譯器不容許隱式轉換,可是你本身知道本身的作法,能夠進行顯式轉換。可是顯式轉換可能出現不少異常狀況,須要充分測試。好比:
int8 y = -3; uint x = uint(y);
若是嘗試去強制轉換到一個小的數據類型,會出現高位數據丟失狀況
uint32 a = 0x12345678; uint16 b = uint16(a); // b will be 0x5678 now
歡迎你們關注微信號:蝸牛講技術。掃下面的二維碼