web3学习笔记(三)

到目前为止都很基础,暂时还没有强度

web3学习笔记(三)

12.映射(Mapping)和地址(Address)

12.1.Addresses (地址)

以太坊区块链由account组成,把他理解为银行账户,每个账户都有地址,这是账户的唯一标识符。

例如:

0x0cE446255506E92DF41614C46F1d6df9Cc969183

12.2.Mapping(映射)

这个和其他语言的概念是一样的:

// 地址 到 uint类型变量的映射,映射名 accountBalance
mapping (address => uint) public accountBalance;
// 反过来的例子 
mapping (uint => string) userIdToName;

13.Msg.sender

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

一个合约只会在区块链上什么也不干,除非说有人调用了合约中的某些方法(函数)。所以说msg.sdner总是存在的。

mapping (address => uint) favoriteNumber;

function setMyNumber(uint _myNumber) public {
    // 更新favoriteNumber这个映射来将_myNumber储存在msg.sender名下
    favoriteNumber[msg.sender] = _myNumber;
}

function whatIsMyNumber() public view returns (uint) {
    // 拿到储存在address(地址)下的值
    return favoriteNumber[msg.sender];
}

这个例子中,任何人都能调用setMyNumber在我们合约的地址下存入一个uint。然后可以调用whatIsMyNumber返回储存的uint

14.Require

require使得函数在执行过程中,当不满足某些条件时抛出错误,并停止执行。这个就很类似try catch或者说return了。

function sayHi(string _name) public returns (string) {
    // 在solidity中,是没有办法直接比较字符串的,所以要用之前学到的keccak256转换成一个256位的16进制数字比较
    require(keccak256(_name) == keccak256("zs"));
    // 当_name的值为 "zs"的时候才能返回 "Hi!",不然就抛出错误停止执行
    return "Hi!"
}

所以说,在函数中使用require做前提判断是非常有必要的。

15.继承(Inheritance)

在一个合约中,如果写了太多的代码,后期管理维护起来非常困难,所以我们要想办法把他们整理好,分散到多个合约中。

contract Father {
    function sayHi() public returns (string) {
        return "Hi!";
    }
}

// 由于Son是从Father那里继承的,所以当编译部署了Son后,他可以访问Father中的公共方法(Son也能说Hi)
contract Son is Father{
    function sayHello() public returns (string) {
        return "Hello!";
    }
}

16.引入(Import)

当有多个文件的时候,就可以使用import把一个文件引入另一个文件,这个就不多说了,太熟悉了。

// father.sol
contract Father {
    function sayHi() public returns (string) {
        return "Hi!";
    }
}
// son.sol
import "./father.sol";
contract Son is Father{
    function sayHello() public returns (string) {
        return "Hello!";
    }
}

17.Storage与Memory

Storage指的是永久存在区块链中的变量。

Memory变量是临时的,我把它理解为存在内存中的,当外部函数对某合约调用完成的时候,这个变量就移除了。

大多数的时候,都用不到这些关键字,默认情况下Solidity会自动处理他们。

contract SandwichFactory {
    struct Sandwich {
        string name;
        string status;
    }
    
    Sandwich[] sandwiches;
    
    function eatSandwich(uint _index) public {
        // 定义了 mySandwich 数据数据,放到链上
        Sandwich storage mySandwich = sandwiches[_index];
        mySandwich.status = "Eaten!";
        // 定义了另一个变量 anotherSandwich,放在内存中
        Sandwich memory anotherSandwich = sandwiches[_index + 1];
        anotherSandwich.status = "Eaten!";
    }
}

18.更多关于函数可见性

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

总结一下这几个的区别:

  1. public

    • 可以在合约内部和外部访问
    • 自动生成一个同名的getter函数
    • 其他合约和外部账户都可以读取该变量
  2. private

    • 只能在定义它的合约内部访问
    • 派生合约不能访问
    • 外部合约不能访问
  3. internal

    • 只能在定义它的合约内部和派生合约中访问
    • 外部合约不能访问
  4. external

    • 只能从合约外部调用
    • 不能在合约内部直接调用(除非使用this.function()
    • 对于接收大量数据的函数,使用external可以更节省gas
contract AccessModifierExample {
    // 状态变量
    uint public publicVar;       // 任何人都可以读取
    uint private privateVar;     // 只有本合约可以访问
    uint internal internalVar;   // 本合约和继承的合约可以访问
    // uint external externalVar; // 错误!状态变量不能是external

    // 函数
    function publicFunction() public {
        // 可以调用合约内的任何函数
        privateFunction();
        internalFunction();
        // 调用external函数需要用this
        this.externalFunction();
    }

    function privateFunction() private {
        // 只能在当前合约内部调用
    }

    function internalFunction() internal {
        // 可以在当前合约和继承合约中调用
    }

    function externalFunction() external {
        // 只能从合约外部调用,或通过this.externalFunction()
    }
}

19.与其他合约的交互

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

假如链上有这样一个合约:

contract LuckyNumber {
  // 定义了一个映射 地址=>uint
  mapping(address => uint) numbers;

  function setNum(uint _num) public {
      // 把一个存入一个num
    numbers[msg.sender] = _num;
  }

  function getNum(address _myAddress) public view returns (uint) {
      // 根据_myAddress获取存入的num
    return numbers[_myAddress];
  }
}

然后定义一个interface

// 这个形式看起来很像一个合约,但是里面只是定义了要使用的函数,而且没有函数体,没有逻辑
contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

使用这个interface

contract MyContract {
  // 假设NumberInterfaceAddress的地址是这个0xab38...
  address NumberInterfaceAddress = 0xab38...;
  
  // NumberInterface(NumberInterfaceAddress)就能获取到要用的那个合约对象,用JS的来说就是那个合约实例,然后把实例给numberContract
  NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);

  // 还是用JS来说,就是用实例上的方法了哈哈哈
  // 但是要注意的就是,那个方法必须是public或者external
  function someFunction() public {
    uint num = numberContract.getNum(msg.sender);
  }
}

20.处理多返回值

和JS不同的地方,JS中的函数只能return一个值,要想返回多个值一般用对象包一层,但是在Solidity中,函数可以返回多个值:

// 可以看到,每个返回值都要定义类型
function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

// 这样写就会报错,不会自动一一对应上的,需要我们按照顺序写类型
function multipleReturns() internal returns(string 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();
}

21.if条件判断

Solidity中的if基本和JS中差不多:

function eatBLT(string sandwich) public {
  // 还是那个道理,Solidity中不能直接比较字符串,要比较字符串就只能比较字符串的 keccak256 哈希码
  if (keccak256(sandwich) == keccak256("BLT")) {
    eat();
  }
}
kucha
“ 大冬天的非常冷,打工人很辛苦,需要一杯奶茶暖暖肚子。 ”
 喜欢文章
头像