有好几种语言可以用于编写以太坊智能合约,不过Solidity是最热门的语言。在本章中,我们将首先学习Solidity编程语言。然后创建一个DApp,用于证明在特定时间的存在、真实性和所有权,即证明一个文件在一个特定时间属于一个特定所有者。
要点:
- Solidity源文件的布局
- 理解Solidity的数据类型
- 合约的特殊变量和函数
- 控制结构
- 合约的结构和功能
- 编译和部署合约
Solidity源文件
Solidity源文件使用的扩展名为.sol。这里面我们使用的是0.4.2版本。
智能合约的结构
合约就像一个类(class),其中包含:
- 状态变量(state variable)
- 函数(function)
- 函数修改器(function modifier)
- 事件(event)
- 结构(structure)
- 枚举(enum)
同时,合约还支持继承与多态。
示例:
contract Sample { //状态变量 uint256 data; address owner; //定义事件 event logData(uint256 dataToLog); //函数修改器 modifier onlyOwner() { if(msg.sender!=owner) throw; } //构造器,名字与合约名一致 function Sample(uint256 initData,address initOwner) { data = initData; owner = initOwner; } //函数 function getData() returns (uint256 returnedData) { return data; } function setData() returns (uint256 newData) onlyOwner { logData(newData); data = newData; } }
代码注释:
- contract 关键字:用于声明一个合约
- data和owner:是两个状态变量。data包含一些数据,owner包含所有者的以太坊钱包地址,即部署合约者的以太坊地址
- event logData 定义事件logData,用于通知客户端:一旦data发生变化,将触发这个事件。所有事件都保存在区块链中。
- 函数修改器:onlyOwner。修改器用于在执行一个函数之前自动检测文件。这里的修改器用于检测合约所有者是否在调用函数。如果没有,则会抛出异常。
- 合约函数构造器constructor:在部署合约时,构造器用于初始化状态变量。
- function,getData()用于得到data状态变量的值,setData()用于改变data的值。
数据位置(较难理解)
通常,变量会存储在内存中。但是,在Solidity中,会根据不同的情况,变量可能会不存储在内存和文件系统中。
通常,在Solidity中,数据有一个默认位置。通常,存储有一个storage位置和一个memory位置,即本地存储与内存存储。
函数参数,包括其返还参数,默认用memory,本地变量默认用storage,例如状态变量,其数据位置强制使用storage。
注意:
- 不能把memory中存储的复杂类型分配给storage;
###什么是不同的数据类型
首先明白3点;
- Solidity是一种静态类型语言,变量存储的数据类型需要预先定义。
- 所有变量默认值都是0。
- 在Solidity中,变量是有函数作用范围的,也就是说,在函数中任何地方声明的变量将对整个函数存在适用范围。
那么Solidity提供了哪些数据类型——》
基本类型
除了数组类型、字符串类型、结构类型、枚举类型和map类型外,
其他类型均称为基本类型。
- 无符号型:例如uint8,uint16,uint24,…,uint256分别用于存储无符号的8位,16
位,24位,…,256位整数 - 有符号型:例如,int8,int16,…,int256分别用于存储8位,16位,24位,…,256位整数
- address类型:用于存储以太坊地址,用16进制表示。address类型有两个属性:balance和send。balance用于检测地址余额,send用于向地址发送以太币。send方法拿出需要转账那
些数量的wei,并根据转账是否成功返回true或者false。
注意:
- uint和int是uint256和int256的别名。
- 如果一个数字超过256位,则使用256位数据类型存储该数字的近似值。
- 数组:Solidity支持generic和byte两种数组类型。
数组有length属性,用于发现数组的长度。
注意:不可以在内存中改变数组大小,也不可以改变非动态数组大小。
字符串类型
有两种方法创建字符串:使用bytes和string。
bytes用于创建原始字符串,而string用于创建UTF-8字符串
示例:
contract sample { string myString = "";// string bytes myRawString; function sample(string initString,bytes rawStringInit) { myString = initString; string storage myString2 = myString; string memory myString3 = "ABCDE"; myString3 = "imaginecode"; myRawString = rawStringInit; myRawString.length++; } }
结构类型struct
示例
contract sample { struct myStruct { bool myBool; string myString; } myStruct s1; myStruct s2 = myStruct{true,""}; function sample(bool initBool,string initString){ s1 = myStruct(initBool,initString); myStruct memory s3 = myStruct(initBool,initString); } }
注意:函数参数不可以是结构类型,且函数不可以返回结构类型。
枚举类型 enum
示例
contract sample { enum OS {OSX, Linux,Unix,windows } OS choice; function sample(OS chosen) { choice = chosen; } function setLinux() { choice = OS.Linux; } function getChoice return (OS chosenOS) { return choice; } }
mapping 类型
- mapping类型只可以存在于storage中,不存在于memory中,因此它们是作为状态变量声明的。
- mapping类型包含key/value对,不是实际存储key,而是存储key的keccak256哈希,用于查询value。
- mapping不可以被分配给另一个mapping。
constract sample { mapping (int => string) myMap; function sample(){ myMap[key] = value; mapping (int => string) myMap2 = myMap; } }
注意:如果想访问mapping中不存在的key,返回的value为0。
delete 操作符
可用于操作任何类型的变量。
- 对动态数组使用delete操作符,则删除所有元素,其长度变为0。
- 对静态数组使用delete操作符,则重置所有索引
- 对map类型使用delete操作符,什么都不会发生,但是,对map类型的一个键使用delete操作符,则会删除与该键相关的值
示例
contract sample { struct Struct { mapping (int => int) myMap; int myNumber; } int[] myArray; Struct myStruct; function sample(int key,int value,int number,int[] array) { myStruct = Struct(number); myStruct = Struct(number); myStruct.myMap[key] = value;//对某个键赋值 myArray = array; } function reset() { delete myArray;//myArray数组长度为0 delete myStruct;//myNumber为0,myMap不变 } function deleteKey(int key) { delete myStruct.myMap[key];//删除myMap中的某个键的值 } }
基本类型之间的转换
- 隐式转换:常用。通常来说,如果没有语义信息丢失,值和类型之间可以进行隐式转换:uint8可转换为uint16,int128可转换为int256,但是int8不可转换为uint256(因为uint256不能存储,例如-1)
- Solidity也支持显式转换,如果编译器不允许在两种数据类型之间隐式转换,则可以进行显式转换。建议尽量避免显式转换,因为可能返回难以预料的结果。
示例:
uint32 a = 0x12345678; uint16 b = uint16(a); // b = 0x5678,将uint32类型显式转换为uint16,也就是说,把较大类型转换为较小类型,因此高位被截掉了
var
使用关键字var声明的变量,其变量类型根据分配给它的第一个值来动态确定。一旦分配了值,类型就固定了,所以如果给它指定另一个类型,将引起类型转换。
int256 x= 12; var y = x;//此时y的类型是int256 uint256 z = 9; y = z;//此时,报出异常,因为uint256不能转换为int256类型
但要注意的是:
- 在定义数组array和map时不能使用var。var也不能用于定义函数参数和状态变量