solidity基础001
关键词:变量 数值类型 函数 数组 印射
仅个人学习使用(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 | function returnMultiple() public pure returns(uint256, bool, uint256[3] memory) { |
此处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 | // memory动态数组 |
如果创建的是动态数组,需要一个一个元素的赋值。
数组成员
- length: 数组有一个包含元素数量的length成员,memory数组的长度在创建后是固定的。
- push(): 动态数组拥有push()成员,可以在数组最后添加一个0元素,并返回该元素的引用。
- push(x): 动态数组拥有push(x)成员,可以在数组最后添加一个x元素。
- pop(): 动态数组拥有**pop()**成员,可以移除数组最后一个元素(与数据结构的栈相同)
2.4.2结构体
创建结构体:
1 | //结构体 |
结构体赋值方法
方法1:在函数中创建一个storage的struct引用
1 | function initStudent1() external{ |
storage
方法2:直接引用状态变量的struct
1 | function initStudent2() external{ |
方法3:构造函数式
1 | function initStudent3() external{ |
方法4:key value
1 | function initStudent4() external{ |
2.4.3印射 (mapping)
定义: Solidity中存储键值对的数据结构,可以理解为哈希表
mapping变量不存长度信息
在映射中,人们可以通过键(Key)来查询对应的值(Value)
比如:通过一个人的id来查询他的钱包地址。
格式为
1 | mapping(_KeyType => _ValueType) |
其中_KeyType和_ValueType分别是Key和Value的变量类型
例子:
1 | mapping(uint => address) public idToAddress; // id映射到地址 |
映射的规则
规则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 | function writeMap(uint key,address Value)public{ |
题:
1.
请解释下面这段代码的意思:
1 | address payable addr; |
选择一个答案
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 | uint256 immutable y1; |
其中,确实有必要在构造函数 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 | //命名式返回 |
命名式返回,依然支持return
1 | Function returnNamed2() public pure returns(uint256 _number,bool _bool,unit256[3] memory _arrray) |
_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
2uint256 price = pricePerToken / 100; // 还原整数部分
uint256 decimals = pricePerToken % 100; // 取出小数部分这样 150 就表示 1.50,即使用整数的 150 来代表 1.50。
举例 3: 更高精度
如果需要更多的精度,比如小数点后 6 位,可以通过乘以 10^6 来处理。1
2uint256 public value = 1234567; // 实际表示 1.234567
总结在 Solidity 中,通过使用整数来表示带有小数点的值,可以避免精度问题。具体方法是根据需要的精度将小数部分转化为整数进行计算,并在使用时再将其缩放回实际数值。例如,通过乘以 10^18 或 10^6 来模拟 18 位或 6 位的小数。
Wei = 1
Gwei = 1e9 = 1000000000
ether = 1e18 = 100000000000000000