1. 程式人生 > >Solidity基礎入門講解

Solidity基礎入門講解

Solidity的語言型別:

  1. 靜態型別的語言:編譯前變數型別需要先確定
  2. 變數可以分為:
    值型別:賦值或者傳參時總是進行值拷貝
    引用型別:傳遞的是地址
    這裡先給出一個總覽圖:
    這裡寫圖片描述

整型:

和其他語言型別,可以用int關鍵字宣告:
有符號整數:int8,int16,int32,……int255,
無符號整數:uint8,uint16,uint32,……uint255,
跟在關鍵字後的數字表示多少位元組,表示數大小的範圍,跟預設int,表示255。
注意
整型的溢位問題分為:向上溢位和向下溢位,例如:
uint8表示的最大數==》255,如果此時加1,會出現向上溢位的錯誤,此時的結果為:0;uint8表示的最小值==》0,此時如果減1,會出現向下溢位的錯誤,結果為:255。
如何解決上述的溢位問題呢?
可以使用assert(斷言)關鍵字,進行預判斷處理。

定點浮點型:

關鍵字:fixedufixed,對應於其他語言的float和double.
3. 使用時需要宣告位數,指明大小
4. 格式:
fixedMN,其中M為:數值,代表所佔的空間位數,N為:小數點位數,M以8步進,可為8到256位,N可為0到80之間。

定長位元組陣列:

關鍵字:bytes
佔空間固定的陣列:
bytes1,bytes2,…bytes32,其中的數字的單位為位元組。
1.通過屬性.length獲取位元組陣列的長度
2.可以像字串一樣使用:bytes2=”couse”
3.可以想整型一樣運算比較和位運算
4.像陣列一樣使用索引:bytes8[k],返回第k+1個位元組,0<=k<8

常量:

整型常量:1,23,1.55
字串常量:不支援“+”運算
十六進位制常量:hex”aabb”

列舉:

1.關鍵字enum用來自定義型別:如下
enum ColorChioce{Red,Bule,Yellow}
2.可與整數進行轉換,但不能進行隱式轉換
3.列舉型別應至少有一名成員

地址型別:

關鍵字:address,表示一個賬戶地址(20位元組)
屬性:.balance,獲取餘額,單位是wei,1eth(以太幣)=10^18wei
成員函式:
1.transfer(),轉賬函式
2.send(),也是轉賬函式,錯誤時不發生異常返回false,使用時一定要檢查返回值,大部分時候使用transfer()。
addr.transfer(y) 等價於 require(addr.send(y))
注意:


send()和transfer()使用時有2300gas限制,當合約接收以太幣時,轉賬容易失敗。
3.call(),可以呼叫另外一個合約地址的函式,如下:
addr.call(函式簽名,引數):呼叫對應函式
addr.call.value(y)()功能上類似addr.transfer(y),但沒有gas的限制。
addr.call.gas():指定gas,指定呼叫函式消耗的gas。
補充:如何區分合約地址及外部地址?
答:evm提供了一個操作碼EXTCODESIZE,用來獲取地址相關聯的程式碼大小(長度),如果是外部帳號地址,沒有程式碼返回0,因此我們可以使用以下方法判斷合約地址及外部帳號地址:

uint256 size;
assembly { size := extcodesize(addr) }
return size > 0;
}

函式型別

solidity中,函式是一種型別。像其他型別⼀一樣,函式型別可以作為變數量型別,也可以作為返回值型別(函式式語⾔言的特點)
函式分為兩種型別:
1.外部(external)函式:發起EVM訊息呼叫,使用:( 地址.函式名) 進行呼叫
2.內部(internal)函式:程式碼跳轉呼叫, gas小的多,但法在合約外部呼叫,使用: (函式名) 進行呼叫,預設是內部(internal)函式。
宣告格式:
1.宣告一個函式:function foo(int) 或者 function foo(int) external returns(int)
2.宣告一個韓式型別變數:function(int) foo 或者 function(int) external returns(int) foo

資料儲存位置:

1.storage(區塊鏈中):永久儲存在區塊鏈鏈中。
例:①狀態變數

contract A {
uint a;
}

②複雜型別的區域性變數

contract A {
function fun() {
uint[] arr;
}
}

2.memory(EVM記憶體中):隨著交易的結束,記憶體釋放。
例:區域性變數

contract A {
function fun(uint[] a) {
uint b;
}}

兩者的賦值表現:
在memory和storage之間總是會建立一個完全獨⽴立的拷貝。
狀態變數量之間相互賦值,總是會建立一個完全獨⽴立的拷貝。
同樣的資料位置之間賦值只是引⽤用的傳遞。
示例程式碼:

contract TestLoc(){
    uint[] x;//x的儲存位置是storage

    function f(uint[] memoryArray) public returns (uint[]){

    x=memoryArray;//從memory 複製到storage

    uint[] y=x;//storage引用傳遞給複雜區域性變數y(y是一個storage引用)
    y[1]=2;     //此時x,y都會被修改

    g(x);//引用傳遞,g可以改變x的內容
    h(x);//拷貝到memory,h無法改變x的內容
}
    function g(uint[] storage storageArray) internal{}
    function h(uint[] memoryArray) public {}
}

陣列:

陣列宣告的兩種格式:
1.普通格式:T[k]
T: 元素型別, k: 陣列長度(可選)
uint [10] tens; uint [ ] us;
uint [] public u = [1, 2, 3]; //public陣列狀態變數量會自動生成一個對應變數量的函式,陣列長度省略
2.使用new 關鍵字:
uint[] c = new uint;、需要指定長度,不能省略
屬性:
1.length,獲取長度
uint [10] tens; //tens.length 為10
storage的變長陣列,可以通過給.length賦值調整陣列長度
memory陣列一旦建立,大小不不可調整
2..push() 新增元素,返回新長度
僅針對變長陣列,memory陣列不支援

位元組陣列:

位元組陣列bytes:
類似 byte[ ] , 不過bytes作為外部函式的引數時佔用的空間更更小,推薦使用,相比前面的定長位元組陣列,這個是不定長的。
宣告形式: bytes mybs;
bytes mybs=“hello world”

字串:

solidity中,字元串也是陣列
宣告: string mystr;
string mystr=”Solidity入門詳解”;
注意:
字串沒有.length屬性 ,但是可以通過bytes(s).length 獲取位元組陣列的長度,但也只是位元組陣列的長度,沒法獲得字串長度。
另外還可通過bytes(s)[k] : 獲取位元組陣列下標k的UTF-8 編碼。
bytes和string都可以儲存字串,那他們的區別是宣告呢?
兩者儲存資料的方式不一樣
bytes: 用來儲存任意長度的位元組資料
string: 用來儲存UTF-8編碼的字串資料
前面說到我們沒法獲取字串的長度,那怎麼才能獲取呢?
引入第三方庫:stringutils,github地址:https://github.com/Arachnid/solidity-stringutils
該第三庫支援很多強大的功能:
獲取字串長度
匹配字串串:find(), startsWith(),endsWith()
字串串拼接
….

對映

關鍵字:mapping
宣告一個對映型別:
mapping(鍵=> 值) public mymapping
鍵型別不能是變長陣列、合約型別、巢狀型別值型別無限制
如下:
mapping(address => uint) public balances;
訪問形式:
索引訪問,mymapping[鍵]
限制:
1.只能作為狀態變數,不能放在函式裡宣告
2.無法遍歷訪問,沒有長度,沒有鍵集合,沒有值集合
解決限制
因為solidity提供的這個mapping的功能實在有限,因此我們可以藉助第三方庫來實現一些有用的功能,第三方庫github地址為:https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol

結構體

關鍵字: struct,類似C語言的結構體
定義一個結構體:

struct First{
bool myBool;
uint myInt;
}

其中結構體的型別可以為:基本型別、陣列、結構體、對映
宣告與初始化:
僅宣告變量,不初始化:

First  f1;

按成員順序初始化:

First  f1 = First(true,1);

命名方式初始化:

First  f1 = First({myBool:true,myInt:1});

訪問與賦值:

First  f1 ;
f1.myBool=true;

使用限制:
結構體目前僅支援在合約內部使用,或繼承合約內使用,如果要在引數和返回值中使用結構體,函式必須宣告internal。

型別轉換:

將一個型別轉為另一個型別,轉換可分為隱式轉換和顯式轉換。
1.隱式轉換:
運算子兩邊有不同的型別,不會丟失資料下,編譯器會嘗試隱式轉換型別,如下:
uint8 -> uint16,uint256
uint16,uint256 -> uint8(出錯)
2.顯式轉換:
如果編譯器不允許隱式的自動轉換,但你知道轉換沒有問題時,可以進行強轉。
注意:不正確的轉換會帶來錯誤,如果轉換為一個更小的型別,高位將被截斷。

重置變數

對一個變數進行重置
關鍵字:delete
**使用:**delete 變數名;
使用重置變數後,變數會變為預設值,下面列出各個型別的預設值:

bool -> false;
uint -> 0
address -> 0x0
bytes -> 0x0
string ->""
注意:
delete 對對映無效

例如:

aa=true;
delete aa;//aa變為false

uint[] memory arr = new uint[](7);
delete arr; // a.length = 0;

CustomType memory ct = CustomType(true, 100);
delete ct; // ct.myBool = false ; myInt = 0;

特別注意:
delete 不影響值拷貝的變數,但是如果是傳遞引用的變數,就會受到影響。

內建API

使用時間日期:
1.now函式:獲取當前時間
2.引入第三方庫執行更多功能,推薦github·上一個第三方庫:https://github.com/pipermerriam/ethereum-datetime
區塊和交易相關API:

blockhash(uint blockNumber) returns (bytes32):返回給定區塊號的雜湊值,只支援最近256個區塊,且不包含當前區塊。
block.coinbase (address): 當前塊礦工的地址。
block.difficulty (uint):當前塊的難度。
block.gaslimit (uint):當前塊的gaslimit。
block.number (uint):當前區塊的塊號。
block.timestamp (uint): 當前塊的Unix時間戳(從1970/1/1 00:00:00 UTC開始所經過的秒數)
gasleft() (uint256): 獲取剩餘gas。
msg.data (bytes): 完整的呼叫資料(calldata)。
msg.gas (uint): 當前還剩的gas(棄用)。
msg.sender (address): 當前呼叫發起人的地址。
msg.sig (bytes4):呼叫資料(calldata)的前四個位元組(例如為:函式識別符號)。
msg.value (uint): 這個訊息所附帶的以太幣,單位為wei。
now (uint): 當前塊的時間戳(block.timestamp的別名)
tx.gasprice (uint) : 交易的gas價格。
tx.origin (address): 交易的傳送者(全呼叫鏈)

什麼是ABI?
ABI是Application Binary Interface的縮寫,即應用程式二進位制介面。
當我們向 合約地址傳送一個交易,交易的內容就ABI編碼資料。
作用:用來計算一個函式以及引數的ABI編碼資料。
ABI的編碼函式有如下這些,用來直接得到ABI編碼資訊:

* abi.encode(...) returns (bytes):計算引數的ABI編碼。
* abi.encodePacked(...) returns (bytes):計算引數的緊密打包編碼
* abi. encodeWithSelector(bytes4 selector, ...) returns (bytes): 計算函式選擇器和引數的ABI編碼
* abi.encodeWithSignature(string signature, ...) returns (bytes): 等價於* abi.encodeWithSelector(bytes4(keccak256(signature), ...)

錯誤處理:
solidity的錯誤處理如下:
當發生錯誤處理的時候,會出現事件的回滾。
do something1
do error
do something2
當執行到do error,錯誤發生,此時 do something2,do something1都不會執行。
錯誤處理函式:
1.assert(bool condition):用於判斷內部錯誤,天劍不滿足時丟擲異常,此時會消耗掉所剩餘的gas。
2.require(bool condition):用於判斷輸或者外部元件錯誤,條件不滿足時判處異常,此時gas會返回還給呼叫者,區別於assert。
require(bool condition,string message):同時上,多了一個錯誤資訊。
3.revert():終止執行還原改變的狀態。
revert(string,reason):同上,提供一個錯誤提醒資訊。
用require還是assert?
使用require的情況:
1.用於檢查使用者輸入;
2.用於檢查合約呼叫返回值,如require(addr.send(1));
3.用於檢查狀態,如:msg.sender==owner;
4.通常用於函式的開頭;
5.不知道使用哪一個的時候
使用assert的情況:
1.用於檢查溢位錯誤,如‘z=x+y;assert(z>=x)’;
2.用於檢查不應該發生的異常情況;
3.用於在狀態改變之後,檢查合約狀態;
4.儘量少用assert;
5.通常用於函式中間或者結尾;

函式修改器:

關鍵字:modifier
函式修改器可以改變一個函式的行為,通常用於在函式執行檢查某種前置條件。

modifier onlyOwener{
        require(msg.sender==owner);
        _;
}

function my() public onlyOwener{
        //do something;
}

函式修改器修飾函式時,函式題被插入到“_”處。所以在執行my()函式前,會執行onlyOwener這個函式,如果檢查通過,再執行my()函式。
一些補充:
1.函式修改器可被繼承
2.函式修改器可接受引數
3.多個函式修改器一起使用(空格隔開,修改器會一次檢查執行)

函式修飾符:

1.payable:邊表示一個函式能附加以太幣呼叫:
function f(uint a) public payable returns (uint):{
}
2.view:表示一個函式不能修改狀態。
view函式和constant等價,constant在0.5版本之後會棄用。使用了這個修飾符的函式不會消耗gas。
3.pure:表示一個函式不讀取狀態,也不修改狀態。本地執行不消耗gas。

solidity的繼承:

1.繼承的合約可以訪問所有非private成員。
external:外部訪問、
public:內外都可以、
internal:內部級繼承、
private:內部,繼承的也不行
2..is表示繼承,通過複製程式碼的方式實現繼承

contract  A{
}
contract  B is A{
}

3.如果父合約與建構函式,那麼派生合約需要呼叫父合約的建構函式,如果有引數,派生合約需要提供引數呼叫父合約建構函式。
形式一:

contract  B is A(uint a){
}
//其中a為傳遞給父合約A的引數

形式二:

contract B is A{
    constructor(uint a) A(a) public{
    }
    }

3.多重繼承:
is後接多個合約,基類合約在is後的順序很重要。繼承順序原則是從最接近基類到最接近派生類。
4.抽象合約:
合約在沒有函式體(實現)的函式
合約不能通過編譯,只能繼承
5.介面:
關鍵字:interface
裡面的函式必須是未實現的。函式不能繼承其他合約或者介面。不能定義構造器,變數,結構體,列舉類。
抽象合約和介面的作用主要是用做基類。

1.庫其實就是一個特殊的合約:
可以像其他合約一樣進行部署,但是沒有狀態變數,不能存以太幣。
2.可重用:
部署一次,在不同的合約內反覆使用。節約gas,相同功能的程式碼不用重複部署
3.定義庫、使用庫:
定義:

library  my{
    myfunction();
}

使用:
先匯入:import “./my”,,然後my.myfunction();
4.網上好用的常用庫:
openzeppelin,github地址是:https://github.com/Openzeppelin/openzeppelin-solidity
另外還有:ethereum-libraries,dapp-bin,Stringutils等

回撥函式

定義:無名稱。無引數,無返回值的函式
一個合約可以有一個回退函式。當給合約轉以太幣時,需要有payable回退函式。如果呼叫合約時,沒有匹配上任何函式,就會呼叫回退函式。

未完。。。