solidity语言基础2

1.杂项
映射/字典(mappings)
映射或字典类型,一种键值对的映射关系存储结构。定义方式为mapping(_KeyType => _KeyValue)。键的类型允许除映射外的所有类型,如数组,合约,枚举,结构体。值的类型无限制。
映射可以被视作为一个哈希表,其中所有可能的键已被虚拟化的创建,被映射到一个默认值(二进制表示的零)。但在映射表中, 我们并不存储键的数据,仅仅存储它的keccak256哈希值,用来查找值时使用。
因此,映射并没有长度,键集合(或列表),值集合(或列表)这样的概念。
映射类型,仅能用来定义状态变量,或者是在内部函数中作为storage类型的引用。引用是指你可以声明一个,如var storage mappVal的用于存储状态变量的引用的对象,但你没办法使用非状态变量来初始化这个引用。
可以通过将映射标记为public,来让Solidity创建一个访问器。要想访问这样的映射,需要提供一个键值做为参数。如果映射的值类型也是映射,使用访问器访问时,要提供这个映射值所对应的键,不断重复这个过程。下面来看一个例子:
contract MappingExample{
    mapping(address => uint) public balances;
    function update(uint amount) returns (address addr){
        balances[msg.sender] = amount;
        return msg.sender;
    }
}
由于调试时,你不一定方便知道自己的发起地址,所以把这个函数,略微调整了一下,以在调用时,返回调用者的地址。编译上述合同后,可以先调用update(),执行成功后,查看调用信息,能看到你更新的地址,这样再查一下这个地址的在映射里存的值。
如果你想通过合约进行上述调用。
pragma solidity ^0.4.0;
//file indeed for compile
//may store in somewhere and import
contract MappingExample{
    mapping(address => uint) public balances;   
    function update(uint amount) returns (address addr){
        balances[msg.sender] = amount;
        return msg.sender;
    }
}
contract MappingUser{    
    address conAddr;
    address userAddr;  
    function f() returns (uint amount){
    //address not resolved!
    //tringing
        conAddr = hex"0xf2bd5de8b57ebfc45dcee97524a7a08fccc80aef";
        userAddr = hex"0xca35b7d915458ef540ade6068dfe2f44e8fa733c";
        
        return MappingExample(conAddr).balances(userAddr);
    }
}
映射并未提供迭代输出的方法,可以自行实现一个数据结构


左值的相关运算符
左值,是指位于表达式左边的变量,可以是与操作符直接结合的形成的,如自增,自减;也可以是赋值,位运算。
可以支持操作符有:-=,+=,*=,%=,|=,&=,^=,++,--。

特殊的运算符delete
delete运算符,用于将某个变量重置为初始值。对于整数,运算符的效果等同于a = 0。而对于定长数组,则是把数组中的每个元素置为初始值,变长数组则是将长度置为0。对于结构体,也是类似,是将所有的成员均重置为初始值。
delete对于映射类型几乎无影响,因为键可能是任意的,且往往不可知。所以如果你删除一个结构体,它会递归删除所有非mapping的成员。当然,你是可以单独删除映射里的某个键,以及这个键映射的某个值。

需要强调的是delete a的行为更像赋值,为a赋予一个新对象。我们来看看下文的示例:
pragma solidity ^0.4.0;
contract DeleteExample {
    uint data;
    uint[] dataArray;
    function f() {
        //值传递
        uint x = data;
        //删除x不会影响data
        delete x;
     //删除data,同样也不会影响x,因为是值传递,它存的是一份原值的拷贝。
        delete data; 
        //引用赋值
        uint[] y = dataArray;
        //删除dataArray会影响y,y也将被赋值为初值。
        delete dataArray;
        //下面的操作为报错,因为删除是一个赋值操作,不能向引用类型的storage直接赋值从而报错
        //delete y;
    }
}
通过上面的代码,我们可以看出,对于值类型,是值传递,删除x不会影响到data,同样的删除data也不会影响到x。因为他们都存了一份原值的拷贝。
对于复杂类型略有不同,复杂类型在赋值时使用的是引用传递。删除会影响所有相关变量。比如上述代码中,删除dataArray同样会影响到y。
由于delete的行为更像是赋值操作,所以不能在上述代码中执行delete y,因为不能对一个storage的引用赋值

类型推断(Type Deduction)
为了方便,并不总是需要明确指定一个变量的类型,编译器会通过第一个向这个对象赋予的值的类型来进行推断。
uint24 x = 0x123;
var y = x;
函数的参数,包括返回参数,不可以使用var这种不指定类型的方式。
需要特别注意的是,由于类型推断是根据第一个变量进行的赋值。所以代码for (var i = 0; i < 2000; i++) {}将是一个无限循环,因为一个uint8的i的将小于2000。

pragma solidity ^0.4.4;
contract Test{
    function a() returns (uint){
      uint count = 0;
        for (var i = 0; i < 2000; i++) {
            count++;
            if(count >= 2100){
                break;
            }
        }
        return count;
    }
}


2.单位
货币单位(Ether Units)
一个字面量的数字,可以使用后缀wei,finney,szabo或ether来在不同面额中转换。不含任何后缀的默认单位是wei。如2 ether == 2000 finney的结果是true。

pragma solidity ^0.4.0;

contract EthUnit{
    uint a;
    
    function f() returns (bool){
      if (2 ether == 2000 finney){
          return true;
      }
      
      return false;
    }
}


时间单位(Time Units)
seconds,minutes,hours,days,weeks,years均可做为后缀,并进行相互转换,默认是seconds为单位。
默认规则如下:
1 == 1 seconds
1 minutes == 60 seconds
1 hours == 60 minutes
1 days == 24 hours
1 weeks = 7 days
1 years = 365 days
如果你需要进行使用这些单位进行日期计算,需要特别小心,因为不是每年都是365天,且并不是每天都有24小时,因为还有闰秒。由于无法预测闰秒,必须由外部的oracle来更新从而得到一个精确的日历库(内部实现一个日期库也是消耗gas的)。
后缀不能用于变量。如果你想对输入的变量说明其不同的单位,可以使用下面的方式。
pragma solidity ^0.4.0;
contract DeleteExample{
    function nowInSeconds() returns (uint256){
        return now;
    }
    function f(uint start, uint daysAfter) {
        if (now >= start + daysAfter * 1 days) { 
            
        }
    }
}


3.语言内置特性
特殊变量及函数(Special Variables and Functions)
有一些变量和函数存在于全局上下文中。主要用来提供一些区块链当前的信息。
区块和交易的属性(Block And Transaction Properties)
block.blockhash(uint blockNumber) returns (bytes32),给定区块号的哈希值,只支持最近256个区块,且不包含当前区块。
block.coinbase (address) 当前块矿工的地址。
block.difficulty (uint)当前块的难度。
block.gaslimit (uint)当前块的gaslimit。
block.number (uint)当前区块的块号。
block.timestamp (uint)当前块的时间戳。
msg.data (bytes)完整的调用数据(calldata)。
msg.gas (uint)当前还剩的gas。
msg.sender (address)当前调用发起人的地址。
msg.sig (bytes4)调用数据的前四个字节(函数标识符)。
msg.value (uint)这个消息所附带的货币量,单位为wei。
now (uint)当前块的时间戳,等同于block.timestamp
tx.gasprice (uint) 交易的gas价格。
tx.origin (address)交易的发送者(完整的调用链)
msg的所有成员值,如msg.sender,msg.value的值可以因为每一次外部函数调用,或库函数调用发生变化(因为msg就是和调用相关的全局变量)。
如果你想在库函数中,用msg.sender实现访问控制,你需要将msg.sender做为参数(就是说不能使用默认的msg.value,因为它可能被更改)。

为了可扩展性的原因,你只能查最近256个块,所有其它的将返回0.

数学和加密函数(Mathematical and Cryptographic Functions)
asser(bool condition):
如果条件不满足,抛出异常。

addmod(uint x, uint y, uint k) returns (uint):
计算(x + y) % k。加法支持任意的精度。但不超过(wrap around?)2**256。
mulmod(uint x, uint y, uint k) returns (uint):
计算(x * y) % k。乘法支持任意精度,但不超过(wrap around?)2**256。
keccak256(...) returns (bytes32):
使用以太坊的(Keccak-256)计算HASH值
。紧密打包。
sha3(...) returns (bytes32):
等同于keccak256()。紧密打包。
sha256(...) returns (bytes32):
使用SHA-256计算HASH值。紧密打包。
ripemd160(...) returns (bytes20):
使用RIPEMD-160计算HASH值。紧密打包。
ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):
通过签名信息恢复非对称加密算法公匙地址。如果出错会返回0

revert():
取消执行,并回撤状态变化。

需要注意的是参数是“紧密打包(tightly packed)”的,意思是说参数不会补位,就直接连接在一起的。下面来看一个例子:
keccak256("ab", "c")
keccak256("abc")
//hex
keccak256(0x616263)
keccak256(6382179)
//ascii
keccak256(97, 98, 99)
上述例子中,三种表达方式都是一致的。
如果需要补位,需要明确的类型转换,如keccak256("\x00\x12")等同于keccak256(uint16(0x12))
需要注意的是字面量会用,尽可能小的空间来存储它们。比如,keccak256(0) == keccak256(uint8(0)),keccak256(0x12345678) == keccak256(uint32(0x12345678))
在私链(private blockchain)上运行sha256,ripemd160或ecrecover可能会出现Out-Of-Gas报错。因为它们实现了一种预编译的机制,但合约要在收到第一个消息后才会存在。向一个不存在的合约发送消息,非常昂贵,所以才会导致Out-Of-Gas的问题。一种解决办法是每个在你真正使用它们前,先发送1 wei到这些合约上来完成初始化。在官方和测试链上没有这个问题。


地址相关(Address Related)
.balance (uint256):
Address的余额,以wei为单位。
.transfer(uint256 amount):
发送给定数量的ether,以wei为单位,到某个地址。失败时抛出异常。
.send(uint256 amount) returns (bool):
发送给定数量的ether,以wei为单位,到某个地址。失败时返回false。
.call(...) returns (bool):
发起底层的call调用。失败时返回false。
.callcode(...) returns (bool):
发起底层的callcode调用,失败时返回false。
.delegatecall(...) returns (bool):
发起底层的delegatecall调用,失败时返回false。

使用send方法需要注意,调用栈深不能超过1024,或gas不足,都将导致发送失败。使用为了保证你的ether安全,要始终检查返回结果。当用户取款时,使用transfer或使用最佳实践的模式。

合约相关
this(当前合约的类型)
当前合约的类型,可以显式的转换为Address
selfdestruct(address recipt):
销毁当前合约,并把它所有资金发送到给定的地址。

另外,当前合约里的所有函数均可支持调用,包括当前函数本身。

你可能感兴趣的:(区块链)