以太坊 / 区块链 · 2024年11月8日 0

Solidity

Solidity变量类型:

  1. 值类型(Value Type):包括布尔型,整数型等等,这类变量赋值时候直接传递数值。
  2. 引用类型(Reference Type):包括数组和结构体,这类变量占空间大,赋值时候直接传递地址(类似指针)。
  3. 映射类型(Mapping Type): Solidity中存储键值对的数据结构,可以理解为哈希表

    // 布尔值

    bool public _bool = true;

    // 布尔运算

    bool public _bool1 = !_bool; //取非

    bool public _bool2 = _bool && _bool1; //与

    bool public _bool3 = _bool || _bool1; //或

    bool public _bool4 = _bool == _bool1; //相等

    bool public _bool5 = _bool != _bool1; //不相等

    // 整数

    int public _int = -1;

    uint public _uint = 1;

    uint256 public _number = 20220330;

    // 整数运算

    uint256 public _number1 = _number + 1; // +,-,*,/

    uint256 public _number2 = 2**2; // 指数

    uint256 public _number3 = 7 % 2; // 取余数

    bool public _numberbool = _number2 > _number3; // 比大小

    // 地址

    address public _address = 0x7A58c0Be72BE218B41C608b7Fe7C5bB630736C71;

    address payable public _address1 = payable(_address); // payable address,可以转账、查余额

    // 地址类型的成员

    uint256 public balance = _address1.balance; // balance of address

    // 固定长度的字节数组

    bytes32 public _byte32 = “MiniSolidity”; // bytes32: 0x4d696e69536f6c69646974790000000000000000000000000000000000000000

    bytes1 public _byte = _byte32[0]; // bytes1: 0x4d

    // Enum

    // 将uint 0, 1, 2表示为Buy, Hold, Sell

    enum ActionSet { Buy, Hold, Sell }

    // 创建enum变量 action

    ActionSet action = ActionSet.Buy;

    // enum可以和uint显式的转换

    function enumToUint() external view returns(uint){

        return uint(action);

    }

  1. function:声明函数时的固定用法。要编写函数,就需要以 function 关键字开头。
  2. <function name>:函数名。
  3. ([parameter types[, ...]]):圆括号内写入函数的参数,即输入到函数的变量类型和名称。
  4. {internal|external|public|private}:函数可见性说明符,共有4种。
    • public:内部和外部均可见。
    • private:只能从本合约内部访问,继承的合约也不能使用。
    • external:只能从合约外部访问(但内部可以通过 this.f() 来调用,f是函数名)。
    • internal: 只能从合约内部访问,继承的合约可以用。
    注意 1:合约中定义的函数需要明确指定可见性,它们没有默认值。注意 2public|private|internal 也可用于修饰状态变量。public变量会自动生成同名的getter函数,用于查询数值。未标明可见性类型的状态变量,默认为internal
  5. [pure|view|payable]:决定函数权限/功能的关键字。payable(可支付的)很好理解,带着它的函数,运行的时候可以给合约转入 ETH。pure 和 view 的介绍见下一节。
  6. [virtual|override]: 方法是否可以被重写,或者是否是重写方法。virtual用在父合约上,标识的方法可以被子合约重写。override用在自合约上,表名方法重写了父合约的方法。
  7. <modifiers>: 自定义的修饰器,可以有0个或多个修饰器。
  8. [returns ()]:函数返回的变量类型和名称。
  9. <function body>: 函数体。

到底什么是 Pure 和View

solidity 引入这两个关键字主要是因为 以太坊交易需要支付气费(gas fee)。合约的状态变量存储在链上,gas fee 很贵,如果计算不改变链上状态,就可以不用付 gas。包含 pure 和 view 关键字的函数是不改写链上状态的,因此用户直接调用它们是不需要付 gas 的(注意,合约中非 pure/view 函数调用 pure/view 函数时需要付gas)。

    constructor() payable {}

    // 函数类型

    // function (<parameter types>) {internal|external} [pure|view|payable] [returns (<return types>)]

    // 默认function

    function add() external{

        number = number + 1;

    }

    // pure: 纯纯牛马

    function addPure(uint256 _number) external pure returns(uint256 new_number){

        new_number = _number+1;

    }

    // view: 看客

    function addView() external view returns(uint256 new_number) {

        new_number = number + 1;

    }

    // internal: 内部函数

    function minus() internal {

        number = number – 1;

    }

    function _minus() private {

        number = number – 1;

    }

    // 合约内的函数可以调用内部函数

    function minusCall() external {

        minus();

    }

    function _minusCall() external {

        _minus();

    }

    // payable: 递钱,能给合约支付eth的函数

    function minusPayable() external payable returns(uint256 balance) {

        minus();    

        balance = address(this).balance;

    }

返回值:return 和 returns

Solidity 中与函数输出相关的有两个关键字:returnreturns。它们的区别在于:

  • returns:跟在函数名后面,用于声明返回的变量类型及变量名。
  • return:用于函数主体中,返回指定的变量。

contract Return {

    // 返回多个变量

    function returnMultiple() public pure returns(uint256, bool, uint256[3] memory){

        return(1, true, [uint256(1),2,5]);

    }

    // 命名式返回

    function returnNamed() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){

        _number = 2;

        _bool = false;

        _array = [uint256(3),2,1];

    }

    // 命名式返回,依然支持return

    function returnNamed2() public pure returns(uint256 _number, bool _bool, uint256[3] memory _array){

        return(1, true, [uint256(1),2,5]);

    }

    // 读取返回值,解构式赋值

    function readReturn() public pure{

        // 读取全部返回值

        uint256 _number;

        bool _bool;

        bool _bool2;

        uint256[3] memory _array;

        (_number, _bool, _array) = returnNamed();

        // 读取部分返回值,解构式赋值

        (, _bool2, ) = returnNamed();

    }

}

Solidity中的引用类型

引用类型(Reference Type):包括数组(array)和结构体(struct),由于这类变量比较复杂,占用存储空间大,我们在使用时必须要声明数据存储的位置。

数据位置

Solidity数据存储位置有三类:storagememorycalldata。不同存储位置的gas成本不同。storage类型的数据存在链上,类似计算机的硬盘,消耗gas多;memorycalldata类型的临时存在内存里,消耗gas少。整体消耗gas从多到少依次为:storage > memory > calldata。大致用法:

  1. storage:合约里的状态变量默认都是storage,存储在链上。
  2. memory:函数里的参数和临时变量一般用memory,存储在内存中,不上链。尤其是如果返回数据类型是变长的情况下,必须加memory修饰,例如:string, bytes, array和自定义结构。
  3. calldata:和memory类似,存储在内存中,不上链。与memory的不同点在于calldata变量不能修改(immutable),一般用于函数的参数。
function fCalldata(uint[] calldata _x) public pure returns(uint[] calldata){
    //参数为calldata数组,不能被修改
    // _x[0] = 0 //这样修改会报错
    return(_x);
}
Solidity中变量按作用域划分有三种,分别是状态变量(state variable),局部变量(local variable)和全局变量(global variable)
1. 状态变量
状态变量是数据存储在链上的变量,所有合约内函数都可以访问,gas消耗高。状态变量在合约内、函数外声明:

contract Variables {
    uint public x = 1;
    uint public y;
    string public z;
}
我们可以在函数里更改状态变量的值:

function foo() external{
    // 可以在函数里更改状态变量的值
    x = 5;
    y = 2;
    z = "0xAA";
}
2. 局部变量
局部变量是仅在函数执行过程中有效的变量,函数退出后,变量无效。局部变量的数据存储在内存里,不上链,gas低。局部变量在函数内声明:

function bar() external pure returns(uint){
    uint xx = 1;
    uint yy = 3;
    uint zz = xx + yy;
    return(zz);
}
3. 全局变量
全局变量是全局范围工作的变量,都是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,他们分别代表请求发起地址,当前区块高度,和请求数据。下面是一些常用的全局变量,更完整的列表请看这个链接:

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 值
block.blobbasefee: (uint) 当前区块的blob基础费用。这是Cancun升级新增的全局变量。
blobhash(uint index): (bytes32) 返回跟当前交易关联的第 index 个blob的版本化哈希(第一个字节为版本号,当前为0x01,后面接KZG承诺的SHA256哈希的最后31个字节)。若当前交易不包含blob,则返回空字节。这是Cancun升级新增的全局变量。
4. 全局变量-以太单位与时间单位
以太单位
Solidity中不存在小数点,以0代替为小数点,来确保交易的精确度,并且防止精度的损失,利用以太单位可以避免误算的问题,方便程序员在合约中处理货币交易。

wei: 1
gwei: 1e9 = 1000000000
ether: 1e18 = 1000000000000000000
时间单位
可以在合约中规定一个操作必须在一周内完成,或者某个事件在一个月后发生。这样就能让合约的执行可以更加精确,不会因为技术上的误差而影响合约的结果。因此,时间单位在Solidity中是一个重要的概念,有助于提高合约的可读性和可维护性。

seconds: 1
minutes: 60 seconds = 60
hours: 60 minutes = 3600
days: 24 hours = 86400
weeks: 7 days = 604800

数组成员

  • length: 数组有一个包含元素数量的length成员,memory数组的长度在创建后是固定的。
  • push()动态数组拥有push()成员,可以在数组最后添加一个0元素,并返回该元素的引用。
  • push(x)动态数组拥有push(x)成员,可以在数组最后添加一个x元素。
  • pop()动态数组拥有pop()成员,可以移除数组最后一个元素。

结构体 struct

Solidity支持通过构造结构体的形式定义新的类型。结构体中的元素可以是原始类型,也可以是引用类型;结构体可以作为数组或映射的元素。创建结构体的方法:

// 结构体
struct Student{
    uint256 id;
    uint256 score; 
}

Student student; // 初始一个student结构体

映射Mapping

在映射中,人们可以通过键(Key)来查询对应的值(Value),比如:通过一个人的id来查询他的钱包地址。

声明映射的格式为mapping(_KeyType => _ValueType),其中_KeyType_ValueType分别是KeyValue的变量类型。例子:

mapping(uint => address) public idToAddress; // id映射到地址
mapping(address => address) public swapPair; // 币对的映射,地址到地址

映射的规则

  • 规则1:映射的_KeyType只能选择Solidity内置的值类型,比如uintaddress等,不能用自定义的结构体。而_ValueType可以使用自定义的类型。下面这个例子会报错,因为_KeyType使用了我们自定义的结构体:// 我们定义一个结构体 Struct struct Student{ uint256 id; uint256 score; } mapping(Student => uint) public testVar;
  • 规则2:映射的存储位置必须是storage,因此可以用于合约的状态变量,函数中的storage变量和library函数的参数(见例子)。不能用于public函数的参数或返回结果中,因为mapping记录的是一种关系 (key – value pair)。
  • 规则3:如果映射声明为public,那么Solidity会自动给你创建一个getter函数,可以通过Key来查询对应的Value
  • 规则4:给映射新增的键值对的语法为_Var[_Key] = _Value,其中_Var是映射变量名,_Key_Value对应新增的键值对。例子:function writeMap (uint _Key, address _Value) public{ idToAddress[_Key] = _Value; }

映射的原理

  • 原理1: 映射不储存任何键(Key)的资讯,也没有length的资讯。
  • 原理2: 对于映射使用keccak256(h(key) . slot)计算存取value的位置。感兴趣的可以去阅读 WTF Solidity 内部规则: 映射存储布局
  • 原理3: 因为Ethereum会定义所有未使用的空间为0,所以未赋值(Value)的键(Key)初始值都是各个type的默认值,如uint的默认值是0。

contract  Maptest{

    mapping(uint=> address) public idToAddress;

    mapping(address => address) public addToAdd;

    function add(uint a, address addr) public{

        idToAddress[a] = addr;

    }

}

contract Mapping {

    mapping(uint => address) public idToAddress; // id映射到地址

    mapping(address => address) public swapPair; // 币对的映射,地址到地址

    // 规则1. _KeyType不能是自定义的 下面这个例子会报错

    // 我们定义一个结构体 Struct

    // struct Student{

    //    uint256 id;

    //    uint256 score;

    //}

    // mapping(Struct => uint) public testVar;

    function writeMap (uint _Key, address _Value) public{

        idToAddress[_Key] = _Value;

    }

}

变量初始值

Solidity中,声明但没赋值的变量都有它的初始值或默认值。这一讲,我们将介绍常用变量的初始值。

值类型初始值

  • booleanfalse
  • string""
  • int0
  • uint0
  • enum: 枚举中的第一个元素
  • address0x0000000000000000000000000000000000000000 (或 address(0))
  • function
    • internal: 空白函数
    • external: 空白函数

引用类型初始值

  • 映射mapping: 所有元素都为其默认值的mapping
  • 结构体struct: 所有成员设为其默认值的结构体
  • 数组array
    • 动态数组: []
    • 静态数组(定长): 所有成员设为其默认值的静态数组

可以用public变量的getter函数验证上面写的初始值是否正确:

// Reference Types
uint[8] public _staticArray; // 所有成员设为其默认值的静态数组[0,0,0,0,0,0,0,0]
uint[] public _dynamicArray; // `[]`
mapping(uint => address) public _mapping; // 所有元素都为其默认值的mapping
// 所有成员设为其默认值的结构体 0, 0
struct Student{
    uint256 id;
    uint256 score; 
}
Student public student;

delete操作符

delete a会让变量a的值变为初始值。

// delete操作符
bool public _bool2 = true; 
function d() external {
    delete _bool2; // delete 会让_bool2变为默认值,false
}

constant和immutable

constant

constant变量必须在声明的时候初始化,之后再也不能改变。尝试改变的话,编译不通过。

// constant变量必须在声明的时候初始化,之后不能改变
uint256 constant CONSTANT_NUM = 10;
string constant CONSTANT_STRING = "0xAA";
bytes constant CONSTANT_BYTES = "WTF";
address constant CONSTANT_ADDRESS = 0x0000000000000000000000000000000000000000;

immutable

immutable变量可以在声明时或构造函数中初始化,因此更加灵活。在Solidity v0.8.21以后,immutable变量不需要显式初始化,未显式初始化的immutable变量将使用数值类型的初始值(见 8. 变量初始值)。反之,则需要显式初始化。 若immutable变量既在声明时初始化,又在constructor中初始化,会使用constructor初始化的值。

// immutable变量可以在constructor里初始化,之后不能改变
uint256 public immutable IMMUTABLE_NUM = 9999999999;
// 在`Solidity v8.0.21`以后,下列变量数值暂为初始值
address public immutable IMMUTABLE_ADDRESS; 
uint256 public immutable IMMUTABLE_BLOCK;
uint256 public immutable IMMUTABLE_TEST;

你可以使用全局变量例如address(this)block.number 或者自定义的函数给immutable变量初始化。在下面这个例子,我们利用了test()函数给IMMUTABLE_TEST初始化为9

// 利用constructor初始化immutable变量,因此可以利用
constructor(){
    IMMUTABLE_ADDRESS = address(this);
    IMMUTABLE_NUM = 1118;
    IMMUTABLE_TEST = test();
}

function test() public pure returns(uint256){
    uint256 what = 9;
    return(what);
}
打赏作者