【从0学习Solidity】控制流,用solidity实现
发布时间:2025-08-29 00:10       
下面用“插入排序”带你把 Solidity 的控制流一次吃透:for/while/if、早返回、内存与存储的差别,以及节省 gas 的写法。🙂
核心要点(先记住)
- 控制流:
for负责外层轮次,while负责元素前移,if/return做边界与早退出。 - 数据区:
calldata(只读、最省 gas)→ 复制到memory排序;storage为链上状态,原地改写更贵。 - 复杂度:插入排序
O(n^2),仅适合小数组(如几十到一两百个元素)。大数组应链下排序或换算法。 - gas 技巧:
unchecked { ++i; --j; }跳过溢出检查;必要时早返回;减少重复读取长度。
可直接部署的示例(Solidity ^0.8.x)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
contract InsertionSortDemo {
uint[] public nums;
// 装载一组数据到 storage(演示用)
function setNums(uint[] calldata data) external {
delete nums;
for (uint i = 0; i < data.length; ) {
nums.push(data[i]);
unchecked { ++i; }
}
}
// 在 memory 上排序并返回新数组(不修改状态)
function sortMemory(uint[] calldata data) external pure returns (uint[] memory out) {
out = new uint[](data.length);
for (uint i = 0; i < data.length; ) { // 复制到 memory
out[i] = data[i];
unchecked { ++i; }
}
// 插入排序主体
for (uint i = 1; i < out.length; ) {
uint key = out[i];
uint j = i;
while (j > 0 && out[j - 1] > key) {
out[j] = out[j - 1];
unchecked { --j; }
}
out[j] = key;
unchecked { ++i; }
}
}
// 对 storage 上的 nums 原地排序(修改链上状态)
function sortStorage() external {
uint len = nums.length;
if (len < 2) return; // 早返回降低 gas
for (uint i = 1; i < len; ) {
uint key = nums[i];
uint j = i;
while (j > 0 && nums[j - 1] > key) {
nums[j] = nums[j - 1];
unchecked { --j; }
}
nums[j] = key;
unchecked { ++i; }
}
}
}
说明
sortMemory:输入放在calldata,复制到memory排序并返回,不改状态,适合链上验证或只读场景。sortStorage:直接对storage的nums排序,便捷但更耗 gas,建议配合长度上限与访问控制(如仅管理员可调)。unchecked:Solidity 0.8+ 默认整型有溢出检查,循环自增自减可用unchecked降低 gas。
工作流程(vditor/Markdown 支持)
Syntax error in textmermaid version 10.8.0
Parse error on line 5:
...n-1] D --> E[key=a[i], j=i] E --> F{ ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'SQS'
...n-1] D --> E[key=a[i], j=i] E --> F{ ----------------------^ Expecting 'SQE', 'DOUBLECIRCLEEND', 'PE', '-)', 'STADIUMEND', 'SUBROUTINEEND', 'PIPE', 'CYLINDEREND', 'DIAMOND_STOP', 'TAGEND', 'TRAPEND', 'INVTRAPEND', 'UNICODE_TEXT', 'TEXT', 'TAGSTART', got 'SQS'
原理对照表(控制流 × 数据区)
| 维度 | 选择 | 影响与理由 |
|---|---|---|
| 外层轮次 | for |
明确起止边界,便于自增优化 |
| 元素前移 | while |
条件驱动,直观表达“向前腾挪” |
| 边界处理 | if (len<2) return |
早返回,避免无效循环 |
| 数据区 | calldata → memory |
排序副本不改状态,成本更低 |
| 原地改写 | storage |
改链上数据,需限长与权限 |
| gas 优化 | unchecked自增/自减 |
避开溢出检查,少量省气 |
实战建议 🔧
- 只读路径优先:能在
memory排序就别碰storage。 - 限制规模:上链排序须强约束长度,防止超时/超费。
- 复用性:提炼为库函数(library)或 internal 工具,复用在不同合约中。
- 可测试性:准备小样本(如
[5,3,4,1,2])与有序/重复/空数组用例,覆盖边界。
一句话总结:用清晰的 for+while+if 组合和恰当的数据区选择,就能把插入排序在 Solidity 中写得既直观又省 gas。🚀