# Learn Solidity Series 9: basis tips

By [Renaissance Labs](https://paragraph.com/@renaissance-labs) · 2022-03-02

---

Version claim
-------------

> 合约文件开头需要声明编译器的版本号，目的是为了该合约在未来版本的升级中引入了不兼容的编译器，其语法为：

    pragma solidity 版本号
    

> 版本号应该遵循“0.x.0”或者“x.0.0”的形式，比如：

    // 代表不允许低于0.4.17版本的编译器，也不允许使用高于0.5.0版本的编译器
    pragma solidity ^0.4.17
    
    // 代表支持0.4.17以上的版本，但是不能超过0.9.0版本
    pragma solidity >=0.4.17 < 0.9.0;
    

Code note
---------

> 与Java类似，使用双斜杠`//`代表单行注释。在双斜杠中间添加星号代表多行注释，比如：

    // 单行注释
    
    /*
    多行注释
    多行注释
    ...
    */
    

> 也可以添加文档注释，其语法为：

    /**
    * @dev 
    * @param
    * @return 
    */
    // eg:
    pragma solidity >=0.4.17 <0.9.0
    
    contract Calculator {
    
        /**
         * @dev 实现两个数的乘法运算
         * @param a 乘数
         * @param b 被乘数
         * @return 返回乘法的结果
        */
        function mul(uint a, uint b) public pure returns(uint) {
            return a * b;
        }
    
    } 
    

Data type
---------

### boolean

> 布尔类型使用bool关键字。它的值是true或false。布尔类型支持的运算符有：

名称 符号

逻辑与`&&`

逻辑或`||`

逻辑非`!`

等于`==`

不等于`!=`

### Integer

> 整型使用int或uint表示。uint代表的是无符号整型。它们都支持uint8、uint16，…, uint256（以8位为步长递增）。如果没有指定多少位，默认为int256或uint256。

整型支持的运算符：

名称 符号

比较运算符`<= < == != >= >`

位运算符`& | ^ ~`

算术运算符`+ - * / ++ -- % ** << >>`

> 注意：如果除数为0或者对0取模都会引发运行时异常。

### fixed float

定长浮点型使用fixed或ufixed表示。但是目前[solidity](https://so.csdn.net/so/search?q=solidity&spm=1001.2101.3001.7020)支持声明定长浮点型的变量，不能够对该类型的变量进行赋值。

### address type

> 地址类型的关键字使用address表示，一般用于存储合约或账号，其长度一般为20个字节。地址类型的变量可以使用`> >= < <= == !=`运算符。

地址类型的变量还可以使用以下成员变量和成员方法：

成员变量 描述 示例

balance 地址余额 x.balance

transfer 向当前地址转账 x.transfer(10)

send 向当前地址转账，与transfer不同的是，如果执行失败，该方法不会因为异常而终止合约执行 x.send(10)

call、delegatecall 调用合约函数，第一个参数是函数签名。与call不同的是，delegatecall只使用存储在库合约中的代码 x.call(“mul”, 2, 3)

> 值得注意的是，当一个合约A调用另外一个合约B的时候，就相当于将控制权交给了合约B。合约B也有可能会反过来调用合约A。因此调用结束时，需要对合约A状态变量的改变做好准备。

### fixed byte array

> 定长字节数组使用bytes1、bytes2、…、bytes32来表示。如果没有指定长度，默认为bytes1。定长字节数组支持的运算符有：

名称 符号

比较运算符 `<= < == != >= >`

位运算符 `& | ^ ~ << >>`

索引访问 `x[i]代表访问第i个字节的数据`

> 定长字节数组包含length属性，用于查询字节数组的长度。虽然可以使用byte\[\]表示字节数组，但是一般推荐使用bytes代替。

### constant

1.  **地址字面常量**
    
    像0x692a70d2e424a56d2c6c27aa97d1a86395877b3a这样通过了地址校验和测试的十六进制字面常量属于address类型。
    
2.  数值字面常量
    
    solidity支持整数和任意精度小数的字面常量。1.或.1都是有效的小数字面常量。也可以使用科学计数法表示，比如：2e10。如果数值字面常量赋值给了数值类型的变量，就会转换成非字面常量类型。
    

    uint128 a = 2.5 + 1 + 0.5; // 编译成功
    uint128 b = 1; 
    uint128 c = 2.5 + b + 0.5; // 编译报错
    

上面代码的第二行，将数值字面常量`1`赋值给了变量b。因为变量b是非字面常量，不同类型的字面常量和非字面常量无法进行运算，所以第三行报错。上面代码的第二行，将数值字面常量`1`赋值给了变量b。因为变量b是非字面常量，不同类型的字面常量和非字面常量无法进行运算，所以第三行报错。

3.  字符串字面常量
    

字符串字面常量使用单引号或双引号引起来的一串字符。字符串字面常量可以隐式地转换成bytes类型。比如：

    bytes3 b = "abc";
    bytes4 b = "abcd";
    bytes4 b = "abcde"; // 报错，因为bytes4只能存储4个字节数据
    

function type
-------------

> solidity支持将一个函数赋值给一个变量，也可以作为参数传递给其他函数，或者作为函数的返回值。

声明语法：function (参数类型) \[internal|external\] \[pure|constant|view|payable\] \[returns (返回值类型)\]

    pragma solidity ^0.4.16;
    
    contract A {
        // 声明函数变量，并将mul函数赋给变量func
        function (uint, uint) pure returns (uint) func = mul; 
        // 函数声明
        function mul(uint a, uint b) public pure returns(uint) {
            return a * b;
        }
    }
    

注意事项：

*   如果函数类型不需要返回，那么需要删除整个returns部分；
    
*   如果没有指定`internal|external`，默认是internal内部函数。内部函数可以在当前合约或子合约中使用；
    

    pragma solidity ^0.4.16;
    
    contract A {
        function (uint, uint) pure returns (uint) func; 
    }
    
    contract B is A {
        
        function mul(uint a, uint b) public pure returns (uint) {
            return a * b;
        }
        
        function test() public {
            // 对父合约的函数变量进行赋值
            func = mul;
            // 通过父合约函数变量调用mul函数
            func(10, 20);
        }
    }
    

如果是external外部函数，可以通过函数调用传递，也可以通过函数返回。

    
    pragma solidity ^0.4.16;
    
    contract A {
        struct Request {
            bytes data;
            // 外部函数类型
            function (bytes memory) external callback;
        }
        
        Request[] requests;
        
        // 该函数的第二个参数类型为外部函数类型
        function query(bytes memory data, function (bytes memory) external callback) public {
            requests.push(Request(data, callback));
        }
        
    }
    
    contract Test {
        
        function getResponse(bytes result) public {
            // TODO ...
        }
        
        function test() public {
            // 创建合约A的实例
            A a = new A();
            // 调用合约A实例的query函数，第二个参数是函数类型
            a.query("abc", this.getResponse);
        }
        
    }
    

Other type
----------

### array

> 如果声明数组的时候指定数组长度，那么数组的大小就固定下来；如果声明数组时候没有指定长度，那么该数组的长度可以发生变化。

    // 定义固定长度的数组
    uint[3] a; 
    
    // 定义长度可变的数组
    uint[] a;
    uint[] a = new intUnsupported embed;
    
    // 通过字面量定义数组，数组长度也是可变的
    uint[] c = [uint(1), 2, 3];
    

*   注意事项： 1）如果是状态变量的数组类型，不能手动指定memory或storage，默认为storage数组； 2）如果是局部变量的数组类型，可以指定为memory或storage，但是如果指定为storage数组，那么就必须对数组变量进行初始化；
    

    contract SimpleContract {
        int[] aa;
        int[3] bb;
        int[] cc = new intUnsupported embed;
        int[] dd = [uint(1), 2, 3];
    
        public test() public { 
            int[] memory a1;
            int[] storage b1 = aa;
    
        }
    }
    

> memory和storage关键字的作用是什么？ memory和storage代表数据的存储位置，即数据是保存在内存中还是存储中（这里的存储可以简单理解为电脑的磁盘）。如果是状态变量和局部变量，默认为storage位置；如果是形参，默认位置为memory。另外如果是外部函数参数，其数据位置为calldata，效果跟memory差不多。数据位置的指定非常重要，它会影响着赋值行为。比如说，如果是状态变量赋值给storage的局部变量，实际上传递的是该变量的一个引用。

还有一个地方值得注意的是，定长数组只能赋给定长数组，并且两个数组的大小必须相同；定长数组不能赋值给变长数组.

![](https://storage.googleapis.com/papyrus_images/5a9a48a560c927fdf3f4698a47976488a3d59c56a3589a178e77ba423298bd00.png)

编译报错：

对于storage动态数组，可以通过它的`length`属性改变数组的大小，所以下面第15行代码编译通过。但是对于memory动态数组，则无法通过`length`属性改变数组的大小，所以下面第14行代码报错：

![](https://storage.googleapis.com/papyrus_images/6c0db148da849be59323a50d4c267aa1ff69ca7276bf52399e807022e23eafb9.png)

上面第14行代码会提示TypeError: Expression has to be an lvalue.。lvalue可以理解为等号左边的值。上面错误提示信息提示我们，等号左边的值a1.length必须是一个lvalue，即可以被修改的值。明显a1.length不是一个lvalue。

添加数组元素可以通过下标方式添加，比如说：

    function test() public pure {
        int[] memory arr;
        arr[0] = 100;
    }  
    

如果是变长的storage数组以及bytes类型，也可以通过`push`方法添加数组元素，该方法会返回数组的最新长度。

    
    contract SimpleContract {
        int[] aa;   // Define an array of variable length
          
        function test() public {
            int[] memory bb;
            bb[0] = 100;
            // 因为aa是一个变长的storage数组，因此可以通过push方法添加元素
            aa.push(100);
            // 下面代码报错，因为bb的位置不是storage
            bb.push(100);
        }   
    }
    

另外一个值得注意的地方是，由于EVM限制，solidity不支持通过外部函数调用返回动态内容。比如下面代码：

    
    function test() public pure returns(int[] memory) {
        int[] memory arr;
        arr[0] = 100;
        return arr;
    } 
    

EVM编译合约时候不会报错，但是当我们调用该合约示例的test函数时候，提示下面错误信息`call to SimpleContract.test errored: VM error: invalid opcode.`。

![](https://storage.googleapis.com/papyrus_images/e58b7391c73476389460e2cb947cc939db8b493b10c84ac6b918f10b5792dbde.png)

但是，如果是通过web3调用test函数，它会返回一些动态内容。

> Tips：可以把bytes看作是byte的数组形式，也可以将bytes当作string类型使用。但是，string无法通过length或索引来访问字符串中的每个字符，如果需要访问字符串中的某个字符，可以先把字符串转换成bytes形式，比如：uint size = bytes(“123”).length; byte c = bytes(“123”)\[0\]。

Struct
------

可以使用结构体用来存储复杂的数据结构。其定义格式：

    struct 结构体名称 {
        变量类型 变量名;
        ...
    }
    

结构体中变量类型可以是任意类型，但是它不能够包含自身。

![](https://storage.googleapis.com/papyrus_images/5595d16a0871afe8a9918b2c9be0fb0b7772237615eb1a51c2fa6db45cdb11b8.png)

如果函数中将结构体类型数据赋给一个局部变量，这个过程并没有创建这个结构体对象的副本，而且将该结构体对象的引用赋给了局部变量（即引用传递）。

Enum
----

> 与结构体类似的是，枚举也是solidity中的一种自定义类型，其定义格式为：

    enum 类型名称 {
        枚举值1,
        枚举值2,
        ...
    }
    

枚举类型中至少要包含一个枚举值。枚举值可以是任意有效的标识符（比如说：必须是字母或下划线开头）。

    enum Week {
        Monday,
        Tuesday,
        ...
    }
    

枚举值的类型默认为uint8。第一个枚举值会被解析成0，第二个枚举值会被解析成1，以此类推。枚举类型无法与其他类型进行隐式转换，只能进行显示转换。

    Weekend weekend = Weekend(0);
    

上面代码将数值`0`转换成枚举类型，对应`Weekend.Monday`枚举值。如果数值超过了最大枚举值，则运行合约时显式转换报错。

mapping
-------

与Java的Map集合类型类似，映射类型用于存储的是一对有关系的数据，其定义格式为：

    mapping(_keyType => _valueType) 变量名;
    

\_keyType不可以是映射、变长数组、枚举、结构体类型；\_valueType可以是任意类型。

> 在映射中，实际上存储的是key的keccak256哈希值。映射没有长度，也没有key和value的集合概念。

如果局部变量使用mapping类型，那么该变量的数据位置必须是storage，并且必须要对该变量进行初始化。

    
    contract SimpleContract {
        mapping(string => string) m1;
        
        function test() public {
            mapping(string => string) storage m2 = m1;
        }
    }
    

tuple
-----

> 元组类似于定长数组，它是一个元素数量固定的对象列表。但是与数组不同的是，元组中元素类型可以不一样。一般来说，可以在函数中通过元组返回多个值，使用元素类型的变量来接收函数的返回值。

    function f() public pure returns(uint, bool) {
        return (10, false);
    }
    
    function test() public pure { 
        (uint a, bool b) = f();
    }
    

元组之间可以进行赋值。

    function test() public pure {
        uint x = 10;
        uint y = 20;
        (x, y) = (y, x);
    }
    

上面代码通过元组之间赋值交互x和y的值。

如果元组只有一个元素，那么元素后的逗号不能省略。

    function f() public pure returns(uint, bool) {
        return (10, false);
    }
    
    function test() public pure {
        (uint a, ) = f();
    }
    

operator
--------

    算数运算符：+ - * / % ++ --
    逻辑运算符：&& || ！
    比较运算符：> < >= <= == !=
    位运算符：& | ^（异或） ~（取反） <<（左移） >>（右移） >>>（右移后左边补0）
    赋值运算符：= += -= *= /= %=
    三目运算符：condition expression ? value1 : value2
    

### lvalue

左值变量`lvalue`，即一个可以赋值给它的变量。如果表达式中包含左值变量，其运算符都可以简写。比如：

    function test() public {
        int a = 10;
        int b = 5;
        a += b;a -= b;a *= b;a /= b;
        a++;a--;
    }
    

> 与左值紧密相关的一个关键字delete。比如说delete a，如果a是一个整型，那么delete a代表将a的值恢复成初始状态；如果a是动态数组，那么delete a相当于将数组的长度length设置为0；如果a是静态数组，那么delete a会将数组中每个元素进行重置；如果a是结构体类型，delete a会将结构体中每个属性进行重置。注意：delete对映射无效。

Type convert
------------

如果两个相同类型但精度不同的变量进行运算，那么低精度会自动转换成高精度的类型。

    
    function test() public {
        int8 a = 10;
        int b = 20;
        int c = a + b;
    }
    

上面代码中变量a的类型是int8，变量b的类型是int256，因此它们两个变量相加，低精度会自动转换成高精度，因此得到的结果是int256。同样地，低精度变量也可以赋给高精度变量，所以下面代码也是没问题的。

    function test() public {
        int8 a = 10;
        int b = a;
    }
    

但是如果需要将高精度转换成低精度的类型，那么就需要强制类型转换，如下所示：

    
    function test() public {
        int a = 10;
        int8 b = int8(a);
    }
    

需要注意的是，显式类型转换可能会导致一些无法预料的结果，比如说：

    
    function test() public {
        int a1 = -10;
        uint a2 = uint(a); // 115792089237316195423570985008687907853269984665640564039457584007913129639926
        uint32 b1 = 0x12345678;
        uint16 b2 = uint16(b1); // 22136，即0x5678的十进制表示形式
    }
    

运行上面代码，最终结果与我们的预期不一样。因此如果使用显式类型转换时候，必须要清楚转换的过程。

与javascript类似，定义变量时候也可以使用var关键字，比如说：

    int a = 10;
    var b = a;
    

上面代码中，因为变量a的类型为int256，因此变量b的类型也是int256。而且一旦变量b的类型确定后，它的类型就不会再发生改变。因此下面程序编译失败：

    bool c = false;
    b = c;
    

另外，solidity不支持对函数形参或反参使用var关键字。

![](https://storage.googleapis.com/papyrus_images/ed76c27bde93b4948b58a0ca205be71878dfbdf9118811ba2b330cc8985edff5.png)

Flow control
------------

solidity支持javascript的大部分语法，比如if…else、while…do、do…while、for等等。

    
    pragma solidity ^0.4.16;
    
    contract SimpleContract {
        
        function test1(int week) public pure returns(string) {
            if (week == 1) {
                return "Monday";
            } else if (week == 2) {
                return "Tuesday";
            } else if (week == 3) {
                return "Wednesday";
            } else if (week == 4) {
                return "Thursday";
            } else if (week == 5) {
                return "Friday";
            } else if (week == 6) {
                return "Saturday";
            } else if (week == 7) {
                return "Sunday";
            } else {
                return "invalid week";
            }
        }
        
        function test2() public pure returns(int) {
            int sum = 0;
            for (int i = 0; i < 100; i++) {
                sum += i;
            }
            return sum;
        }   
        
        function test3() public pure returns(int) {
            int i = 0;
            int sum = 0;
            while (i < 100) {
                sum += i;
                i++;
            }
            return sum;
        }  
        
        function test4() public pure returns(int) {
            int i = 0;
            int sum = 0;
            do {
                if (i % 2 == 0) continue;
                sum += i;
                i++;
            } while(i < 100);
            return sum;
        }  
        
    }

---

*Originally published on [Renaissance Labs](https://paragraph.com/@renaissance-labs/learn-solidity-series-9-basis-tips)*
