Win10下开发部署Dapp(4):solidity快速入门

基础篇

Solidity是一门静态类型的脚本语言,我们可以对照C++的语法进行快速记忆。

1.基本保留字与基本类型

  • contract:类似于class,定义一个合约,具有构造函数,仅在创建合约时被调用。
  • function:定义一个函数。
  • event:定义一个事件,外部Dapp可以监控这些事件,以获知合约内的变化。
  • var:声明变量,类似于C++11的auto,可以在初始化时进行自动类型推导,之后不能更改类型。
  • bool: 布尔类型,有true跟false两种值。
  • uint8、uint16、。。。uint256:无符号整型,uint是uint256的别名。
  • nint8、int16、。。。int256:有符号整形,int是int256的别名。
  • ufixedMxN:无符号定点小数,M表示整个类型占用的bit数,N表示小数位数。ufixed64x7:7位小数,剩下的是整数部分
  • fixedMxN:定点小数。
  • address:地址类型,表示账户地址或者合约地址。20Byte。
    addr.balance:返回uint256类型的值,表示addr账户的余额(Wei)。
    addr.send(uint256 N):转给addr地址N Wei数量的以太币,失败是返回false。
    addr.transfer(uint256 N):包装了send方法,失败时直接抛出异常,会导致整个交易回退。
    addr.call、addr.callcode、addr.delegatecall:调用addr合约的指定的方法,区别稍后详述。

2.基本操作符

  • 逻辑操作符:!(逻辑非)、&&(逻辑与)、||(逻辑或)。跟C++完全一致,并且&&与||同样存在短路求值。
  • 比较操作符:<、<=、>、>=、==、!=,跟C++完全一致。
  • 算术操作符:+(正号)、-(负号)、 +、-、*、/、%(取余)、<<(左移)、>>(右移)、**(幂)。除了幂,其余的都跟C++一致。
  • 位操作符:&(与)、|(或)、~(非)、^(异或)。

3.数组、字符串、结构体、枚举类型、mapping

  • 数组

    • 定长数组:编译期长度就固定下来的数组是定长数组,这样定义一个定常数组:T[k](例如 uint8[ 5 ] arr)。bytes1、bytes2, bytes3, …, bytes32,这些也是定长数组。bytes1可以简写成byte。定长数组是值类型(value-type),可以进行比较操作、位操作、索引操作。
    • 动态数组:编译期长度不固定,类似于C++中的vector,这样定义一个动态数组:T[] (例如 int256[] arr)。
      string是特殊的动态数组。普通的定长数组、动态数组都可以进行取长度操作:arr.length,以及下标索引操作,但是string暂时不支持这两种操作。bytes也是动态数组,相当于byte[],但是比byte[]要更、廉价一些,应该尽量使用bytes。另外,动态数组、bytes还可以调用push方法,在数组末尾添加数据,返回最新的长度。
  • 字符串
    string本质上是经过UTF8编码的byte数组。当前版本的solidity对string的实现十分不完整,无法支持串联、比较、下标索引等操作,甚至连取长度都不支持。当前的string仅可以用来做mapping的key。

  • 结构体
    跟C++很像,这样定义一个结构体:

    struct MyStruct{
        bool flag;
        string name;
    }
    MyStruct a;
    结构体、数组里未被初始化的元素,都是0。
    
  • 枚举

  • mapping
    solidity里使用频率比较高的类型。mapping (address => uint256) balanceOf; 定义了一个map,使用地址做索引,值位uint256.

4.全局可用的单位、函数、对象

  • 以太币单位
    1 Ether = 1000 Finny
    1 Finny = 1000 Szabo
    1 Szabo = 1000 Gwei
    1 Gwei = 1000 Mwei
    1 Mwei = 1000 Kwei
    1 Kwei = 1000 wei
    1 Ether = 10^18 wei
  • 时间单位
    1 == 1 seconds
    1 minutes == 60 seconds
    1 hours == 60 minutes
    1 days == 24 hours
    1 weeks == 7 days
    1 years == 365 days
  • block对象
    block.blockhash(uint blockNumber) returns (bytes32):返回指定高度的块的hash值,仅限最近256块。
    block.coinbase (address):当前块的矿工
    block.difficulty (uint):当前块的难度
    block.gaslimit (uint):当前块的gaslimit
    block.number (uint):当前块高度
    block.timestamp (uint): 当前块时间戳
  • msg对象
    msg.data (bytes):当前调用完整的原始数据
    msg.gas (uint): 剩余的gas,0.4.21版本之后弃用,替换为gasleft。
    msg.sender (address): 当前调用的发起者。
    msg.sig (bytes4):调用数据的头四字节
    msg.value (uint):当前消息携带的以太币,单位wei。
  • tx对象
    tx.gasprice (uint):当前交易的gas price
    tx.origin (address): 当前交易的发起者。
  • 数学函数、hash函数
    addmod(uint x, uint y, uint k) returns (uint):
    mulmod(uint x, uint y, uint k) returns (uint):
    keccak256(…) returns (bytes32):
    sha256(…) returns (bytes32):
    sha3(…) returns (bytes32):
    ripemd160(…) returns (bytes20):
    ecrecover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) returns (address):
  • 异常处理函数
    assert(bool condition):
    require(bool condition):
    revert():
  • 其他
    gasleft() returns (uint256)
    now (uint):
    this
    suicide

5.contract(合约)

contract类似于class,他有:

  • 构造函数。跟contract名称相同的function即为构造函数,在合约创建时被调用,可以有参数,无返回值。
  • 自杀函数。selfdestruct(address)销毁合约,并把合约账户里的ether转移到指定的地址,花费比调用transfer小。suicide是selfdestruct的别名。
  • this。在合约内,this可以转化成address。在合约内部调用自己的external函数也需要用到this。
  • 成员变量。
  • 成员函数。
  • 继承、被继承。一个合约可以继承其他合约。

6.可见性:external、public、internal、private

合约内的成员变量、成员函数需要使用可见性来修饰,不修饰默认为public(跟C++刚好相反)。

  • external:只能用来修饰成员函数,这样的函数只能被外部合约调用,合约内部想要调用该函数,需要使用this.func();
  • public:修饰变量,则编译器会自动生成一个同名的getter。修饰函数,则外部可以调用该函数。
  • internal:类似于protect,可被合约内部以及子类合约访问。
  • private:私有的,仅合约内部可以访问,子类不可访问。

7.函数及其修饰词

函数可以有多个返回值:
直接通过一个稍微复杂点的例子来看下solidity的函数是啥样子的。

扫描二维码关注公众号,回复: 2331002 查看本文章
uint16 uCount; //合约内的成员变量

function add(uint8 a, uint8 b) public pure returns(uint8){
    return a + b; //先忽略溢出。
}

function GetCount() public view returns(uint16){
    return uCount;
}

function setCount(uint16 count) public {
    uCount = count;
}

modifier validAddress(address addr){
    assert(addr != address(0));
    _;
}
modifier validAmount(uint256 amount){
    reqire(amount > 0);
    _;
}

function myTransferFunc(address to, uint256 amount) public validAddress(to) validAmount(amount){
    //无需再检查参数的合法性,如果不合法,会在modifier中抛出异常,进不到函数体中。
    //........
    //........
}

function deposit(uint256 amount) public payable returns(bool, string){ 
    //........
    return (true, "successfull");
}

function () public payable{
    //.......
}

  从上往下依次看,add方法很简单,实现了两个数相加,但是pure是啥意思?GetCount方法的view又是啥意思?SetCount为啥没有这两个东西?pure:不改变合约状态,也不读取合约状态的函数,开发者应当主动使用pure修饰;读取合约状态但是不修改合约状态的函数,使用view修饰;SetCount改变了合约状态,不能被这两个中的任何一个修饰。(老版本的solidity没有view与pure,只有一个constant,凡是不改变状态的函数需要被constant修饰,后来细化成两部分view + pure)。
  接下来的modifier,业界普遍翻译成函数修改器,我觉得应该叫 函数卫词,它就像是一个卫语句,在函数运行之前过滤参数的合法性。不合法直接抛出异常,退出函数体,整个交易都不会被执行。
  再往下,一个新关键词:payable。被这个修饰的函数,才能够被转账,否则只能是普通调用。
  最后,一个没有名字的函数,叫回退函数(fallback function)。当且仅当一个合约的回退函数被实现了且被payable修饰了,才能向这个合约地址直接转账。

8.事件

  事件是另一个新东西,可以让外部dapp监控合约内的变化。这里先简单介绍下事件的定义以及触发,事件的监听后面补上。

event OnTrasnsfer(address from, address to, uint value);
function myTransferFunc(address to, uint256 amount) public validAddress(to) validAmount(amount){
    //转账
    。。。
    //新版本的solidity触发事件需要使用emit关键字,之前的版本不用,但是看起来像是个函数调用。
    emit OnTrasnsfer(address(this), to, amount);
}

扩展篇

1.继承、多态、super

solidity支持继承,并且支持多重继承。

下面的代码展示了基本的继承的作用:代码重用。

contract ERC20Token{
    string public name;
    string public symbol;
    uint256 public decimals;
    uint256 public totalSupply;
    mapping (address => uint256) public balanceOf;
    mapping (address => mapping (address => uint256)) public allowance;

    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);

    //基类里可以不给实现,只做函数声明。也可给一个实现,子类根据情况,选择直接使用用或者重新写一个实现覆盖父类的
    function transfer(address _to, uint256 _value) public returns (bool success);
    function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
    function approve(address _spender, uint256 _value)public returns (bool success);
}

contract TokenA is ERC20Token {
    function MyToken(string _name, string _symbol, uint256 _decimals) public{
        name = _name;
        symbol = _symbol;
        decimals = _decimals;
    }
    //如果父类只声明了该方法,此处为实现。若父类实现了,此处会覆盖父类的实现。
    function transfer(address _to, uint256 _value) public returns (bool success){

    }
}

contract TokenB is ERC20Token {
        .....
        .....
}

假如ERC20Token里的方法只给了声明,那ERC20Token就是一个抽象合约,该合约不能被部署只能做父类。
接下来就是由继承+重写(override)而引出的多态:

contract Base{
    function Func() public pure returns(uint256){
        return 11112222;
    }
}

contract Drived is Base{
    function Func() public pure returns(uint256){
        return 33334444;
    }
}

contract TestContract{
    function TestContract() public{

    }
    function TestPolymorphism(address subClassAddr) public pure returns(uint256, uint256){
        Drived objDrived = Drived(subClassAddr);
        Base objBase     = Base(subClassAddr);

        return (objDrived.Func(), objBase.Func());
    }
}

我们先部署Base、Drive两个合约,分别得到合约地址baseAddr、drivedAddr,再部署TestContract合约,分别把两个地址做参数调用TestPolymorphism方法,结果如下:

  • 使用baseAddr做参数,输出为:
    11112222
    11112222
    *使用drivedAddr做参数,输出为:
    33334444
    33334444
    有多重继承,及不可避免地会引起棱形继承,看下面代码:

contract Ancestor{
    function Func(uint256 val)public pure returns(uint256){
        return val;
    }
}
contract BaseA is Ancestor{
    function Func(uint256 val)public pure returns(uint256){
        return Ancestor.Func(val * 2);
    }
}
contract BaseB is Ancestor{
    function Func(uint256 val)public pure returns(uint256){
        return Ancestor.Func(val * 4);
    }
}
contract Final is BaseA, BaseB {

}

我们部署了Final合约之后,该合约对外只有一个可以调用的方法:Func,使用一个数字N做参数,输出会是多少呢?答案是4N。如果把继承的顺序改为 contract Final is BaseB, BaseA ,则输出的结果便是2N。solidity在处理多继承的时候,有一个结构叫继承图(inheritance graph),对于第一种情况,继承图是这样的:Final-BaseB-BaseA-Ancestor。所以调用Final.Func实际上调用的是BaseB里的Func。
接下来看看神奇的super

contract BaseA is Ancestor{
    function Func(uint256 val)public pure returns(uint256){
        return super.Func(val * 2);
    }
}
contract BaseB is Ancestor{
    function Func(uint256 val)public pure returns(uint256){
        return super.Func(val * 4);
    }
}
contract Final is BaseA, BaseB {

}

部署了Final之后调用Func方法,传入N,结果是8N。为啥?super不是简单的调用所在的合约的父合约,而是调用继承图中的下一个合约里的方法:继承图为Final-BaseB-BaseA-Ancestor,BaseB中的super.Func实际上调用的是BaseA.Func,所以最终的N被放大了4*2=8倍。

import、library、using for

合约间的访问:call、callcode、delegatecall

storage、memory、new、delete

throw、require、assert、revert

猜你喜欢

转载自blog.csdn.net/johnnymartin/article/details/79565875