solidity簡(jiǎn)介
本文默認(rèn)讀者已掌握至少一種面向?qū)ο缶幊陶Z(yǔ)言卫枝,所以文中一些概念會(huì)借助其他語(yǔ)言進(jìn)行類比掂榔。
solidity是用于實(shí)現(xiàn)智能合約的一種面向合約的高級(jí)編程語(yǔ)言视卢,solidity受到C++酥泛、Python和JavaScript的影響月趟,被設(shè)計(jì)為可運(yùn)行在以太坊虛擬機(jī)(EVM)上杨幼,所以用戶無(wú)需擔(dān)心代碼的可移植性和跨平臺(tái)等問(wèn)題撇簿。solidity是一種靜態(tài)類型的語(yǔ)言聂渊,支持繼承、庫(kù)引用等特性四瘫,并且用戶可自定義復(fù)雜的結(jié)構(gòu)類型汉嗽。
目前嘗試 Solidity 編程的最好的方式是使用 Remix (由于是網(wǎng)頁(yè)IDE可能加載起來(lái)需要一定的時(shí)間)。Remix 是一個(gè)基于 Web 的 IDE找蜜,它可以讓你編寫(xiě) Solidity 智能合約饼暑,然后部署并運(yùn)行該智能合約,它看起來(lái)是這樣子的:
也可以使用sublime或vs code等編輯器編寫(xiě) Solidity 代碼洗做,然后復(fù)制粘貼到Remix上部署運(yùn)行弓叛。
solidity官網(wǎng)地址如下:
合約文件
本小節(jié)我們來(lái)說(shuō)說(shuō)合約文件,眾所周知任何語(yǔ)言所編寫(xiě)的代碼都需要存儲(chǔ)在一個(gè)文件里诚纸,并且該文件都有一個(gè)特定的后綴名撰筷,我們一般將這種文件稱之為代碼文件。
solidity代碼文件的后綴名為.sol
畦徘,但我們通常會(huì)把使用solidity編寫(xiě)的文件稱之為合約文件毕籽,一個(gè)合約文件通常會(huì)包含四個(gè)部分,其實(shí)與我們平時(shí)所編寫(xiě)其他語(yǔ)言的代碼文件是類似的井辆,如下圖所示:
版本聲明的代碼需寫(xiě)在合約文件的開(kāi)頭关筒,接著可以根據(jù)實(shí)際情況導(dǎo)入一些合約,所謂導(dǎo)入合約也就類似于其他面向?qū)ο蟮恼Z(yǔ)言導(dǎo)入某個(gè)類的概念掘剪。然后就是聲明一個(gè)合約平委,在合約里編寫(xiě)具體的代碼,其實(shí)這里的合約與我們所熟悉的類的概念基本上是一樣的夺谁,可以暫時(shí)將它們當(dāng)做同一個(gè)東西廉赔。
我們先來(lái)對(duì)一個(gè)較為完整的合約代碼進(jìn)行一個(gè)預(yù)覽,在之后會(huì)對(duì)代碼中的每個(gè)部分進(jìn)行逐一介紹:
// 版本聲明
pragma solidity ^0.4.0;
// 導(dǎo)入一個(gè)合約
import "solidity_for_import.sol";
// 定義一個(gè)合約
contract ContractTest {
// 定義一個(gè)無(wú)符號(hào)整型變量
uint a;
// 定義一個(gè)事件
event Set_A(uint a);
// 定義一個(gè)函數(shù)
function setA(uint x) public {
a = x;
// 觸發(fā)一個(gè)事件
emit Set_A(x);
}
// 定義一個(gè)具有返回值的函數(shù)
function getA() public returns (uint) {
return a;
}
// 自定義一個(gè)結(jié)構(gòu)類型
struct Pos {
// 定義一個(gè)有符號(hào)整型變量
int lat;
int lng;
}
// 定義一個(gè)地址類型匾鸥,每個(gè)合約都運(yùn)行在一個(gè)特定的地址上
address public addr;
// 定義一個(gè)函數(shù)修改器
modifier owner () {
require(msg.sender == addr);
_;
}
// 讓函數(shù)使用函數(shù)修改器
function mine() public owner {
a += 1;
}
}
這里對(duì)函數(shù)修改器做一個(gè)簡(jiǎn)單的說(shuō)明:
函數(shù)修改器的概念類似于python中的裝飾器蜡塌,其核心目的都是給函數(shù)增加函數(shù)內(nèi)沒(méi)有定義的功能,也就是對(duì)函數(shù)進(jìn)行增強(qiáng)
從以上代碼中勿负,可以看到owner
函數(shù)修改器里定義了一句條件代碼馏艾,其意義為:
當(dāng)
msg.sender
等于addr
地址變量時(shí),才繼續(xù)往下執(zhí)行奴愉,因?yàn)檫@個(gè)require函數(shù)是solidity校驗(yàn)條件用的琅摩,若不符合條件就會(huì)拋出異常
mine函數(shù)使用了owner函數(shù)修改器后,那么mine函數(shù)在執(zhí)行之前锭硼,會(huì)先執(zhí)行owner函數(shù)修改器里的條件代碼房资,也就是說(shuō)當(dāng)msg.sender
等于addr
成立的話,才會(huì)執(zhí)行mine函數(shù)里a += 1;
的代碼檀头,否則就不會(huì)執(zhí)行轰异。從中也可以看出函數(shù)修改器里的_;
語(yǔ)句岖沛,其實(shí)表示的就是mine函數(shù)里的代碼,如此一來(lái)在不修改mine函數(shù)的前提下搭独,給mine函數(shù)增加了額外的功能婴削。
solidity 類型
Solidity是一種靜態(tài)類型語(yǔ)言,意味著每個(gè)變量(本地或狀態(tài)變量)需要在編譯時(shí)指定變量的類型(或至少可以推導(dǎo)出類型)牙肝,Solidity提供了一些基本類型可以用來(lái)組合成復(fù)雜類型唉俗。
Solidity和大多數(shù)語(yǔ)言一樣,有兩種類型:
- 值類型(Value Type) - 變量在賦值或傳參時(shí)惊奇,總是進(jìn)行值拷貝互躬。
- 引用類型(Reference Types)
solidity所包含的值類型如下:
注:其中標(biāo)紅的是最常用的類型
官網(wǎng)關(guān)于solidity類型的文檔地址如下:
1.布爾類型取值范圍是true和false播赁,使用bool關(guān)鍵字進(jìn)行聲明颂郎,聲明方式如下:
// 版本聲明
pragma solidity ^0.4.0;
// 定義一個(gè)合約
contract ContractTest {
bool b1 = true;
bool b2 = false;
}
2.solidity中有兩種整型的定義方式,一種是無(wú)符號(hào)整型容为,另一種則是有符號(hào)整型乓序。并且支持關(guān)鍵字uint8 到 uint256 (以8步進(jìn)),uint 和 int 默認(rèn)對(duì)應(yīng)的是 uint256 和 int256坎背。如下示例:
// 版本聲明
pragma solidity ^0.4.0;
// 定義一個(gè)合約
contract ContractTest {
// 定義一個(gè)無(wú)符號(hào)的整型變量
uint a;
// 定義一個(gè)有符號(hào)的整型變量
int i;
}
solidity常量
在solidity里使用constant關(guān)鍵字來(lái)聲明常量替劈,但并非所有的類型都支持常量,當(dāng)前支持的僅有值類型和字符串:
pragma solidity ^0.4.0;
contract C {
uint constant x = 32**22 + 8;
string constant text = "abc";
bytes32 constant myHash = keccak256("abc");
}
在solidity中還可以將函數(shù)聲明為常量得滤,該函數(shù)的返回值就是常量值陨献,這類函數(shù)將承諾自己不修改區(qū)塊鏈上任何狀態(tài):
// 定義有理數(shù)常量
function testLiterals() public constant returns (int) {
return 1;
}
// 定義字符串常量
function testStringLiterals() public constant returns (string) {
return "string";
}
// 定義16進(jìn)制常量,以關(guān)鍵字hex打頭懂更,后面緊跟用單或雙引號(hào)包裹的字符串眨业,內(nèi)容是十六進(jìn)制字符串
function testHexLiterals() public constant returns (bytes2) {
return hex"abcd";
}
有理數(shù)常量函數(shù)里的運(yùn)算可以是任意精度的,不會(huì)有溢出的問(wèn)題:
// 定義有理數(shù)常量
function testLiterals() public constant returns (int) {
return 1859874861811128585416.0 + 123.0;
}
科學(xué)符號(hào)也支持沮协,基數(shù)可以是小數(shù)龄捡,但指數(shù)必須是整數(shù),如下:
// 定義有理數(shù)常量
function testLiterals() public constant returns (int) {
return 2e10;
}
solidity地址類型
solidity中使用address關(guān)鍵字聲明地址類型變量慷暂,該類型屬于值類型聘殖,地址類型主要用于表示一個(gè)賬戶地址,一個(gè)以太坊地址的長(zhǎng)度為20字節(jié)的16進(jìn)制數(shù)行瑞,地址類型也有成員奸腺,地址是所有合約的基礎(chǔ)。
地址類型的主要成員:
- 屬性:balance血久,用來(lái)查詢賬戶余額
- 函數(shù):transfer()突照,用來(lái)轉(zhuǎn)移以太幣(默認(rèn)以wei為單位)
代碼示例如下:
pragma solidity ^0.4.7;
contract AddrTest {
// payable關(guān)鍵字定義一個(gè)可接受以太幣的函數(shù)
function deposit() public payable {
}
// 查詢賬戶余額
function getBalance() public constant returns (uint) {
return this.balance;
}
// 轉(zhuǎn)移以太幣
function transferEther(address towho) public {
towho.transfer(10);
}
}
然后我們將這段代碼復(fù)制粘貼到remix中編譯運(yùn)行看看,首先需要在Compile選項(xiàng)卡中將代碼進(jìn)行編譯:
編譯成功后洋魂,到Run選項(xiàng)卡中绷旗,部署該合約:
部署成功后喜鼓,可以查看到合約中的各個(gè)函數(shù),并且只需要點(diǎn)擊就可以運(yùn)行指定的函數(shù):
此時(shí)我們來(lái)點(diǎn)擊執(zhí)行一下getBalance函數(shù):
可以看到衔肢,此時(shí)該合約的賬戶余額為0庄岖,現(xiàn)在我們來(lái)存儲(chǔ)10個(gè)wei的以太幣到合約中:
此時(shí)再執(zhí)行g(shù)etBalance函數(shù),合約余額為10個(gè)wei:
然后我們?cè)賮?lái)看看轉(zhuǎn)移/發(fā)送以太幣的transferEther函數(shù)角骤,此時(shí)我們這個(gè)合約地址的余額為10個(gè)wei隅忿,當(dāng)我將這10個(gè)wei的以太轉(zhuǎn)移到另一個(gè)地址后,當(dāng)前合約的余額為0:
在solidity中一個(gè)能通過(guò)地址合法性檢查(address checksum test)的十六進(jìn)制常量就會(huì)被認(rèn)為是地址邦尊,如:
0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF
而不能通過(guò)地址合法性檢查的39到41位長(zhǎng)的十六進(jìn)制常量背桐,會(huì)提示一個(gè)警告,被視為普通的有理數(shù)常量蝉揍。
關(guān)于賬戶地址的合法性檢查定義參考如下提案:
solidity數(shù)組
在上文中我們提到Solidity 類型分為值類型和引用類型链峭,以上小節(jié)介紹了常見(jiàn)的值類型,接下來(lái)會(huì)介紹一下引用類型又沾。
引用類型是一個(gè)復(fù)雜類型弊仪,占用的空間通常超過(guò)256位, 拷貝時(shí)開(kāi)銷很大杖刷,因此我們需要考慮將它們存儲(chǔ)在什么位置励饵,是存儲(chǔ)在memory(內(nèi)存,數(shù)據(jù)不是永久存在)中還是存儲(chǔ)在storage(永久存儲(chǔ)在區(qū)塊鏈)中滑燃。
所有的復(fù)雜類型如數(shù)組和結(jié)構(gòu)體都有一個(gè)額外的屬性:數(shù)據(jù)的存儲(chǔ)位置(data location)役听,可為memory和storage。根據(jù)上下文的不同表窘,大多數(shù)時(shí)候數(shù)據(jù)存儲(chǔ)的位置有默認(rèn)值典予,也可以通過(guò)指定關(guān)鍵字storage和memory修改它。
函數(shù)參數(shù)(包含返回的參數(shù))默認(rèn)是memory蚊丐。而局部復(fù)雜類型變量(local variables)和狀態(tài)變量(state variables) 默認(rèn)是storage熙参。局部變量即部作用域(越過(guò)作用域即不可被訪問(wèn),等待被回收)的變量麦备,如函數(shù)內(nèi)的變量孽椰,狀態(tài)變量則是合約內(nèi)聲明的公有變量。
除此之外凛篙,還有一個(gè)存儲(chǔ)位置是:calldata黍匾,用來(lái)存儲(chǔ)函數(shù)參數(shù),是只讀的呛梆,不會(huì)永久存儲(chǔ)的一個(gè)數(shù)據(jù)位置锐涯。外部函數(shù)的參數(shù)(不包括返回參數(shù))被強(qiáng)制指定為calldata。效果與memory差不多填物。還有一個(gè)存儲(chǔ)位置是:calldata纹腌,用來(lái)存儲(chǔ)函數(shù)參數(shù)霎终,是只讀的,不會(huì)永久存儲(chǔ)的一個(gè)數(shù)據(jù)位置升薯。外部函數(shù)的參數(shù)(不包括返回參數(shù))被強(qiáng)制指定為calldata莱褒。效果與memory差不多。
數(shù)組是一種典型的引用類型涎劈,在solidity中數(shù)組的定義方式如下:
- T[k]:元素類型為T(mén)广凸,固定長(zhǎng)度為k的數(shù)組
- T[]:元素類型為T(mén),長(zhǎng)度可動(dòng)態(tài)調(diào)整的數(shù)組
- bytes和string 是一種特殊的數(shù)組蛛枚,string 可轉(zhuǎn)為 bytes谅海,而bytes則類似于byte[]
數(shù)組類型有兩個(gè)主要成員:
- 屬性:length
- 函數(shù):push()
具體的示例代碼如下:
pragma solidity ^0.4.7;
contract ArrayTest {
// 定義一個(gè)無(wú)符號(hào)整型的變長(zhǎng)數(shù)組
uint[] public numbers = [1, 2, 3];
// 定義一個(gè)字符串
string str = "abcdefg";
function getNumbersLength() public returns (uint) {
// 往數(shù)組中添加一個(gè)元素
numbers.push(4);
// 返回?cái)?shù)組的長(zhǎng)度
return numbers.length;
}
function getStrLength() public constant returns (uint) {
// 將字符串轉(zhuǎn)換為bytes并返回長(zhǎng)度
return bytes(str).length;
}
function getFirst() public constant returns (byte) {
// 將字符串轉(zhuǎn)換為bytes后,通過(guò)下標(biāo)訪問(wèn)元素
return bytes(str)[0];
}
function newMemory(uint len) public constant returns (uint) {
// 定義一個(gè)定長(zhǎng)數(shù)組并通過(guò)memory指定數(shù)組的存儲(chǔ)位置
uint[] memory memoryArr = new uint[] (len);
return memoryArr.length;
}
function changeFirst(uint[3] _data) public constant returns (uint[3]) {
// 通過(guò)索引操作元素
_data[0] = 0;
return _data;
}
}
solidity結(jié)構(gòu)體和映射
Solidity提供struct關(guān)鍵字來(lái)定義自定義類型也就是結(jié)構(gòu)體蹦浦,自定義的類型屬于引用類型扭吁,如果學(xué)習(xí)過(guò)go語(yǔ)言的話應(yīng)該對(duì)其不會(huì)陌生。如下示例:
// 版本聲明
pragma solidity ^0.4.7;
// 定義一個(gè)合約
contract ContractTest {
// 聲明一個(gè)結(jié)構(gòu)體
struct Funder {
address addr;
uint amount;
}
// 將自定義的結(jié)構(gòu)體聲明為狀態(tài)變量
Funder funder;
// 使用結(jié)構(gòu)體
function newFunder() public {
funder = Funder({addr: msg.sender, amount: 10});
}
}
solidity擁有映射類型白筹,映射類型是一種鍵值對(duì)的映射關(guān)系存儲(chǔ)結(jié)構(gòu)智末,有點(diǎn)類似于python語(yǔ)言中的字典谅摄。定義方式為mapping(_KeyType => _KeyValue)
徒河。鍵類型允許除映射、變長(zhǎng)數(shù)組送漠、合約顽照、枚舉、結(jié)構(gòu)體外的幾乎所有類型值類型沒(méi)有任何限制闽寡,可以為任何類型包括映射類型代兵。
映射可以被視作為一個(gè)哈希表,所有可能的鍵會(huì)被虛擬化的創(chuàng)建爷狈,映射到一個(gè)類型的默認(rèn)值(二進(jìn)制的全零表示)植影。在映射表中,并不存儲(chǔ)鍵的數(shù)據(jù)涎永,僅僅存儲(chǔ)它的keccak256哈希值思币,這個(gè)哈希值在查找值時(shí)需要用到。正因?yàn)榇讼畚ⅲ成涫菦](méi)有長(zhǎng)度的谷饿,也沒(méi)有鍵集合或值集合的概念。
映射類型有一點(diǎn)比較特殊妈倔,它僅能用來(lái)作為狀態(tài)變量博投,或在內(nèi)部函數(shù)中作為storage類型的引用。
可以通過(guò)將映射標(biāo)記為public盯蝴,來(lái)讓Solidity創(chuàng)建一個(gè)訪問(wèn)器毅哗。通過(guò)提供一個(gè)鍵值做為參數(shù)來(lái)訪問(wèn)它听怕,將返回對(duì)應(yīng)的值。映射的值類型也可以是映射虑绵,使用訪問(wèn)器訪問(wèn)時(shí)叉跛,要提供這個(gè)映射值所對(duì)應(yīng)的鍵,不斷重復(fù)這個(gè)過(guò)程蒸殿。
示例代碼如下:
// 版本聲明
pragma solidity ^0.4.7;
// 定義一個(gè)合約
contract ContractTest {
// 定義一個(gè)映射類型筷厘,key類型為address,value類型為uint
mapping(address => uint) public balances;
function updateBalance(uint newBalance) public {
// msg.sender作為鍵宏所,newBalance作為值酥艳,將這對(duì)鍵值添加到該映射中
balances[msg.sender] = newBalance;
}
}