当前位置: 首页 > news >正文

【区块链安全 | 第十七篇】类型之引用类型(一)

文章目录

  • 引用类型
    • 数据存储位置
      • 分配行为
    • 数组
      • 特殊数组:bytes 和 string 类型
      • bytes.concat 和 string.concat 的功能
      • 分配 memory 数组
      • 数组字面量(Array Literals)
      • 二维数组字面量
      • 数组成员(Array Members)
      • 悬空引用(Dangling References)到存储数组元素

在这里插入图片描述

引用类型

引用类型的值可以通过多个不同的名称进行修改。这与值类型形成对比,在值类型中,每当使用一个值类型的变量时,都会获得一个独立的副本。因此,引用类型比值类型需要更谨慎地处理。目前,引用类型包括结构体(structs)、数组(arrays)和映射(mappings)。如果使用引用类型,必须明确提供数据存储的位置:memory(其生命周期仅限于外部函数调用期间)、storage(存储状态变量的位置,其生命周期与合约的生命周期一致)或 calldata(一个特殊的数据存储区域,其中包含函数参数)。

如果赋值或类型转换导致数据存储位置发生变化,则会自动触发复制操作,而在同一数据存储位置内部进行赋值时,仅在某些情况下会触发复制(对于 storage 类型)。

数据存储位置

每个引用类型都有一个额外的注释,即“数据存储位置”,用于指明其存储位置。数据存储位置包括 memory、storage 和 calldata。calldata 是一个不可修改、不可持久化的区域,其中存储了函数参数,其行为大多数情况下类似于 memory。

注意
transient 作为引用类型的数据存储位置目前尚不受支持。 如果可能,尽量使用 calldata 作为数据存储位置,因为这样可以避免复制,同时确保数据不可修改。具有 calldata 存储位置的数组和结构体可以作为函数的返回值,但无法直接分配此类类型。

注意
在函数体中声明或作为返回参数的 calldata 位置的数组和结构体必须在使用或返回之前进行赋值。在某些使用非平凡控制流的情况下,编译器可能无法正确检测初始化。在这些情况下,一个常见的解决方法是先将受影响的变量赋值给自身,然后再进行正确的初始化。

注意
在 0.6.9 版本之前,外部函数的引用类型参数数据存储位置仅限于 calldata,公共函数为 memory,内部和私有函数则可以是 memory 或 storage。而现在,所有可见性(visibility)的函数都允许使用 memory 和 calldata。

注意
构造函数的参数不能使用 calldata 作为数据存储位置。

注意
在 0.5.0 版本之前,数据存储位置可以省略,并且会根据变量类型、函数类型等默认使用不同的位置。但从 0.5.0 版本开始,所有复杂类型都必须显式指定数据存储位置。

分配行为

数据存储位置不仅与数据的持久性相关,还会影响赋值的语义:

  • 在 storage 和 memory(或 calldata)之间的赋值总是会创建一个独立的副本。
  • 在 memory 之间的赋值仅创建引用。这意味着对一个 memory 变量的修改会影响所有引用同一数据的 memory 变量。
  • 从 storage 赋值给本地 storage 变量时,也只是赋值引用。
  • 其他所有对 storage 的赋值都会进行复制。例如,对状态变量的赋值,或对 storage 结构体类型的本地变量的成员赋值,即使本地变量本身只是一个引用。

举个例子:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;contract C {// x 的数据存储位置是 storage。// 这是唯一可以省略数据存储位置的地方。uint[] x;// memoryArray 的数据存储位置是 memory。function f(uint[] memory memoryArray) public {x = memoryArray; // 可以执行,会复制整个数组到 storageuint[] storage y = x; // 可以执行,赋值的是指针,y 的数据存储位置是 storagey[7]; // 合法,返回第 8 个元素y.pop(); // 合法,通过 y 修改 xdelete x; // 合法,清空数组,同时影响 y// 以下操作无法执行,因为它需要在 storage 中创建一个新的临时/匿名数组,// 但 storage 是静态分配的:// y = memoryArray;// 同样,“delete y” 也是不合法的,因为对指向 storage 对象的本地变量的赋值// 只能来自已有的 storage 对象。// 它会“重置”指针,但没有合理的位置可以指向。// 更多细节请参考“delete” 运算符的文档。// delete y;g(x); // 调用 g,传递 x 的引用h(x); // 调用 h,创建一个独立的临时副本存储在 memory}function g(uint[] storage) internal pure {}function h(uint[] memory) public pure {}
}

数组

数组可以是编译时固定大小的,也可以是动态大小的。

固定大小为 k,元素类型为 T 的数组写作 T[k],而动态大小的数组写作 T[]

例如,一个包含 5 个 uint 动态数组的数组写作 uint[][5]。该表示法与某些其他语言相反。在 Solidity 中,X[3] 始终是一个包含 3 个 X 类型元素的数组,即使 X 本身是一个数组。而在 C 语言等其他语言中,这种情况可能不同。

索引从 0 开始,访问顺序与声明顺序相反。

例如,如果有一个变量 uint[][5] memory x,要访问第三个动态数组中的第七个 uint,应使用 x[2][6],而访问第三个动态数组则使用 x[2]。同样,如果有一个数组 T[5] a,其中 T 也可以是数组,则 a[2] 的类型始终为 T

数组元素可以是任何类型,包括 mappingstruct。但一般的类型限制仍然适用,例如 mapping 只能存储在 storage 数据位置,并且 public 可见性的函数参数必须是 ABI 类型。

可以将状态变量数组标记为 public,Solidity 会自动为其创建一个 getter。数值索引会成为 getter 的必填参数。

访问超出数组末尾的索引会导致断言失败(Assertion Failure)。

动态大小数组的 push()push(value) 方法可用于在数组末尾追加新元素:

  • .push() 追加一个零初始化的元素,并返回对该元素的引用。
  • .push(value) 追加指定值的元素。

注意
动态数组只能在 storage 中调整大小。在 memory 中,此类数组可以是任意大小,但一旦分配后,其大小就无法更改。

特殊数组:bytes 和 string 类型

bytesstring 类型是特殊的数组。bytes 类型类似于 bytes1[],但在 calldatamemory 中会进行紧密打包(packed)。string 等同于 bytes,但不允许使用长度或索引访问。

Solidity 不提供字符串操作函数,但可以使用第三方字符串库。也可以通过 keccak256(abi.encodePacked(s1)) == keccak256(abi.encodePacked(s2)) 来比较两个字符串的哈希值,或者使用 string.concat(s1, s2) 来连接两个字符串。

应优先使用 bytes 而非 bytes1[],因为 bytes 的开销更小。使用 bytes1[]memory 中存储时,每个元素之间会填充 31 个字节的填充数据(padding),而 storage 中由于紧密打包不存在填充。通常,bytes 适用于任意长度的原始字节数据,而 string 适用于任意长度的字符串(UTF-8 编码)。如果可以限制长度,应使用 bytes1bytes32 这样的值类型,因为它们的成本更低。

注意
如果想要访问字符串 s 的字节表示,可使用 bytes(s).length 获取长度,或 bytes(s)[7] = 'x'; 进行修改。但这样访问的是 UTF-8 编码的底层字节,而不是独立的字符。

bytes.concat 和 string.concat 的功能

string.concat 可用于连接任意数量的 string 值。该函数返回一个 memory 位置的 string,其中包含所有参数的内容,不包含填充(padding)。如果参数的类型不能隐式转换为 string,需要先进行转换。

类似地,bytes.concat 可用于连接任意数量的 bytesbytes1bytes32 类型的值。该函数返回一个 memory 位置的 bytes,其中包含所有参数的内容,不包含填充。如果参数是 string 或其他不能隐式转换为 bytes 的类型,需要先转换为 bytesbytes1bytes32

示例如下:

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.12;contract C {string s = "Storage";function f(bytes calldata bc, string memory sm, bytes16 b) public view {// 连接多个字符串string memory concatString = string.concat(s, string(bc), "Literal", sm);assert((bytes(s).length + bc.length + 7 + bytes(sm).length) == bytes(concatString).length);// 连接多个字节数组bytes memory concatBytes = bytes.concat(bytes(s), bc, bc[:2], "Literal", bytes(sm), b);assert((bytes(s).length + bc.length + 2 + 7 + bytes(sm).length + b.length) == concatBytes.length);}
}

如果调用 string.concat()bytes.concat() 时不传递任何参数,则返回一个空数组。

分配 memory 数组

可以使用 new 关键字创建动态长度的 memory 数组。与 storage 数组不同,memory 数组无法调整大小(例如,push() 方法不可用)。因此,必须在创建时确定所需大小,或创建一个新数组并复制所有元素。

与 Solidity 中的所有变量一样,新分配的数组元素总是初始化为默认值。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;contract C {function f(uint len) public pure {uint ; // 创建一个长度为 7 的 uint 数组bytes memory b = new bytes(len); // 创建一个长度为 len 的 bytes 数组assert(a.length == 7);assert(b.length == len);a[6] = 8; // 赋值}
}

数组字面量(Array Literals)

数组字面量是用 [...] 包裹的一个或多个逗号分隔的表达式。例如:[1, a, f(3)]

数组字面量的类型遵循以下规则:

  • 它总是一个 静态大小的 memory 数组,其长度等于表达式的个数。
  • 其基本类型是列表中 第一个表达式 的类型,并且其他所有表达式都必须能隐式转换为该类型。如果无法转换,则会报错。
  • 仅仅存在一个可以转换的共同类型是不够的,必须有一个元素的原始类型就是该类型。

示例:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;contract C {function f() public pure {g([uint(1), 2, 3]); // 显式转换第一个元素的类型}function g(uint[3] memory) public pure {// ...}
}

在上例中,[1, 2, 3] 的类型是 uint8[3] memory,因为 123 默认都是 uint8 类型。如果想让其成为 uint[3] memory,需要将第一个元素转换为 uint

无效示例

[1, -1] // 无效,1 是 uint8,-1 是 int8,无法隐式转换

有效示例

[int8(1), -1] // 有效,所有元素都是 int8

二维数组字面量

不同类型的定长 memory 数组之间无法相互转换,即使它们的基本类型可以转换。因此,在使用二维数组字面量时,必须显式指定共同的基本类型。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;// 声明合约 C
contract C {/*** @dev 返回一个 4 行 2 列的 `uint24` 类型的二维静态数组* @return x 返回的数组 `uint24[2][4] memory`*/function f() public pure returns (uint24[2][4] memory) {// 定义一个 `uint24[2][4]` 类型的二维静态数组,并初始化uint24[2][4] memory x = [[uint24(0x1), 1],      // 第一行: 0x1(16 进制)转换为 uint24,第二个元素为 1[0xffffff, 2],         // 第二行: 0xffffff(最大 uint24 值),第二个元素为 2[uint24(0xff), 3],     // 第三行: 0xff(255),第二个元素为 3[uint24(0xffff), 4]    // 第四行: 0xffff(65535),第二个元素为 4];// 返回二维数组return x;}
}

如果没有显式指定 uint24,如下代码会 报错

uint[2][4] memory x = [[0x1, 1], [0xffffff, 2], [0xff, 3], [0xffff, 4]];

固定大小的 memory 数组不能赋值给动态大小的 memory 数组,示例如下:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.0 <0.9.0;// 这段代码无法编译
contract C {function f() public {uint[] memory x = [uint(1), 3, 4]; // 无法将 `uint[3] memory` 赋值给 `uint[] memory`}
}

未来可能会移除此限制,但由于 ABI 传递数组的方式,此限制目前仍然存在。

如果要初始化动态大小的数组,则必须分配各个元素:

// 使用 new 关键字创建动态数组并手动赋值
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.16 <0.9.0;contract C {function f() public pure {uint[] memory x = new uint[](3);x[0] = 1;x[1] = 3;x[2] = 4;}
}

数组成员(Array Members)

length:
数组有一个 length 成员,它包含数组的元素数量。
内存(memory)数组的长度在创建后是固定的(但在运行时可以动态确定)。

push():

  • 动态存储(storage)数组bytes(但不包括 string)有一个 push() 成员函数,
    你可以使用它在数组末尾追加一个 零初始化 的元素。
  • 它返回对新元素的引用,因此可以像 x.push().t = 2x.push() = b 这样使用。

push(x):

  • 动态存储(storage)数组bytes(但不包括 string)有一个 push(x) 成员函数,
    你可以使用它在数组末尾追加指定的元素。
  • 该函数不返回任何值。

pop():

  • 动态存储(storage)数组bytes(但不包括 string)有一个 pop() 成员函数,
    你可以使用它从数组末尾移除一个元素。
  • 这也会 隐式调用 delete 来清除被移除的元素。
  • 该函数不返回任何值。

注意
通过调用 push() 增加存储数组的长度具有 恒定的 gas 费用,因为存储在 Solidity 中默认会被初始化为零。

但通过 pop() 减少存储数组的长度的费用 取决于被移除元素的大小。如果被移除的元素是 数组,则会非常昂贵,因为它的删除行为类似于调用 delete 来显式清除所有被移除的元素。

注意
要在external(而不是(public))函数中使用数组的数组(arrays of arrays),你需要启用 ABI 编码器 v2(ABI coder v2)。

在Byzantium 之前的 EVM 版本,无法访问从函数调用返回的动态数组。如果你的函数返回动态数组,请确保你的 EVM 版本设置为Byzantium 或更高版本。

悬空引用(Dangling References)到存储数组元素

在操作存储数组时,需要小心避免悬空引用。悬空引用是指指向某个已经不存在或者已被移动但没有更新引用的元素的引用。

例如,悬空引用可能发生在您将一个数组元素的引用存储到一个局部变量中,然后对包含该元素的数组进行 .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 进行 push 操作时,不会添加一个空数组,// 而是会导致 s 的最后一个元素长度为 1,且其第一个元素是 0x42。s.push();assert(s[s.length - 1][0] == 0x42);}
}

在上述代码中,ptr.push(0x42) 不会回滚,尽管 ptr 已不再指向一个有效的 s 数组元素。因为编译器假设未使用的存储区域始终被零化,因此后续的 s.push() 操作不会显式地将零写入存储空间,导致 s 的最后一个元素在该 push() 后的长度为 1,并且第一个元素是 0x42。

需要注意的是,Solidity 不允许声明对值类型(如 uintbool 等)的存储引用。这类显式的悬空引用仅限于嵌套引用类型。但是,在使用复杂表达式进行元组赋值时,暂时也可能会产生悬空引用:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;contract C {uint[] s;uint[] t;constructor() {// 向存储数组中推送一些初始值。s.push(0x07);t.push(0x03);}function g() internal returns (uint[] storage) {s.pop();return t;}function f() public returns (uint[] memory) {// 以下代码首先会评估 `s.push()` 作为对新元素索引 1 的引用,// 然后调用 `g` 函数将此新元素弹出,导致左侧的元组元素成为悬空引用。// 尽管如此,赋值仍然会进行,并且会写入 `s` 数据区外。(s.push(), g()[0]) = (0x42, 0x17);// 随后对 `s` 进行 push 操作时,会暴露上一条语句写入的值,// 即函数结束时 `s` 的最后一个元素的值为 0x42。s.push();return s;}
}

在编写代码时,为了安全起见,最好每次赋值时只操作一次存储,并避免在赋值语句的左侧使用复杂表达式。

当操作字节数组(bytes array)元素的引用时,您需要特别小心,因为对字节数组执行 .push() 操作时,可能会导致存储布局从短格式转换为长格式。

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;// 这段代码会报告警告
contract C {bytes x = "012345678901234567890123456789";function test() external returns(uint) {(x.push(), x.push()) = (0x01, 0x02);return x.length;}
}

在这里,当第一次执行 x.push() 时,x 仍然以短格式存储,因此 x.push() 返回的是 x 第一个存储槽中的元素的引用。然而,第二次执行 x.push() 时,字节数组的存储格式会变成长格式。此时,x.push() 所引用的元素已经被移动到数组的数据区域,而引用仍指向原始位置(即长度字段)。因此,赋值操作会有效地破坏 x 的长度字段。

为了安全起见,建议在单次赋值语句中,只增加字节数组最多一个元素,并且不要同时在同一语句中进行数组的索引访问。

虽然上述描述的是当前版本编译器中的悬空存储引用的行为,但任何包含悬空引用的代码都应被视为具有未定义行为。因此,需确保在编写代码时避免悬空引用。

相关文章:

【区块链安全 | 第十七篇】类型之引用类型(一)

文章目录 引用类型数据存储位置分配行为 数组特殊数组&#xff1a;bytes 和 string 类型bytes.concat 和 string.concat 的功能分配 memory 数组数组字面量&#xff08;Array Literals&#xff09;二维数组字面量数组成员&#xff08;Array Members&#xff09;悬空引用&#x…...

2025国内DevOps新手突围指南:从Gitee零门槛入门到工具链深度对比

对于刚接触DevOps的新手&#xff0c;推荐优先选择Gitee DevOps平台&#xff0c;其次是Jenkins和GitLab。Gitee DevOps作为国内领先的一站式研发效能平台&#xff0c;深度融合代码托管、持续集成/持续交付&#xff08;CI/CD&#xff09;、项目协作等功能&#xff0c;不仅界面简洁…...

【C语言】文件操作(2)

一、文件的随机读写 在前面我们学习了文件的顺序读写的函数&#xff0c;那么当我们要读取某个指定位置的内容的时候&#xff0c;是否只能顺序的读取到这个内容&#xff1f;还有在对文件进行输入的时候&#xff0c;需要对指定的位置进行写入&#xff0c;那么此时应该怎么办呢&a…...

将内网的IP地址映射到外网的几种方案

文章目录 1. 背景与目标2. 核心方案选型3. 方案A&#xff1a;路由器端口映射&#xff08;详细步骤&#xff09;3.1 前置条件3.2 配置流程3.3 验证访问 4. 方案B&#xff1a;云平台NAT网关配置&#xff08;以阿里云为例&#xff09;4.1 前置条件4.2 配置流程4.3 验证访问 5. 方案…...

基于深度学习的图像超分辨率技术研究与实现

一、引言 在数字图像处理领域,图像超分辨率技术一直是一个备受关注的热点话题。随着人们对图像质量要求的不断提高,如何将低分辨率图像提升到高分辨率,同时保持图像的细节和清晰度,成为了一个极具挑战性的问题。传统的图像超分辨率技术主要依赖于插值方法,如双线性插值、双…...

A股复权计算_权息数据整理

目录 前置&#xff1a; 步骤&#xff1a; 1 以通达信为参照 2 从优矿获取所需数据 2.1 股票配股信息 2.2 股票分红信息 2.3 股票拆股信息 3 合并数据&#xff0c;制成权息数据表 权息数据截止20250329.7z 视频 前置&#xff1a; 1 本系列将以 “A股复权计算_” 开头…...

如何进行Prompt调优?

一. 神奇的咒语 在输入prompt前&#xff0c;加入下面这一段“神奇的咒语”&#xff0c;中文或者英文&#xff0c;就能帮你优化提示词。 I want you to become my Expert Prompt Creator. Your goal is to help me craft the best possible prompt for my needs. The prompt yo…...

Git Tag 详解:版本管理与实战指南

文章目录 Git Tag 详解&#xff1a;版本管理与实战指南1. Git Tag 的类型2. Git Tag 的常见操作(1) 创建标签① 创建轻量标签② 创建附注标签③ 给指定的提交打标签 (2) 查看标签(3) 删除标签(4) 推送标签到远程① 推送单个标签② 推送所有标签 (5) 删除远程标签 3. 使用 Tag 的…...

从零开始打造HTML5拼图游戏:一个Canvas实战项目

从零开始打造HTML5拼图游戏&#xff1a;一个Canvas实战项目 先看效果&#xff1a; 你是否曾经被那些精美的网页拼图游戏所吸引&#xff1f;用 HTML5 的 Canvas 技术&#xff0c;从零开始&#xff0c;教你怎么画图、处理鼠标事件&#xff0c;还有游戏的核心逻辑&#xff0c…...

【数据分享】2000—2024年我国乡镇的逐年归一化植被指数(NDVI)数据(年最大值/Shp/Excel格式)

之前我们分享过2000-2024年我国逐年的归一化植被指数&#xff08;NDVI&#xff09;栅格数据&#xff0c;该逐年数据是取的当年月归一化植被指数&#xff08;NDVI&#xff09;的年最大值&#xff01;另外&#xff0c;我们基于此年度栅格数据按照行政区划取平均值&#xff0c;得到…...

设计模式 Day 2:工厂方法模式(Factory Method Pattern)详解

继 Day 1 学习了单例模式之后&#xff0c;今天我们继续深入对象创建型设计模式——工厂方法模式&#xff08;Factory Method&#xff09;。工厂方法模式为对象创建提供了更大的灵活性和扩展性&#xff0c;是实际开发中使用频率极高的一种设计模式。 一方面&#xff0c;我们将简…...

TensorFlow SegFormer 实战训练代码解析

一、SegFormer 实战训练代码解析 SegFormer 是一个轻量级、高效的语义分割模型&#xff0c;结合了 ViT&#xff08;视觉 Transformer&#xff09; 和 CNN 的高效特征提取能力&#xff0c;适用于边缘 AI 设备&#xff08;如 Jetson Orin&#xff09;。下面&#xff0c;我们深入…...

51c嵌入式~单片机~合集7~※

我自己的原文哦~ https://blog.51cto.com/whaosoft/13692314 一、芯片工作的心脏--晶振 在振荡器中采用一个特殊的元件——石英晶体&#xff0c;它可以产生频率高度稳定的交流信号&#xff0c;这种采用石英晶体的振荡器称为晶体振荡器&#xff0c;简称晶振。 制作方法 …...

私有知识库 Coco AI 实战(一):Linux 平台部署

Coco AI 是一个完全开源、跨平台的统一搜索和生产力工具&#xff0c;能够连接各种数据源&#xff0c;包括应用程序、文件、Google Drive、Notion、Yuque、Hugo 等&#xff0c;帮助用户快速智能地访问他们的信息。通过集成 DeepSeek 等大型模型&#xff0c;Coco AI 实现了智能个…...

大模型高质量rag构建:A Cheat Sheet and Some Recipes For Building Advanced RAG

原文&#xff1a;A Cheat Sheet and Some Recipes For Building Advanced RAG — LlamaIndex - Build Knowledge Assistants over your Enterprise DataLlamaIndex is a simple, flexible framework for building knowledge assistants using LLMs connected to your enterpris…...

LeetCode 78.子集

问题描述 给定一个不含重复元素的整数数组 nums&#xff0c;返回其所有可能的子集&#xff08;幂集&#xff09;。 示例 输入&#xff1a; nums [1,2,3] 输出&#xff1a; [ [], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3] ]解法&#xff1a;回溯算法 回溯是一种 暴力…...

变量(Variable)

免责声明 如有异议请在评论区友好交流&#xff0c;或者私信 内容纯属个人见解&#xff0c;仅供学习参考 如若从事非法行业请勿食用 如有雷同纯属巧合 版权问题请直接联系本人进行删改 前言 提示&#xff1a;从小学解方程变量x&#xff0c;到中学阶段函数自变量x因变量y&…...

【STM32】最后一刷-江科大Flash闪存-学习笔记

FLASH简介 STM32F1系列的FLASH包含程序存储器、系统存储器和选项字节三个部分&#xff0c;通过闪存存储器接口&#xff08;外设&#xff09;可以对程序存储器和选项字节进行擦除和编程&#xff0c;&#xff08;系统存储器用于存储原厂写入的BootLoader程序&#xff0c;用于串口…...

Dify 深度集成 MCP实现灾害应急响应

一、架构设计 1.1 分层架构 #mermaid-svg-5dVNjmixTX17cCfg {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-5dVNjmixTX17cCfg .error-icon{fill:#552222;}#mermaid-svg-5dVNjmixTX17cCfg .error-text{fill:#552222…...

2025 年上半年软考信息系统项目管理师备考计划

2025 年上半年软考信息系统项目管理师备考计划​ 2025 年上半年软考信息系统项目管理师考试时间为 5 月 24 日 - 27 日&#xff0c;从现在开始备考&#xff0c;需合理规划&#xff0c;高效学习。以下为详细备考计划&#xff1a;​ 一、基础学习阶段&#xff08;现在 - 4 月上…...

Scikit-learn使用指南

1. Scikit-learn 简介 定义&#xff1a; Scikit-learn&#xff08;简称 sklearn&#xff09;是基于 Python 的开源机器学习库&#xff0c;提供了一系列算法和工具&#xff0c;用于数据挖掘、数据预处理、分类、回归、聚类、模型评估等任务。特点&#xff1a; 基于 NumPy、SciP…...

学习大模型需要具备哪些技术、知识和基础

数学基础 概率论与数理统计&#xff1a;用于理解模型中的不确定性、概率分布&#xff0c;以及进行数据的统计分析、评估模型的性能等。例如&#xff0c;通过概率分布来描述模型预测结果的可信度&#xff0c;利用统计方法对数据进行抽样、估计模型的参数等。线性代数&#xff1…...

十五届蓝桥杯省赛Java B组(持续更新..)

目录 十五届蓝桥杯省赛Java B组第一题&#xff1a;报数第二题&#xff1a;类斐波那契数第三题&#xff1a;分布式队列第四题&#xff1a;食堂第五题&#xff1a;最优分组第六题&#xff1a;星际旅行第七题&#xff1a;LITS游戏第八题&#xff1a;拼十字 十五届蓝桥杯省赛Java B…...

Flink SQL Client bug ---datagen connector

原始sql语句如下 CREATE TABLE test_source (event_time TIMESTAMP(3), -- 事件时间&#xff08;精确到毫秒&#xff09;click INT, -- 随机数值字段WATERMARK FOR event_time AS event_time - INTERVAL 5 SECOND WITH (connector datagen, …...

股指期货的多头套期保值是什么意思?

多头套期保值&#xff0c;又叫“买入套期保值”&#xff0c;听起来很复杂&#xff0c;其实很简单。它的核心就是“提前锁定价格&#xff0c;防止未来价格上涨”。 举个例子&#xff0c;假设你是一家工厂的老板&#xff0c;过几个月要买一批原材料。现在原材料的价格是100元/吨…...

hadoop集群配置-scp命令

scp 命令用于在不同主机之间复制文件或目录&#xff0c;在Hadoop集群配置中常用于将配置文件或相关资源分发到各个节点。以下是 scp 命令的基本用法和在Hadoop集群配置中的示例&#xff1a; 基本语法 scp [-r] [源文件或目录] [目标用户目标主机:目标路径] - -r &#xff1a;…...

Redis 源码硬核解析系列专题 - 结语:从源码看Redis的设计哲学

1. 引言 通过前七篇的源码解析,我们从Redis的整体架构、核心数据结构、事件驱动模型,到内存管理、持久化、主从复制与集群模式,逐步揭开了Redis高性能与简洁性的秘密。本篇将总结这些技术细节,提炼Redis的设计哲学,并探讨如何将源码学习成果应用到实际开发中。 2. Redis的…...

解决QSharedPointer栈变量的崩溃问题

目录 参考崩溃代码现象 解决 参考 QSharedPointer的陷阱 qt中的共享指针&#xff0c;QSharedPointer类 崩溃 代码 #include <QtCore/QCoreApplication> #include <QDebug> #include <QSharedPointer>class MyClass { public:void doSomething() {qDebug…...

Lambda 表达式是什么以及如何使用

目录 &#x1f4cc; Kotlin 的 Lambda 表达式详解 &#x1f3af; 什么是 Lambda 表达式&#xff1f; &#x1f525; 1. Lambda 表达式的基本语法 ✅ 示例 1&#xff1a;Lambda 基本写法 ✅ 示例 2&#xff1a;使用 it 关键字&#xff08;单参数简化&#xff09; ✅ 示例 3…...

C++自定义迭代器

实现自己的迭代器 最近在写数据结构&#xff0c;使用类模板实现&#xff0c;碰到了一些问题&#xff0c;其中有一个就是遍历的问题&#xff0c;查阅资料最后实现了自己的迭代器&#xff0c;让我实现的数据结构能像STL一样进行for循环遍历。 类的构成 #include <stdexcept…...

PWA 中的 Service Worker:如何实现应用离线功能

前言 在当今快速发展的互联网时代&#xff0c;Progressive Web App (PWA) 正在逐步成为现代 Web 开发的主流选择。PWA 将 Web 应用和原生应用的最佳特性相结合&#xff0c;提供了丰富的用户体验。而在 PWA 的众多技术中&#xff0c;Service Worker 无疑是其核心组件之一。 作…...

dockerfile制作镜像

1.docker pull centos:centos7 2.dockerfile内容 FROM centos:centos7 #指定镜像维护的作者和邮箱 MAINTAINER csdn< **********qq.com #设置环境变量mypath ENV MYPATH /usr/local #设置进入容器的默认目录是/usr/local WORKDIR $MYPATH # 下载并替换 CentOS 镜像源 RUN …...

网络空间安全(46)DevSecOps概述

一、定义与核心理念 DevSecOps是“开发&#xff08;Development&#xff09;、安全&#xff08;Security&#xff09;和运营&#xff08;Operations&#xff09;”的结合&#xff0c;它将安全实践融入软件开发生命周期的每个阶段&#xff0c;从需求、设计、开发、测试到部署和运…...

LeetCode 211

实现支持通配符的字典树&#xff08;Trie&#xff09;&#xff1a;解决单词匹配问题 一、问题描述 我们需要设计一个数据结构&#xff0c;支持以下功能&#xff1a; 添加新单词搜索字符串是否与任何已添加的单词匹配&#xff0c;其中搜索字符串可能包含通配符 .&#xff08;…...

Docker Compose 启动jar包项目

参考文章安装Docker和Docker Compose 点击跳转 配置 创建一个文件夹存放项目例如mydata mkdir /mydata上传jar包 假设我的jar包名称为goudan.jar 编写dockerfile文件 vim app-dockerfile按键盘上的i进行编辑 # 使用jdk8 FROM openjdk:8-jre# 设置时区 上海 ENV TZAsia/Sh…...

利用deepseek直接调用其他文生图网站生成图片

这次deepseek输入中文后&#xff0c;其实翻译英文后&#xff0c;是可以丢到比如pollinations.这个网站&#xff0c;来生成图片&#xff0c;用法如下&#xff1a; 你是一个图像生成助手&#xff0c;请根据我的简单描述&#xff0c;想象并详细描述一幅完整的画面。 然后将你的详…...

远程装个Jupyter-AI协作笔记本,Jupyter容器镜像版本怎么选?安装部署教程

通过Docker下载Jupyter镜像部署&#xff0c;输入jupyter会发现 有几个版本&#xff0c;不知道怎么选&#xff1f;这几个版本有什么差别&#xff1f; 常见版本有&#xff1a; jupyter/base-notebookjupyter/minimal-notebookjupyter/scipy-notebookjupyter/datascience-notebo…...

11. 盛最多水的容器

leetcode Hot 100系列 文章目录 一、核心操作二、外层配合操作三、核心模式代码总结 一、核心操作 最左右两边逐步往中间走&#xff0c;每次在左右中选取小的一个或–记录最大面积 提示&#xff1a;小白个人理解&#xff0c;如有错误敬请谅解&#xff01; 二、外层配合操作…...

Selenium Web自动化如何快速又准确的定位元素路径,强调一遍是元素路径

如果文章对你有用&#xff0c;请给个赞&#xff01; 匹配的ChromeDriver和浏览器版本是更好完成自动化的基础&#xff0c;可以从这里去下载驱动程序&#xff1a; 最全ChromeDriver下载含win linux mac 最新版本134.0.6998.165 持续更新..._chromedriver 134-CSDN博客 如果你问…...

Kotlin 基础语法解析

详细的 Kotlin 基础语法解析&#xff0c;结合概念说明和实用场景&#xff1a; --- ### **一、变量与常量** #### **1. 变量类型** - **val**&#xff08;不可变变量&#xff09;&#xff1a;声明后不可重新赋值&#xff0c;类似 Java 的 final。 kotlin val name "Kotl…...

html 列表循环滚动,动态初始化字段数据

html <div class"layui-row"><div class"layui-col-md4"><div class"boxall"><div class"alltitle">超时菜品排行</div><div class"marquee-container"><div class"scroll-…...

【大模型基础_毛玉仁】5.4 定位编辑法:ROME

目录 5.4 定位编辑法&#xff1a;ROME5.4.1 知识存储位置1&#xff09;因果跟踪实验2&#xff09;阻断实验 5.4.2 知识存储机制5.4.3 精准知识编辑1&#xff09;确定键向量2&#xff09;优化值向量3&#xff09;插入知识 5.4 定位编辑法&#xff1a;ROME 定位编辑&#xff1a;…...

Using SAP an introduction for beginners and business users

Using SAP an introduction for beginners and business users...

Android学习总结之RecyclerView补充篇

在 Android 开发中&#xff0c;列表数据更新的性能一直是关键痛点。传统的 notifyDataSetChanged() 会触发全量刷新&#xff0c;导致不必要的界面重绘。而 DiffUtil 作为 Android 提供的高效差异计算工具&#xff0c;能精准识别数据变化&#xff0c;实现局部更新&#xff0c;成…...

mapbox基础,使用geojson加载cluster聚合图层

👨‍⚕️ 主页: gis分享者 👨‍⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨‍⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象1.2 ☘️mapboxgl.Map style属性1.3 ☘️circle点图层样式二、🍀使用geojson加…...

函数:static和extern

0.前言 在正式开始之前先说作用域和生命周期 作用域&#xff1a; 作用域有分为局部变量和全局变量 局部变量&#xff1a;一个变量仅在其中一段代码内起作用 全局变量&#xff1a;所有的代码都可以使用这个变量 生命周期&#xff1a; 生命周期是一个代码从运行开始到结束…...

【QT】练习1

1、设计一个颜色选择器&#xff0c;可以输入RGB的颜色值&#xff0c;点击确认&#xff0c;可以把主界面的背景颜色改成设置的颜色 修改背景颜色&#xff1a;setStyleSheet(“background-color 红绿蓝颜色值”); // mainwindow.cpp #include "mainwindow.h" #include…...

GreenPlum学习

简介 Greenplum是一个面向数据仓库应用的关系型数据库&#xff0c;因为有良好的体系结构&#xff0c;所以在数据存储、高并发、高可用、线性扩展、反应速度、易用性和性价比等方面有非常明显的优势。Greenplum是一种基于PostgreSQL的分布式数据库&#xff0c;其采用sharednothi…...

张量-pytroch基础(2)

张量-pytroch网站-笔记 张量是一种特殊的数据结构&#xff0c;跟数组&#xff08;array&#xff09;和矩阵&#xff08;matrix&#xff09;非常相似。 张量和 NumPy 中的 ndarray 很像&#xff0c;不过张量可以在 GPU 或其他硬件加速器上运行。 事实上&#xff0c;张量和 Nu…...

Linux多线程编程的艺术:封装线程、锁、条件变量和信号量的工程实践

目录 &#x1f4cc;这篇博客能带给你什么&#xff1f; &#x1f525;为什么需要封装这些组件&#xff1f; 一、线程封装 框架设计 构造与析构 1.线程创建 2.线程分离 3.线程取消 4.线程等待 二、锁封装 框架设计 构造与析构 1.加锁 2.解锁 3.RAII模式 三、条件…...