學習途徑:https://cryptozombies.io/zh/
但是這個網站代碼校對的時候應該不是跑編譯器,而是直接和正確代碼校對,所以常常明明是對的也過不了。
第1章: 合約
從最基本的開始入手:
Solidity 的代碼都包裹在合約裏面. 一份合約就是以太應幣應用的基本模塊, 所有的變量和函數都屬於一份合約, 它是你所有應用的起點.
一份名爲 HelloWorld 的空合約如下:
contract HelloWorld {
}
版本指令
所有的 Solidity 源碼都必須冠以 “version pragma” — 標明 Solidity 編譯器的版本. 以避免將來新的編譯器可能破壞你的代碼。
例如: pragma solidity ^0.4.19; (當前 Solidity 的最新版本是 0.4.19).
綜上所述, 下面就是一個最基本的合約 — 每次建立一個新的項目時的第一段代碼:
pragma solidity ^0.4.19;
contract HelloWorld {
}
第2章: 狀態變量和整數
真棒!我們已經爲我們的合約做了一個外殼, 下面學習 Solidity 中如何使用變量。
狀態變量是被永久地保存在合約中。也就是說它們被寫入以太幣區塊鏈中. 想象成寫入一個數據庫。
例子:
contract Example {
// 這個無符號整數將會永久的被保存在區塊鏈中
uint myUnsignedInteger = 100;
}
在上面的例子中,定義 myUnsignedInteger 爲 uint 類型,並賦值100。
無符號整數: uint
uint 無符號數據類型, 指其值不能是負數,對於有符號的整數存在名爲 int 的數據類型。
注: Solidity中, uint 實際上是 uint256代名詞, 一個256位的無符號整數。你也可以定義位數少的uints — uint8, uint16, uint32, 等…… 但一般來講你願意使用簡單的 uint, 除非在某些特殊情況下,這我們後面會講。
第3章: 數學運算
在 Solidity 中,數學運算很直觀明瞭,與其它程序設計語言相同:
加法: x + y
減法: x - y,
乘法: x * y
除法: x / y
取模 / 求餘: x % y (例如, 13 % 5 餘 3, 因爲13除以5,餘3)
Solidity 還支持 乘方操作 (如:x 的 y次方) // 例如: 5 ** 2 = 25
uint x = 5 ** 2; // equal to 5^2 = 25
第4章: 結構體
有時你需要更復雜的數據類型,Solidity 提供了 結構體:
struct Person {
uint age;
string name;
}
結構體允許你生成一個更復雜的數據類型,它有多個屬性。
注:我們剛剛引進了一個新類型, string。 字符串用於保存任意長度的 UTF-8 編碼數據。 如: string greeting = “Hello world!”。
第5章: 數組
如果你想建立一個集合,可以用 數組_這樣的數據類型. Solidity 支持兩種數組: 靜態 數組和_動態 數組:
// 固定長度爲2的靜態數組:
uint[2] fixedArray;
// 固定長度爲5的string類型的靜態數組:
string[5] stringArray;
// 動態數組,長度不固定,可以動態添加元素:
uint[] dynamicArray;
你也可以建立一個 結構體類型的數組 例如,上一章提到的 Person:
Person[] people; // 這是動態數組,我們可以不斷添加元素
記住:狀態變量被永久保存在區塊鏈中。所以在你的合約中創建動態數組來保存成結構的數據是非常有意義的。
公共數組
你可以定義 public 數組, Solidity 會自動創建 getter 方法. 語法如下:
Person[] public people;
其它的合約可以從這個數組讀取數據(但不能寫入數據),所以這在合約中是一個有用的保存公共數據的模式。
第6章: 定義函數
在 Solidity 中函數定義的句法如下:
function eatHamburgers(string _name, uint _amount) {
}
這是一個名爲 eatHamburgers 的函數,它接受兩個參數:一個 string類型的 和 一個 uint類型的。現在函數內部還是空的。
注:: 習慣上函數裏的變量都是以(_)開頭 (但不是硬性規定) 以區別全局變量。我們整個教程都會沿用這個習慣。
我們的函數定義如下:
eatHamburgers("vitalik", 100);
第7章: 使用結構體和數組
創建新的結構體
還記得上個例子中的 Person 結構嗎?
struct Person {
uint age;
string name;
}
Person[] public people;
現在我們學習創建新的 Person 結構,然後把它加入到名爲 people 的數組中.
// 創建一個新的Person:
Person satoshi = Person(172, "Satoshi");
// 將新創建的satoshi添加進people數組:
people.push(satoshi);
你也可以兩步並一步,用一行代碼更簡潔:
people.push(Person(16, "Vitalik"));
注:array.push() 在數組的 尾部 加入新元素 ,所以元素在數組中的順序就是我們添加的順序, 如:
uint[] numbers;
numbers.push(5);
numbers.push(10);
numbers.push(15);
// numbers is now equal to [5, 10, 15]
第8章: 私有 / 公共函數
Solidity 定義的函數的屬性默認爲公共。 這就意味着任何一方 (或其它合約) 都可以調用你合約裏的函數。
顯然,不是什麼時候都需要這樣,而且這樣的合約易於受到攻擊。 所以將自己的函數定義爲私有是一個好的編程習慣,只有當你需要外部世界調用它時纔將它設置爲公共。
如何定義一個私有的函數呢?
uint[] numbers;
function _addToArray(uint _number) private {
numbers.push(_number);
}
這意味着只有我們合約中的其它函數才能夠調用這個函數,給 numbers 數組添加新成員。
可以看到,在函數名字後面使用關鍵字 private 即可。和函數的參數類似,私有函數的名字用(_)起始。
第9章: 函數的更多屬性
本章中我們將學習函數的返回值和修飾符。
返回值
要想函數返回一個數值,按如下定義:
注意是returns
string greeting = "What's up dog";
function sayHello() public returns (string) {
return greeting;
}
Solidity 裏,函數的定義裏可包含返回值的數據類型(如本例中 string)。
函數的修飾符
上面的函數實際上沒有改變 Solidity 裏的狀態,即,它沒有改變任何值或者寫任何東西。
這種情況下我們可以把函數定義爲view, 意味着它只能讀取數據不能更改數據:
function sayHello() public view returns (string) {
Solidity 還支持 pure 函數, 表明這個函數甚至都不訪問應用裏的數據,例如:
function _multiply(uint a, uint b) private pure returns (uint) {
return a * b;
}
這個函數甚至都不讀取應用裏的狀態 — 它的返回值完全取決於它的輸入參數,在這種情況下我們把函數定義爲 pure.
注:可能很難記住何時把函數標記爲 pure/view。 幸運的是, Solidity 編輯器會給出提示,提醒你使用這些修飾符。
第10章: Keccak256 和 類型轉換
如何讓 _generateRandomDna 函數返回一個全(半) 隨機的 uint?
Ethereum 內部有一個散列函數keccak256,它用了SHA3版本。一個散列函數基本上就是把一個字符串轉換爲一個256位的16進制數字。字符串的一個微小變化會引起散列數據極大變化。
這在 Ethereum 中有很多應用,但是現在我們只是用它造一個僞隨機數。
例子:
//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256("aaaab");
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256("aaaac");
顯而易見,輸入字符串只改變了一個字母,輸出就已經天壤之別了。
注: 在區塊鏈中安全地產生一個隨機數是一個很難的問題, 本例的方法不安全,但是在我們的Zombie DNA算法裏不是那麼重要,已經很好地滿足我們的需要了。
類型轉換
有時你需要變換數據類型。例如:
uint8 a = 5;
uint b = 6;
// 將會拋出錯誤,因爲 a * b 返回 uint, 而不是 uint8:
uint8 c = a * b;
// 我們需要將 b 轉換爲 uint8:
uint8 c = a * uint8(b);
上面, a * b 返回類型是 uint, 但是當我們嘗試用 uint8 類型接收時, 就會造成潛在的錯誤。如果把它的數據類型轉換爲 uint8, 就可以了,編譯器也不會出錯。
第11章: 事件
我們的合約幾乎就要完成了!讓我們加上一個事件.
事件 是合約和區塊鏈通訊的一種機制。你的前端應用“監聽”某些事件,並做出反應。
例子:
// 這裏建立事件
event IntegersAdded(uint x, uint y, uint result);
function add(uint _x, uint _y) public {
uint result = _x + _y;
//觸發事件,通知app
IntegersAdded(_x, _y, result);
return result;
}
你的 app 前端可以監聽這個事件。JavaScript 實現如下:
YourContract.IntegersAdded(function(error, result) {
// 幹些事
}