美文网首页
智能合约编程语言 - solidity快速入门(上)

智能合约编程语言 - solidity快速入门(上)

作者: 端碗吹水 | 来源:发表于2019-11-19 12:05 被阅读0次

    solidity简介

    本文默认读者已掌握至少一种面向对象编程语言,所以文中一些概念会借助其他语言进行类比。

    solidity是用于实现智能合约的一种面向合约的高级编程语言,solidity受到C++、Python和JavaScript的影响,被设计为可运行在以太坊虚拟机(EVM)上,所以用户无需担心代码的可移植性和跨平台等问题。solidity是一种静态类型的语言,支持继承、库引用等特性,并且用户可自定义复杂的结构类型。

    目前尝试 Solidity 编程的最好的方式是使用 Remix (由于是网页IDE可能加载起来需要一定的时间)。Remix 是一个基于 Web 的 IDE,它可以让你编写 Solidity 智能合约,然后部署并运行该智能合约,它看起来是这样子的:

    image.png

    也可以使用sublime或vs code等编辑器编写 Solidity 代码,然后复制粘贴到Remix上部署运行。

    solidity官网地址如下:

    https://solidity.readthedocs.io/en/latest/index.html


    合约文件

    本小节我们来说说合约文件,众所周知任何语言所编写的代码都需要存储在一个文件里,并且该文件都有一个特定的后缀名,我们一般将这种文件称之为代码文件。

    solidity代码文件的后缀名为.sol,但我们通常会把使用solidity编写的文件称之为合约文件,一个合约文件通常会包含四个部分,其实与我们平时所编写其他语言的代码文件是类似的,如下图所示:

    image.png

    版本声明的代码需写在合约文件的开头,接着可以根据实际情况导入一些合约,所谓导入合约也就类似于其他面向对象的语言导入某个类的概念。然后就是声明一个合约,在合约里编写具体的代码,其实这里的合约与我们所熟悉的类的概念基本上是一样的,可以暂时将它们当做同一个东西。

    我们先来对一个较为完整的合约代码进行一个预览,在之后会对代码中的每个部分进行逐一介绍:

    // 版本声明
    pragma solidity ^0.4.0;
    
    // 导入一个合约
    import "solidity_for_import.sol";
    
    // 定义一个合约
    contract ContractTest {
        // 定义一个无符号整型变量
        uint a;
    
        // 定义一个事件
        event Set_A(uint a);
        
        // 定义一个函数
        function setA(uint x) public {
            a = x;
            // 触发一个事件
            emit Set_A(x);
        }
    
        // 定义一个具有返回值的函数
        function getA() public returns (uint) {
            return a;
        }
        
        // 自定义一个结构类型
        struct Pos {
            // 定义一个有符号整型变量
            int lat;
            int lng;
        }
    
        // 定义一个地址类型,每个合约都运行在一个特定的地址上
        address public addr;
    
        // 定义一个函数修改器
        modifier owner () {
            require(msg.sender == addr);
            _;
        }
    
        // 让函数使用函数修改器
        function mine() public owner {
            a += 1;
        }
    }
    

    这里对函数修改器做一个简单的说明:

    函数修改器的概念类似于python中的装饰器,其核心目的都是给函数增加函数内没有定义的功能,也就是对函数进行增强

    从以上代码中,可以看到owner 函数修改器里定义了一句条件代码,其意义为:

    msg.sender等于addr地址变量时,才继续往下执行,因为这个require函数是solidity校验条件用的,若不符合条件就会抛出异常

    mine函数使用了owner函数修改器后,那么mine函数在执行之前,会先执行owner函数修改器里的条件代码,也就是说当msg.sender等于addr成立的话,才会执行mine函数里a += 1;的代码,否则就不会执行。从中也可以看出函数修改器里的_;语句,其实表示的就是mine函数里的代码,如此一来在不修改mine函数的前提下,给mine函数增加了额外的功能。


    solidity 类型

    Solidity是一种静态类型语言,意味着每个变量(本地或状态变量)需要在编译时指定变量的类型(或至少可以推导出类型),Solidity提供了一些基本类型可以用来组合成复杂类型。

    Solidity和大多数语言一样,有两种类型:

    • 值类型(Value Type) - 变量在赋值或传参时,总是进行值拷贝。
    • 引用类型(Reference Types)

    solidity所包含的值类型如下:


    image.png

    注:其中标红的是最常用的类型

    官网关于solidity类型的文档地址如下:

    https://solidity.readthedocs.io/en/latest/types.html

    1.布尔类型取值范围是true和false,使用bool关键字进行声明,声明方式如下:

    // 版本声明
    pragma solidity ^0.4.0;
    
    // 定义一个合约
    contract ContractTest {
        bool b1 = true;
        bool b2 = false;
    }   
    

    2.solidity中有两种整型的定义方式,一种是无符号整型,另一种则是有符号整型。并且支持关键字uint8 到 uint256 (以8步进),uint 和 int 默认对应的是 uint256 和 int256。如下示例:

    // 版本声明
    pragma solidity ^0.4.0;
    
    // 定义一个合约
    contract ContractTest {
        // 定义一个无符号的整型变量
        uint a;
        // 定义一个有符号的整型变量
        int i;
    }
    

    solidity常量

    在solidity里使用constant关键字来声明常量,但并非所有的类型都支持常量,当前支持的仅有值类型和字符串:

    pragma solidity ^0.4.0;
    
    contract C {
        uint constant x = 32**22 + 8;
        string constant text = "abc";
        bytes32 constant myHash = keccak256("abc");
    }
    

    在solidity中还可以将函数声明为常量,该函数的返回值就是常量值,这类函数将承诺自己不修改区块链上任何状态:

    // 定义有理数常量
    function testLiterals() public constant returns (int) {
        return 1;
    }
    
    // 定义字符串常量
    function testStringLiterals() public constant returns (string) {
        return "string";
    }
    
    // 定义16进制常量,以关键字hex打头,后面紧跟用单或双引号包裹的字符串,内容是十六进制字符串
    function testHexLiterals() public constant returns (bytes2) {
        return hex"abcd";
    }
    

    有理数常量函数里的运算可以是任意精度的,不会有溢出的问题:

    // 定义有理数常量
    function testLiterals() public constant returns (int) {
        return 1859874861811128585416.0 + 123.0;
    }
    

    科学符号也支持,基数可以是小数,但指数必须是整数,如下:

    // 定义有理数常量
    function testLiterals() public constant returns (int) {
        return 2e10;
    }
    

    solidity地址类型

    solidity中使用address关键字声明地址类型变量,该类型属于值类型,地址类型主要用于表示一个账户地址,一个以太坊地址的长度为20字节的16进制数,地址类型也有成员,地址是所有合约的基础。

    地址类型的主要成员:

    • 属性:balance,用来查询账户余额
    • 函数:transfer(),用来转移以太币(默认以wei为单位)

    代码示例如下:

    pragma solidity ^0.4.7;
    
    contract AddrTest {
    
        // payable关键字定义一个可接受以太币的函数
        function deposit() public payable {
    
        }
    
        // 查询账户余额
        function getBalance() public constant returns (uint) {
            return this.balance;
        }
    
        // 转移以太币
        function transferEther(address towho) public {
            towho.transfer(10);
        }
    }
    

    然后我们将这段代码复制粘贴到remix中编译运行看看,首先需要在Compile选项卡中将代码进行编译:


    image.png

    编译成功后,到Run选项卡中,部署该合约:


    image.png

    部署成功后,可以查看到合约中的各个函数,并且只需要点击就可以运行指定的函数:


    image.png

    此时我们来点击执行一下getBalance函数:


    image.png

    可以看到,此时该合约的账户余额为0,现在我们来存储10个wei的以太币到合约中:


    image.png

    此时再执行getBalance函数,合约余额为10个wei:


    image.png

    然后我们再来看看转移/发送以太币的transferEther函数,此时我们这个合约地址的余额为10个wei,当我将这10个wei的以太转移到另一个地址后,当前合约的余额为0:


    image.png

    在solidity中一个能通过地址合法性检查(address checksum test)的十六进制常量就会被认为是地址,如:

    0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF

    而不能通过地址合法性检查的39到41位长的十六进制常量,会提示一个警告,被视为普通的有理数常量。

    关于账户地址的合法性检查定义参考如下提案:

    https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md


    solidity数组

    在上文中我们提到Solidity 类型分为值类型和引用类型,以上小节介绍了常见的值类型,接下来会介绍一下引用类型。

    引用类型是一个复杂类型,占用的空间通常超过256位, 拷贝时开销很大,因此我们需要考虑将它们存储在什么位置,是存储在memory(内存,数据不是永久存在)中还是存储在storage(永久存储在区块链)中。

    所有的复杂类型如数组和结构体都有一个额外的属性:数据的存储位置(data location),可为memory和storage。根据上下文的不同,大多数时候数据存储的位置有默认值,也可以通过指定关键字storage和memory修改它。

    函数参数(包含返回的参数)默认是memory。而局部复杂类型变量(local variables)和状态变量(state variables) 默认是storage。局部变量即部作用域(越过作用域即不可被访问,等待被回收)的变量,如函数内的变量,状态变量则是合约内声明的公有变量。

    除此之外,还有一个存储位置是:calldata,用来存储函数参数,是只读的,不会永久存储的一个数据位置。外部函数的参数(不包括返回参数)被强制指定为calldata。效果与memory差不多。还有一个存储位置是:calldata,用来存储函数参数,是只读的,不会永久存储的一个数据位置。外部函数的参数(不包括返回参数)被强制指定为calldata。效果与memory差不多。

    数组是一种典型的引用类型,在solidity中数组的定义方式如下:

    • T[k]:元素类型为T,固定长度为k的数组
    • T[]:元素类型为T,长度可动态调整的数组
    • bytes和string 是一种特殊的数组,string 可转为 bytes,而bytes则类似于byte[]

    数组类型有两个主要成员:

    • 属性:length
    • 函数:push()

    具体的示例代码如下:

    pragma solidity ^0.4.7;
    
    contract ArrayTest {
        // 定义一个无符号整型的变长数组
        uint[] public numbers = [1, 2, 3];
    
        // 定义一个字符串
        string str = "abcdefg";
    
        function getNumbersLength() public returns (uint) {
            // 往数组中添加一个元素
            numbers.push(4);
    
            // 返回数组的长度
            return numbers.length;
        }
    
        function getStrLength() public constant returns (uint) {
            // 将字符串转换为bytes并返回长度
            return bytes(str).length;
        }
    
        function getFirst() public constant returns (byte) {
            // 将字符串转换为bytes后,通过下标访问元素
            return bytes(str)[0];
        }
    
        function newMemory(uint len) public constant returns (uint) {
            // 定义一个定长数组并通过memory指定数组的存储位置
            uint[] memory memoryArr = new uint[] (len);
            return memoryArr.length;
        }
    
        function changeFirst(uint[3] _data) public constant returns (uint[3]) {
            // 通过索引操作元素
            _data[0] = 0;
            return _data;
        }
    }
    

    solidity结构体和映射

    Solidity提供struct关键字来定义自定义类型也就是结构体,自定义的类型属于引用类型,如果学习过go语言的话应该对其不会陌生。如下示例:

    // 版本声明
    pragma solidity ^0.4.7;
    
    // 定义一个合约
    contract ContractTest {
        // 声明一个结构体
        struct Funder {
            address addr;
            uint amount;
        }
    
        // 将自定义的结构体声明为状态变量
        Funder funder;
    
        // 使用结构体
        function newFunder() public {
            funder = Funder({addr: msg.sender, amount: 10});
        }
    }
    

    solidity拥有映射类型,映射类型是一种键值对的映射关系存储结构,有点类似于python语言中的字典。定义方式为mapping(_KeyType => _KeyValue)。键类型允许除映射、变长数组、合约、枚举、结构体外的几乎所有类型值类型没有任何限制,可以为任何类型包括映射类型。

    映射可以被视作为一个哈希表,所有可能的键会被虚拟化的创建,映射到一个类型的默认值(二进制的全零表示)。在映射表中,并不存储键的数据,仅仅存储它的keccak256哈希值,这个哈希值在查找值时需要用到。正因为此,映射是没有长度的,也没有键集合或值集合的概念。

    映射类型有一点比较特殊,它仅能用来作为状态变量,或在内部函数中作为storage类型的引用。

    可以通过将映射标记为public,来让Solidity创建一个访问器。通过提供一个键值做为参数来访问它,将返回对应的值。映射的值类型也可以是映射,使用访问器访问时,要提供这个映射值所对应的键,不断重复这个过程。

    示例代码如下:

    // 版本声明
    pragma solidity ^0.4.7;
    
    // 定义一个合约
    contract ContractTest {
        // 定义一个映射类型,key类型为address,value类型为uint
        mapping(address => uint) public balances;
    
        function updateBalance(uint newBalance) public {
            // msg.sender作为键,newBalance作为值,将这对键值添加到该映射中
            balances[msg.sender] = newBalance;
        }
    }
    

    相关文章

      网友评论

          本文标题:智能合约编程语言 - solidity快速入门(上)

          本文链接:https://www.haomeiwen.com/subject/etewictx.html