solidity0.5.x笔记(1)-值类型

值类型和引用类型的区别

Solidity变量类型分为两大类——值类型、引用类型

值类型:变量的存储空间存的是变量的数据
引用类型:变量的存储空间存的是变量数据所在的存储空间的地址

注意:值传递和引用传递。值类型的变量,赋值是简单的值传递,即两个变量占有独立的存储区域。引用类型赋值传递的是变量的引用,即两个变量指向同一存储区域*

值类型——布尔 (bool)

bool: 只有两种值true和false(默认false)。

支持的运算符:

  • ! 逻辑非
  • && 逻辑与
  • || 逻辑或
  • == 等于
  • != 不等于

实例:

bool a = true;
bool b = !a;

// a == b -> false
// a != b -> true
// a || b -> true
// a && b -> false

逻辑与(&&)和逻辑或(||)都遵循短路原则,即如果根据前一个表达式可以得到运算结果,则不会执行后面的表达式

值类型——整型(int/uint)

  • int(m):有符号整数
  • uint(m):无符号整数
  • m关键字取值为8~256步幅是8 ,表示在内存中2进制的位数,控制了整数的取值范围,不写默认为256。
  • uint和int分别是uint256和int256的别名。
  • m一定要是8的整数倍

操作

比较:<=,<,==,!=,>=,>(结果为bool)
位操作符:&,|,^(按位异或),~(按位取反)
算术运算符:+, - ,一元 - ,一元 +,*,/,%(取余),**(幂),<<(左移),>>(右移)

注意:

  • 除零和取余有零引发异常。
  • 左移几位和右移几位相当于乘以或者除以2的几次方,如果参数为负数的话会引发异常。
  • 在Solidity中不支持八进制。
  • 整形的上溢和下溢

值类型——定点数(Fixed Point Numbers)

注意:Solidity 还没有完全支持定长浮点型。可以声明定长浮点型的变量,但不能给它们赋值或把它们赋值给其他变量。

fixed/ufixed 各种大小的有符号和无符号定点小数,ufixedMxN and fixedMxN关键字M代表定点数占用的二进制位数,N代表定点数能表示多少位小数,M必须是8-256之间的,以8为步幅的整数,N必须是0-80之间的整数,ufixed 和fixed 默认为ufixed128x18和fixed128x18

  • 比较运算: <=, <, ==, !=, >=, > (结果为bool)
  • 算数运算: +, -, 一元-, 一元 +, *, /, % 取余

值类型——定长字节数组

bytes1, … ,bytes32,允许值以步长1递增。byte默认表示bytes1。

操作:

  • 比较:<=,<,==,!=,>=,>(评估为bool)
  • 位运算符:&,|,^(按位异或),~(按位取反),<<(左移),>>(右移)
  • 索引访问:如果x的类型为bytesI,则0 <= k 成员:
    .length产生字节数组的固定长度(只读)。

注意:

  • 十六进字面量赋值给定长字节数组时,长度必须和定长数组相同
  • 字符串复制给定长数组时,长度必须和定长数组相同

值类型——地址类型(address和address payable)

地址类型有两种形式,大致相同:

  • address:保存一个20字节的值(以太坊地址的大小)
  • address payable:相同address,但附加成员transfer和send。

运算符

<=, <, ==, !=, >= 和 >

address payable地址成员

  • 以wei位单位返回该地址的余额
.balance(uint256)
  • 从当前合约地址中给调用函数的地址账户转入amounts数量(以wei为单位)的金额,如果执行失败,将抛出异常,需要支付2300gas的费用,不可以调整。
.transfer(uint256 amount)

范例:

pragma solidity ^0.5.0;

contract AddressExample {
    constructor() public{
        
    }

    function giveEthersTo(address payable _toAccount,uint _amount) public{
        if (address(this).balance >= _amount){
             _toAccount.transfer(_amount);
        }
    }
    function getBalance() view public returns(uint){
        return address(this).balance;
    }
    
      function() payable external {
        
    }
}
  • 如果合约地址调用transfer,那么需要合约地址有payable类型的回退函数,并且会随着转账一块执行回退函数中的代码,如果因为回退函数中的代码执行把gas消耗光了,EVM会抛出异常,转账也会被回退。

  • send是低级对等的转账。执行失败,不会抛出异常,会返回false,需要支付2300gas的费用,不可以调整。推荐使用transfer而不使用send

.send(uint256 amount) returns (bool)
  • call(), callcode() 和 delegatecall() 函数
<address>.call(...) returns (bool)
<address>.callcode(...) returns (bool)
<address>.calldelegate(...) returns (bool)

为了和非ABI协议的合约进行交互,可以使用call() 函数, 它用来向另一个合约发送原始数据,支持任何类型任意数量的参数,每个参数会按规则(ABI协议)打包成32字节并一一拼接到一起。如下面的例子:

address(nameReg).call(abi.encodeWithSignature("register(string)", "MyName"));

call函数返回一个bool值,以表明执行成功与否。正常结束返回true,异常终止返回false。但无法获取到结果数据。还可以提供.gas()修饰器进行调用:

address(nameReg).call.gas(1000000)(abi.encodeWithSignature("register(string)", "MyName"));

类似还可以提供附带以太币:

address(nameReg).call.value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));

修饰器可以混合使用,修饰器调用顺序无所谓。

address(nameReg).call.gas(1000000).value(1 ether)(abi.encodeWithSignature("register(string)", "MyName"));
  • delegatecall()

仅执行代码,而其它方面,如(存储,余额等)都是用的当前的合约的数据。
delegatecall()方法的目的是用来执行另一个合约中的库代码。所以开发者需要保证两个合约中的存储变量能兼容(这两个合约的存储中的变量定义顺序需要一致,以便被调用的合约代码可以正确地通过变量名访问合约的存储变量),来保证delegatecall()能顺利执行。

  • callcode()

与delegatecall类似,但不能获取委托方的msg.sender,msg.value,在未来也会将其移除。

上面的这三个方法call(),delegatecall(),callcode()都是底层的消息传递调用,最好仅在万不得已才进行使用,因为他们破坏了Solidity的类型安全。
.gas() 在call(), callcode() 和 delegatecall() 函数下都可以使用, delegatecall()不支持.value()。

pragma solidity ^0.5.0;

contract Person{
    uint public age = 10;

    string public name="zhangsan";
    constructor() public{
        
    }
    
    event log(address, uint);
    function update(string memory _name, uint _num) payable public {
        emit log(msg.sender, msg.value);
        name = _name;
        age = _num;
    }
    
}

contract CallTest{
    uint public age = 10;
    string public name = "zhangsan";
    
    function callByFun(address addr)public returns (bool){
        bytes memory payload = abi.encodeWithSignature("update(string,uint256)",  "lisi", 99);
        (bool success, bytes memory returnData) = address(addr).call(payload);
       return success;
       
    }
}

注意

  • 合约中使用的this表示当前合约地址
  • 所有的合约对象都可以被转成地址类型,查询当前合约的余额

address和address payablede 转换:

  • address payable可以隐式转换成address类型,但address类型无法转换成address payable类型,可以使用如下方式转换:
 address payable e = address(uint160(c));
  • 地址字面量可以隐式转换成address payable

  • 和address互相显示转换的类型如下:
    整形
    整形字面量
    bytes20
    contract types

  • 合约对象包含payable 类型的fallback函数,address(contract)的类型是address payable, 如果合约对象不包含payable 类型的fallback函数,address(contract)的类型是address 。

值类型——地址常量

十六进制整数常量,如果能够通过地址校验,就会被认为是地址类型,例如

0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF

就是一个地址类型的常量,如果长度为39~40,没有通过地址校验的十六进制整数常量,会被视为有理数常量,并且会产生一个warning(警告提示)。
校验规则[https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md]

值类型——有理数和整数字面量

整数字面量和有理数字面量均支持科学计数法。基数可以是小数,指数必须是整数。
例如2e10,-2e10,2e-10,2.5e10

有理数常量(特指小数常量)

  • 有理数常量带一个.,在.的两边至少要有一个数字,有效的表示如下1.,.1,1.2
  • 不允许位运算,小数不能用作指数
  • 有理数本身支持任意精度,任何运算不会发生溢出或除法截断,当被转换成对应的其他类型,或者与其他类型运算时,不再保证精度。

整数常量

  • 由一系列0-9的数字组成的10进制数,存在十六进制表示形式“0x”开头,不存在以“0”八进制的表示形式。
  • 整数常量表达式的除法运算,不做截断处理,结果是有理数。

注意:
数字常量表达式一旦其中含有非常量表达式,它就会被转为一个非常量表达式

pragma solidity ^0.5.0;

contract IntegerLiteralConvert{
 
  constructor()public{
      
  }
  function literalTest()public{
    uint128 a = 1;
    //uint128 b = 2.5 + a + 0.5;
    //2.5+a不能转换成一个非常量表达式
  }
}

值类型——字符串常量

字符串常量是用双引号或单引号(“foo”或‘bar’)编写,长度可变。 它们不像C语言那样默认以0结尾; “foo”代表三个不是四个字节。 它们可以隐式转换为bytes1,…,bytes32,如果合适的话,还可以转换成 bytes 以及 string。

pragma solidity ^0.5.0;

contract StringLiteralsTest{
    bytes15 public name;
    
    function setName() public{
        name = 'lianrenxueyuan';
    }
}

值类型——十六进制常量

十六进制常量,以关键字hex打头,后面紧跟用单或双引号包裹的字符串。如hex"001122ff"。在内部会被表示为二进制流,与字符串的存储形式相同,可以与字符串进行隐式转换。

pragma solidity ^0.5.0;

contract HexLiteral{
    string name;
   
    function setName()public{
        name = hex"6c69616e6b7561697875657975616e";
      
    }
}

值类型——枚举

枚举类型是在Solidity中的一种用户自定义类型。

pragma solidity ^0.5.0;

contract test {
    enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }
   
    ActionChoices choice;
    ActionChoices constant defaultChoice = ActionChoices.GoStraight;

    function setGoStraight() public {
        choice = ActionChoices.GoStraight;        
        // choice = ActionChoices(2);
    }

    function getChoice() public view returns (ActionChoices) {
        return choice;
    }

    function getDefaultChoice() public pure returns (uint) {
        return uint(defaultChoice);
    }
}

注意:

  • 枚举可以显示的转换与整数进行转换,但不能进行隐式转换。显示的转换会在运行时检查数值范围,如果不匹配,将会引起异常。
  • 枚举类型应至少有一名成员。

值类型——函数

  • 函数的定义
  • 函数的调用方式
  • 函数的可见性
  • pure函数
  • constant、view函数
  • payable函数
  • Fallback函数
  • 构造函数
  • 函数参数

函数的定义:

function关键字声明的,合约中的可执行单元,一个函数的完整定义如下:

function (funcName) (<parameter types>) {public|external|internal|private} [constant|view|payable] [returns (<return types>)]

函数的调用方式

Solidity封装了两种函数的调用方式internal(内部调用)和external(外部调用)。

注意:
上面所提到的internal和external指的函数调用方式,请不要与后面的函数可见性声明的external,public,internal,private弄混。声明只是意味着这个函数需要使用相对应的调用方式去调用。

  • internal(内部调用方式)

internal调用,实现时转为简单的EVM跳转,所以它能直接使用上下文环境中的数据,对于引用传递时将会变得非常高效(不用拷贝数据)。

在当前的代码单元内,如对合约内函数,引入的库函数,以及父类合约中的函数直接使用即是以internal方式的调用。我们来看个简单的例子:

pragma solidity ^0.5.0;

contract Test {
    function f()public{}
    
    //以internal的方式调用
    function callInternally()public{
        f();
    }
}

在上述代码中,callInternally()以internal的方式对f()函数进行了调用。
简而言之,internal(内部调用方式)就是直接使用函数名去调用函数。

  • external(外部调用方式)

external调用,实现为合约的外部消息调用。所以在合约初始化时不能external的方式调用自身函数,因为合约还未初始化完成。下面来看一个以external方式调用的例子:

pragma solidity ^0.5.0;

contract A{
    function f()public {}
}

contract B{
    //以external的方式调用另一合约中的函数
    function callExternal(A a)public{
        a.f();
    }
}

虽然当前合约A和B的代码放在一起,但部署到网络上后,它们是两个完全独立的合约,它们之间的方法调用是通过消息调用。上述代码中,在合约B中的callExternal()以external的方式调用了合约A的f()。
简而言之,external(外部调用方式)就是使用合约实例名.函数名的方式去调用函数。
使用this强制external方式调用:
我们可以在合约的调用函数前加this.来强制以external方式的调用。

pragma solidity ^0.5.0;

contract A{
    function f() public{}
    
    function callExternally() public{
        this.f();
    }
}

函数的可见性

Solidity为函数提供了四种可见性,external,public,internal,private。

external(外部函数)

  • 声明为external的函数可以从其它合约来进行调用,所以声明为external的函数是合约对外接口的一部分。
  • 不能以internal的方式进行调用。
  • 有时在接收大的数据数组时性能更好。
pragma solidity ^0.5.0;

contract FuntionTest{
    function externalFunc() external{}

    function callFunc()public {
        //以`internal`的方式调用函数报错
        //Error: Undeclared identifier.
        //externalFunc();
        //以`external`的方式调用函数
        this.externalFunc();
    }
}

声明为external的externalFunc()只能以external的方式进行调用,以internal的方式调用会报Error: Undeclared identifier.。

public(公有函数)

  • public的函数既允许以internal的方式调用,也允许以external的方式调用。
  • public的函数由于允许被外部合约访问,是合约对外接口的一部分。
pragma solidity ^0.5.0;

contract FuntionTest{
    //默认是public函数
    function publicFunc()public{}

    function callFunc()public{
        //以`internal`的方式调用函数
        publicFunc();
        
        //以`external`的方式调用函数
        this.publicFunc();
    }
}

我们可以看到声明为public的publicFunc()允许两种调用方式。

internal(内部函数)

在当前的合约或继承的合约中,只允许以internal的方式调用。

pragma solidity ^0.5.0;

contract A{

    function internalFunc() internal{}

    function callFunc()public{
        //以`internal`的方式调用函数
        internalFunc();
    }
}
contract B is A{
    //子合约中调用
    function callFunc()public{
        internalFunc();
    }
}

上述例子中声明为internal的internalFunc()在定义合约,和子合约中均只能以internal的方式可以进行调用。

private(私有函数)

  • 只能在当前合约中被访问(不可在被继承的合约中访问)。
  • 即使声明为private,仍能被所有人查看到里面的数据。访问权限只是阻止了其它合约访问函数或修改数据。
pragma solidity ^0.5.0;

contract A{

    function privateFunc() private{}

    function callFunc()public{
        //以`internal`的方式调用函数
        privateFunc();
    }
}
contract B is A{
    //不可调用`private`
    function callFunc()public{
        //privateFunc();
    }
}

pure函数

既不从状态读取数据也不写入数据的函数可以被声明为pure
除了之前修改状态数据的情况外,我们认为以下情况属于从状态读取数据。

  1. 读取状态变量
  2. 调用this.balance或者address.balance
  3. 访问 block,tx, msg 中任意成员(除 msg.sig 和 msg.data 之外)
  4. 调用任何未标记为 pure 的函数
  5. 使用了包含某些操作码的内联汇编
pragma solidity ^0.4.24;

contract C {
    function f(uint a, uint b) public pure returns (uint) {
        return a * (b + 42);
    }
}

view函数

不改变状态的函数可以被声明为只读函数

以下几种情况被视为修改了状态:

  1. 修改状态变量
  2. 产生事件
  3. 创建了其他合约
  4. 使用了selfdestruct自我销毁
  5. 通过调用发送以太币
  6. 调用任何没有标记为view或者pure的函数。
  7. 使用了底层调用
  8. 使用了包含某些操作码的内联汇编

payable函数(接收以太币)

声明了该函数涉及接收以太币操作,如果函数没有声明为payable,并且在调用过程中有以太币通过被调用的函数转入合约,那么EVM虚拟机将会抛出异常,状态回退。

pragma solidity ^0.5.0;

contract AddressExample {
    uint public balance;
    constructor() public payable{}
    
    function deposit()public payable{
      balance += msg.value;  
    }
}

Fallback函数

合约可以有一个未命名的函数。这个函数不能有参数也不能有返回值。

如果在一个到合约的调用中,没有其他函数与给定的函数标识符匹配(或没有提供调用数据),那么这个函数(fallback 函数)会被执行。

每当合约收到以太币(没有任何数据),这个函数就会执行。此外,为了接收以太币,fallback 函数必须标记为 payable。 如果不存在这样的函数,则合约不能通过常规交易接收以太币。

pragma solidity ^0.5.0;

contract Test {

 constructor() public{
     
 }

 event log(uint);

 function() payable external{
     emit log(1);
 }
}


contract Caller {
    constructor()public{
        
    }
    
    function call(address addr) public{

        (bool success, bytes memory returnData) = address(addr).call.value(10)("");
    }
}

如果涉及支付以太币,即回退函数被声明为payable类型,并且通过send或者transfer被调用,那么回退函数仅有你2300gas可以使用,如果回退函数中的代码执行消耗超过2300gas那么被转入的以太币将会退回,修改过的数据状态回退。

用做接收以太币回退函数内部仅能进行触发事件操作
即使 fallback 函数不能有参数,仍然可以使用 msg.data 来获取随调用提供的任何有效数据。

构造函数

构造函数是一个用constructor关键字声明的可选函数,它在创建合约时执行。构造函数可以是public,也可以是internal。如果没有构造函数,则该合约将生成默认构造函数:contructor() public {}。

pragma solidity ^0.5.0;

contract A {
    uint public a;

    constructor(uint _a) internal {
        a = _a;
    }
}

在版本0.4.22之前,构造函数被定义为与合同名称相同的函数。这个语法现在不推荐使用。

函数的输入参数与输出参数

Solidity函数的输入参数的数量是可选的,也可以有任意数量的返回参数。

入参(Input Parameter)与变量的定义方式一致,稍微不同的是,不会用到的参数可以省略变量名称。一种可接受两个整型参数的函数如下:

pragma solidity ^0.5.0;
contract Simple {
    function taker(uint _a, uint) public{
        // do something with _a.
    }
}

出参(Output Paramets)在returns关键字后定义,语法类似变量的定义方式。返回结果的数量需要与定义的一致。如果给定了参数名,则函数可以不适用return关键字返回,如果没有给定参数名则需要函数体重使用return关键字按照顺序返回。

pragma solidity ^0.5.0;

contract Simple {
    //return sum and product
    function arithmetics(uint _a, uint _b) public  pure returns (uint o_sum, uint o_product) {
        o_sum = _a + _b;
        o_product = _a * _b;
    }
}


你可能感兴趣的:(solidity)