Cover photo

Web3.0学习树|5.Solidity基础 Ⅱ

这是Web3.0学习的第二章Solidity基础,文内如出现知识性和理解性错误还请各位斧正,原创文章,完全开源,若能得转载不胜荣幸,推特会分享Coding学习,生活日常和NBA等内容。**

目录

  • Solidity基础函数

  • 数组和结构体

  • 错误和警告

  • 变量/常量/Immutable

Solidity基础函数

"函数"或者"方法"指的是独立模块,在我们调用的时候会执行某些指令,Solidity的函数性质和Java一样,函数通过"function"关键字表示。

为了查看一个函数的实际运行结果,我们需要把合约部署在一个测试环境上,把它部署在本地网络,或者叫javascript VM(现在称其为Remix VM),在部署之前,我们需要看看它有没有正确编译。

Remix VM表示合约将被部署到本地的Remix虚拟机上,Remix VM是本地测试用的区块链,在上面可以快速模拟交易,不需要等待测试网的流程。

运行Remix VM的时候,我们有很多账户可以部署,每个账户中都有100个以太币,这些账户和Metamask中的账户类似,区别是,这些是在Remix VM中的测试以太币。

在部署合约的交易中,可以设置Gas limit,同时可以选择要部署的合约

post image

在测试环境中,合约也有一个地址,每个合约都有一个地址,就像每个钱包账户都有地址一样。

post image

部署一个合约其实就是发送一个交易(任何时候你改变链上的任何东西,包括制定一个新的合约,它都会发生在一个交易中),部署一个合约就修改了区块链,让链上拥有这个合约

post image

参数的访问级别如果没有特别指定的话默认是internal.

将参数的访问级别设置为public后的合约:

favoriteNumbre按钮,类似一个函数,显示变量的值。

这些按钮都是被因代码语句而被Solidity自动赋予的视图功能。

post image
post image

权限声明:

函数分为不同的可见性,用户使用不同的关键字进行声明:

public:在外部和内部都可见,任何与合约交互的人,都可以看到favoriteNumber中存的值(public会创建storage(存储)和state(状态)变量的getter函数)给favoriteNumber加上public,实际上就是给favoriteNumber创建了getter函数,我们其实创建了一个函数,返回favoriteNumber的值,上图中的蓝色按钮就是一个返回favoriteNumber值的函数。

带有public的变量favoriteNumber,可以看作是一个返回uint256的view函数

private:表示只有这个合约可以调用这个函数,对于Storage来说,它不是说只有这个合约才能读取它的值。private表示这个合约是唯一可以调用favoriteNumber函数的合约。private表示只对合约内部可见。

external:表示只对合约外部可见,表示合约外的账户可以调用这个函数。

internal:表示只有这个合约或者继承它的合约可以调用。

智能合约的函数越复杂,消耗的计算量就越多,我们花掉的Gas费就越多(The more "stuff" in your function the more gas it costs)

查询数据的合约函数也有不同的声明方式:

  • view可以读取变量,但不能修改

  • pure不可以读也不可以修改

Scope(作用域)

favoriteNumber是在Global scope(全局作用域)中,表示所有在花括号中的任何函数都可以获取它。

当你创建一个变量的时候,它只有在这个作用域(花括号)才可见

因为something函数不在store函数中,所以something函数无法获取testVar变量。

这就是作用域的原理,看变量是否在这个花括号中被创建,然后判断是否可以被别的函数获取

post image

标识函数的调用不需要消耗Gas,Solidity中有两个可以把普通函数转换为标识函数的关键字,这两个关键字是view和pure

如果一个函数是view函数,意味着我们只会读取这个合约的状态

例如:retrive函数只读取favoriteNumber的值

view函数和pure函数不允许修改任何状态,但是pure函数也不允许读取区块链数据(所以我们也不能读取favoriteNumber的值)

post image

通过pure函数,你想做的事情可能是这样的,在pure函数中,返回1+1的结果(类似于这样的东西,可能是常用的方法,或者某个不需要读取数据的算法)。

post image

为什么我们调用view或者pure函数,是不需要支付Gas的?

因为只是读区块链数据,记住,只有更改状态的时候才支付Gas。

但是如果一个要改变区块链状态的函数调用了类似retrive这种view或者pure函数才会消耗Gas

假设:store不是view函数,它在某处调用了retrive,那它就要支付retrive的Gas。

关键字return表示,我们调用函数后会得到什么类型的数据。

数组和结构体

数组是一种存储同类元素的有序集合,通过uint[] public arr;来进行定义,在定义时可以预先指定数组大小,如uint[10] public myFixedSizeArr;。

需要注意的是,我们可以在内存中创建数组,但是必须固定大小,如uint[] memory a = new uint

数组类型有一些基本操作方法,如下:

//定义数组类型
uint[7] public arr;

//添加数据
arr.push(7);

//删除最后一个数据
arr.pop();

//删除某个索引值数据
delete arr[1];

//获取数组长度
uint len = arr.length;

示例:

//SPDX-License-Identifier:MIT
pragma solidity ^0.8.8;

contract SimpleStorage {

    uint256 public favoriteNumber;


    function store(uint256 _favoriteNumber) public {
        favoriteNumber = _favoriteNumber;
        uint256 testVar = 5;
    }

    //创建一个函数来返回favoriteNumber,模拟被自动创建的getter函数
    //view,pure
    function retrieve() public view returns(uint256){
        return favoriteNumber;
    }

    function add() public pure returns(uint256){
        return(1+1);
    }
    
}

之前我们的合约允许我们存储一个喜欢的号码,但是如果我们想存储一系列最喜欢的数字,或者我们想存储一大群不同的人,每个人都有不同的所喜欢的数字,该如何去存储呢?

方法引入:

post image
post image

每当你有一个变量列表在一个对象内部时,在Solidity中,它们会自动编入索引。

创建列表的更好方法是使用结构类型,称为Struct,Struct是一种存储列表或一系列对象的方式

Struct

struct是结构类型,对于复杂业务,我们经常需要定义自己的结构,将关联的数据组合起来,可以在合约内进行定义。

各类操作:

contract Struct {
    struct Data {
        string id;
        string hash;
    }
    
    Data public data;
    
    //添加数据
    function create(string calldata _id) public {
        data = Data{id: _id, hash:"111222"};
    }
    
    //更新数据
    function updata(string _id) public {
        //查询数据
        string id = data.id;
        
        //更新
        data.hash = "222333"
    }
}

也可以分离出一个文件定义所有需要的结构类型,由合约按需导入

// 'StructDeclaration.sol'
struct Data {
        string id;
        string hash;
}
// 'Struct.sol'

import "./StructDeclaration.sol"
contract Struct {
        Data public data;
}

示例代码:

    //创建一种新的类型
    //有点像uint256/boolean/string
    struct People {
        uint256 favoriteNumber;
        string name;
    }

    // uint256[] public favoriteNumberList;
    //People[3] public people;//静态数组(指定数组大小,该数组中只能放下三个人)
    People[] public people;//动态数组(数组的大小没有给出,我们不给它一个尺寸,这意味着它可以是任何尺寸,并且数组的大小可以随着我们加减人而增长和缩小)
    //0: 2,Patrick | 1: 7,Jon | 

    function addPerson(string memory _name, uint256 _favoriteNumber) public{
        People memory newPerson = People({favoriteNumber: _favoriteNumber, name: _name});
        people.push(newPerson);
        //上面那两句的效果相当于下面这一句
       //people.push(People(_favoriteNumber, _name));
    }
post image
post image

错误(红色)

预期得到";"但是实际得到了"}"意味着你的代码没有编译。这些错误意味着你的代码没有编译,它不像预期的那样工作

post image

警告(黄色)

警告不会阻止您的代码工作,但检查它们通常是个好主意,因为它们通常会给出很有见地的信息。

SPDX 许可证未提供标识符

post image

变量/常量/Immutable

变量是Solidity中可改变值的一种数据结构,分为以下三种:

  • local变量

  • state变量

  • global变量

其中,local变量定义在方法中,而不会存储在链上,如string var = "Hello";

state变量在方法之外定义,会存储在链上,通过string public var;定义变量,写入值时会发送交易,而读取值则不会;

global变量则是提供了链信息的全局变量,如当前区块时间戳变量,uint timestamp = block.timestamp;

合约调用者地址变量,address sender = msg.sender;等。

变量可以通过不同关键字进行声明,表示不同的存储位置。

  • storage,会存储在链上

  • memory,在内存中,只有方法被调用的时候才存在

  • calldata,作为调用方法传入参数时存在

而常量是一种不可以改变值的变量,使用常量可以节约Gas费用,我们可以通过string public constant MY_CONSTANT = "0707";来进行定义。immutable则是一种特殊的类型,它的值可以在constructor中初始化,但不可以再次改变。灵活使用这几种类型可以有效节省Gas费并保障数据安全。