# Solidity 课程 4: 数据存储和作用域

By [Novar](https://paragraph.com/@novar) · 2022-05-16

---

数据位置
----

_“什么是数据位置？”_

所有的复杂类型，即 **数组** 和 **结构** 类型，都有一个额外属性，“数据位置”，说明数据是保存在 内存`memory` 中还是 存储`storage` 中。

`solidity`数据存储位置有三类：`storage`，`memory`和`calldata`。不同存储位置的`gas`成本不同。

*   `storage`：类型的数据存在链上，类似计算机的硬盘，消耗`gas`多；
    
*   `memory`和`calldata`类型的临时存在内存里，消耗`gas`少。
    

### 用法

1.  `storage`：合约里的状态变量默认都是`storage`，存储在链上；
    
2.  `memory`：函数里的参数和临时变量一般用`memory`，存储在内存中，不上链；
    
3.  `calldata`：和`memory`类似，存储在内存中，不上链。与`memory`的不同点在于`calldata`**变量不能修改**（`immutable`），一般用于函数的参数。
    

    contract DataStorage{
       uint[] x; //x 的数据存储位置是 storage
    
       //函数参数的存储位置必须声明，否则报错
       function f(uint[] memory memoryArray) public{
           uint[] storage _storage = x;
           // 将合约变量storage数组拷贝到storage中
    
           uint[] memory _memory = x;
           // 将合约变量storage数组拷贝到memory中
       }
    
       function fCalldata(uint[] calldata _x) public pure      returns(uint[] calldata){
           //参数为calldata数组，不能被修改
           // _x[0] = 0; //这样修改会报错
           return(_x);
      }
    }
    

### 不同类型相互赋值

在不同存储类型相互赋值时候，有时会产生独立的副本（修改新变量不会影响原变量），有时会产生引用（修改新变量会影响原变量）。规则如下：

1.  `storage`（合约的状态变量）赋值给本地`storage`（函数里的）时候，会创建引用，改变新变量会影响原变量：
    
        uint[] x = [1,2,3]; // 状态变量：数组 x
        
        function fStorage() public{
            //声明一个storage的变量 xStorage，指向x。修改xCopy也会影响x
            uint[] storage xStorage = x;
            xStorage[0] = 100;
        }
        
    
2.  `storage`赋值给`memory`，会创建独立的复本，修改其中一个不会影响另一个；反之亦然：
    
        uint[] x = [1,2,3]; // 状态变量：数组 x
            
        function fMemory() public view{
            //声明一个Memory的变量xMemory，复制x。修改xMemory不会影响x
            uint[] memory xMemory = x;
            xMemory[0] = 100;
        }
        
    

**总结：**

1.  `storage`赋值`storage`，和`memory`赋值给`memory`，会创建新的引用，改变新变量会影响原变量；
    
2.  `storage`赋值`memory`，和`memory`赋值给`storage`，会创建独立的复本，改变新变量不会影响原变量；
    
3.  （注意）因为`storage`只能静态分配，无法动态分配，因此`memory`无法复制给临时的`storage`变量，编译就会报错；
    
        uint[] x;// x 的数据存储位置是 storage
        
        function f(uint[] memory memoryArray) public{
        
            uint[] storage _storage = x;
            //合约storage变量 -> 赋值给临时storage变量，
            //因为合约storage变量已分配完，因此可行
            
        
            uint[] storage _storage2 = memoryArray;(不可行)
            //因为storage需要静态分配，无法动态分配，因此把 memory 传给还未分配的storage地址，编译就会报错       
        }
        
    

变量的作用域
------

`Solidity`中变量按作用域划分有三种，分别是:

*   状态变量（state variable）
    
*   局部变量（local variable）
    
*   全局变量 (global variable)
    

### 状态变量

状态变量是数据存储在链上的变量，所有合约内函数都可以访问，`gas`消耗高。状态变量在合约内、函数外声明：

    contract Variables {
        uint public x = 1;
        uint public y;
        string public z;
    }
    

我们可以在函数里更改状态变量的值：

    function foo() external{
        // 可以在函数里更改状态变量的值
        x = 5;
        y = 2;
        z = "0xAA";
    }
    

### 局部变量

局部变量是仅在函数执行过程中有效的变量，函数退出后，变量无效。局部变量的数据存储在内存里，不上链，`gas`低。局部变量在函数内声明：

    function bar() external pure returns(uint){
        uint xx = 1;
        uint yy = 3;
        uint zz = xx + yy;
        return(zz);
    }
    

### 全局变量

全局变量是全局范围工作的变量，都是`solidity`预留关键字。他们可以在函数内不声明直接使用：

    function global() external view returns(address, uint, bytes memory){
        address sender = msg.sender;
        uint blockNum = block.number;
        bytes memory data = msg.data;
        return(sender, blockNum, data);
    }
    

在上面例子里，我们使用了3个常用的全局变量：`msg.sender`, `block.number`和`msg.data`，他们分别代表请求发起地址，当前区块高度，和请求数据。下面是一些常用的全局变量，更完整的列表请看这个[链接](https://learnblockchain.cn/docs/solidity/units-and-global-variables.html#special-variables-and-functions)：

*   `blockhash(uint blockNumber)`: (`bytes32`)给定区块的哈希值 – 只适用于256最近区块, 不包含当前区块。
    
*   `block.coinbase`: (`address payable`) 当前区块矿工的地址
    
*   `block.gaslimit`: (`uint`) 当前区块的gaslimit
    
*   `block.number`: (`uint`) 当前区块的number
    
*   `block.timestamp`: (`uint`) 当前区块的时间戳，为unix纪元以来的秒
    
*   `gasleft()`: (`uint256`) 剩余 gas
    
*   `msg.data`: (`bytes calldata`) 完整call data
    
*   `msg.sender`: (`address payable`) 消息发送者 (当前 caller)
    
*   `msg.sig`: (`bytes4`) calldata的前四个字节 (function identifier)
    
*   `msg.value`: (`uint`) 当前交易发送的`wei`值
    
*   `now` : (`uint`) 当前块的时间戳
    

总结：
---

这节学习了**数据存储位置**和**变量的作用域**，数据存储位置有三类：`storage`、`memory`和`calldata`，不同存储位置的`gas`成本不同；变量按作用域划分有三种，状态变量、局部变量和全局变量。理解好数据存储位置和变量的作用域，才能正确使用变量，节省链上有限的存储空间和降低`gas`。下一节，我们将学习引用类型。

* * *

**代码地址：**

[https://github.com/Luca-Hsu/SuperSolidity/blob/main/04\_DataStorage/DataStorage.sol](https://github.com/Luca-Hsu/SuperSolidity/blob/main/04_DataStorage/DataStorage.sol)

**Reference**:

[https://mirror.xyz/ninjak.eth/w5zJWGwElN2ei4tgtuJbTTpKwrGhb-igPY7wTVKRCgY](https://mirror.xyz/ninjak.eth/w5zJWGwElN2ei4tgtuJbTTpKwrGhb-igPY7wTVKRCgY)

[https://docs.soliditylang.org/en/v0.8.4/types.html](https://docs.soliditylang.org/en/v0.8.4/types.html)

---

*Originally published on [Novar](https://paragraph.com/@novar/solidity-4)*
