区块链学习(solidity)【Day07-10 | 5.22-5.25】

Vs Code 中使用 solidity

 安装插件

安装 solidity 插件

书写合约

在编写 solidity 的合约时要在首行加上 MIT 许可证,并选择适当的 SPDX 许可证标识符。

// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

// Contract code goes here...

contract Hello{
    function hello() public pure returns(string memory){
        return "hello world";
    }
}

编译代码

F5 编译当前合约,Cmd+F5 编译所有合约


solidity 学习

入门篇

pragma

        指定 solidity 的版本,只对自己的源文件生效

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

contract Hello {
    // 在solidity 中中文字符无法被识别,要使用Unicode编码进行转译
    string public message = unicode"你好";

    function helloWorld() view public returns (string memory) {
        return message; // 返回 message 变量的值
    }
}

引入 SPDX 许可证

        Solidity ^0.6.8 以上版本要求引入 SPDX 许可证,否则会出现警告

        有关于 SPDX 的详细解析可以跳转查看:Solidity SPDX是什么 - 编程宝库 (codebaoku.com)

一般是在 .sol 文件第一句加上:
// SPDX-License-Identifier: MIT 

或者
// SPDX-License-Identifier: SimPL-3.0

contract/智能合约

        智能合约是位于以太坊区块链上特定地址的代码和数据的集合。

        contract 关键字表示一个智能合约。

导入文件

//要从当前目录导入文件 x,请使用 import "./x"。如果不指定当前路径,可能会在全局 “include” 目录中引用另一个文件。

// 从 data01.sol 中导入所有全局符号
import "./data01.sol";


// 创建一个新的全局符号 symbolName,它的成员都是来自 data01.sol 的全局符号。
import * as storedData from "./data01.sol";

保留关键字

        Solidity 中的保留关键字在以下地址中有所展示:

        Solidity 基础语法 - 编程宝库 (codebaoku.com)

代码注释

function getResult() public view returns(uint){
   // 这是一行注释,类似于c++中的注释

   /*
    * 这是多行注释
    * 类似于c语言中的注释
    */
   uint a = 1;
   uint b = 2;
   uint result = a + b;
   return result;
}

数据类型

  • 值类型
    • 布尔型        bool
    • 整型       int/uint        int8 to int256       uint8 to unit256
    • 定长浮点型       fixed/unfixed       fixedMxN       ufixedMxN
uint x = 100;
int x = -200;
byte32 b = 0x01020304
// 测量类型的最大值和最小值:

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

contract Test { 
    uint public a = type(uint).min;
    uint public b = type(uint).max;
}
  • 地址类型
    • 地址类型表示以太坊地址,长度为20字节。
    • 地址可以使用 .balance 属性获得余额
    • 也可以使用 .transfer() 方法将余额转到另一个地址。
address x = 0x212;
address myAddress = this;

if (x.balance < 10 && myAddress.balance >= 10) 
    x.transfer(10);
  • 引用类型
    • 数组 (字符串与bytes是特殊的数组,所以也是引用类型)
    • struct (结构体)
    • map (映射)

变量

变量基础内容

  • 状态变量
    • 变量值永久保存在智能合约存储空间中的变量。
    • 定义形式类似于类中的成员变量。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest {
   uint storedData;      // 状态变量
   constructor() {
      storedData = 10;   // 使用状态变量
   }
}
  • 局部变量
    • 变量值仅在函数执行过程中有效的变量,函数退出后,变量无效。
    • 函数参数也是局部变量。
    • 局部变量不会上链,只存在于所处函数的生命周期。
pragma solidity ^0.8.0;

contract SolidityTest {
   function sum() public pure returns(uint){
      uint a = 1; // 局部变量
      uint b = 2;
      uint result = a + b;
      return result; // 访问局部变量
   }
}

// 运行上述程序,输出:
0: uint256: 3
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest {
   // 获取当前区块号、时间戳、调用者地址
   function getGlobalVars() public view returns(uint,uint,address){
     return (block.number,block.timestamp,msg.sender);
   }
}
  • 变量命名规则
    • 不应使用 Solidity 保留关键字作为变量名。例如:break 或 boolean 变量名无效。
    • 不应以数字(0-9)开头,必须以字母或下划线开头。
    • 变量名区分大小写。例如:Name和name是两个不同的变量。

特殊变量/全局变量

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

交易的发送方

// 示例展示如何使用特殊变量msg,该变量在Solidity中用于获取发送者地址。

pragma solidity ^0.5.0;

contract LedgerBalance {
   mapping(address => uint) public balances;

   function updateBalance(uint newBalance) public {
      balances[msg.sender] = newBalance;
   }
}
contract Updater {
   function updateBalance() public returns (uint) {
      LedgerBalance ledgerBalance = new LedgerBalance();
      ledgerBalance.updateBalance(10);
      return ledgerBalance.balances(address(this));
   }
}

这段代码定义了两个 Solidity 合约:LedgerBalance 和 Updater。

LedgerBalance 合约定义了一个名为 balances 的公共状态变量,它是一个映射,将每个地址映射到一个无符号整数。该合约还定义了一个名为 updateBalance 的公共函数,它接受一个参数 newBalance,将调用者的地址映射到 newBalance。

Updater 合约定义了一个名为 updateBalance 的公共函数,该函数创建新的 LedgerBalance 实例,并调用 updateBalance 函数,将调用者地址映射到值 10。最后,该函数返回 address(this) 在 balances 映射中对应的值,因此相当于查询当前合约在 LedgerBalance 合约中的余额。

需要注意的是,由于 Solidity 适用于以太坊虚拟机(EVM),因此这些合约的执行和状态更改都发生在以太坊区块链上。例如,在 updateBalance 函数中, balances[msg.sender] = newBalance; 将会修改以太坊区块链上的状态,而不仅仅是本地内存变量。

变量默认值

  • bool 类型变量默认值为 false

  • int 类型变量默认值为 0

  • uint 类型变量默认值为 0

  • address 类型变量默认值为:0x000....,共 40个 0

  • bytes32 类型变量默认值为:0x00000000000000000000.....,共 64个 0

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

contract SolidityTest {
   bool public a;    // false
   int public b;     // 0
   uint public c;     // 0
   address public d;     // 0x0000000000000000000000000000000000000000
   bytes32 public e;     // 0x0000000000000000000000000000000000000000000000000000000000000000
}

变量作用域

  • public
    • 公共状态变量可以在内部访问,也可以从外部访问
    • 对于公共状态变量,将自动生成一个 getter 函数。
  • private
    • 私有状态变量只能从当前合约内部访问,派生合约内不能访问。
  • internal
    • 内部状态变量只能从当前合约或其派生合约内访问。
  • external
    • 外部状态变量只能在合约之外调用 ,不能被合约内的其他函数调用。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract C{
    uint public data = 30;
    uint internal iData = 20;
    uint private pData = 10;

    function x() public returns (uint){
        data = 3;   // 内部访问
        iData = 2;  // 内部访问
        pData = 1;  // 内部访问
        return data;
    }
}

// 调用外部合约
contract Caller{
    C c = new C();
    function f() public view returns(uint){
    //  return c.data();    // 外部访问
    //  return c.iData();   // error 不允许外部访问
    //  return c.pData();   // error 不允许外部访问
    }
}

// 派生合约
contract D is C{
    uint storedData;    // 状态变量
    function y() public returns (uint){
        data = 3;   // 派生合约内部访问
        iData = 2;  // 派生合约内部访问
    //  pData = 1;  //  error 不允许派生合约内部访问

        return iData;
    }

    function getResult() public view returns(uint){
        return storedData;   // 访问状态变量
    }
    
}


这段 Solidity 代码定义了三个合约 C、Caller 和 D。

C 合约定义了三个状态变量:data(公共)、iData(内部)和 pData(私有)。它还定义了名为 x() 的公共函数,该函数可以从内部访问所有三个状态变量并将 data 的值返回。

Caller 合约是一个外部合约,它创建了一个新的 C 实例,并定义了名为 f() 的公共函数。然而,在该函数中,尝试调用 c.data()、c.iData() 和 c.pData() 会导致编译错误,因为 iData 和 pData 是不能被外部访问的。

D 合约是一个派生合约,它继承了 C 合约所有的状态变量和函数。它定义了两个新的函数:y() 和 getResult()。函数 y() 可以从派生合约内部访问 data 和 iData 状态变量,但不能访问 pData,因为它是私有的。函数 getResult() 是一个查看函数,它可以返回 storedData 状态变量的值,该状态变量在函数中被声明,但没有被使用。

总体来说,此代码演示了 Solidity 中的状态变量的不同可见性级别。同时也展示了如何在内部、外部和派生合约中访问这些状态变量。
  • public 与 private

    • public修饰的变量和函数,任何用户或者合约都能调用和访问。

    • private修饰的变量和函数,只能在其所在的合约中调用和访问,即使是其子合约也没有权限访问。

  • external 与 internal

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

    • internal 和 private 类似,不过, 如果某个合约继承自其父合约,这个合约可以访问父合约中定义的“内部”函数。

    • external 与public 类似,不过这些函数只能在合约之外调用,不能被合约内的其他函数调用。

常量

  • 状态变量的值如果恒定不变,就可以通过 constant 进行修饰,定义为常量

    • 常量的命名常常使用大写字母表示,单词之间用下划线“_”连接。

    • 不是所有的类型都支持常量,当前仅支持值类型字符串

    • constant常量必须在编译期间通过一个表达式赋值

    • 编译器并不会为constant常量storage上预留空间

  • 常量 constant 特点

    • 常量与变量相对,需要硬编码在合约中,合约部署之后,无法改变。

    • 常量更加节约gas,一般用大写来代表常量。

运算符

算术 比较 逻辑 赋值 条件
+(加)
求和
例: A + B = 30
== (等于) && (逻辑与)
如果两个操作数都非零,则条件为真。
例: (A && B) 为真
& (位与)
对其整数参数的每个位执行位与操作。
例: (A & B) 为 2.
=(简单赋值)
将右侧操作数的值赋给左侧操作数
例: C = A + B 表示 A + B 赋给 C
? : (条件运算符 )
如果条件为真 ? 则取值X : 否则值Y
- (减)
相减
例: A - B = -10
!= (不等于) || (逻辑或)
如果这两个操作数中有一个非零,则条件为真。
例: (A || B) 为真
| (位或)
对其整数参数的每个位执行位或操作。
例: (A | B) 为 3.
+= (相加赋值)
将右操作数添加到左操作数并将结果赋给左操作数。
例: C += A 等价于 C = C + A
* (乘)
相乘
例: A * B = 200
> (大于) ! (逻辑非)
反转操作数的逻辑状态。如果条件为真,则逻辑非操作将使其为假。
例: ! (A && B) 为假
^ (位异或)
对其整数参数的每个位执行位异或操作。
例: (A ^ B) 为 1.
−= (相减赋值)
从左操作数减去右操作数并将结果赋给左操作数。
例: C -= A 等价于 C = C – A
/ (除)
相除
例: B / A = 2
< (小于) ~ (位非)
一元操作符,反转操作数中的所有位。
例: (~B) 为 -4.
*= (相乘赋值)
将右操作数与左操作数相乘,并将结果赋给左操作数。
例: C *= A 等价于 C = C * A
% (取模)
取模运算
例: B % A = 0
>= (大于等于) << (左移位))
将第一个操作数中的所有位向左移动,移动的位置数由第二个操作数指定,新的位由0填充。将一个值向左移动一个位置相当于乘以2,移动两个位置相当于乘以4,以此类推。
例: (A << 1) 为 4.
/= (相除赋值)
将左操作数与右操作数分开,并将结果分配给左操作数。
例: C /= A 等价于 C = C / A
++ (递增)
递增
例: A++ = 11
<= (小于等于) >> (右移位)
左操作数的值向右移动,移动位置数量由右操作数指定
例: (A >> 1) 为 1.
%= (取模赋值)
使用两个操作数取模,并将结果赋给左边的操作数。
例: C %= A 等价于 C = C % A
-- (递减)
递减
例: A--= 9'

算术运算符

+        -        *        /        %        ++        --

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

contract SolidityTest{
    uint storedData;
    constructor(){
        storedData = 10;
    }
    function getResult() public pure returns(string memory){
        uint a = 1;     // 局部变量
        uint b = 2;
        uint result = a + b;
        return integerToString(result);
    }
    function integerToString(uint _i) internal pure returns(string memory _uintAsString){
        if(_i == 0){
            return "0";
        }
        uint j = _i;
        uint len;
        while(j!=0){
            len++;
            j/=10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;
        while(_i != 0){
            bstr[k--] = bytes1(uint8(48 + _i % 10 ));
            _i /= 10;
        }
        return string(bstr);    // 访问局部变量
    }
}


这段 Solidity 代码定义了一个名为 SolidityTest 的合约。它有一个状态变量 storedData 和一个构造函数,在构造函数中将 storedData 的初始值设置为 10。

合约还定义了两个函数:getResult() 和 integerToString()。getResult() 函数是一个查看函数,它执行两个无符号整数相加并返回结果的字符串表示形式。为了实现这一点,它声明了三个局部变量 a、b 和 result,并使用 integerToString() 函数将结果从无符号整数转换为字符串。需要注意的是,getResult() 函数被声明为 pure,因为它不会修改任何状态变量。

integerToString() 函数接受一个无符号整数 _i,并返回其字符串表示形式。在该函数中声明了两个局部变量 j 和 len,分别用于存储 _i 的副本和 _i 的位数。然后,在 while 循环中,使用整数除法操作和递减运算符来计算 _i 的长度。接下来,该函数创建了一个长度为 len 的 bytes 数组,并从右往左填充该数组的每个位置,以便生成 _i 的字符串表示形式。最后,integerToString() 函数将 bytes 数组转换为字符串并将其返回。

值得注意的是,该函数中的所有变量都是局部变量,因此它不会修改任何状态变量。

比较运算符

==        !=        >        <        >=        <=

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    uint storedData;
    constructor(){
        storedData  = 10;
    }

    function getResult() public pure returns(string memory){
        uint a = 1; // 局部变量 
        uint b = 2;
        uint result = a + b;
        return integerToString(result);
    }
    function integerToString(uint _i) internal pure returns(string memory _uintAsString){
        if(_i == 0){
            // 比较运算符
            return "0";
        }
        uint j = _i;
        uint len;
        
        while (j!=0){
            // 比较运算符
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;

        while(_i != 0){
            bstr[k--] = bytes1(uint8(48 + _i % 10));
            _i /= 10;
        }
        return string(bstr);    // 访问局部变量
    }
}

逻辑运算符

&&        ||        !

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    uint storedData;
    constructor(){
        storedData = 10;
    }
    function getResult() public pure returns(string memory){
        uint a = 2; // 局部变量
        uint b = 2;
        uint result = a & b;
        return integerToString(result);
    }
    function integerToString(uint _i)internal pure returns(string memory){
        if(_i == 0){
            return "0";
        }
        uint j = _i;
        uint len = 0;
        while(j!=0){
            len++;
            j/=10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;

        while(_i != 0){
            bstr[k--] = bytes1(uint8(48 + _i % 10));
            _i/=10;
        }
        return string(bstr);    // 访问局部变量

    }
}

位运算符

&        |        ^        ~        <<        >>

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    uint storedData;
    constructor(){
        storedData = 10;
    }
    function getResult() public pure returns(string memory){
        uint a = 2;     // 局部变量 
        uint b = 2;
        uint result = a & b;    // 位与
        return integerToString(result);
    }
    function integerToString(uint _i)internal pure returns (string memory){
        if(_i == 0){
            return "0";
        }
        uint j = _i;
        uint len;
        while ( j!=0){
            len++;
            j/=10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;
        while(_i != 0){
            bstr[k--] = bytes1(uint8(48+_i%10));
            _i /= 10;
        }
        return string(bstr);    // 访问局部变量
    }
}

storedData 是一个无符号整数类型的状态变量,用于存储数据。默认初始值为 10。
constructor 是合约的构造函数,用于在创建合约时初始化 storedData 值为 10。
getResult() 函数是一个公共函数,其返回值类型为 string memory。它定义了两个无符号整数类型的局部变量 a 和 b,并使用位运算符 & 来计算它们的与结果。然后,它将此结果转换为字符串并返回。
integerToString() 函数是一个内部函数,其输入参数 _i 是一个无符号整数类型。它将整数 _i 转换为字符串类型并返回。此函数首先检查传入的整数是否为 0。如果是,则直接返回字符串“0”。否则,它使用循环来计算此整数的位数,并创建一个长度为 len 的字节数组来存储相应的字符。最后它通过将字节数组转换为字符串来返回结果。

赋值运算符

=        +=        -=        *=        /=        %=

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest {
   uint storedData; 
   constructor() {
      storedData = 10;   
   }
   function getResult() public pure returns(string memory){
      uint a = 1; 
      uint b = 2;
      uint result = a + b;
      return integerToString(result); 
   }
   function integerToString(uint _i) internal pure 
      returns (string memory) {
      if (_i == 0) {
         return "0";
      }
      uint j = _i;
      uint len;
      while (j != 0) {
         len++;
         j /= 10; // 赋值运算
      }
      bytes memory bstr = new bytes(len);
      uint k = len - 1;
      while (_i != 0) {
         bstr[k--] = bytes1(uint8(48 + _i % 10));
         _i /= 10;// 赋值运算
      }
      return string(bstr);  // 访问局部变量
   }
}

storedData 是一个无符号整数类型的状态变量,用于存储数据。默认初始值为 10。
constructor 是合约的构造函数,用于在创建合约时初始化 storedData 值为 10。
getResult() 函数是一个公共函数,其返回值类型为 string memory。它定义了两个无符号整数类型的局部变量 a 和 b,并使用加法运算符 + 来计算它们的和。然后,它将此结果转换为字符串并返回。
integerToString() 函数是一个内部函数,其输入参数 _i 是一个无符号整数类型。它将整数 _i 转换为字符串类型并返回。此函数首先检查传入的整数是否为 0。如果是,则直接返回字符串“0”。否则,它使用循环来计算此整数的位数,并创建一个长度为 len 的字节数组来存储相应的字符。最后它通过将字节数组转换为字符串来返回结果。

条件运算符

?

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    uint storedData;
    constructor(){
        storedData =  10;
    }
    function getResult() public pure returns(string memory){
        uint a = 1;
        uint b = 2;
        uint result = (a>b?a:b);
        return integerToString(result); 
    }
    function integerToString(uint _i)internal pure returns(string memory){
        if(_i == 0){
            return "0";
        }
        uint j = _i;
        uint len;
        while(j!=0){
            len++;
            j/=10;
        }
        bytes memory bstr = new bytes(len);
        uint k = len - 1;
        while(_i != 0){
            bstr[k--] = byte(uint8(48 + _i % 10));
            _i /= 10;
        }
        return string(bstr);
    }
}

storedData 是一个无符号整数类型的状态变量,用于存储数据。默认初始值为 10。
constructor 是合约的构造函数,用于在创建合约时初始化 storedData 值为 10。
getResult() 函数是一个公共函数,其返回值类型为 string memory。它定义了两个无符号整数类型的局部变量 a 和 b,并使用三元运算符 ? : 来计算它们的最大值。然后,它将此结果转换为字符串并返回。
integerToString() 函数是一个内部函数,其输入参数 _i 是一个无符号整数类型。它将整数 _i 转换为字符串类型并返回。此函数首先检查传入的整数是否为 0。如果是,则直接返回字符串“0”。否则,它使用循环来计算此整数的位数,并创建一个长度为 len 的字节数组来存储相应的字符。最后它通过将字节数组转换为字符串来返回结果。

条件语句

  • if 
if (条件表达式) {
   被执行语句(如果条件为真)
}
  • if...else
if (条件表达式) {
   被执行语句(如果条件为真)
} else {
   被执行语句(如果条件为假)
}
  • if...else if
if (条件表达式 1) {
   被执行语句(如果条件 1 为真)
} else if (条件表达式 2) {
   被执行语句(如果条件 2 为真)
} else if (条件表达式 3) {
   被执行语句(如果条件 3 为真)
} else {
   被执行语句(如果所有条件为假)
}

循环

  • while
while (表达式) {
   // 如果表达式的结果为真,就循环执行以下语句
   ......
}
  • do ... while
do {
   // 如果表达式的结果为真,就循环执行以下语句
   ......
} while (表达式);
  • for
for (初始化; 测试条件; 迭代语句) {
   // 如果表达式的结果为真,就循环执行以下语句
   ......
}
  • 循环控制语句:break、continue
    • continue – 跳出本次循环,继续执行下一次循环
    • break – 跳出循环,不再执行当前循环

字符串

  • 字符串值使用双引号(")和单引号(')包括,字符串类型用 string 表示。
  • 字符串是特殊的数组,是引用类型。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest{
    string public data1 = "test1";
    bytes public data2 = "test2";
}


/*在上面的例子中,"test" 是一个字符串,data 是一个字符串变量。

Solidity 提供字节与字符串之间的内置转换,可以将字符串赋给 bytes 类型变量。*/

转义字符

序号 转义字符 序号 转义字符
1 \n
开始新的一行
7 \r
回车
2 \\
反斜杠
8 \t
制表符
3 \'
单引号
9 \v
垂直制表符
4 \"
双引号
10 \xNN
表示十六进制值并插入适当的字节
5 \b
退格
11 \uNNNN
表示Unicode值并插入UTF-8序列
6 \f
换页

bytes 到字符串的转换

  • 可以使用 string() 构造函数将 bytes 转换为字符串
bytes memory bstr = new bytes(10);
string message = string(bstr);   
  • bytes 到字符串的转换
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest {
   string public data;
   bytes  bstr = new bytes(2);
   function trans() external{
      bstr[0] = 'a';
      bstr[1] = 'b';
      data = string(bstr);
   }
}

字符串到 bytes 的转换

  • 可以使用bytes()构造函数将字符串转换为bytes
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest {   
   constructor(){       
   }
   
   function getResult(string memory s) public pure returns(bytes memory){
      return bytes(s); // 字符串转换到bytes
   }
}

数组

声明数组

  • 固定长度数组
    • 声明一个固定长度的数组,需要指定元素类型和数量
    • arraySize 必须是一个大于零的整数数字,type 可以是任何数据类型
type arrayName [arraySize];

// 声明一个 uint 类型,长度为10的数组:balance,如下所示:
uint balance[10];
  • 动态数组
    • 声明一个动态数组,只需要指定元素类型,无需指定数量
    • arraySize 必须是一个大于零的整数数字,type 可以是任何数据类型
type arrayName [];

// 声明一个 uint 类型的动态数组:balance,如下所示:
uint balance[];

初始化数组

uint balance[3] = [1,2,3];    // 初始化固定长度数组
uint balance[] = [1,2,3];     // 初始化动态数组
balance[2] = 5;               // 设置第3个元素的值为5

访问数组元素

// 可以通过索引访问数组元素
uint salary = balance[2];

动态数组和固定长度数据的区别

  • 固定长度的数组在运行期间,是无法改变长度的。而动态数组可以改变
  • 动态数组使用 push 方法,在末尾追加一个元素
  • 使用 pop 方法,截掉末尾元素
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    // 动态数组
    uint[] public arr = [1,2,3,4];
    // 固定长度数组
    uint[4] public arrFixed=[1,2,3,4];

    // 获得数组全部元素
    function getArr() external view returns(uint[] memory){
        return arr;
    }
    
    // 固定长度数组操作
    function operatFixedArr() external{
        // 获得数组长度
        uint len1 = arrFixed.length;    // len=4
        // 获得第二个元素
        uint x = arrFixed[1];   // x=2
        // 删除第二个元素,delete只将元素设置为初值,并不改变数组长度
        delete arrFixed[1];     // [1,0,3,4]
    }

    // 动态数组操作
    function operateDynamicArr() external{
        // 获得数组长度
        uint len2 = arr.length; // len=4
        // 追加一个元素
        arr.push(5);    // [1,2,3,4,5]
        // 弹出一个元素
        arr.pop();  // [1,2,3,4]
        // 获得第二个元素
        uint x = arr[1];    // x=2
        // 删除第二个元素,delete 只将元素设置为初值,并不改变数组长度
        delete arr[1];      // [1,0,3,4]
        // 删除第二个元素,并且长度减1
        removeAt(1);        // [1,3,4]
    }   

    // 删除数组元素,并且长度减1
    function removeAt(uint i) public {
        require(i>=0&&i<arr.length);
        for(uint k=i;k<arr.length-1;k++){
            arr[k] = arr[k+1];
        }
        arr.pop();
    }
}

/*getArr:返回动态数组 arr 中的所有元素。
operatFixedArr:演示如何使用固定长度数组 arrFixed,获取数组长度、访问特定元素以及删除特定元素。
operateDynamicArr:演示如何使用动态数组 arr,获取数组长度、追加/弹出元素、访问特定元素以及删除特定元素。
removeAt:删除指定索引位置的元素,并将数组长度减1,其中使用了 for 循环和 pop() 函数。*/

创建内存数组

使用 new 关键字在内存中创建动态数组。内存动态数组的长度一旦确定,不能改变。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SolidityTest{
    function getMemoryArr() external pure returns(uint[] memory){
        uint[] memory arr = new uint[](3);
        arr[0] = 1;
        return arr;
    }
}

结构体

定义结构体

struct struct_name{
    type1 type_name_1;
    type2 type_name_2;
    type3 type_name_3;
}

// 示例
struct Book{
    string title;
    string author;
    uint book_id;
}

访问结构体成员

// 要访问结构体的任何成员,要使用成员访问操作符(.)

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

contract SolidityTest{
struct Book{
    string title;
    string author;
    uint id;
}
Book book;

function setBook() public {
    book = Book('learn','codebaoku.com',1);
}
function getBookAuthor() public view returns(string memory){
    return book.author;
    }
}

结构体操作方法

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

contract SolidityTest{
    struct Book{
        string title;
        string author;
        uint id;
        address owner;
    }
    Book public book;
    Book[] public books;
    mapping(address=>Book[])public booksByOwner;

    function operations() external{
        // 结构体直接按照字段顺序,进行初始化
        Book memory book1 = Book('learn Java','codebaoku.com',1,msg.sender);

        // 结构体按照字段名,进行初始化
        Book memory book2 = Book({title:'learn',author:'codebaoku.com',id:2,owner:msg.sender});

        // 结构体按照默认值,进行初始化
        Book memory book3;
        book3.id = 3;
        book3.title = 'learn';
        book3.author = '11';
        book3.owner = msg.sender;

        // 结构体数组操作
        books.push(book1);
        books.push(book2);
        books.push(book3);

        // 结构体状态变量操作
        Book storage _book = books[0];
        delete _book.id;
        delete books[0];
        _book.id = 100;
    }
}

映射

  • mapping 用于以键值对的形式存储数据,等同于其它编程语言的哈希表或字典
    • _KeyType:可以是任何内置类型,或者types和字符串,不允许使用引用类型或复杂对象
    • _ValueType:可以是任何类型
mapping(_KeyType => _ValueType)
  • 注意
    • 映射的数据位置(data location)只能是 storage,通常用于状态变量
    • 映射可以标记为 public,Solidity 自动为它创建 getter
    • 映射可以视为哈希表,在实际的初始化过程中创建每个可能的 key,并将其映射到字节形式全是零的值:一个类型的 默认值
    • 映射和哈希表不同:在映射中,实际并不存储 key,而是存储它的 keccak256哈希值,从而便于查询实际的值
    • 正因为如此,映射是没有长度的,也没有 Key 的集合或 value 的集合的概念。映射只能是存储的数据位置,因此只存在作为状态变量或作为函数内的存储引用 或 作为库函数的参数。它们不能用于合约共有函数的参数或返回值。
    • 可以将映射声明为 public,然后来让 Solidity 创建一个 getter 函数。_KeyType 将成为 getter 的必须参数,并且 getter 会返回 _ValueType
  • 状态变量示例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract LederBalance{
    mapping(address => uint)public balances;
    function updateBalance(uint newBalance) public {
        // 设置 mapping 的 key 和 value
        balances[msg.sender] = newBalance;
    }

    function get() public view returns(uint){
        // 通过 key 获取 mapping 的 value
        return balances[msg.sender];
    }
}

contract Updater{
    function updateBalance() public returns(uint){
        LederBalance ledgerBalance = new LederBalance();
        ledgerBalance.updateBalance(10);
        return ledgerBalance.get();
    }
}
  • 局部变量示例
    • mapping 类型可以用做局部变量,但只能引用状态变量,而且存储位置为 storeage
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest{
    struct Book{
        string title;
        string author;
        uint id;
        address owner;
    }
    Book public book;
    Book[] public books;
    mapping(address=>Book[])public booksByOwner;

    function operations() external{
        // 结构体直接按照字段顺序,进行初始化
        Book memory book1 = Book('learn Java','codebaoku.com',1,msg.sender);

        // 结构体按照字段名,进行初始化
        Book memory book2 = Book({title:'learn',author:'codebaoku.com',id:2,owner:msg.sender});

        // 结构体按照默认值,进行初始化
        Book memory book3;
        book3.id = 3;
        book3.title = 'learn';
        book3.author = '11';
        book3.owner = msg.sender;

        // 结构体数组操作
        books.push(book1);
        books.push(book2);
        books.push(book3);

        // 结构体状态变量操作
        Book storage _book = books[0];
        delete _book.id;
        delete books[0];
        _book.id = 100;
    }
}

枚举

  • enum 是一种用户自定义类型,用于表示多种状态
  • enum 内部是一个自定义的整型,默认的类型为 uint8,当枚举数足够多时,自动变成 uint16
  • enum至少应该有一名成员
  • enum 可以与整数进行显式转换,但不能进行隐式转换。
    • 显示转换会在运行时检查数值范围,如果不匹配,将会引起异常
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SolidityTest{
    enum Status{
        None,
        Pending,
        Completed,
        Canceled
    }


Status public status;

// 获取状态
function getStatus() external view returns(Status){
    return status;
}

// 设置状态
function setStatus(Status _status) external{
    status = _status;
}

//设置完成状态
function setCompleted() external{
    status = Status.Completed;
    }
}

类型转换

隐式转换

  • 隐式转换时必须符合一定条件,不能导致信息丢失。
    • uint8 可以转换为 uint16
    • 但是 int8 不可以转换为 uint256,因为 int8 可以包含 uint256中不允许的负值

显式转换

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

contract SolidityTest{
    function test1() public pure returns(int){
        int8 y = -3;
    //  uint x = uint(y);   // int8不可以转换为uint256

        // 转换成更小的类型,会丢失高位
        uint32 a = 0x12345678;
        uint16 b = uint16(a);       // b = 0x5678
       
        // 转换为更大的类型,将会向左侧添加填充位
        uint16 a1 = 0x1234;
        uint32 b1 = uint32(a1);    // b1 = 0x00001234

        // 转换到更小的字节类型,会丢失后面数据
        bytes2 a2 = 0x1234;
        bytes1 b2 = bytes1(a2);     // b2 = 0x12

        // 转换为更大的字节类型时,向右添加填充位
        bytes2 a3 = 0x1234;
        bytes4 b3 = bytes4(a3);     // b3 = 0x12340000

        // 只有当字节类型和 int 类型大小相同时,才可与进行类型转换
        bytes2 a4 = 0x1234;
        uint32 b4 = uint16(a4);         // b4 = 0x00001234
        uint32 c = uint32(bytes4(a4));  // c = 0x12340000
        uint8 d = uint8(uint16(a4));    // d = 0x34
        uint8 e = uint8(bytes1(a4));     // e = 0x12

        // 把整数赋值给整型时,不能超出范围而发生截断,否则会报错
        uint8 a5 = 12;          // no error
        uint32 b5 = 1234;       // no error
    //  uint16 c5 = 0x123456;   // error,有截断,变成0x3456
       
       
        return 0;

    }
}

数据位置

引用类型 / 复合数据类型

  • 有一些数据类型由简单数据类型组合而成,通过名称引用,这些类型通常被称为引用类型
    • 数组(字符串与 bytes 是特殊的数组,也是引用类型)
    • struct (结构体)
    • map (映射)
  • 这些类型涉及到的数据量较大,复制它们可能要消耗大量Gas,非常昂贵,所以使用它们时,必须考虑存储位置。例如,是保存在内存中,还是在 EVM 存储区中。

数据位置(data location)

  • 在合约中声明和使用的变量都有一个数据位置,指明变量值应该存储在哪里
    • 合约变量的数据位置将会影响 Gas 消耗量
    • Solidity 提供 3 种类型的数据位置
      • storage
      • memory
      • calldata
  1. storage
    1. 存储位置 storage 用来存储永久数据,可以被合约中的所有函数访问
    2. storage 变量,通常用于存储智能合约的状态变量,它在函数调用之间保持持久性
    3. storage 与其他数据位置相比,成本较高
    4. 可以理解为计算机的硬盘数据,所有数据都永久存储
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      contract SolidityTest{
          struct MyData{
              uint id;
              string value;
          }
          MyData[] public myData;
          constructor(){
              myData.push(MyData(1,"value1"));
              myData.push(MyData(2,"value2"));
          }
          
          function operations() external{
              // storage 存储位置
              MyData storage d = myData[0];
      
              // 修改状态变量 myData 的内容
              d.value = "new value";
          }
      }
      
      /*合约 SoldityTest 部署后,调用 operations 方法,状态变量 myData 的数据被修改,其中 myData[0].value 变为 "new value"。*/
  2. memory
    1. 存储位置 memory 用来存储临时数据,比 storage 便宜
    2. memory 变量,通常用于保存临时变量,以便在函数执行期间进行计算
    3. 一旦函数执行完毕,它的内容就会被丢弃,它只能在所在的函数中访问
    4. 可以理解为每个单独函数的内存 RAM 
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract SolidityTest{
          struct MyData{
              uint id;
              string value;
          }
      
          MyData[] public myData;
      
          constructor(){
              myData.push(MyData(1,"value1"));
              myData.push(MyData(2,"value2"));
          }
      
          function operations() external{
              // memory 存储位置
              MyData memory d = myData[0];
      
              // 修改状态变量 myData 的内容
              d.value = "new value";
          }
      }
      
      /*合约 SoldityTest 部署后,调用 operations 方法,状态变量 myData 的数据未被修改,其中 myData[0].value 仍然为 "value1"*/
  3. calldata
    1. 是不可修改的非持久性数据位置,类似memory,但只能出现在函数的输入参数位置
    2. 外部函数 external function 的传入参数强制为 calldata 类型
    3. 使用 calldata 的好处是在内部函数中作为输入参数传递时,省掉了数据拷贝的成本
    4. 节省了 gas 费用,memory 作为参数时,需要进行数据拷贝
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract SolidityTest{
          function operations(string calldata val)pure external{
              // 使用 calldata 传递参数,省去复制成本
              internalFunc1(val);
      
              // 使用 memory 传递参数,需要进行复制
              internalFunc2(val);
          }
          
          function internalFunc1(string calldata val) internal pure{
              //...
          }
      
          function internalFunc2(string memory val) internal pure{
              //...
          }
      }

变量数据位置

  1. 规则1 - 状态变量
    1. 状态变量总是存储在存储区中
    2. 此外,不能显式地标记状态变量的位置
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract DataLocation{
          // 状态变量总是存储在存储区中。
          // storage
          uint stateVariable;
          uint[] stateArray;
      
          // 此外,不能显式地标记状态变量的位置
          uint storage stateVariable;     // 错误
          uint[] memory stateArray;       // 错误
      }
  2. 规则2 - 函数参数与返回值
    1. 函数参数包括返回参数都存储在内存中
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract DataLocation{
          // storage
          uint stateVariable;
          uint[] stateArray;
      
          // 函数参数 uint num1 与 uint num2,返回值 uint result 都存储在内存中。
          function calculate(uint num1,uint num2) public pure returns(uint result){
              return num1 + num2;
          }
      }
  3. 规则3 - 局部变量
    1. 值类型的局部变量存储在内存中。
    2. 对于引用类型的局部变量,需要显式地指定数据位置
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      contract Locations{
          /*  此处都是状态变量 */
          //  存储在storage(存储区)中 
          bool flag;
          uint number;
          address account;
      
          function doSomething() public pure{
              /*  此处都是局部变量 */
              //  值类型
              //  所以它们被存储在memory(内存)中
              bool flag2;
              uint number2;
              address account2;
      
              // 引用类型,需要显示指定数据位置,此处指定为内存
              uint[] memory localArray;
          }
      }
    3. 不能显式覆盖具有值类型的局部变量

      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      function doSomething() public {
          /*  此处都是局部变量 */
          //  值类型
          bool memory flag2;      // 错误 
          uint storage number2;   // 错误
          address account2;
      }
  4. 规则4 - 外部函数的参数

    1. 外部函数的参数(不包括返回参数)存储在 calldata 中

赋值数据位置

  • solidity 数据可以通过两种方式从一个变量复制到另一个变量。

    • 一种方法是复制整个数据(按值复制)

    • 另一种方法是引用复制。

  1. 规则1 - 状态变量赋值给状态变量

    1. 将一个状态变量赋值给另一个状态变量,将创建一个新的副本
      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract Locations{
          uint public stateVar1 = 10;
          uint public stateVar2 = 20;
      
          function doo() public returns(uint){
              stateVar1 = stateVar2;
              stateVar2 = 30;
      
              return stateVar1;   // returns 20
      
          /*  本例中,stateVar1 和 stateVar2 是状态变量。
              在 do 函数中,我们将 stateVar2 的值赋值给 stateVar1。
              stateVar1 的值是 20,但是为了确定它创建了一个新的副本,我们改变了stateVar2 的值。
              因此,如果它不创建副本,那么 stateVar1 的值应该是30,创建副本则是20。
              这同样适用于引用类型的状态变量。 */
          }
      }
  2. 规则2 - 内存局部变量复制到状态变量

    1. 从内存局部变量复制到状态变量,总是会创建一个新的副本

      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract Locations{
          uint stateVar = 10;     // storage
      
          function doone() public returns(uint){
              uint localVar = 20; // memory
              stateVar = localVar;
              localVar = 40;
      
              return stateVar;    // returns 20 
      
          /*    在上面的例子中,我们有一个状态变量和一个局部内存变量。
                在函数中,我们把局部变量的值赋给状态变量。
                为了检查赋值后的行为,我们改变了局部变量的值,并返回状态变量的值。
                这里可以看到,它会返回20,说明从内存局部变量复制到状态变量,会创建一个新的副本。*/
          }
      }
  3. 规则3 - 状态变量复制到内存局部变量

    1. 从状态变量复制到内存局部变量,将创建一个副本。

      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract Locations{
          uint stateVar = 10;     // storage
      
          function dothree() public returns(uint){
              uint localVar = 20; // memory
              localVar = stateVar;
              stateVar = 40;
              return localVar;     // returns 10
      
          /*  在上面的例子中,我们有一个状态变量和一个局部内存变量。在函数中,我们把局部变量的值赋给状态变量。
              为了检查赋值后的行为,我们改变了局部变量的值,并返回状态变量的值。
              这里可以看到,它会返回20,说明从内存局部变量复制到状态变量,会创建一个新的副本。  */
          }
      }
  4. 规则4 - 内存变量复制到内存变量

    1. 对于引用类型的局部变量,从一个内存变量复制到另一个内存变量,不会创建副本

      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract Locations{
          function doSomething() public view returns(uint[] memory,uint[] memory){
              uint[] memory localMemoryArray1 = new  uint[](3);
              localMemoryArray1[0] = 4;
              localMemoryArray1[1] = 5;
              localMemoryArray1[2] = 6;
      
              uint[] memory localMemoryArray2 = localMemoryArray1;
              localMemoryArray1[0] = 10;
      
              return(localMemoryArray1,localMemoryArray2);
              // returns 10,4,6 | 10,4,6
          
          /*  在上面的示例中,我们在内存中初始化了一个名为localMemoryArray1的数组变量,并赋值为4、5和6。
              然后,我们将该变量复制到名为localMemoryArray2的新内存变量中。
              然后,我们修改了localMemoryArray1中第一个索引的值,并返回了两个数组。
              这将得到相同的结果,因为它们都指向相同的位置。 */
          } 
      }
    2. 对于值类型的局部变量,从一个内存变量复制到另一个内存变量,仍然创建一个新副本

      // SPDX-License-Identifier: MIT
      pragma solidity ^0.8.0;
      
      contract Locations{
          function dofour() public pure returns(uint){
              uint localVar1 = 10;    // memory
              uint localVar2 = 20;    // memory
      
              localVar1 = localVar2;
              localVar2 = 40;
      
              return localVar1;       // returns 20
          }
      }

猜你喜欢

转载自blog.csdn.net/BingjieDreams/article/details/130812149