1. 基本概念
delete 是 Solidity 中的一个运算符,用于重置变量到其类型的默认值,而不是从存储中完全移除数据(尽管变量名可能不再可访问)。
2. 对不同类型的影响
2.1 值类型(Value Types)
// 数值类型 - 重置为 0
uint256 public num = 100;
bool public flag = true;
address public addr = 0x123...;
function resetValues() public {
delete num; // 变为 0
delete flag; // 变为 false
delete addr; // 变为 address(0)
}
2.2 数组
// 动态数组 - 长度变为 0
uint[] public dynamicArray = [1, 2, 3];
// 固定大小数组 - 所有元素重置为默认值
uint[3] public fixedArray = [1, 2, 3];
function resetArrays() public {
delete dynamicArray; // 数组变为 [],长度 0
delete fixedArray; // 变为 [0, 0, 0]
}
2.3 结构体(Struct)
struct Person {
string name;
uint age;
address wallet;
}
Person public person = Person("Alice", 25, 0x123...);
function resetStruct() public {
delete person;
// 变为: Person("", 0, address(0))
}
2.4 映射(Mapping)
mapping(address => uint) public balances;
mapping(uint => mapping(address => bool)) public nestedMapping;
function resetMapping() public {
// 注意:delete 不能直接用于整个映射
// delete balances; // ❌ 编译错误
// 但可以删除映射中的特定键值
address user = msg.sender;
delete balances[user]; // 将 balances[user] 设为 0
// 嵌套映射同理
delete nestedMapping[1][user]; // 设为 false
}
重要 :delete 映射键不会影响映射大小或删除键本身,只是将值重置。
3. Gas 消耗特性
delete 操作有特殊的 Gas 返还机制:
function gasComparison() public {
uint256 x = 10;
// 赋值 0 - 花费 Gas
x = 0; // 花费约 5000 Gas
// 使用 delete - 可能获得 Gas 返还
delete x; // 花费约 5000 Gas,但可能获得部分返还
// 存储中的复杂结构
Person storage p = person;
delete p; // 如果释放了存储空间,可能获得 Gas 返还
}
Gas 返还规则:
- 从存储中清除数据(设为默认值)可能获得 Gas 返还
- 每个存储槽清除最多返还 15,000 Gas
- 最多返还交易消耗 Gas 的一半
4. 特殊用法和注意事项
4.1 与 new 的区别
contract Example {
uint[] public arr;
function test() public {
arr = [1, 2, 3];
// delete 只重置内容
delete arr; // arr.length = 0
// new 创建全新数组
uint[] memory newArr = new uint[](3);
}
}
4.2 在函数参数中使用
function processData(uint[] memory data) public pure returns (uint[] memory) {
// 不能在 memory 上使用 delete(编译错误)
// delete data; // ❌
// 但可以在函数内创建副本并删除
uint[] memory copy = data;
// 对copy的操作不影响原data
return copy;
}
4.3 删除结构体成员
struct Complex {
uint a;
uint[] b;
mapping(uint => uint) c;
}
function deleteStructMember() public {
Complex storage comp;
comp.a = 10;
comp.b.push(1);
// 删除特定成员
delete comp.a; // 重置为 0
delete comp.b; // 数组长度变为 0
// 注意:不能删除包含映射的结构体
// delete comp; // ❌ 如果结构体包含映射
}
5. 实用示例
5.1 清空合约状态
contract Resetable {
string public name = "MyContract";
uint256 public count = 100;
address[] public users;
function resetContract() public {
delete name; // 变为 ""
delete count; // 变为 0
// 清空数组
while (users.length > 0) {
users.pop();
}
// 或直接
delete users;
}
}
5.2 安全删除模式
contract Token {
mapping(address => uint) private _balances;
mapping(address => bool) private _frozen;
function safeDeleteAccount(address account) public {
require(!_frozen[account], "Account is frozen");
// 先记录值(用于事件)
uint oldBalance = _balances[account];
// 删除余额
delete _balances[account];
// 标记账户为已删除
_frozen[account] = true;
emit AccountDeleted(account, oldBalance);
}
}
6. 常见陷阱
6.1 删除包含映射的结构体
struct BadStruct {
uint id;
mapping(address => uint) balances; // 包含映射
}
BadStruct public bs;
function problematic() public {
// delete bs; // ❌ 编译错误!不能删除包含映射的结构体
// 正确做法:单独删除非映射成员
delete bs.id;
}
6.2 删除后的引用
function referenceIssue() public {
uint[] storage ref = dynamicArray;
ref.push(100);
delete dynamicArray; // 清空数组
// ref 仍然存在,但现在指向空数组
// ref.length == 0
}
7.应用场景
7.1. 合约状态管理
7.1.1 合约重置/初始化
// 可重置的投票合约
contract ResetableVoting {
struct Proposal {
string description;
uint voteCount;
mapping(address => bool) voters;
}
Proposal[] public proposals;
bool public votingActive;
uint public endTime;
// 管理员重置整个投票
function resetVoting() public onlyOwner {
// 清空提案数组
delete proposals;
// 重置状态变量
delete votingActive; // false
delete endTime; // 0
emit VotingReset();
}
// 重置单个提案
function resetProposal(uint proposalId) public onlyOwner {
require(proposalId < proposals.length, "Invalid proposal");
Proposal storage proposal = proposals[proposalId];
// 不能直接 delete proposal(包含映射)
// 但可以重置非映射成员
delete proposal.description; // 重置为 ""
proposal.voteCount = 0; // 赋值为 0(等同于 delete)
// 映射需要单独处理 - 无法直接清空
// 通常需要添加重置逻辑或使用新的提案ID
}
}
7.1.2 用户账户清理
// DeFi 协议中的账户管理
contract LendingProtocol {
struct Account {
uint collateral;
uint debt;
uint lastUpdate;
bool active;
}
mapping(address => Account) public accounts;
address[] public allAccounts;
// 用户关闭账户
function closeAccount() public {
Account storage account = accounts[msg.sender];
require(account.debt == 0, "Must repay all debt");
// 退还抵押品
uint collateral = account.collateral;
// 重置账户状态
delete accounts[msg.sender]; // 所有字段设为默认值
// 从活跃账户列表中移除(简化示例)
// 实际中可能需要更复杂的逻辑
emit AccountClosed(msg.sender, collateral);
}
// 批量清理零余额账户(节省存储空间)
function cleanupInactiveAccounts(address[] calldata inactiveUsers) public onlyOwner {
for (uint i = 0; i < inactiveUsers.length; i++) {
address user = inactiveUsers[i];
Account storage acc = accounts[user];
if (acc.collateral == 0 && acc.debt == 0) {
// 只有完全清理时才使用 delete
delete accounts[user];
emit AccountCleaned(user);
}
}
}
}
7.2. 数组和数据结构操作
7.2.1 动态数组管理
// 游戏中的玩家管理
contract Game {
struct Player {
address addr;
uint score;
uint joinTime;
}
Player[] public players;
uint public gameRound;
// 开始新游戏回合 - 清空玩家列表
function startNewRound() public {
// 保存当前回合信息
uint oldRound = gameRound;
// 重置玩家数组
delete players; // 更高效且可能返还 Gas
// 等同于: players = new Player[](0);
gameRound++;
emit NewRoundStarted(oldRound, gameRound);
}
// 移除特定玩家(通过交换和删除)
function removePlayer(address playerAddr) public {
for (uint i = 0; i < players.length; i++) {
if (players[i].addr == playerAddr) {
// 将最后一个元素移到当前位置
players[i] = players[players.length - 1];
// 删除最后一个元素
players.pop();
// 或者使用 delete(如果顺序不重要)
// delete players[i]; // 重置为默认值
// 然后需要特殊处理这个"空位"
emit PlayerRemoved(playerAddr);
return;
}
}
}
}
7.2.2 缓存和临时数据处理
// 批量处理合约
contract BatchProcessor {
uint[] public pendingTransactions;
uint[] public processedTransactions;
function processBatch(uint batchSize) public {
uint[] memory toProcess = new uint[](batchSize);
uint count = 0;
// 从待处理数组中取出批量数据
for (uint i = 0; i < batchSize && pendingTransactions.length > 0; i++) {
toProcess[i] = pendingTransactions[pendingTransactions.length - 1];
pendingTransactions.pop();
count++;
}
// 处理逻辑...
// 处理完成后清空内存数组引用
// 注意:不能直接 delete toProcess(memory数组)
// 但可以重新赋值为空数组
// 清空 processedTransactions 以开始新的批次
if (processedTransactions.length > 1000) {
// 定期清理以避免数组过大
delete processedTransactions;
emit CacheCleared();
}
}
}
7.3. 优化 Gas 消耗
7.3.1 存储清理返还 Gas
// 竞拍合约 - 清理过期数据
contract Auction {
struct Bid {
address bidder;
uint amount;
uint timestamp;
}
mapping(uint => Bid) public bids; // auctionId -> Bid
uint[] public activeAuctions;
// 结束拍卖并清理数据
function finalizeAuction(uint auctionId) public {
Bid memory winningBid = bids[auctionId];
require(winningBid.bidder != address(0), "No bids");
// 转移资金...
// 清理存储数据以获得 Gas 返还
delete bids[auctionId]; // 可能获得 15,000 Gas 返还
// 从活跃拍卖列表中移除
for (uint i = 0; i < activeAuctions.length; i++) {
if (activeAuctions[i] == auctionId) {
activeAuctions[i] = activeAuctions[activeAuctions.length - 1];
activeAuctions.pop();
break;
}
}
// 如果存储了大量过期数据,定期清理
if (activeAuctions.length == 0) {
// 当没有活跃拍卖时,完全清理数组
delete activeAuctions;
}
}
}
7.3.2 临时变量重置
// 复杂计算合约
contract Calculator {
uint public lastResult;
uint[] public intermediateResults;
function complexCalculation(uint[] memory inputs) public returns (uint) {
// 清空中间结果数组
delete intermediateResults;
uint temp;
for (uint i = 0; i < inputs.length; i++) {
temp = inputs[i] * 2;
intermediateResults.push(temp);
// 每10次迭代后重置临时变量
if (i % 10 == 9) {
delete temp; // 重置为 0,虽然作用有限但展示用法
}
}
// 计算最终结果
uint result = 0;
for (uint i = 0; i < intermediateResults.length; i++) {
result += intermediateResults[i];
}
// 保存结果并清理中间数据
lastResult = result;
delete intermediateResults; // 清理存储获得 Gas 返还
return result;
}
}
7.4. 安全相关场景
7.4.1 权限和访问控制清理
// 多签名钱包
contract MultiSigWallet {
struct Transaction {
address to;
uint value;
bytes data;
bool executed;
uint confirmationsCount;
mapping(address => bool) isConfirmed;
}
Transaction[] public transactions;
address[] public owners;
mapping(address => bool) public isOwner;
uint public required;
// 移除所有者(需要重新设置权限)
function removeOwner(address owner) public onlyWallet {
require(isOwner[owner], "Not an owner");
require(owners.length > required, "Cannot remove required owner");
// 重置权限标志
delete isOwner[owner];
// 从所有者数组中移除
for (uint i = 0; i < owners.length; i++) {
if (owners[i] == owner) {
owners[i] = owners[owners.length - 1];
owners.pop();
break;
}
}
// 清理该所有者确认的所有交易
for (uint i = 0; i < transactions.length; i++) {
if (transactions[i].isConfirmed[owner]) {
delete transactions[i].isConfirmed[owner];
transactions[i].confirmationsCount--;
}
}
emit OwnerRemoved(owner);
}
}
7.4.2 敏感数据清理
// 隐私保护合约
contract PrivateData {
struct Secret {
bytes32 encryptedData;
uint timestamp;
address authorized;
}
mapping(address => Secret) private secrets;
// 用户可随时删除自己的隐私数据
function deleteMySecret() public {
Secret storage secret = secrets[msg.sender];
// 记录删除事件(用于审计)
emit SecretDeleted(msg.sender, secret.timestamp);
// 彻底删除数据
delete secrets[msg.sender];
// 确保数据被清除
require(secrets[msg.sender].timestamp == 0, "Deletion failed");
}
// 自动清理过期数据
function cleanupExpiredSecrets(address[] calldata expiredUsers) public {
for (uint i = 0; i < expiredUsers.length; i++) {
address user = expiredUsers[i];
if (secrets[user].timestamp + 90 days < block.timestamp) {
// 90天前的数据自动删除
delete secrets[user];
emit AutoCleanup(user);
}
}
}
}
7.5. 升级和迁移场景
7.5.1 合约升级前的状态清理
// 可升级合约模式
contract UpgradeableToken {
string public name;
string public symbol;
uint public totalSupply;
mapping(address => uint) public balances;
address public newContract;
bool public migrationActive;
// 准备升级到新合约
function prepareUpgrade(address _newContract) public onlyOwner {
newContract = _newContract;
migrationActive = true;
// 可选:清理不必要的状态
delete name; // 新合约会重新设置
delete symbol; // 新合约会重新设置
}
// 迁移用户余额
function migrate(address user) public {
require(migrationActive, "Migration not active");
uint balance = balances[user];
require(balance > 0, "No balance");
// 调用新合约的迁移函数
NewToken(newContract).acceptMigration(user, balance);
// 清理旧数据
delete balances[user];
totalSupply -= balance;
emit Migrated(user, balance);
}
// 完成迁移后完全重置
function completeMigration() public onlyOwner {
require(migrationActive, "Migration not active");
// 验证所有余额已迁移
require(totalSupply == 0, "Balances remaining");
// 重置所有状态
delete totalSupply;
delete migrationActive;
delete newContract;
// 注意:不能删除整个映射,但所有键的值应为0
}
}
总结
| 类型 | delete 效果 |
是否返还 Gas |
|---|---|---|
| 值类型 | 设为默认值 | 可能 |
| 动态数组 | 长度设为 0 | 可能 |
| 固定数组 | 所有元素设为默认值 | 可能 |
| 结构体(无映射) | 所有成员设为默认值 | 可能 |
| 结构体(含映射) | 不允许直接删除 | - |
| 映射键值 | 值设为默认值,键仍存在 | 可能 |
| 整个映射 | 不允许 | - |
最佳实践:
- 使用
delete重置状态变量而非赋默认值,可能节省 Gas - 注意映射和包含映射的结构体的删除限制
- 在删除前考虑是否需要记录数据(用于事件)
- 理解
delete是重置而非完全移除