到目前为止都很基础,暂时还没有强度
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.更多关于函数可见性
除 public
和 private
属性之外,Solidity 还使用了另外两个描述函数可见性的修饰词:internal
(内部) 和 external
(外部)。
总结一下这几个的区别:
public
- 可以在合约内部和外部访问
- 自动生成一个同名的getter函数
- 其他合约和外部账户都可以读取该变量
private
- 只能在定义它的合约内部访问
- 派生合约不能访问
- 外部合约不能访问
internal
- 只能在定义它的合约内部和派生合约中访问
- 外部合约不能访问
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();
}
}