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

By [链上巫师](https://paragraph.com/@looplove) · 2022-11-25

---

这是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,同时可以选择要部署的合约

![](https://storage.googleapis.com/papyrus_images/78d6736ba71a50be22653fad89364e3c57d4ebabe7a06cc135426b731a01f3d9.png)

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

![](https://storage.googleapis.com/papyrus_images/b122b9e10620cc2c9ada5cc5a2425819e40b5ef1027d6011d4656e91645cc120.png)

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

![](https://storage.googleapis.com/papyrus_images/c955fe6f01c3e21bbe89815ba8f7484a966b882fdfaa2c47f33c5c6a01197cf4.png)

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

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

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

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

![](https://storage.googleapis.com/papyrus_images/11ee14f90d8770664944e0bff61eb12923c6a61302f72ac9f0f55f515656bbd4.png)

![](https://storage.googleapis.com/papyrus_images/c2bdb5dbef5e0304841a63a8ae7fe4f2e40fcc0f7cb65fff9c78f59d85c89592.png)

**权限声明:**

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

> 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变量。

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

![](https://storage.googleapis.com/papyrus_images/7ca9a27276f7c9c47011261a067ec34802fb02396535b7ebf63a65f38c1a2263.png)

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

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

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

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

![](https://storage.googleapis.com/papyrus_images/7fca4fff38909dae6bc5b76fb6fa2206063638292949f737a6a7496ffbf2a1a4.png)

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

![](https://storage.googleapis.com/papyrus_images/11c261eecd896e4727977cce6744f2dd0cbe49faec4e276d1fb1a1efc62d84f9.png)

**为什么我们调用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);
        }
        
    }
    

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

方法引入:

![](https://storage.googleapis.com/papyrus_images/8a010fa5cd386f7e4dd86aa457aabdeede1fa5e97a1636ccc4aad75301b3320a.png)

![](https://storage.googleapis.com/papyrus_images/3ddb6346232d0ce0f1f5735d1c83534b11f589d6ae98ad38a259cad49d3c23ab.png)

每当你有一个变量列表在一个对象内部时,在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));
        }
    

![](https://storage.googleapis.com/papyrus_images/5480d99e05a86314a8976c67b68d98e2f5b3433738669e896e263ffebdc7edcb.png)

![](https://storage.googleapis.com/papyrus_images/d0d5387875d91bc559cc55d8bfab13a4a39c5a8c202989d07ad96cf440e62302.png)

**错误(红色)**

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

![](https://storage.googleapis.com/papyrus_images/0d3b7414a0cb32cfd12963331a519c610ccfbe8a49e5a9d9e9899c91f28db7ac.png)

**警告(黄色)**

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

SPDX 许可证未提供标识符

![](https://storage.googleapis.com/papyrus_images/31d1f55eeb73430974b3a68ecfa51c1491563779308397a8d422c7112e00172a.png)

### 变量/常量/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费并保障数据安全。

---

*Originally published on [链上巫师](https://paragraph.com/@looplove/web3-0-5-solidity)*
