继续学习solidity语法。
2.1 函数类型
以下是在Solidity中声明函数的方式。
function sampleFunc(string name, uint amount) {
}
上面声明的是一个空体函数,它有两个参数:一个字符串和一个 uint。
可以这样调用此函数:
sampleFunc("Shashank", 10000);
谈到函数,Solidity还提供函数修饰符。
2.2 函数四种访问权限
函数声明有public、private、internal和external四种访问权限
-
1.函数默认声明为public,即可以以internal方式调用,也可以通过external方式调用。可以理解为能够被内部合约访问和外部合约访问。
-
2.Internal声明的只允许通过internal方式调用,不能被外部合约。而external能够被外部合约访问。
-
3.private和internal类似,都不能被外部合约访问,唯一的不同是private函数不能被子类调用,而internal可以。
contract FunctionTest{
function publicFunc() {}
function callFunc(){
//以`internal`的方式调用函数
publicFunc();
//以`external`的方式调用函数
this.publicFunc();
}
function internalFunc() internal{}
function externalFunc() external{}
}
contract FunctionTest1 {
function externalCall(FuntionTest ft){
//调用另一个合约的外部函数
ft.publicFunc();
ft.externalFunc();
//ft.internalFunc();调用失败,无法调用internal函数
}
}
2.3 pure、view、constant函数返回值定义类型
当函数有返回值时,可以添加这三种定义,用这三种方式定义的函数都只执行读操作,不会进行编译执行。即用了这三种方式定义的函数,不会执行函数里的逻辑,只会执行一个返回的读操作。所以执行这些函数不需要消耗gas费用。
-
pure区别是用于返回非变量,如returns 10;
-
而view和constant用于返回全局变量,两者的区别为新旧版本
-
但是此功能4.x版本可用,5.x版本废弃
-
声明才能返回
contract HelloWorld4{
uint public a = 1;
//由于被constant声明的函数执行读操作,所以a无法被修改
//执行为f(),a依然为1
function f() constant{
a = 3;
}
}
2.4 函数修饰符
函数修饰符看起来跟函数没什么不同,不过关键字modifier 告诉编译器,这是个modifier(修饰符),而不是个function(函数)。它不能像函数那样被直接调用,只能被添加到函数定义的末尾,用以改变函数的行为。
示例:如果要仅通过函数的所有者或创建者调用kill contract函数。
/**
* @dev 调用者不是‘主人’,就会抛出异常
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
onlyOwner 函数修饰符是这么用的:onlyOwner,它会限制陌生人的访问,将访问某些函数的权限锁定在 owner 上
contract MyContract is Ownable {
event LaughManiacally(string laughter);
//注意!`onlyOwner`上场 :
function likeABoss() external onlyOwner {
LaughManiacally("Muahahahaha");
}
}
修改器(Modifiers)可以用来轻易的改变一个函数的行为。比如用于在函数执行前检查某种前置条件。修改器是一种合约属性,可被继承,同时还可被派生的合约重写(override)。
2.5 继承
Solidity通过复制包含多态的代码来支持多重继承。
contract Owned {
address Owner ;
function owned() {
owner = msg.sender;
}
}
contract Mortal is Owned { // 'is' keyword is used for inheritance
function kill(){
self-destruct(owner);
}
}
contract User is Owned, Mortal{ //Multiple inheritance(多重继承)
string public UserName;
function User(string _name){
UserName = _name;
}
}
2.6 事件
在Solidity 代码中,使用event 关键字来定义一个事件,如:
event EventName(address bidder, uint amount);
这个用法和定义函数式一样的,并且事件在合约中同样可以被继承。触发一个事件使用emit(说明,之前的版本里并不需要使用emit),如:
emit EventName(msg.sender, msg.value);
触发事件可以在任何函数中调用,如:
function testEvent() public {
// 触发一个事件
emit EventName(msg.sender, msg.value);
}
2.7 异常处理
Solidity使用状态恢复来处理异常,就是说当抛出异常时将恢复到调用(包括自调用)前的状态。
抛出异常的方式有assert,require,revert,throw。
assert函数,用于条件检查,只能测试内部错误和检查常量。
//检查内部计算是否会整型溢出
function add(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
require函数,也是用于条件检查,用于测试调用的输入或者合约状态变量。
function sendHalf(address addr) payable returns (uint balance) {
require(msg.value % 2 == 0); // 只允许偶数
.....
}
revert 函数用于标记错误并恢复当前调用。
function buy(uint amount) payable {
if (amount > msg.value / 2 ether)
revert("Not enough Ether provided.");
}
throw 和revert一样,但是throw在0.4.13被弃用,将来会被淘汰。
2.8 状态变量storage和局部变量memory
两者区别很容易理解,memory可以理解为临时变量,不会记录在链上,而storage是永久存储的。
变量定义时默认为storage,而作为函数参数时,默认为memory
contract HelloWorld{
//等价于 string storage public a;
string public a;
//参数等价于string memory _a
function changeNum(string _a){
}
}
当函数参数为memory类型时,相当于值传递,storage才是指针传递
contract HelloWorld2{
string public a;
function HelloWorld2(){
a = "abc";
}
function f(){
changeNum(a);
}
function changeNum(string _a){
bytes(_a)[0] = "d";
//由于_a默认为memory,所以_a只是值传递,所以此时修改a的值是不成功的,输出还是abc
//需要把函数参数修改为string storage _a,才能输出dbc
}
}
将变量赋值给一个新变量时,新变量的类型由赋值给它的类型决定。
function changeNum(string _a){
//_a默认为memory类型,所以b也为memory
string b = _a;
bytes(_a)[0] = "d";
}
2.9 接口
如果我们的合约需要和区块链上的其他的合约会话,则需先定义一个 interface (接口)。
假设在区块链上有这么一个合约:
contract LuckyNumber {
mapping(address => uint) numbers;
function setNum(uint _num) public {
numbers[msg.sender] = _num;
}
function getNum(address _myAddress) public view returns (uint) {
return numbers[_myAddress];
}
}
现在假设我们有一个外部合约,使用 getNum 函数可读取其中的数据。
首先,我们定义 LuckyNumber 合约的 interface :
contract NumberInterface {
function getNum(address _myAddress) public view returns (uint);
}
在我们的 app 代码中使用这个接口,合约就知道其他合约的函数是怎样的,应该如何调用,以及可期待什么类型的返回值。
上面的接口,我们可以在合约中这样使用:
contract MyContract {
address NumberInterfaceAddress = 0xab38...;
// ^ 这是FavoriteNumber合约在以太坊上的地址
NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
// 现在变量 `numberContract` 指向另一个合约对象
function someFunction() public {
// 现在我们可以调用在那个合约中声明的 `getNum`函数:
uint num = numberContract.getNum(msg.sender);
// ...在这儿使用 `num`变量做些什么
}
}
通过这种方式,只要将您合约的可见性设置为public(公共)或external(外部),它们就可以与以太坊区块链上的任何其他合约进行交互。
处理多返回值
function multipleReturns() internal returns(uint a, uint b, uint c) {
return (1, 2, 3);
}
function processMultipleReturns() external {
uint a;
uint b;
uint c;
// 这样来做批量赋值:
(a, b, c) = multipleReturns();
}
// 或者如果我们只想返回其中一个变量:
function getLastReturnValue() external {
uint c;
// 可以对其他字段留空:
(,,c) = multipleReturns();
}
3.0 payable 修饰符
payable 方法是让 Solidity 和以太坊变得如此酷的一部分 —— 它们是一种可以接收以太的特殊函数。
在以太坊中, 因为钱 (以太), 数据 (事务负载), 以及合约代码本身都存在于以太坊。你可以在同时调用函数 并付钱给另外一个合约。
contract OnlineStore {
function buySomething() external payable {
// 检查以确定0.001以太发送出去来运行函数:
require(msg.value == 0.001 ether);
// 如果为真,一些用来向函数调用者发送数字内容的逻辑
transferThing(msg.sender);
}
}
在这里,msg.value 是一种可以查看向合约发送了多少以太的方法,另外 ether 是一个內建单元。
这里发生的事是,一些人会从 web3.js 调用这个函数 (从DApp的前端), 像这样 :
// 假设 OnlineStore 在以太坊上指向你的合约:
OnlineStore.buySomething().send(from: web3.eth.defaultAccount, value: web3.utils.toWei(0.001))
注意这个 value 字段, JavaScript
调用来指定发送多少(0.001)以太。如果把事务想象成一个信封,你发送到函数的参数就是信的内容。添加一个 value 很像在信封里面放钱
—— 信件内容和钱同时发送给了接收者。
3.1 基本类型之间的转换
隐式转换
如果一个运算符用在两个不同类型的变量之间,编译器将会隐式地将其中一个类型转换成另一个类型。一般来说,只要值类型之间的转换在语义上能行,而且转换过程没有信息丢失,基本上都是可行的。
如uint8转换成uint16,uint128转换成uint256,但是uint8不能转换成uint256(因为uint256不能涵盖某些值如-1)。通常无符号整型内转换成与它大小相等或者更大的字节类型,反之不能。任何可以转换成uint160的类型都能转换成地址类型。
显式转换
某些情况编译器不支持隐式转换,但是用户清楚他在做什么,这时候可以使用显式转换。一定要先进行测试,保证结果可控。
示例:
int8 y = -3;
uint x = uint(y);
x值将为 0xfff...fd(63个f),这是-3的256位补码形式
如果一个显式转换成更小的类型,相应的高位会被抛弃:
uint32 a = 0x12345678;
uint16 b = uint16(a); // b的值为 0x5678
3.2 内置单位
1、货币单位
wei
finney
szabo
ether
若不加后缀,则默认都为wei
2、时间单位
1==1 seconds
1 minutes == 60 seconds
……
特别注意,使用这些单位时要特别小心,因为一年并不总有365天;同时因为有闰秒的存在,一天也并不总是24小时。为了保证日历库的精确性,最好由外部供应商定期更新。
3、区块和交易属性
block.blockhash(uint blockNumber) returns(bytes32):获取特定区块的散列值,只对不包括当前区块的256个最近的区块有效。block.coinbase:类型为address,表示当前区块“矿工”的帐号地址
block.difficulty:类型为uint,表示当前区块的挖矿难度
block.gaslimit:类型为uint,表示当前区块的Gas限制
block.number:类型为uint,表示当前区块编号
block.timestamp:类型为uint,以UNIX时间戳的形式表示当前区块的产生时间
msg.data:类型为bytes,表示完整的调用数据
msg.gas:类型为uint,表示剩余的Gas
msg.sender:类型为address,表示当前消息的发送者地址
msg.sig:类型为bytes4,调用数据的前4字节,函数标识符
msg.value:类型为uint,表示该消息转账的以太币数额,单位为wei
now:类型为uint,表示当前时间,是block.timestamp的别名。tx.gasprice:类型为uint,表示当前交易Gas价格
tx.origin:类型为address,表示完整调用链的发起者。
5、数学和加密函数
addmod(uint x,uint y,uint k) returns(uint):计算(x+y)%k,加法支持任意精度,但不超过2的256次方
mulmod(uint x,uint y,uint k) returns(uint):计算(x*y)%k,乘法支持任意精度,但不超过2的256次方
keccak256(...) returns(bytes32):计算Ethereum-SHA-3散列值
sha3(...) returns(bytes32):上面的别名,跟上面功能一样
sha256(...) returns(bytes20):计算RIPEMD-160散列值
ecrecover(bytes32 hash,uint8 v,bytes32 r,bytes32 s) returns (address):根据公钥,使用ECDSA算法对地址进行解密,返回解密后的地址,如果发生错误,则返回0
6、与合约相关的变量和函数
this:指代当前的合约,可以转换为地址类型
selfdestruct(address recipient):销毁当前合约,并且将全部的以太币余额转账到该地址。suicide(address recipient):同上