关键词:变量 数值类型 函数 数组 印射
仅个人学习使用(WTF学习笔记)

数值类型

初始值

bytes1 是固定大小的字节类型,表示一个 1 字节的值(8 位)。它的初始值(默认值)是 0x00,即所有位都为 0。

具体解释:

  • 在 Solidity 中,任何未赋值的变量都会有一个默认值。对于 bytes1 类型,它的默认值是一个长度为 1 字节的值,所有的位都被初始化为 0,用十六进制表示为 0x00。
  • 如果用二进制表示,它是 00000000。

值类型初始值

  • boolean: false

  • string: “”

  • int: 0

  • uint: 0

  • enum: 枚举中的第一个元素

  • address: 0x0000000000000000000000000000000000000000 (或 address(0))

  • function

    • internal: 空白函数
    • external: 空白函数

引用类型初始值

  • 映射mapping: 所有元素都为其默认值的mapping

  • 结构体struct: 所有成员设为其默认值的结构体

  • 数组array

    • 动态数组: []
    • 静态数组(定长): 所有成员设为其默认值的静态数组

Bytes4 类型表示的是一个固定长度的字节数组,长度为 4 个字节。每个字节由 2 个十六进制字符表示,因此:
• 1 字节 = 2 个十六进制字符
• 4 字节 = 4 × 2 = 8 个十六进制字符
因此,bytes4类型具有 8 个十六进制位。
示例:
例如,一个 bytes4 类型的值可能是 0x12345678,这里的 12345678 就是 8 个十六进制字符。

函数

声明

可见性说明符

pure|view|payable

函数输出

1
2
3
function returnMultiple() public pure returns(uint256, bool, uint256[3] memory) {
return (1, true, [uint256(1), 2, 5]);
}

此处memory是什么?
是用于指定数据存储位置的关键字。它标识的是数据在内存中存储,而不是在区块链上永久存储,通常用于临时存储复杂数据类型(如数组或结构体)并在函数执行期间操作它们,执行完毕后释放资源。具体来说,memory 指定了在函数执行期间,数据只临时存储在内存中,一旦函数执行完毕,内存中的数据就会被释放。
例如此处:uint256[3] memory 中的 memory 指定了数组 [uint256(1), 2, 5] 将在内存中临时存储,而不是在区块链的持久存储(即 storage)中存储。

此处代码解析:

  • uint256[3] memory: 定义了一个长度为 3 的 uint256 类型数组,并将其存储在内存中。
  • [unit256(1),2,5]:
    • uint256(1):这个表示法将 1 明确地转换为 uint256 类型。在某些情况下Solidity 可能会自动推断出整数类型为 uint256,但是为了确保类型一致性,可以使用这种显式类型转换。
    • 2 和 5:这两个值直接写为整数,Solidity会自动将它们识别为 uint256 类型,因它们是未指定类型的字面常量,且默认情况下 Solidity 会将整数推断为 uint256。

变量数据存储和作用域:

1. 声明类型

1.1 Solidity数据存储位置

有三类:storage,memory和calldata。

[1]

数据存储类型介绍

(1)Storage(存储)

该存储位置存储永久数据,这意味着该数据可以被合约中的所有函数访问。可以把它视为计算机的硬盘数据,所有数据都永久存储。保存在存储区(Storage)中的变量,以智能合约的状态存储,并且在函数调用之间保持持久性。与其他数据位置相比,存储区数据位置的成本较高。

(2) Memor y(内存)

内存位置是临时数据,比存储位置便宜。它只能在函数中访问。通常,内存数据用于保存临时变量,以便在函数执行期间进行计算。一旦函数执行完毕,它的内容就会被丢弃。你可以把它想象成每个单独函数的内存(RAM)。

(3)Calldata

Calldata是不可修改的非持久性数据位置,所有传递给函数的值,都存储在这里。此外,Calldata是外部函数的参数(而不是返回参数)的默认位置。

存储规则

【2】

【3】

【4】

【5】

1.2 赋值和引用

1.3常数 constant和immutable

只有数值变量可以声明constant和immutable;string和bytes可以声明为constant,但不能为immutable

  • constant变量必须在声明的时候初始化,之后再也不能改变。尝试改变的话,编译不通过
  • immutable变量可以在声明时或构造函数中初始化
  • immutable变量既在声明时初始化,又在constructor中初始化,会使用constructor初始化的值。

2. 作用域

2.1

2.2

2.3全局变量

2.3.1以太单位&时间单位

2.4引用类型

2.4.1数组

分为:
固定数组;
可变数组;
(bytes比较特殊,是数组,但是不用加[]另外,不能用byte[]声明单字节数组,可以使用bytes或bytes1[]。bytes比bytes1[]省gas。

创建数组的规则
对于memory修饰的动态数组,可以用new操作符号来创建,但是必须声明长度,并且声明后长度不能改变。

eg:

1
2
3
// memory动态数组
uint[] memory array8 = new uint[](5);
bytes memory array9 = new bytes(9);

如果创建的是动态数组,需要一个一个元素的赋值。

数组成员

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

2.4.2结构体

创建结构体

1
2
3
4
5
6
7
//结构体
struct Student{
uint256 id;
uint256 score;
}

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

结构体赋值方法

方法1:在函数中创建一个storage的struct引用

1
2
3
4
5
function initStudent1() external{
Student storage _student = student;
_stdent.id = 11;
_student.score = 100;
}

storage

方法2:直接引用状态变量的struct

1
2
3
4
function initStudent2() external{
student.id = 11;
stundet.score = 100;
}

方法3:构造函数式

1
2
3
function initStudent3() external{
student = Student(11,100);
}

方法4:key value

1
2
3
function initStudent4() external{
student = Student({id: 11,score: 100});
}

2.4.3印射 (mapping)

定义: Solidity中存储键值对的数据结构,可以理解为哈希表

mapping变量不存长度信息

在映射中,人们可以通过键(Key)来查询对应的值(Value)

比如:通过一个人的id来查询他的钱包地址。

格式为

1
mapping(_KeyType => _ValueType)

其中_KeyType和_ValueType分别是Key和Value的变量类型

例子:

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

规则1

映射的_keytype只能选择solidity内置的值类型,比如uint,address等,不能用自定义的结构体,而_Valuetype可以使用自定义的类型(例如:结构体)。

规则2

映射的存储位置必须是storage,因此可以用于合约的状态变量,函数中的storage变量和library函数的参数(例子)。不能用于public函数的参数或返回结果中,因为mapping记录的是一种关系(key-value pair)。

规则3

如果映射声明为 public,那么Solidity会自动给你创建一个 getter 函数,可以通过 key 来查询对应的 value 。
规则4

给映射新增的键值对的语法为_var[_Key]= _value,其中 _var 是映射变量名,_Key 和_value 对应新增的键值对。

eg:

1
2
3
function writeMap(uint key,address Value)public{
idToAddress[ Key]= Value;
}

题:

1.

请解释下面这段代码的意思:

1
2
address payable addr;
addr.transfer(1);

选择一个答案
A. 调用者向合约转账1wei
B. 调用者向addr转账1wei
C. 合约向addr转账1wei
D. 合约向addr转账1ether

解析:

  • address payable: 这是一个 payable 地址类型,表示这个地址能够接收以太币(ether)转账。只有 payable 地址才允许进行 transfer() 或 send() 操作。
  • addr.transfer(1): 这是一个转账操作,向地址 addr 转账 1 单位的以太币,单位是 wei。在以太坊中,最小的货币单位是 wei,而 1 ether = 10^18 wei。因此,这里转账的数额是 1 wei,而不是 1 ether。
  • transfer操作是合约向addr转账。
    正确答案选c

2.

以下关于数组的说法中,正确的是(选择一个答案)

A. 固定长度数组和bytes拥有push()成员,可以在数组最后添加一个0元素。

B. 数组字面常数,例如[uint(1),2,3],需要声明第一个元素的类型,不然默认用存储空间最大的类型

C. 内存数组的长度在创建后是固定的。

D. 对于memory可变长度数组,可以用new操作符来创建,并且不用声明长度,例如uint[]

解析

  • A. 固定长度数组和 bytes 拥有 push() 成员,可以在数组最后添加一个0元素。
    这句话是错误的。固定长度数组没有 push() 成员,只有 可变长度数组 才有 push() 成员,而 bytes 是可变长度数组,因此 bytes 可以使用 push(),但固定长度数组不能。

  • 选项 B 的说法 不正确。原因:在 Solidity 中,数组字面常量的类型推断并不会自动默认为存储空间最大的类型。类型推断基于数组字面量中的元素值:对于一个字面量数组,如 [1, 2, 3],Solidity 会推断这些数字为最小的适当类型。例如,在这种情况下,uint8 就足够了,因为所有数字都能用 8 位来表示,而不会默认推断为 uint256。只有在数组的元素中存在更大范围的数值时,编译器才会选择相应的更大类型。如果你希望指定一个更大的类型(比如 uint256),需要显式地声明。因此,数组字面常量中的类型并不会默认使用存储空间最大的类型,而是基于实际的数值大小来推断最适合的类型。

    示例:

    [1, 2, 3] 会推断为 uint8。

    [uint256(1), 2, 3] 才会显式地使用 uint256 进行类型推断。

所以,B 选项的说法是 不正确的。

  • C. 内存数组的长度在创建后是固定的。

  • D. 这句话是错误的。使用 new 操作符创建内存数组时,必须指定数组的长度,不能省略长度声明。正确的用法是

    1
    uint[] memory array = new uint[](length);

3.

以下关于结构体的说法中,错误的是

选择一个答案

A. 通过结构体的形式可以定义新的类型。

B. 结构体内可以包含字符串,整型等基本数据类型,也可以包含数组,映射,结构体等复杂类型。

C. 结构体内可以包含其本身。

解析

  • A. 这是正确的。结构体在 Solidity 中用于定义新的自定义类型,允许开发者创建更复杂的数据结构。
  • B. 这是正确的。结构体可以包含基本数据类型(如 uint、string),也可以包含复杂类型(如数组、映射、其他结构体)。
  • C. 这是错误的。在 Solidity 中,结构体不能直接包含其本身,因为这会导致无限嵌套,无法确定内存的大小。不过,结构体可以包含指向其他结构体的 引用,例如使用指针或映射的方式来实现递归关系。

因此,选项 C 是错误的。

4.

在如下的合约中,我们定义了四个 immutable 的变量 y1, y2, y3, y4。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
 uint256 immutable y1; 

address immutable y2;

address immutable y3;

uint256 immutable y4;

constructor (uint256 _y4){

y1 = block.number;

y2 = address(this);

y3 = msg.sender;

y4 = _y4;
}

其中,确实有必要在构造函数 constructor 中才赋值的一项是:

A. y1 B. y2 C. y3 D. y4

解析:

  • y4 是通过构造函数参数 _y4 传入的值,因此只有在部署合约时才能确定它的值,这使得它必须在构造函数中赋值。

其他变量在构造函数外也可以直接赋值,或者它们的值是在合约部署时自动确定的:

  • y1:可以在合约外直接赋值为 block.number,表示合约部署时的区块号。
  • y2:可以直接设置为 address(this),即合约自身的地址,在合约部署时自动确定。
  • y3:可以直接设置为 msg.sender,即部署合约的地址,也是在合约部署时自动确定的。

因此,只有 y4 是依赖于构造函数参数,必须在构造函数中进行赋值。选D。

5.

下面定义变量的语句中,会报错的一项是:

A. string constant x5 = “hello world”;

B. address constant x6 = address(0);

C. string immutable x7 = “hello world”;

D. address immutable x8 = address(0);

解析:

  • 在 Solidity 中,immutable 变量必须在 构造函数中 赋值,而不能在声明时直接赋值。

具体说明:

  • **A. string constant x5 = “hello world”;**:constant 变量必须在声明时赋值,因此这是正确的。
  • **B. address constant x6 = address(0);**:constant 变量在声明时赋值是正确的。
  • **C. string immutable x7 = “hello world”;**:这是错误的,因为 immutable 变量不能在声明时赋值,必须在构造函数中进行赋值。
  • **D. address immutable x8 = address(0);**:虽然是 immutable 变量,但它的赋值可以在构造函数中完成,定义时不能直接赋值。此处代码如果在构造函数中赋值是正确的。

因此,选项 C 会报错。

ps

1
2
3
4
5
6
//命名式返回
function returnNamed() public pure returns(uint256_number, bool _bool, uint256[3] memory _array)(
_number = 2;
_bool = false;
_array = [uint256(3),2,1];
}

命名式返回,依然支持return

1
2
3
4
Function returnNamed2() public pure returns(uint256 _number,bool _bool,unit256[3] memory _arrray)
{
return(1,true,[unit256(1),2,5]);
}
  • _array 被赋值为 [uint256(3), 2, 1]。这表示它是一个包含三个元素的数组,其中:
    • 第一个元素是 3(明确类型转换为 uint256)。
    • 第二个元素是 2(自动推断为 uint256)。
    • 第三个元素是 1(自动推断为 uint256)。

  • Solidity中不存在小数点,以0代替为小数点,举例
    在Solidity 中,确实没有内置的浮点数(即小数点),因此所有数字必须使用整数类型来表示。这就需要通过一些技巧来模拟小数点,通常是通过将小数值放大为整数来进行计算。例如,如果要表示金额,可以假设整数中的最低位是 “小数点后的一位” 或更多位,这样就能间接表示小数。
    例子:
    假设我们想在 Solidity 中表示货币金额,而没有浮点数。我们可以使用整数表示金额,并通过约定整数的最后几位代表小数部分。常见的方法是将金额放大 10^18(即使用以太坊中的 wei 单位),将以太作为以太坊中的基本单位
    举例 1: 表示金额
    假设要表示 1.5 个以太币。

    1
    uint256 public amount = 1.5 * 10**18; // 1.5 Ether 等于 1.5 * 10^18 wei

    • 在这里,我们使用 10^18 来表示一个以太币,因为1 ether = 10^18 wei。为了表示 1.5 个以太币,我们将 1.5 乘以 10^18,最终得到 1500000000000000000 wei。
    • 尽管 Solidity 不支持浮点数,但我们通过使用整数和固定的精度来实现。
    举例 2: 模拟小数点计算
    假设我们有一个代币,代币的精度是小数点后 2 位。我们可以通过放大 100 倍来处理小数点。

    1
    uint256 public pricePerToken = 150; // 实际上表示 1.50 单位(放大了100倍)

    我们可以通过数学计算来恢复小数点后的值

    1
    2
    uint256 price = pricePerToken / 100;      // 还原整数部分
    uint256 decimals = pricePerToken % 100; // 取出小数部分

    这样 150 就表示 1.50,即使用整数的 150 来代表 1.50。
    举例 3: 更高精度
    如果需要更多的精度,比如小数点后 6 位,可以通过乘以 10^6 来处理

    1
    2
    uint256 public value = 1234567; // 实际表示 1.234567
    总结

    在 Solidity 中,通过使用整数来表示带有小数点的值,可以避免精度问题。具体方法是根据需要的精度将小数部分转化为整数进行计算,并在使用时再将其缩放回实际数值。例如,通过乘以 10^18 或 10^6 来模拟 18 位或 6 位的小数。

  • Wei = 1

  • Gwei = 1e9 = 1000000000

  • ether = 1e18 = 100000000000000000