Solidity-3-类型
Solidity 是一种静态类型语言,这意味着每个变量(状态变量和局部变量)都需要在编译时指定变量的类型。
“undefined”或“null”值的概念在Solidity中不存在,但是新声明的变量总是有一个 默认值 ,具体的默认值跟类型相关。 要处理任何意外的值,应该使用 错误处理 来恢复整个交易,或者返回一个带有第二个 bool 值的元组表示成功。
值类型
以下类型也称为值类型,因为这些类型的变量将始终按值来传递。 也就是说,当这些变量被用作函数参数或者用在赋值语句中时,总会进行值拷贝。
布尔类型
bool :可能的取值为字面常量值 true 和 false
运算符:
! (逻辑非)
&& (逻辑与, “and” )
|| (逻辑或, “or” )
== (等于)
!= (不等于)
整型
int / uint :分别表示有符号和无符号的不同位数的整型变量。 支持关键字 uint8 到 uint256 (无符号,从 8 位到 256 位)以及 int8 到 int256,以 8 位为步长递增。 uint 和 int 分别是 uint256 和 int256 的别名。
运算符:
比较运算符: <= , < , == , != , >= , > (返回布尔值)
位运算符: & , | , ^ (异或), ~ (位取反)
移位运算符: << (左移位) , >> (右移位)
算数运算符: + , - , 一元运算负 - (仅针对有符号整型), * , / , % (取余或叫模运算) , ** (幂)
对于整形 X,可以使用 type(X).min 和 type(X).max 去获取这个类型的最小值与最大值。
0.8.0 开始,算术运算有两个计算模式:一个是 “wrapping”(截断)模式或称 “unchecked”(不检查)模式,一个是”checked” (检查)模式。 默认情况下,算术运算在 “checked” 模式下,即都会进行溢出检查,如果结果落在取值范围之外,调用会通过 失败异常 回退。 你也可以通过 unchecked { … } 切换到 “unchecked”模式
比较运算
位运算
位运算在数字的二进制补码表示上执行。 这意味着: ~int256(0)== int256(-1)
移位
移位操作的结果具有左操作数的类型,同时会截断结果以匹配类型。 右操作数必须是无符号类型。 尝试按带符号的类型移动将产生编译错误。
x << y 等于数学表达式 x * 2 ** y。x >> y 等于数学表达式 x / 2 ** y , 四舍五入到负无穷。
对于移位操作不会像算术运算那样执行溢出检查,其结果总是被截断。
加、减、乘法运算
表达式 -x 相当于 (T(0) - x) 这里 T 是指 x 的类型。 -x 只能应用在有符号型的整数上。 如果 x 为负数, -x 为正数。 由于使用两进制补码表示数据,你还需要小心:
如果有 int x = type(int).min;, 那 -x 将不在正数取值的范围内。 这意味着这个检测 unchecked { assert(-x == x); } 是可以通过的(即这种情况下,不能假设它的负数会是正数),如果是 checked 模式,则会触发异常。
除法运算
除法运算结果的类型始终是其中一个操作数的类型,整数除法总是产生整数。 在Solidity中,分数会取零。 这意味着 int256(-5) / int256(2) == int256(-2) 。
除以0 会发生 Panic 错误 , 而且这个检查,不可以通过 unchecked { … } 禁用掉。
模运算(取余)
模运算 a%n 是在操作数 a 的除以 n 之后产生余数 r ,其中 q = int(a / n) 和 r = a - (n * q) 。 这意味着模运算结果与左操作数相同的符号相同(或零)。 对于 负数的a : a % n == -(-a % n), 几个例子:
int256(5) % int256(2) == int256(1)
int256(5) % int256(-2) == int256(1)
int256(-5) % int256(2) == int256(-1)
int256(-5) % int256(-2) == int256(-1)
幂运算
幂运算仅适用于无符号类型。 结果的类型总是等于基数的类型. 请注意类型足够大以能够容纳幂运算的结果,要么发生潜在的assert异常或者使用截断模式。
x**3 的例子,表达式 xxx 也许更便宜。 在任何情况下,都建议进行 gas 消耗测试和使用优化器。
定长浮点型
Solidity 还没有完全支持定长浮点型。可以声明定长浮点型的变量,但不能给它们赋值或把它们赋值给其他变量。。
地址类型 Address
地址类型有两种形式,他们大致相同:
address:保存一个20字节的值(以太坊地址的大小)。address payable :可支付地址,与 address 相同,不过有成员函数 transfer 和 send 。
这种区别背后的思想是 address payable 可以向其发送以太币,而不能先一个普通的 address 发送以太币,例如,它可能是一个智能合约地址,并且不支持接收以太币。
类型转换:
允许从 address payable 到 address 的隐式转换,而从 address 到 address payable 必须显示的转换, 通过 payable(
) 进行转换。地址类型成员变量
balance 和 transfer 成员
可以使用 balance 属性来查询一个地址的余额, 也可以使用 transfer 函数向一个可支付地址(payable address)发送 以太币 (以 wei 为单位):
address x = 0x123;
address myAddress = this;
if (x.balance < 10 && myAddress.balance >= 10) x.transfer(10);
如果当前合约的余额不够多,则 transfer 函数会执行失败,或者如果以太转移被接收帐户拒绝, transfer 函数同样会失败而进行回退。
如果 x 是一个合约地址,它的代码(更具体来说是, 如果有receive函数, 执行 receive 接收以太函数, 或者存在fallback函数,执行 Fallback 回退函数 函数)会跟 transfer 函数调用一起执行(这是 EVM 的一个特性,无法阻止)。 如果在执行过程中用光了 gas 或者因为任何原因执行失败,以太币 交易会被打回,当前的合约也会在终止的同时抛出异常。
send 成员
send 是 transfer 的低级版本。如果执行失败,当前的合约不会因为异常而终止,但 send 会返回 false。
在使用 send 的时候会有些风险:如果调用栈深度是 1024 会导致发送失败(这总是可以被调用者强制),如果接收者用光了 gas 也会导致发送失败。 所以为了保证 以太币 发送的安全,一定要检查 send 的返回值,使用 transfer 或者更好的办法: 使用接收者自己取回资金的模式
call, delegatecall 和 staticcall
为了与不符合 应用二进制接口 的合约交互,或者要更直接地控制编码,提供了函数 call,delegatecall 和 staticcall 。 它们都带有一个 bytes memory 参数和返回执行成功状态(bool)和数据(bytes memory)。
函数 abi.encode,abi.encodePacked,abi.encodeWithSelector 和 abi.encodeWithSignature 可用于编码结构化数据。
bytes memory payload = abi.encodeWithSignature("register(string)", "MyName");
(bool success, bytes memory returnData) = address(nameReg).call(payload);
require(success);
可以使用 gas 修改器 调整提供的 gas 数量:
address(nameReg).call{gas: 1000000}(abi.encodeWithSignature("register(string)", "MyName"));
delegatecall 的目的是使用另一个合约中的库代码。 用户必须确保两个合约中的存储结构都适合委托调用 (delegatecall)。
从以太坊拜占庭(byzantium)版本开始 提供了 staticcall ,它与 call 基本相同,但如果被调用的函数以任何方式修改状态变量,都将回退。
所有三个函数 call , delegatecall 和 staticcall 都是非常低级的函数,应该只把它们当作 最后一招 来使用,因为它们破坏了 Solidity 的类型安全性。
所有三种方法都提供 gas 选项,而 value 选项仅 call 支持 。
code 和 codehash 成员
你可以查询任何智能合约的部署代码。使用 .code 来获取EVM的字节码,其返回 bytes memory ,值可能是空。 使用 .codehash 获得该代码的 Keccak-256哈希值 (为 bytes32 )。注意, addr.codehash 比使用 keccak256(addr.code) 更便宜。
所有合约都可以转换为 address 类型,因此可以使用 address(this).balance 查询当前合约的余额。
合约类型
每一个 contract 定义都有他自己的类型。
您可以隐式地将合约转换为从他们继承的合约。 合约可以显式转换为 address 类型。
只有当合约具有 接收receive函数 或 payable 回退函数时,才能显式和 address payable 类型相互转换 转换仍然使用 address(x) 执行, 如果合约类型没有接收或payable 回退功能,则可以使用 payable(address(x)) 转换为 address payable 。
合约不支持任何运算符。
合约类型的成员是合约的外部函数及 public 的 状态变量。
对于合约 C 可以使用 type© 获取合约的类型信息
定长字节数组
关键字有:bytes1, bytes2, bytes3, …, bytes32。
运算符:
比较运算符: <=, <, ==, !=, >=, > (返回布尔型)
位运算符: &, |, ^ (按位异或), ~ (按位取反)
移位运算符: << (左移位), >> (右移位)
索引访问:如果 x 是 bytesI 类型,那么 x[k] (其中 0 <= k < I)返回第 k 个字节(只读)。
该类型可以和作为右操作数的无符号整数类型进行移位运算(但返回结果的类型和左操作数类型相同),右操作数表示需要移动的位数。 进行有符号整数位移运算会引发运行时异常
变长字节数组
bytes: 变长字节数组,参见 数组。它并不是值类型。
string: 变长 UTF-8 编码字符串类型,参见 数组。并不是值类型。
地址字面常量
比如像 0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF 这样的通过了地址校验和测试的十六进制字面常量会作为 address 类型。 而没有通过校验测试, 长度在 39 到 41 个数字之间的十六进制字面常量,会产生一个错误,您可以在零前面添加(对于整数类型)或在零后面添加(对于bytesNN类型)以消除错误。
字符串字面常量及类型
字符串字面常量是指由双引号或单引号引起来的字符串( “foo” 或者 ‘bar’)。 它们也可以分为多个连续的部分( “foo” “bar” 等效于 “foobar”),这在处理长字符串时很有用。 不像在 C 语言中那样带有结束符; “foo” 相当于 3 个字节而不是 4 个。 和整数字面常量一样,字符串字面常量的类型也可以发生改变,
但它们可以隐式地转换成 bytes1,……, bytes32,如果合适的话,还可以转换成 bytes 以及 string。
例如: bytes32 samevar = “stringliteral” 字符串字面常量在赋值给 bytes32 时被解释为原始的字节形式。
字符串字面常量支持下面的转义字符:
\<newline> (转义实际换行)
\\ (反斜杠)
\' (单引号)
\" (双引号)
\b (退格)
\f (换页)
\n (换行符)
\r (回车)
\t (标签 tab)
\v (垂直标签)
\xNN (十六进制转义,见下文)
\uNNNN (unicode 转义,见下文)
Unicode 字面常量
常规字符串文字只能包含ASCII,而Unicode文字(以关键字unicode为前缀)可以包含任何有效的UTF-8序列。 它们还支持与转义序列完全相同的字符作为常规字符串文字。
string memory a = unicode"Hello 😃";
十六进制字面常量
十六进制字面常量以关键字 hex 打头,后面紧跟着用单引号或双引号引起来的字符串(例如,hex"001122FF" )。 字符串的内容必须是一个十六进制的字符串,它们的值将使用二进制表示。
枚举类型
枚举是在Solidity中创建用户定义类型的一种方法。 它们是显示所有整型相互转换,但不允许隐式转换。 从整型显式转换枚举,会在运行时检查整数时候在枚举范围内,否则会导致异常( Panic异常 )。 枚举需要至少一个成员,默认值是第一个成员,枚举不能多于 256 个成员。
数据表示与C中的枚举相同:选项从“0”开始的无符号整数值表示。
使用 type(NameOfEnum).min 和 type(NameOfEnum).max 你可以得到给定枚举的最小值和最大值。
用户定义的值类型
一个用户定义的值类型允许在一个基本的值类型上创建一个零成本的抽象。 这类似于一个别名,但有更严格的类型要求。
用户定义值类型使用 type C is V 来定义,其中 C 是新引入的类型的名称, V 必须是内置的值类型(”底层类型”)。 函数 C.wrap 被用来从底层类型转换到自定义类型。同样地,函数函数 C.unwrap 用于从自定义类型转换到底层类型。
type UFixed256x18 is uint256;
函数类型
函数类型是一种表示函数的类型。可以将一个函数赋值给另一个函数类型的变量,也可以将一个函数作为参数进行传递,还能在函数调用中返回函数类型变量。 函数类型有两类: - 内部(internal) 函数类型 - 外部(external) 函数类型。
内部函数只能在当前合约内被调用(更具体来说,在当前代码块内,包括内部库函数和继承的函数中),因为它们不能在当前合约上下文的外部被执行。 调用一个内部函数是通过跳转到它的入口标签来实现的,就像在当前合约的内部调用一个函数。
外部函数由一个地址和一个函数签名组成,可以通过外部函数调用传递或者返回。
function () {internal|external} [pure|constant|view|payable] [returns ()]
与参数类型相反,返回类型不能为空 —— 如果函数类型不需要返回,则需要删除整个 returns () 部分。
函数类型默认是内部函数,因此不需要声明 internal 关键字。
类型转换:
函数类型 A 可以隐式转换为函数类型 B 当且仅当: 它们的参数类型相同,返回类型相同,它们的内部/外部属性是相同的,并且 A 的状态可变性比 B 的状态可变性更具限制性,比如:
pure 函数可以转换为 view 和 non-payable 函数
view 函数可以转换为 non-payable 函数
payable 函数可以转换为 non-payable 函数
public(或 external)函数都有下面的成员:
.address 返回函数的合约地址。
.selector 返回 ABI 函数选择器
引用类型
引用类型可以通过多个不同的名称修改它的值
引用类型包括结构,数组和映射,如果使用引用类型,则必须明确指明数据存储哪种类型的位置(空间)里:
内存 即数据在内存中,因此数据仅在其生命周期内(函数调用期间)有效。不能用于外部调用。
存储 状态变量保存的位置,只要合约存在就一直存储.
调用数据 用来保存函数参数的特殊数据位置,是一个只读位置。
数据位置
所有的引用类型,如 数组 和 结构体 类型,都有一个额外注解 数据位置 ,来说明数据存储位置。
有三种位置: 内存memory 、 存储storage 以及 调用数据 calldata。
调用数据 是不可修改的、非持久的函数参数存储区域,效果大多类似 内存 。 主要用于外部函数的参数,但也可用于其他变量。
数据位置与赋值行为
在 存储 和 内存 之间两两赋值(或者从 调用数据 赋值 ),都会创建一份独立的拷贝。
从 内存 到 内存 的赋值只创建引用, 这意味着更改内存变量,其他引用相同数据的所有其他内存变量的值也会跟着改变。
从 存储 到本地存储变量的赋值也只分配一个引用。
其他的向 存储 的赋值,总是进行拷贝。 这种情况的示例如对状态变量或 存储 的结构体类型的局部变量成员的赋值,即使局部变量本身是一个引用,也会进行一份拷贝(译者注:查看下面 ArrayContract 合约 更容易理解)。
数组
数组可以在声明时指定长度,也可以动态调整大小(长度)。
一个元素类型为 T,固定长度为 k 的数组可以声明为 T[k],而动态数组声明为 T[]。
可以使用 .push() 方法在末尾追加一个新元素,其中 .push() 追加一个零初始化的元素并返回对它的引用。
bytes 和 string 也是数组
bytes 和 string 类型的变量是特殊的数组。
bytes 类似于 bytes1[],但它在 调用数据 和 内存 中会被“紧打包”(译者注:将元素连续地存在一起,不会按每 32 字节一单元的方式来存放)。
string 与 bytes 相同,但不允许用长度或索引来访问。
函数 bytes.concat 和 string.concat
可以使用 string.concat 连接任意数量的 string 字符串。 该函数返回一个 string memory ,包含所有参数的内容,无填充方式拼接在一起。 如果你想使用不能隐式转换为 string 的其他类型作为参数,你需要先把它们转换为 string。
同样, bytes.concat 函数可以连接任意数量的 bytes 或 bytes1 … bytes32 值。 该函数返回一个 bytes memory ,包含所有参数的内容,无填充方式拼接在一起。
创建内存数组
可使用 new 关键字在 内存 中基于运行时创建动态长度数组。 与 存储 数组相反的是,你 不能 通过修改成员变量 .push 改变 内存 数组的大小。
必须提前计算所需的大小或者创建一个新的内存数组并复制每个元素。
在Solidity中的所有变量,新分配的数组元素总是以 默认值 初始化。
pragma solidity >=0.4.16 <0.9.0;contract TX {function f(uint len) public pure {uint[] memory a = new uint[](7);bytes memory b = new bytes(len);assert(a.length == 7);assert(b.length == len);a[6] = 8;}
}
数组常量
它总是一个静态大小的内存数组,其长度为表达式的数量。
数组的基本类型是列表上的第一个表达式的类型。
[1, 2, 3] 的类型是 uint8[3] memory。 因为每个常量的类型都是 uint8 ,如果你希望结果是 uint[3] memory 类型,你需要将第一个元素转换为 uint 。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;contract LBC {function f() public pure {g([uint(1), 2, 3]);}function g(uint[3] memory) public pure {// ...}
}
数组常量 [1, -1] 是无效的,因为第一个表达式类型是 uint8 而第二个类似是 int8 他们不可以隐式的相互转换。 为了确保可以运行,你是可以使用例如: [int8(1), -1] 。
如果要初始化动态长度的数组,则必须显示给各个元素赋值:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;contract C {function f() public pure {uint[] memory x = new uint[](3);x[0] = 1;x[1] = 3;x[2] = 4;}
}
数组成员
- length: 数组有 length 成员变量表示当前数组的长度。 一经创建,内存 数组的大小就是固定的(但却是动态的,也就是说,它可以根据运行时的参数创建)。
- push():动态的 存储 数组以及 bytes 类型( string 类型不可以)都有一个 push() 的成员函数,它用来添加新的零初始化元素到数组末尾,并返回元素引用. 因此可以这样: x.push().t = 2 或 x.push() = b.
- push(x):动态的 存储 数组以及 bytes 类型( string 类型不可以)都有一个 push(x) 的成员函数,用来在数组末尾添加一个给定的元素,这个函数没有返回值.
- pop():变长的 存储 数组以及 bytes 类型( string 类型不可以)都有一个 pop() 的成员函数, 它用来从数组末尾删除元素。 同样的会在移除的元素上隐含调用 delete ,这个函数没有返回值。
对存储数组元素的悬空引用
当使用存储数组时,你需要注意避免悬空的引用。 悬空引用是指一个指向不再存在的东西的引用,或者是对象被移除而没有更新引用。 例如,如果你将一个数组元素的引用存储在一个局部的引用中,然后从包含数组中 .pop() 出来,就会发生悬空引用。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;contract C {uint[][] s;function f() public {// 保存s最后一个元素的指向。uint[] storage ptr = s[s.length - 1];// 移除 s 最后一个元素s.pop();// 向不再属于数组的元素写入数据ptr.push(0x42);// 现在添加元素到 ``s`` 不会添加一个空元素, 而是数组长度为 1, ``0x42`` 作为其元素。s.push();assert(s[s.length - 1][0] == 0x42);}
}
ptr.push(0x42) 写入 不 会回退, 尽管 ptr 不再指向有效的 s 元素, 由于编译器假定未使用的存储空间总是被清零的, 所以随后的 s.push() 不会明确地将零写入存储空间。 所以在 push() 之后, s 的最后一个元素的长度是 1 ,并且包含 0x42 作为第一个元素。 0x42
作为其第一个元素。
数组切片
数组切片是数组连续部分的视图,用法如:x[start:end] , start 和 end 是 uint256 类型(或结果为 uint256 的表达式)。 x[start:end] 的第一个元素是 x[start] , 最后一个元素是 x[end - 1] 。
如果 start 比 end 大或者 end 比数组长度还大,将会抛出异常。
start 和 end 都可以是可选的: start 默认是 0, 而 end 默认是数组长度。
结构体
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.6.0 <0.9.0;// 定义的新类型包含两个属性。
// 在合约外部声明结构体可以使其被多个合约共享。 在这里,这并不是真正需要的。
struct Funder {address addr;uint amount;
}contract CrowdFunding {// 也可以在合约内部定义结构体,这使得它们仅在此合约和衍生合约中可见。struct Campaign {address beneficiary;uint fundingGoal;uint numFunders;uint amount;mapping (uint => Funder) funders;}
}
映射
映射类型在声明时的形式为 mapping(KeyType => ValueType)。 其中 KeyType 可以是任何基本类型,即可以是任何的内建类型, bytes 和 string 或合约类型、枚举类型。 而其他用户定义的类型或复杂的类型如:映射、结构体、即除 bytes 和 string 之外的数组类型是不可以作为 KeyType 的类型的。
可迭代映射
映射本身是无法遍历的,即无法枚举所有的键。不过,可以在它们之上实现一个数据结构来进行迭代。 例如,以下代码实现了 IterableMapping 库,然后 User 合约可以添加数据, sum 函数迭代求和所有值。
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.8;struct IndexValue { uint keyIndex; uint value; }
struct KeyFlag { uint key; bool deleted; }struct itmap {mapping(uint => IndexValue) data;KeyFlag[] keys;uint size;
}type Iterator is uint;library IterableMapping {function insert(itmap storage self, uint key, uint value) internal returns (bool replaced) {uint keyIndex = self.data[key].keyIndex;self.data[key].value = value;if (keyIndex > 0)return true;else {keyIndex = self.keys.length;self.keys.push();self.data[key].keyIndex = keyIndex + 1;self.keys[keyIndex].key = key;self.size++;return false;}}function remove(itmap storage self, uint key) internal returns (bool success) {uint keyIndex = self.data[key].keyIndex;if (keyIndex == 0)return false;delete self.data[key];self.keys[keyIndex - 1].deleted = true;self.size --;}function contains(itmap storage self, uint key) internal view returns (bool) {return self.data[key].keyIndex > 0;}function iterateStart(itmap storage self) internal view returns (Iterator) {return iteratorSkipDeleted(self, 0);}function iterateValid(itmap storage self, Iterator iterator) internal view returns (bool) {return Iterator.unwrap(iterator) < self.keys.length;}function iterateNext(itmap storage self, Iterator iterator) internal view returns (Iterator) {return iteratorSkipDeleted(self, Iterator.unwrap(iterator) + 1);}function iterateGet(itmap storage self, Iterator iterator) internal view returns (uint key, uint value) {uint keyIndex = Iterator.unwrap(iterator);key = self.keys[keyIndex].key;value = self.data[key].value;}function iteratorSkipDeleted(itmap storage self, uint keyIndex) private view returns (Iterator) {while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)keyIndex++;return Iterator.wrap(keyIndex);}
}// 如何使用
contract User {// Just a struct holding our data.itmap data;// Apply library functions to the data type.using IterableMapping for itmap;// Insert somethingfunction insert(uint k, uint v) public returns (uint size) {// This calls IterableMapping.insert(data, k, v)data.insert(k, v);// We can still access members of the struct,// but we should take care not to mess with them.return data.size;}// Computes the sum of all stored data.function sum() public view returns (uint s) {for (Iterator i = data.iterateStart();data.iterateValid(i);i = data.iterateNext(i)) {(, uint value) = data.iterateGet(i);s += value;}}
}
三元运算符
三元运算符是一个表达是形式: <expression> ? <trueExpression> : <falseExpression>
复合操作及自增自减操作
如果 a 是一个 LValue(即一个变量或者其它可以被赋值的东西),以下运算符都可以使用简写:
a += e 等同于 a = a + e。其它运算符如 -=, *=, /=, %=, |=, &= , ^= , <<= 和 >>= 都是如此定义的。 a++ 和 a-- 分别等同于 a += 1 和 a -= 1,但表达式本身的值等于 a 在计算之前的值。 与之相反, --a 和 ++a 虽然最终 a 的结果与之前的表达式相同,但表达式的返回值是计算之后的值。
delete
delete a 的结果是将 a 类型初始值赋值给 a。即对于整型变量来说,相当于 a = 0,delete 也适用于数组,对于动态数组来说,是将重置为数组长度为0的数组,而对于静态数组来说,是将数组中的所有元素重置为初始值。对数组而言, delete a[x] 仅删除数组索引 x 处的元素,其他的元素和长度不变,这以为着数组中留出了一个空位。如果打算删除项,映射可能是更好的选择。
如果对象 a 是结构体,则将结构体中的所有属性(成员)重置。
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;contract DeleteLBC {uint data;uint[] dataArray;function f() public {uint x = data;delete x; // 将 x 设为 0,并不影响数据delete data; // 将 data 设为 0,并不影响 x,因为它仍然有个副本uint[] storage y = dataArray;delete dataArray;// 将 dataArray.length 设为 0,但由于 uint[] 是一个复杂的对象,y 也将受到影响,// 因为它是一个存储位置是 storage 的对象的别名。// 另一方面:"delete y" 是非法的,引用了 storage 对象的局部变量只能由已有的 storage 对象赋值。assert(y.length == 0);}
}
显式转换
如果某些情况下编译器不支持隐式转换,但是你很清楚你要做的结果,这种情况可以考虑显式转换。 注意这可能会发生一些无法预料的后果,因此一定要进行测试,确保结果是你想要的! 下面的示例是将一个 int8 类型的负数转换成 uint:
int8 y = -3;
uint x = uint(y);
这段代码的最后, x 的值将是 0xfffff…fd (64 个 16 进制字符),因为这是 -3 的 256 位补码形式。
相关文章:
Solidity-3-类型
Solidity 是一种静态类型语言,这意味着每个变量(状态变量和局部变量)都需要在编译时指定变量的类型。 “undefined”或“null”值的概念在Solidity中不存在,但是新声明的变量总是有一个 默认值 ,具体的默认值跟类型相…...

【mask转json】文件互转
mask图像转json文件 当只有mask图像时,可使用下面代码得到json文件 import cv2 import os import json import sysdef func(file:str) -> dict:png cv2.imread(file)gray cv2.cvtColor(png, cv2.COLOR_BGR2GRAY)_, binary cv2.threshold(gray,10,255,cv2.TH…...

华清远见嵌入式学习——ARM——作业1
要求: 代码: mov r0,#0 用于加mov r1,#1 初始值mov r2,#101 终止值loop: cmp r1,r2addne r0,r0,r1addne r1,r1,#1bne loop 效果:...

如何在公网环境使用固定域名远程访问内网BUG管理系统协同办公
文章目录 前言1. 本地安装配置BUG管理系统2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射本地服务3. 测试公网远程访问4. 配置固定二级子域名4.1 保留一个二级子域名5.1 配置二级子域名6. 使用固定二级子域名远程 前言 BUG管理软件,作为软件测试工程师的必备工具之一。在…...
k8s pod网络排查教程
1、背景 背景:在日常的k8s运维中,经常会遇到pod之间网络无法访问,域名无法解释的情况。且容器中网络排查命令不全,导致无法准确定位问题。 2、nsenter介绍 #Centos 下载方式 $ yum install util-linux -ynsenter 是一个 Linux …...
Apollo Planning——换道:LANE_CHANGE_DECIDER
LaneChangeDecider 是lanefollow 场景下,所调用的第一个task,它的作用主要有两点:判断当前是否进行变道,以及变道的状态,并将结果存在变量lane_change_status中;变道过程中将目标车道的reference line放置到…...

Python 爬虫之简单的爬虫(三)
爬取动态网页(上) 文章目录 爬取动态网页(上)前言一、大致内容二、基本思路三、代码编写1.引入库2.加载网页数据3.获取指定数据 总结 前言 之前的两篇写的是爬取静态网页的内容,比较简单。接下来呢给大家讲一下如何去…...

为突发事件提供高现势性数据支撑!大势智慧助力中山市2023应急测绘保障演练举行
12月14日,2023年度中山市应急测绘保障演练在中山树木园举行,市自然资源局总工程师邓宇文出席本次演练活动。来自全市自然资源、应急管理部门和部分测绘单位的近70人现场观摩了演练。本次演练由中山市自然资源局主办、中山市自然资源信息中心承办…...

图片速览 OOD用于零样本 OOD 检测的 CLIPN:教 CLIP 说不
PAPERCODEhttps://arxiv.org/pdf/2308.12213v2.pdfhttps://github.com/xmed-lab/clipn 文章创新 以往由CLIP驱动的零样本OOD检测方法,只需要ID的类名,受到的关注较少。 本文提出了一种新的方法,即CLIP说“不”(CLIPN)…...

a16z:加密行业2024趋势“无缝用户体验”
近日,知名加密投资机构a16z发布了“Big ideas 2024”,列出了加密行业在 2024 年几个具备趋势的“大想法”,其中 Seamless UX(无缝用户体验)赫然在列。 从最为直观的理解上,Seamless UX 是在强调用户在使用产…...

C# WPF上位机开发(属性页面的设计)
【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 在软件开发中,属性或者参数设置是很重要的一个部分。这个时候如果不想通过动态添加控件的方法来处理的话,那么可以通过tab控…...

macOS 安装 oh-my-zsh 后 node 报错 command not found : node
最近为了让终端中显示 git 分支的名称,安装了 oh-my-zsh ,安装之后呢,我原先安装的 Volta、 node 都没法用了,报错如下: 这时候粗略判断应该是系统变量出了问题,oh-my-zsh 的变量文件是 ~/.zshrc࿰…...
AI 绘画 | Stable Diffusion 视频数字人
前言 本篇文章教会你如何利用Stable Diffusion WEB UI,使用一个人物图片转换成为一个口播视频。本篇内容的教程以WINDOWS系统为例,教你如何安装使用。 先看视频效果 彭于晏图片生成口播视频 安装 首先需要在windows电脑上安装ffmpeg,按照本教程《在 Windows PC 上轻松下载并…...

《代码随想录》--二叉树(一)
《代码随想录》--二叉树 第一部分 1、二叉树的递归遍历2、二叉树的迭代遍历3、统一风格的迭代遍历代码4、二叉树的层序遍历226.翻转二叉树 1、二叉树的递归遍历 前序遍历 中序遍历 后序遍历 代码 前序遍历 class Solution {public List<Integer> preorderTraversal(T…...
shell编程-数组与运算符详解(超详细)
文章目录 前言一、 Shell数组1. 声明和初始化数组2. 访问数组元素3. 数组长度4. 遍历数组5. 修改数组元素6. 删除数组元素7. 示例 二、Shell运算符1. 算术运算符1.1 加法运算符 ()1.2 减法运算符 (-)1.3 乘法运算符 (*)1.4 除法运算符 (/)1.5 取余运算符 (%) 2. 关系运算符2.1 …...

Vim入门
Vim使用入门 1.Vim编辑器的三种常用模式 一般模式:刚打开文件是它,从编辑模式按“ESC”退回的模式也是它。可以执行各种编辑操作,如移动光标、复制、粘贴、删除、查找替换等 ; 编辑模式:在一般模式下按下 i、I、a、A、o、O 等键…...

动态加载库
no_mangle 不要改标识符 首先是认识这个标注:mangle,英文的含义“撕裂、碾压”。我第一次把这个单次误以为是manage,说实话两个单词还挺像的。 RUS中函数或静态变量使用#[no_mangle]这个标注属性后,编译器就不会修改它们的名字了…...
React中渲染html结构---dangerouslySetInnerHTML
dangerouslySetInnerHTML胡子{}语法绑定的内容全部作为普通文本渲染,渲染html结构基于---dangerouslySetInnerHTMLdangerouslySetInnerHTML是React标签的一个属性,类似于vue的v-html有2个{{}},第一个{}代表jsx语法开始,第二个是代表dangerous…...

计网02-计算机网络参考模型
一、OSI七层参考模型 1、分层的思想 分层模型用于网络协议的设计方法,本质是将网络节点间复杂的通信问题分成若干简单的问题逐一解决,通过网络的层次去找问题,将复杂问题简单化。 2、OSI参考模型 由于早期计算机厂商使用的是私有的网络模…...
模块测试:确保软件质量的关键步骤
引言: 在软件开发过程中,模块测试是确保软件质量的关键环节。通过模块化的设计和测试方法,可以提高开发效率、降低错误率,并最终提供稳定可靠的软件产品。本文将介绍模块测试的概念、重要性以及实施步骤,帮助读者了解如…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...

React第五十七节 Router中RouterProvider使用详解及注意事项
前言 在 React Router v6.4 中,RouterProvider 是一个核心组件,用于提供基于数据路由(data routers)的新型路由方案。 它替代了传统的 <BrowserRouter>,支持更强大的数据加载和操作功能(如 loader 和…...
逻辑回归:给不确定性划界的分类大师
想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
Rust 异步编程
Rust 异步编程 引言 Rust 是一种系统编程语言,以其高性能、安全性以及零成本抽象而著称。在多核处理器成为主流的今天,异步编程成为了一种提高应用性能、优化资源利用的有效手段。本文将深入探讨 Rust 异步编程的核心概念、常用库以及最佳实践。 异步编程基础 什么是异步…...

什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...

【网络安全】开源系统getshell漏洞挖掘
审计过程: 在入口文件admin/index.php中: 用户可以通过m,c,a等参数控制加载的文件和方法,在app/system/entrance.php中存在重点代码: 当M_TYPE system并且M_MODULE include时,会设置常量PATH_OWN_FILE为PATH_APP.M_T…...