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

solidity的变量学习小结

一、 变量的定义

1. 状态变量格式

变量类型 [可见性] [可变状态] 变量名 [= 初始值];

  • 可见性: 可选 (public/internal/private), 缺省时表示internal
  • 可变状态: 可选 (constant/immutable), 缺省时表示普通变量,可以修改
  • 初始值: 可选
2. 局部变量格式

变量类型 [数据位置] 变量名 [= 初始值];

  • 数据位置: 引用类型必须指定 (memory/calldata/storage),值类型不需要指定
  • 初始值: 推荐初始化

二、 变量的可见性

1. 可见性关键字用来控制变量是否可以被外部使用
2. 可见性关键字仅用来修饰状态变量,而不用来修饰局部变量
  • 状态变量存在于区块链存储中,需要可见性控制;
  • 局部变量存在于临时内存中,只在函数执行期间存在,不需要可见性控制
3. 状态变量的三种可见性
  • public:自动生成一个同名的 getter 函数,可以在合约内外被调用。状态变量本身在合约内部可直接访问。
  • private:仅在当前合约内部可见,派生合约都无法访问。
  • internal:仅在当前合约及其派生合约内部可见。这是状态变量的默认可见性

三、 变量的可变状态

1. 变量的可变状态有两个关键字
  • constant:必须在编译时确定值,使用硬编码,gas效率高。
  • immutable:允许在构造函数中赋值一次,然后不可变。比 constant 更灵活,gas 效率同样很高。
2. 变量的可变状态关键字仅用来修饰状态变量,而不用来修饰局部变量
3.当状态变量前边没有constantimmutable修饰时,表示普通的状态变量,它的值可以随时修改
4.并不是所有类型的状态变量都可以用constantimmutable修饰,只有值类型、string、bytes支持。

四、 变量的数据位置(存储位置)

1. EVM的存储布局

在这里插入图片描述

2. 所有状态变量都存储在 storage 位置,无论它们是值类型还是引用类型
3. 对于局部变量而言,引用类型必须指定数据位置,值类型不需要指定数据位置
  • 值类型不需要指定数据位置的原因是编译器知道它们应该在栈上

    • 固定大小 - 编译时就知道需要多少空间
    • 栈存储 - EVM 自动管理栈空间
    • 简单复制 - 赋值时直接复制值
    • 无需生命周期管理 - 函数结束时自动清理
  • 引用类型需要指定数据位置的原因是编译器需要知道是在内存还是存储中

    • 动态大小 - 编译时不知道需要多少空间
    • 需要显式分配 - 必须在 memory 或 storage 中分配空间
    • 指针管理 - 需要管理内存指针和布局
    • 生命周期管理 - 需要明确数据的生存期

Q:如果对值类型的局部变量使用数据位置修饰会怎样呢?
A:我验证了一下: uint memory a = 0;会编译报错: Data location can only be specified for array, struct or mapping types, but "memory" was given.

4. 理解赋值行为
  • 4.1 内存memory中的赋值行为
    在这里插入图片描述
    出于“安全性考虑”、“Gas 成本优化”、“简化存储管理”这几个原因,字符串string被设为不可变的

  • 4.2 存储storage中的赋值行为

    理解storage引用,要区分“storage引用” 与 “状态变量”

当您为局部引用类型变量使用 storage 关键字时,您并不是在创建一个新的、独立的数据副本。相反,您是在创建一个指向已存在于存储中的某个状态的指针或引用,可以理解为一个指向链上存储中某个具体位置的“快捷方式”或“别名”

storage 引用不能跨越函数调用边界返回(只在函数执行期间存在),因为它只是一个函数内部的临时指针

试图在函数返回参数中使用storage修饰会编译报错:
Data location must be "memory" or "calldata" for return parameter in function, but "storage" was given.

通过storage引用进行操作,就是在操作它所指向的那个状态变量,对存储数据所做的修改会被永久保存下来

创建storage引用不同于直接状态变量赋值

contract DirectAssignment {struct User {uint256 id;string name;}User public user = User(1, "Alice");User public anotherUser;function directAssign() public {// 【直接状态变量赋值】这会创建完整的副本(包括所有字段)anotherUser = user; // 复制整个结构体}
}
contract StorageReference {struct User {uint256 id;string name;}User public user = User(1, "Alice");function useStorageRef() public {// 【创建 storage 引用】,不是复制数据User storage userRef = user;// 通过引用修改原数据userRef.id = 2;      // 直接修改 user.iduserRef.name = "Bob"; // 直接修改 user.name}
}

创建storage引用 VS 直接状态变量赋值
在这里插入图片描述

创建storage引用后,就不能再通过赋值指向memory引用了

在这里插入图片描述

  • 4.3 内存memory和存储storage之间的赋值行为
    在这里插入图片描述
    尽量避免在 memory 和 storage 之间不必要的数据复制,因为这会消耗大量Gas
    使用 storage 引用来直接操作存储数据,这是最Gas高效的方式
5. 存储位置对比总结

在这里插入图片描述
transient关键字只是提案,仅作了解即可
在这里插入图片描述

五、 变量的类型

1. 变量类型的整体概览

在这里插入图片描述

2. 值类型
  • 布尔型
bool public isActive = true;
bool public isCompleted = false;function toggle() public {isActive = !isActive;
}function logicalOperations(bool a, bool b) public pure returns (bool, bool, bool) {return (a && b, a || b, !a);
}
  • 整数
// 有符号整型
int8 public smallInt = -128;
int256 public largeInt = 1000;// 无符号整型
uint8 public smallUint = 255;
uint256 public largeUint = 1000000;function arithmeticOperations(uint a, uint b) public pure returns (uint, uint, uint, uint) {return (a + b, a - b, a * b, a / b);
}function bitOperations(uint a, uint b) public pure returns (uint, uint, uint) {return (a & b, a | b, a ^ b); // 与、或、异或
}

int 就是 int256
uint 就是 uint256

获取整型的最小值和最大值
在这里插入图片描述
注意整数可能会发生溢出

contract BestPractices {using SafeMath for uint256; // 对于 Solidity < 0.8// 使用合适的类型存储时间uint32 public constant MAX_UINT32 = 2**32 - 1;uint32 public creationTime;// 代币余额使用 uint256mapping(address => uint256) public balances;// 计数器使用合适的范围uint16 public userCount; // 假设最多 65,535 用户constructor() {creationTime = uint32(block.timestamp); // 时间戳适合用 uint32}// 安全的数学运算function safeTransfer(address to, uint256 amount) public {require(balances[msg.sender] >= amount, "Insufficient balance");require(to != address(0), "Invalid recipient");balances[msg.sender] -= amount;balances[to] += amount;}// 处理除法的精度function calculateShare(uint256 total, uint256 percentage) public pure returns (uint256) {require(percentage <= 100, "Invalid percentage");return (total * percentage) / 100;}
}
  • 地址
address public owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
address payable public payableAddress = payable(owner);function addressOperations() public view returns (uint, address) {return (payableAddress.balance, address(this));
}function transfer() public payable {payableAddress.transfer(msg.value);
}

address payable表示可支付地址,可以调用transfer和send

特性transfersendcall
转账资产原生 ETH原生 ETH原生 ETH
Gas 限制固定 2300固定 2300转发所有剩余 Gas(可自定义)
错误处理失败则回滚返回 false返回 (bool, bytes)
安全性高(防重入)高(防重入)低(有重入风险),需手动防护
推荐度推荐不推荐推荐(但需加防护)

在这里插入图片描述

Q: 为什么要用payable做区分?
A: 为了‌明确表示地址是否具备接收以太币的逻辑‌,避免开发者误将以太币转入无法处理的合约地址导致资产锁定

特性维度外部账户合约账户
英文全称Externally Owned AccountContract Account
简称EOACA
创建者用户(通过钱包)一个 EOA 或另一个 CA(通过部署合约的交易)
控制方式私钥合约代码
能否主动发起交易可以(签名并广播交易)不可以。只能通过接收到的交易或消息调用来被动执行代码。
是否有代码,包含编译后的智能合约字节码
是否有存储空间,拥有独立的存储(storage),可以持久化存储状态变量
费用需要 ETH 支付 Gas 费需要 ETH 支付 Gas 费(由其创建者或调用者间接支付)
通俗比喻个人:可以主动做事,用私钥签名代表自己的意志。自动售货机:其行为完全由预先设定的代码(规则)决定。你不能让它做规则之外的事,它自己也不会动,只有当你(EOA)向它发起交互时,它才按照规则运行。
  • 枚举
    Solidity中的枚举本质上就是uint8类型
    在这里插入图片描述
enum Status { Pending, Approved, Rejected }
// 实际上相当于:
// uint8 constant Pending = 0;
// uint8 constant Approved = 1;
// uint8 constant Rejected = 2;
Status public currentStatus = Status.Pending;function setStatus(Status _status) public {currentStatus = _status;
}function getStatus() public view returns (Status) {return currentStatus;
}// 枚举与uint8的互操作
function enumUint8Interop() public pure {// 从 uint8 创建枚举Status statusFromUint8 = Status(1); // Status.Approved// 转换为 uint8uint8 asUint8 = uint8(Status.Rejected); // 2// 枚举值范围检查require(asUint8 <= 2, "Enum value within range");
}
  • 自定义类型
    在这里插入图片描述
    只有值类型可以支持 自定义类型

验证type Duration is string;编译报错:The underlying type of the user defined value type "Duration" is not a value type.

3. 引用类型
  • 数组
    a、动态数组 VS 固定长度数组
    在这里插入图片描述
    即便是动态数组,如果数据位置是memory,也不能进行push/pop操作
    在这里插入图片描述
    Member "push" is not available in uint256[] memory outside of storage.

b、对数组的操作
1️⃣length属性获取长度 :使用 array.length
2️⃣pop操作:从数组末尾删除元素
Q: 对一个已初始化但无元素的动态数组进行pop操作,会怎样呢?
A: 交易回滚,抛出panic异常(错误码0x32)
在这里插入图片描述
3️⃣push(value): 在数组末尾添加一个给定的元素,没有返回值
4️⃣push(): 添加新的零初始化元素到数组末尾
在这里插入图片描述
push() 的用法示例:
在这里插入图片描述
5️⃣数组切片,仅支持calldata, 主要用于bytes
在这里插入图片描述

c、Solidity 的类型系统和数组长度推断规则
我在solidity的智能合约函数体内声明了一个变量:uint[] memory myArr = [7,8,9];Remix给我红色提示:Type uint8[3] memory is not implicitly convertible to expected type uint256[] memory.

[7,8,9] 被推断为 uint8[3] 类型,调整方案:
uint[] memory myArr = new uint;
myArr = [uint(7), uint(8), uint(9)];

d、数组操作中的gas问题

删除数组中指定索引位置的元素
最佳实践: 先把最后一个元素覆盖到指定索引位置,然后把最后一个元素pop掉
在这里插入图片描述
如果遍历查找,Gas效率会较低。

e、bytes、string是一种特殊的数组

存储优化:紧密打包,节省Gas
特殊编码:长度字段包含紧凑存储标志
内置函数:bytes有专门的成员函数
类型限制:string不可直接索引访问
Gas效率:相比普通数组更节省Gas
专用场景:专门为字节数据和文本数据优化
生成的getter函数:在作为public的状态变量时,生成的getter函数不带参数,而其他数组是带参数的

1️⃣string没有长度和下标
2️⃣将string赋值为中文字符串
Solidity默认字符串字面量仅支持ASCII字符集,直接使用中文等Unicode字符会触发编译错误

从Solidity 0.8.0版本开始,引入了unicode前缀来支持多语言字符。该语法要求字符串必须显式声明为unicode类型,否则编译器无法正确处理非ASCII编码。
在这里插入图片描述
正确的写法: string memory country = unicode"中国";
3️⃣字符串拼接方法对比

contract Comparison {using Strings for uint256;function compareMethods() public pure returns (string[3] memory) {string memory a = "Hello";string memory b = "World";// 方法1: abi.encodePacked (所有版本),还支持所有值类型、动态类型string memory result1 = string(abi.encodePacked(a, " ", b));// 方法2: bytes.concat (0.8.4+)string memory result2 = string(bytes.concat(bytes(a), " ", bytes(b)));// 方法3: string.concat (0.8.12+) - 最简洁!string memory result3 = string.concat(a, " ", b);return [result1, result2, result3];// 所有结果都是: "Hello World"}// Gas 消耗比较function gasComparison() public pure {string memory str1 = "Test";string memory str2 = "String";// 三种方法的Gas消耗大致相当// string.concat 通常是最易读的选择string(abi.encodePacked(str1, str2));string(bytes.concat(bytes(str1), bytes(str2)));string.concat(str1, str2);}
}

当然,也可以用循环方式手动拼接

4️⃣理解十六进制
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 结构体
    在这里插入图片描述
4. 映射类型

1️⃣基本用法

   // 地址到余额的映射mapping(address => uint256) public balances;// 地址到布尔值的映射(常用于白名单)mapping(address => bool) public isWhitelisted;// 用户ID到地址的映射mapping(uint256 => address) public idToAddress;

2️⃣嵌套映射

    mapping(address => UserInfo) public users;mapping(uint256 => mapping(address => uint256)) public poolDeposits; // 池ID -> (用户 -> 存款金额)mapping(uint256 => address[]) public categoryMembers; // 分类ID -> 成员地址列表

3️⃣最佳实践
在这里插入图片描述
4️⃣结构体和映射组合使用,两种做法均有使用

  • 把结构体作为映射的value
  • 把映射作为结构体的成员属性
http://www.dtcms.com/a/498739.html

相关文章:

  • 【Java 开发日记】MySQL 与 Redis 如何保证双写一致性?
  • 基于知识图谱(Neo4j)和大语言模型(LLM)的图检索增强(GraphRAG)的台风灾害知识问答系统(vue+flask+AI算法)
  • 短剧APP开发性能优化专项:首屏加载提速技术拆解
  • 2025年远程控制软件横评:UU远程、ToDesk、向日葵
  • 前端核心理论深度解析:从基础到实践的关键知识点
  • 合肥官方网站建设有哪些公司
  • 大模型-高效优化技术全景解析:微调 量化 剪枝 梯度裁剪与蒸馏 下
  • 微信个人号开发中如何高效实现API二次开发
  • 网页设计与网站建设实战大全wordpress文章页实现图片幻灯展现
  • Ubuntu22.04 VMware虚拟机文件拖放问题:文字复制正常但文件拖放失效
  • Vue Router 路由守卫钩子详解
  • 开源 Linux 服务器与中间件(三)服务器--Nginx
  • Java 大视界 -- Java 大数据在智能农业无人机植保作业路径规划与药效评估中的应用
  • 【OpenGL】模板测试(StencilTest)
  • 文本描述驱动的可视化工具在IDE中的应用与实践
  • C#程序实现将MySQL的存储过程转换成Oracle的存储过程
  • IDEA 中 Tomcat 部署 Java Web 项目
  • 全景网站模版校园微网站建设方案ppt模板
  • 东莞公司网站建设公司哪家好制作网站链接
  • 【Linux】Socket编程UDP
  • “桌面自动化”解救“浏览器自动化”受阻(反爬虫检测)(pywinauto、pyautogui、playwright)
  • 线程安全集合源码速读:Hashtable、Vector、Collections.synchronizedMap
  • 大文件上传与文件下载
  • React Native 项目中 WebSocket 的完整实现方案
  • 电脑建设银行怎样设置网站查询密码手机网站建设价钱是多少
  • Linux内核ida数据结构使用
  • SAP MM委外采购订单执行报表分享
  • Docker中授权普通用户使用docker命令以及解决无权限访问/var/run/docker.sock错误
  • 算法奇妙屋(八)-泰波那契数列模型
  • 荆门哪里做网站女生学建筑工程技术就业前景