Solidity语法(一)值类型(Value-Types)

96
yuyangray
2018.02.27 17:24* 字数 2483

由于Solidity是一个静态类型的语言,所以编译时需明确指定变量的类型(包括本地变量或状态变量),Solidity编程语言提供了一些基本类型(elementary types)可以用来组合成复杂类型。

Solidity值类型官方文档

值类型(Value Type)

值类型包含

  • 布尔(Booleans)
  • 整型(Integer)
  • 地址(Address)
  • 定长字节数组(fixed byte arrays)
  • 有理数和整型(Rational and Integer LiteralsString literals)
  • 枚举类型(Enums)
  • 函数(Function Types)

为什么会叫值类型,是因为上述这些类型在传值时,总是值传递。比如在函数传参数时,或进行变量赋值时。

布尔(Booleans)

bool: 可能的取值为常量值true和false。

支持的运算符:

!逻辑非

&& 逻辑与

|| 逻辑或

== 等于

!= 不等于

备注:运算符&&和||是短路运算符,如f(x)||g(y),当f(x)为真时,则不会继续执行g(y)。

整型(Integer)

int & uint

int代表有符号的整型,也就是可以带负数。
uint代表没有符号的整型,也就是从0开始的正整数。

uint8 代表为2的8次方。
uint256 代表为2的256次方。
uint 默认为 uint256。

int8 代表为-2的7次方到正2的7次方。
int256 代表为-2的255次方,到正2的255次方;。
int 默认为int256。

支持的运算符:

比较:<=,<,==,!=,>=,>,返回值为bool类型。

位运算符:&,|,(^异或),(~非)。

数学运算:+,-,一元运算+,,/,(%求余),(*平方)。

示例

pragma solidity 0.4.20;
contract testInt {

   /*
    * @dev 输入一个值,返回乘以8的结果
    * @param val uint, 要传入的数值
    * @return uint,返回结果
    */
  function f(uint a) returns (uint b)
  {
    uint result = a * 8;
    return result;
  }
  
   /*
    * @dev 输入width & height,返回两者相乘的结果
    * @param val int, 要传入的数值
    * @param val int, 要传入的数值
    * @return int,返回结果
    */
  function f2(int width, int height) returns (int square) {
    if (width < 0 || height < 0) throw;
    int result = width * height;
    return result;
  }
  
  /*
    * @dev 输入一个值,返回求和的结果
    * @param val uint, 要传入的数值
    * @return uint,返回结果
    */
  function f3(uint n) returns (uint sum) {
    if (n == 0) throw; uint result = 0;
    for (uint i=0; i<=n; i++) {
      result +=I;
    }
    return result;
  }
}

在Browser-solidity中调试:

int

地址(Address)

address类型是一个由 20 字节长度的值(以太坊的地址)组成的。地址类型有很多成员变量,是所有合约的基础。所有的合约都会继承地址对象,也可以随时将一个地址串,得到对应的代码进行调用。当然地址代表一个普通帐户时,就没有这么多丰富的功能啦。

支持的运算符

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

地址类型的成员

属性:
<address>.balance,地址的余额(单位为:wei)
<address>.transfer,发送以太币(单位为:wei)到一个地址,如果失败会停止并抛出异常。

函数:
send()
call()
delegatecall()
callcode()

地址字面量

十六进制的字符串,凡是能通过地址合法性检查(address checksum test),就会被认为是地址,如0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF。需要注意的是39到41位长的没有通过地址合法性检查的,会提示一个警告,但会被视为普通的有理数字面量。

示例:

pragma solidity 0.4.20;
/**
 * 对 Address 的测试
 */
contract testAddress {

    // 定义全局变量
    address public a;
    address public b;

    function testAddress() public{
      a = msg.sender;  //调用合约用户的地址
      b = this;  //当前合约的地址
    }

    function getA() public view returns(address){
      return a;
    }

    function getB() public view returns(address){
      return b;
    }
}

在Browser-solidity中调试:

address

balance示例:得到一个地址的余额

pragma solidity 0.4.20;
contract addressTest{
    
    function getBalance(address addr) returns (uint){
        return addr.balance;
    }
}

得到当前合约的余额

pragma solidity 0.4.20;
contract addressTest{
    
    function getBalance() returns (uint){
        return this.balance;
    }
}

原因是对于合约来说,地址代表的就是合约本身,合约对象默认继承自地址对象,所以内部有地址的属性。

transfer示例:

address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);

send()

用来向某个地址发送货币(货币单位是wei)。

Send is the low-level counterpart of transfer. If the execution fails, the current contract will not stop with an exception, but send will return false.

相对于transfer,send的级别较低。如果执行失败,当前合约不会因为异常而停止,但是send方法会返回false。

send()方法执行时有一些风险:

  • 调用递归深度不能超1024。
  • 如果gas不够,执行会失败。
  • 所以使用这个方法要检查成功与否。或为保险起见,货币操作时要使用一些最佳实践。
  • 如果执行失败,将会回撤所有交易,所以务必留意返回结果。

call()callcode()delegatecall()

为了同一些不支持ABI协议的进行直接交互(一般的web3.jssoldity都是支持的)。可以使用call()函数,用来向另一个合约发送原始数据。参数支持任何类型任意数量。每个参数会按规则(规则是按ABI打包成32字节并一一拼接到一起。

call()方法支持ABI协议定义的函数选择器。如果第一个参数恰好4个字节,在这种情况下,会被认为根据ABI协议定义的函数器指定的函数签名。所以如果你只是想发送消息体,需要避免第一个参数是4个字节。

call方法返回一个bool值,以表明执行成功还是失败。正常结束返回true,异常终止返回false。我们无法解析返回结果,因为这样我们得事前知道返回的数据的编码和数据大小(这里的潜在假设是不知道对方使用的协议格式,所以也不会知道返回的结果如何解析,有点祼协议测试的感觉)。

同样我们也可以使用delegatecall(),它与call方法的区别在于,仅仅是代码会执行,而其它方面,如(存储,余额等)都是用的当前的合约的数据。delegatecall()方法的目的是用来执行另一个合约中的工具库。所以开发者需要保证两个合约中的存储变量能兼容,来保证delegatecall()能顺利执行。

在homestead阶段之前,仅有一个受限的多样的callcode()方法可用,但并未提供对msg.sendermsg.value的访问权限。

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

关于call()函数究竟发的什么消息体,函数选择器究竟怎么用,参见这个文章的挖掘。

上述的函数都是底层的函数,使用时要异常小心。当调用一个未知的,可能是恶意的合约时,当你把控制权交给它,它可能回调回你的合约,所以要准备好在调用返回时,应对你的状态变量可能被恶意篡改的情况。

十六进制字面量

十六进制字面量,以关键字hex打头,后面紧跟用单或双引号包裹的字符串。如hex"001122ff"。在内部会被表示为二进制流。通过下面的例子来理解下是什么意思:

pragma solidity ^0.4.0;
contract HexLiteral{
    function test() returns (string){
      var a = hex"001122FF";

      //var b = hex"A";
      //Expected primary expression
      
      return a;
  }
}

由于一个字节是8位,所以一个hex是由两个[0-9a-z]字符组成的。所以var b = hex"A";不是成双的字符串是会报错的。

Enums

Enums是一个用户可以定义类型的方法,可以使用uint转换,默认从0开始递增,但不可以隐性转换,转换失败会抛出异常,声明Enums时,里面至少要有一个成员。一般用来模拟合约的状态。

示例:

pragma solidity 0.4.20;

contract testEnums {

    //定义枚举
    enum ActionChoices { GoLeft, GoRight, GoStraight, SitStill }

    //声明变量
    ActionChoices choice;
    ActionChoices constant defaultChoice = ActionChoices.GoStraight;

    function setGoRight() {
        choice = ActionChoices.GoRight;
    }

    function setGoLeft() {
        choice = ActionChoices.GoLeft;
    }

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

    function setSitStill() {
        choice = ActionChoices.SitStill;
    }

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

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

以上代码,第一次调用getChoice方法时,返回的值为0。调用setGoRight方法,对choice设置一个枚举值ActionChoices.GoRight,再次调用getChoice方法时,返回的值为1。

在Browser-solidity中调试:

enum

函数(Function Types)

函数类型即是函数这种特殊的类型。

  • 可以将一个函数赋值给一个变量,一个函数类型的变量。
  • 还可以将一个函数作为参数进行传递。
  • 也可以在函数调用中返回一个函数。

函数类型有两类;可分为internalexternal函数。

内部函数(internal)

因为不能在当前合约的上下文环境以外的地方执行,内部函数只能在当前合约内被使用。如在当前的代码块内,包括内部库函数,和继承的函数中。

外部函数(External)

外部函数由地址和函数方法签名两部分组成。可作为外部函数调用的参数,或者由外部函数调用返回。

函数的定义

完整的函数的定义如下:

function (<parameter types>) {internal(默认)|external} [constant] [payable] [returns (<return types>)]

若不写类型,默认的函数类型是internal的。如果函数没有返回结果,则必须省略returns关键字。下面我们通过一个例子来了解一下。

pragma solidity ^0.4.0;
contract Test{
    //默认是internal类型的
    function noParameter() returns (uint){}

    //无返回结果
    function noReturn1(uint x) {}

    //如果无返回结果,必须省略`returns`关键字
    //function noReturn2(uint x) returns {} 
}

如果一个函数变量没有初始化,直接调用它将会产生异常。如果delete了一个函数后调用,也会发生同样的异常。

如果外部函数类型在Solidity的上下文环境以外的地方使用,他们会被视为function类型。编码为20字节的函数所在地址,紧跟4字节的函数方法签名的共占24字节的bytes24类型。

合约中的public的函数,可以使用internalexternal两种方式来调用。下面来看看,两种方式的不同之处。

函数的internal与external:

调用一个函数f()时,我们可以直接调用f(),或者使用this.f()。但两者有一个区别。前者是通过internal的方式在调用,而后者是通过external的方式在调用。请注意,这里关于this的使用与大多数语言相背。下面通过一个例子来了解他们的不同:

pragma solidity ^0.4.5;

contract FuntionTest{
    function internalFunc() internal{}

    function externalFunc() external{}

    function callFunc(){
        //直接使用内部的方式调用
        internalFunc();

        //不能在内部调用一个外部函数,会报编译错误。
        //Error: Undeclared identifier.
        //externalFunc();

        //不能通过`external`的方式调用一个`internal`
        //Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
        //this.internalFunc();

        //使用`this`以`external`的方式调用一个外部函数
        this.externalFunc();
    }
}
contract FunctionTest1{
    function externalCall(FuntionTest ft){
        //调用另一个合约的外部函数
        ft.externalFunc();
        
        //不能调用另一个合约的内部函数
        //Error: Member "internalFunc" not found or not visible after argument-dependent lookup in contract FuntionTest
        //ft.internalFunc();
    }
}
以太坊知识
Web note ad 1