最近在学习Solidity,简单记录一下。

推荐0xAA师傅的Solidity极简入门教程

# 变量

变量是编程语言重要的组成部分之一,可以让我们方便的追踪和查看程序中的数据。

# 变量类型

Solidity中的变量类型主要有以下几种:

  1. 数值类型:最常见的变量类型,包括布尔型、整型等变量类型。
  2. 引用类型:常用的变量类型,包括数组,结构体等变量类型,与数值类型不同的地方在于赋值的时候是传递指针而非直接传递数值(这一点在其他语言也有体现)。
  3. 映射类型:类似哈希表,可以通过唯一的key来查询对应的value

# 数值类型

最常见的变量类型,包括布尔型、整型等变量类型。

# 布尔型

与其他语言并无太大不同,布尔型变量取值只能为truefalse

布尔型变量的运算符为逻辑运算符:

  • !
  • &&
  • ||
  • == 等于
  • != 不等于
pragma solidity ^0.4.23;
contract _bool {
	bool public _bool1 = true;
	bool public _bool2 = !_bool1;
	bool public _bool3 = _bool1 && _bool2;
//其他运算符依此类推
}

# 整型

没啥好说的,比较常用的有

  • int 包括负数的整数。
  • uint 正整数,在没有具体指定位数的情况下默认为256位,即uint256
  • uint256 256位正整数,

整型变量的运算符与其他语言并无不同,需要注意的是uint类型容易发生整数上溢/下溢。

pragma solidity ^0.4.23;
contract _int {
	int public _int = -1;
	uint public _uint = 1;
	uint256 public _uint256 = 1111111111;
	uint256 public _overflow = 1;

   function overflow() public { //整型下溢
	   _overflow=_overflow-2;
   }
}

# 地址型

地址型变量是Solidity中特有的一种变量,它用来储存以太坊的地址。

地址型变量在0.6.0之后的版本分为addressaddress payable两种,payableaddress拥有两个成员变量balancetransfer,分别用来查询余额以及向其他地址转账。

pragma solidity ^0.6.12;
//SPDX-License-Identifier: SimPL-2.0
contract _address {
	address public _address1 = 0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3;
   address payable public _address2 = 0x358AA13c52544ECCEF6B0ADD0f801012ADAD5eE3;
   uint public balance = _address2.balance;
}

# 定长字节数组

定长字节数组跟C语言中的数组相似,在定义变量类型时需要确定数组长度(例如bytes8bytes16bytes32),并且后期不能改变数组长度。因为长度固定,消耗的gas也比较少。

字符串在定长数组中以16进制的形式存储。

pragma solidity ^0.6.12;
//SPDX-License-Identifier: SimPL-2.0
contract _address {
	bytes32 public _byte32 = "Solidity"; 
	bytes1 public _byte1 = _byte32[0]; 
}

# 引用类型

# 数组

数组分为定长数组动态数组

  • 定长数组与之前的定长字节数组类似,但是声明方式不同,并且与C语言中的数组也有一点区别。
  • 动态数组在声明时不确定数组长度,可以根据后期的使用情况改变长度。

数组也有四个成员,分别是lengthpush()push(x)pop(),后三个是动态数组独有。

  • length:数组长度。
  • push():在数组结尾添加一个0。
  • push(x):在数组结尾添加一个x。
  • pop():移除数组最后一一个元素。
pragma solidity ^0.6.12;
//SPDX-License-Identifier: SimPL-2.0
contract _array {

   //定长数组
	uint[8] public _uint1;
   address[2] public _address1;

   //动态数组
   uint[] public _uint2;
   address[] public _address2;
   bytes _bytes;//bytes数组不用加[]
}

# 结构体

在一些情况下,使用结构体来管理数据显然比单独声明多个变量更加方便,Solidity也支持通过结构体定义新的类型。

pragma solidity ^0.6.12;
//SPDX-License-Identifier: SimPL-2.0
contract _struct {
   //声明结构体Book并初始化变量book。
   struct Book{
      uint256 id;
      string name;
   } Book book;
   //结构体变量赋值
   function editbook(uint256 _id, string memory _name) external {
      book.name=_name;
      book.id=_id;
   }
}

# 映射类型

映射类似哈希表,可以通过唯一的key来查询对应的value。 声明映射的格式为mapping(_KeyType => _ValueType),对应keyvalue的变量类型。

pragma solidity ^0.6.12;
//SPDX-License-Identifier: SimPL-2.0
contract _mapping {
   struct Book{
      uint256 id;
      string name;
   }
   //利用mapping来映射用户地址address到存款balance。
   mapping (address => uint) public balance;
   //还可以映射到结构体。
   mapping (uint => Book) public book;
   //但是需要注意的是,_KeyType只能使用默认的变量类型,不能使用结构体作为_KeyType。
   //下面这条语句会报错,因为把结构体作为了_KeyType。
   mapping (Book => uint) public book;
}

如果映射声明使用public修饰,会自动生成一个函数,可以输入key来查询对应的value。

# 变量储存位置

Solidity与其他语言显著的不同点之一在于Solidity生成的合约在进行操作时,会花费一定的gas,由于gas需要花费eth来购买,因此在保证合约的可读性的同时,我们希望合约操作的gas费用越少越好。

因此,对于一些中间变量,抑或是不重要的变量,我们自然希望它使用完就消失,而对于那些重要的变量,就需要储存在链上(即使这么做会花费更多的gas)。

所以在Solidity中,我们需要在使用变量时声明其数据储存的位置,特别是占据空间较大的引用类型变量(数组、结构体、映射等)。

Solidity有三种储存位置:

  • storage:数据储存在链上,花费的gas多,变量储存的默认位置就是storage
  • memory:数据储存在内存中,花费的gas少,函数传参、临时变量或者不重要的变量一般都使用memory
  • calldata:数据储存在内存中,花费的gas少,与memory不同的地方在于calldata变量不能修改。
pragma solidity ^0.8.4;
//SPDX-License-Identifier: SimPL-2.0
contract Storagelocation {
   //_uint2默认为storage类型
   uint[] public _uint2;
   function _memory(uint[] memory _uint) public returns(uint[] memory){
      _uint2=_uint;
      return _uint2;
   }
   function _calldata(uint[] calldata _x) public pure returns(uint[] calldata){
      //参数为calldata数组,不能被修改
      //试图修改_x,报错
      _x[0] = 0;
      return(_x);
    }
}

# 变量作用域

Solidity有三个作用域,变量根据不同作用域可以大致分为三种:

  • 状态变量:状态变量在合约内、函数外声明,其数据储存在链上,合约内的所有函数都可以使用状态变量。
  • 局部变量:局部变量在函数内声明,其数据储存在内存中,只有该函数内才能使用这些变量。
  • 全局变量:全局变量一般是solidity的预留关键字,可以在函数内不经过声明直接使用。
function global() public returns(address,uint){
      address _address1 = msg.sender;
      uint _balance = address(this).balance;
      return (_address1,_balance);
   }
}

# 变量默认值

如果我们声明了一个变量但是没给它赋值,那么Solidity就会给这个变量一个默认的初始值。

# 数值类型

  • boolfalse
  • string""
  • int0
  • uint0
  • address0x0000000000000000000000000000000000000000 (或 address(0))
pragma solidity ^0.8.4;
//SPDX-License-Identifier: SimPL-2.0
contract Defaults {
   bool public _bool;
   string public _string;
   int public _int;
   uint public _uint;
   address public _address;
}

# 引用类型

  • mapping:映射的默认值为其_ValueType的默认值。
  • struct:结构体的默认值为其成员的默认值。
  • array
    • 动态数组:[]
    • 定长数组:定长数组的默认值为其成员的默认值。
pragma solidity ^0.8.4;
//SPDX-License-Identifier: SimPL-2.0
contract Defaults {
   mapping (address => uint) public balance;

   struct Book{
      uint256 id;
      string name;
   } Book public book;

   uint[] public _uint1;
   uint[8] public _uint2;
}

# 还原初始值

存在delete操作符,会让变量的值变为默认值。

pragma solidity ^0.8.4;
//SPDX-License-Identifier: SimPL-2.0
contract Defaults {
   uint public _uint=666;
   function _delete() public {
      delete _uint;
   } 
}

# 常量

在Solidity中,常量是一种特殊的变量,通常是通过constantimmutable两个关键字声明。常量只有在声明的时候可以赋值,在后面任何情况都不能够改变其数值,这意味着使用常量可以节省gas费用。

除此之外,只有数值类型的变量可以声明constantimmutable,字符型变量只能使用constant声明。

# constant

constant变量只能在声明的时候初始化,之后就不能改变其数值。

pragma solidity ^0.8.4;
//SPDX-License-Identifier: SimPL-2.0
contract Defaults {
   uint256 constant _uint = 666;
   string constant _string = "solidity";
   address constant _address = 0x0000000000000000000000000000000000000000;
}

# immutable

immutable变量除了在声明的时候初始化,还能再构造函数中初始化,使用更加方便灵活。

pragma solidity ^0.8.4;
//SPDX-License-Identifier: SimPL-2.0
contract Constant {
   uint256 public immutable _uint1 = 666;
   address public immutable _address;
   uint256 public immutable _uint2;
   constructor() public {
      _address = 0x0000000000000000000000000000000000000000;
      _uint2 = test();
   }
   function test() public pure returns(uint256){
      uint256 _uint3 = 999;
      return(_uint3);
   }
}