美文网首页区块study
Solidity -- CryptoZombies 学习笔记2

Solidity -- CryptoZombies 学习笔记2

作者: CoderBigBear | 来源:发表于2018-08-24 15:51 被阅读12次

    第1章

    第2章: 映射(Mapping)和地址(Address)

    Addresses (地址)
    以太坊区块链由 account (账户)组成,你可以把它想象成银行账户。一个帐户的余额是 以太 (在以太坊区块链上使用的币种),你可以和其他帐户之间支付和接受以太币,就像你的银行帐户可以电汇资金到其他银行帐户一样。

    每个帐户都有一个“地址”,你可以把它想象成银行账号。这是账户唯一的标识符,它看起来长这样:

    0xe8528030e1620014AA5d339577e72C650ec94CEc

    现在你只需要了解地址属于特定用户(或智能合约)的。

    Mapping(映射)
    之前学习,我们看到了 结构体 和 数组 。 映射 是另一种在 Solidity 中存储有组织数据的方法。

    映射是这样定义的:

    //对于金融应用程序,将用户的余额保存在一个 uint类型的变量中:

    mapping (address => uint) public accountBalance;
    

    //或者可以用来通过userId 存储/查找的用户名

    mapping (uint => string) userIdToName;
    

    映射本质上是存储和查找数据所用的键-值对。在第一个例子中,键是一个 address,值是一个 uint,在第二个例子中,键是一个uint,值是一个 string。

    第3章: Msg.sender

    msg.sender
    在 Solidity 中,有一些全局变量可以被所有函数调用。 其中一个就是 msg.sender,它指的是当前调用者(或智能合约)的 address。

    注意:在 Solidity 中,功能执行始终需要从外部调用者开始。 一个合约只会在区块链上什么也不做,除非有人调用其中的函数。所以 msg.sender总是存在的。

    以下是使用 msg.sender 来更新 mapping 的例子:

    mapping (address => uint) favoriteNumber;
    
    function setMyNumber(uint _myNumber) public {
      // 更新我们的 `favoriteNumber` 映射来将 `_myNumber`存储在 `msg.sender`名下
      favoriteNumber[msg.sender] = _myNumber;
      // 存储数据至映射的方法和将数据存储在数组相似
    }
    
    function whatIsMyNumber() public view returns (uint) {
      // 拿到存储在调用者地址名下的值
      // 若调用者还没调用 setMyNumber, 则值为 `0`
      return favoriteNumber[msg.sender];
    }
    

    在这个小小的例子中,任何人都可以调用 setMyNumber 在我们的合约中存下一个 uint 并且与他们的地址相绑定。 然后,他们调用 whatIsMyNumber 就会返回他们存储的 uint。

    使用 msg.sender 很安全,因为它具有以太坊区块链的安全保障 —— 除非窃取与以太坊地址相关联的私钥,否则是没有办法修改其他人的数据的。

    第4章: Require

    限定:每个玩家只能调用一次某个函数。

    怎样才能限定每个玩家只调用一次某个函数呢?

    答案是使用require。 require使得函数在执行过程中,当不满足某些条件时抛出错误,并停止执行:

    function sayHiToVitalik(string _name) public returns (string) {
      // 比较 _name 是否等于 "Vitalik". 如果不成立,抛出异常并终止程序
      // (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较
      // 两字符串的 keccak256 哈希值来进行判断)
      require(keccak256(_name) == keccak256("Vitalik"));
      // 如果返回 true, 运行如下语句
      return "Hi!";
    }
    

    如果你这样调用函数 sayHiToVitalik(“Vitalik”) ,它会返回“Hi!”。而如果调用的时候使用了其他参数,它则会抛出错误并停止执行。

    因此,在调用一个函数之前,用 require 验证前置条件是非常有必要的。

    第5章: 继承(Inheritance)

    我们的合约代码越来越长。 当代码过于冗长的时候,最好将代码和逻辑分拆到多个不同的合约中,以便于管理。

    有个让 Solidity 的代码易于管理的功能,就是合约 inheritance (继承):

    contract Doge {
      function catchphrase() public returns (string) {
        return "So Wow CryptoDoge";
      }
    }
    
    contract BabyDoge is Doge {
      function anotherCatchphrase() public returns (string) {
        return "Such Moon BabyDoge";
      }
    }
    

    由于 BabyDoge 是从 Doge 那里 inherits (继承)过来的。 这意味着当你编译和部署了 BabyDoge,它将可以访问 catchphrase() 和 anotherCatchphrase()和其他我们在 Doge 中定义的其他公共函数。

    这可以用于逻辑继承(比如表达子类的时候,Cat 是一种 Animal)。 但也可以简单地将类似的逻辑组合到不同的合约中以组织代码。

    第6章: 引入(Import)

    代码已经够长了,我们把它分成多个文件以便于管理。 通常情况下,当 Solidity 项目中的代码太长的时候我们就是这么做的。

    在 Solidity 中,当你有多个文件并且想把一个文件导入另一个文件时,可以使用 import 语句

    import "./someothercontract.sol";
    
    contract newContract is SomeOtherContract {
    
    }
    

    这样当我们在合约(contract)目录下有一个名为 someothercontract.sol 的文件( ./ 就是同一目录的意思),它就会被编译器导入。

    第7章: Storage与Memory

    在 Solidity 中,有两个地方可以存储变量 —— storage 或 memory。

    Storage 变量是指永久存储在区块链中的变量。 Memory 变量则是临时的当外部函数对某合约调用完成时,内存型变量即被移除。你可以把它想象成存储在你电脑的硬盘或是RAM中数据的关系。

    大多数时候你都用不到这些关键字,默认情况下 Solidity 会自动处理它们。 状态变量(在函数之外声明的变量)默认为“存储”形式,并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失。

    然而也有一些情况下,你需要手动声明存储类型,主要用于处理函数内的 结构体 和 数组 时:

    contract SandwichFactory {
      struct Sandwich {
        string name;
        string status;
      }
    
      Sandwich[] sandwiches;
    
      function eatSandwich(uint _index) public {
        // Sandwich mySandwich = sandwiches[_index];
    
        // ^ 看上去很直接,不过 Solidity 将会给出警告
        // 告诉你应该明确在这里定义 `storage` 或者 `memory`。
    
        // 所以你应该明确定义 `storage`:
        Sandwich storage mySandwich = sandwiches[_index];
        // ...这样 `mySandwich` 是指向 `sandwiches[_index]`的指针
        // 在存储里,另外...
        mySandwich.status = "Eaten!";
        // ...这将永久把 `sandwiches[_index]` 变为区块链上的存储
    
        // 如果你只想要一个副本,可以使用`memory`:
        Sandwich memory anotherSandwich = sandwiches[_index + 1];
        // ...这样 `anotherSandwich` 就仅仅是一个内存里的副本了
        // 另外
        anotherSandwich.status = "Eaten!";
        // ...将仅仅修改临时变量,对 `sandwiches[_index + 1]` 没有任何影响
        // 不过你可以这样做:
        sandwiches[_index + 1] = anotherSandwich;
        // ...如果你想把副本的改动保存回区块链存储
      }
    }
    

    如果你还没有完全理解究竟应该使用哪一个,也不用担心 —— 在本教程中,我们将告诉你何时使用 storage 或是 memory,并且当你不得不使用到这些关键字的时候,Solidity 编译器也发警示提醒你的。

    现在,只要知道在某些场合下也需要你显式地声明 storage 或 memory就够了!

    第8章: 更多关于函数可见性

    internal 和 external

    除 public 和 private 属性之外,Solidity 还使用了另外两个描述函数可见性的修饰词:internal(内部) 和 external(外部)。

    internal 和 private 类似,不过, 如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”函数。(嘿,这听起来正是我们想要的那样!)。

    external 与public 类似,只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。稍后我们将讨论什么时候使用 external 和 public。

    声明函数 internal 或 external 类型的语法,与声明 private 和 public类 型相同:

    contract Sandwich {
      uint private sandwichesEaten = 0;
    
      function eat() internal {
        sandwichesEaten++;
      }
    }
    
    contract BLT is Sandwich {
      uint private baconSandwichesEaten = 0;
    
      function eatWithBacon() public returns (string) {
        baconSandwichesEaten++;
        // 因为eat() 是internal 的,所以我们能在这里调用
        eat();
      }
    }
    

    第9章: 更多关于函数可见性

    与其他合约的交互
    如果我们的合约需要和区块链上的其他的合约会话,则需先定义一个 interface (接口)。

    先举一个简单的例子。 假设在区块链上有这么一个合约:

    contract LuckyNumber {
      mapping(address => uint) numbers;
    
      function setNum(uint _num) public {
        numbers[msg.sender] = _num;
      }
    
      function getNum(address _myAddress) public view returns (uint) {
        return numbers[_myAddress];
      }
    }
    

    这是个很简单的合约,您可以用它存储自己的幸运号码,并将其与您的以太坊地址关联。 这样其他人就可以通过您的地址查找您的幸运号码了。

    现在假设我们有一个外部合约,使用 getNum 函数可读取其中的数据。

    首先,我们定义 LuckyNumber 合约的 interface :

    contract NumberInterface {
      function getNum(address _myAddress) public view returns (uint);
    }
    

    请注意,这个过程虽然看起来像在定义一个合约,但其实内里不同:

    首先,我们只声明了要与之交互的函数 —— 在本例中为 getNum —— 在其中我们没有使用到任何其他的函数或状态变量。

    其次,我们并没有使用大括号({ 和 })定义函数体,我们单单用分号(;)结束了函数声明。这使它看起来像一个合约框架。

    编译器就是靠这些特征认出它是一个接口的。

    在我们的 app 代码中使用这个接口,合约就知道其他合约的函数是怎样的,应该如何调用,以及可期待什么类型的返回值。

    第10章: 使用接口

    继续前面 NumberInterface 的例子,我们既然将接口定义为:

    contract NumberInterface {
      function getNum(address _myAddress) public view returns (uint);
    }
    

    我们可以在合约中这样使用:

    contract MyContract {
      address NumberInterfaceAddress = 0xab38...;
      // ^ 这是FavoriteNumber合约在以太坊上的地址
      NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
      // 现在变量 `numberContract` 指向另一个合约对象
    
      function someFunction() public {
        // 现在我们可以调用在那个合约中声明的 `getNum`函数:
        uint num = numberContract.getNum(msg.sender);
        // ...在这儿使用 `num`变量做些什么
      }
    }
    

    通过这种方式,只要将您合约的可见性设置为public(公共)或external(外部),它们就可以与以太坊区块链上的任何其他合约进行交互。

    第11章: 处理多返回值

    getKitty 是我们所看到的第一个返回多个值的函数。我们来看看是如何处理的:

    function multipleReturns() internal returns(uint a, uint b, uint c) {
      return (1, 2, 3);
    }
    
    function processMultipleReturns() external {
      uint a;
      uint b;
      uint c;
      // 这样来做批量赋值:
      (a, b, c) = multipleReturns();
    }
    
    // 或者如果我们只想返回其中一个变量:
    function getLastReturnValue() external {
      uint c;
      // 可以对其他字段留空:
      (,,c) = multipleReturns();
    }
    

    **第13章: if 语句

    if语句的语法在 Solidity 中,与在 JavaScript 中差不多:

    当我们比较字符串的时候,需要比较他们的 keccak256 哈希码

    function eatBLT(string sandwich) public {
      // 看清楚了,当我们比较字符串的时候,需要比较他们的 keccak256 哈希码
      if (keccak256(sandwich) == keccak256("BLT")) {
        eat();
      }
    }
    

    相关文章

      网友评论

        本文标题:Solidity -- CryptoZombies 学习笔记2

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