在上一节中,我们了解了 Solidity 的类型系统。现在让我们深入学习最常用的类型之一:整型。
和大多数编程语言一样,当我们要表达一个数值时,通常用整型来表达。整型是智能合约中使用最频繁的数据类型,用于处理代币数量、余额、时间戳等各种数值。
Solidity 提供了 int 和 uint 两类整型,分别表示有符号整数和无符号整数。
支持从 uint8 到 uint256(以及对应的 int8 到 int256),关键字末尾的数字以 8 步进,表示变量所占的位数(bit)。
uint 默认等同于 uint256int 默认等同于 int256| 类型 | 位数 | 取值范围 | 存储大小 |
|---|---|---|---|
uint8 |
8 位 | 0 到 255 (2^8-1) | 1 字节 |
uint32 |
32 位 | 0 到 4,294,967,295 (2^32-1) | 4 字节 |
uint256 |
256 位 | 0 到 2^256-1 | 32 字节 |
int8 |
8 位 | -128 到 127 | 1 字节 |
int32 |
32 位 | -2,147,483,648 到 2,147,483,647 | 4 字节 |
int256 |
256 位 | -2^255 到 2^255-1 | 32 字节 |
之前我们的 Counter 合约里就定义了一个 uint 变量 counter:
pragma solidity ^0.8.0;
contract Counter {
uint public counter; // 默认值为 0
}
默认值:当没有为整型变量赋值时,会使用默认值 0。
pragma solidity ^0.8.0;
contract DefaultValues {
uint public a; // 默认值:0
int public b; // 默认值:0
uint8 public c; // 默认值:0
function checkDefaults() public view returns (uint, int, uint8) {
return (a, b, c); // 返回 (0, 0, 0)
}
}
提示:
在 Solidity 0.8版本之前, 如果整数运算结果不在取值范围内,则会被溢出截断。
从 0.8.0 开始,算术运算有两个计算模式:一个是 unchecked(不检查)模式,一个是"checked" (检查)模式。
默认情况下,算术运算在 "checked" 模式下,即都会进行溢出检查,如果结果落在取值范围之外,调用会通过 失败异常 回退。 你也可以通过
unchecked { ... }切换到 "unchecked"模式,更多可参考文档 unchecked 。Gas 优化: 当我们确定一个运算不会发生溢出时,使用
unchecked模式,有更高的 GAS 效率。
整型支持的运算符包括以下几种:
<=(小于等于)、<(小于)、==(等于)、!=(不等于)、>=(大于等于)、>(大于)pragma solidity ^0.8.0;
contract ComparisonExample {
function compare(uint a, uint b) public pure returns (bool, bool, bool) {
return (a > b, a == b, a < b);
}
}
+(加)、-(减)、*(乘)、/(除)、%(取余)、**(幂)pragma solidity ^0.8.0;
contract ArithmeticExample {
function arithmetic(uint a, uint b) public pure returns (uint, uint, uint, uint, uint) {
return (
a + b, // 加法
a - b, // 减法(a 必须 >= b,否则会 revert)
a * b, // 乘法
a / b, // 除法(会截断小数部分)
a % b // 取余
);
}
function power(uint base, uint exponent) public pure returns (uint) {
return base ** exponent; // 幂运算
}
}
&(按位与)、|(按位或)、^(按位异或)、~(按位取反)pragma solidity ^0.8.0;
contract BitwiseExample {
function bitwiseOperations(uint a, uint b) public pure returns (uint, uint, uint, uint) {
return (
a & b, // 按位与:5 & 3 = 1 (0101 & 0011 = 0001)
a | b, // 按位或:5 | 3 = 7 (0101 | 0011 = 0111)
a ^ b, // 按位异或:5 ^ 3 = 6 (0101 ^ 0011 = 0110)
~a // 按位取反
);
}
}
<<(左移位)、>>(右移位)pragma solidity ^0.8.0;
contract ShiftExample {
function shiftOperations(uint x) public pure returns (uint, uint) {
return (
x << 1, // 左移 1 位,相当于乘以 2
x >> 1 // 右移 1 位,相当于除以 2(整数除法)
);
}
}
整数除法会截断:整型变量除法总是会截断取整(向下取整)
uint result = 5 / 2; // 结果是 2,不是 2.5
除零会抛出异常:整数除以 0 会导致交易回退
uint result = 10 / 0; // ❌ 会 revert
负数右移的特殊行为:有符号整数右移会保留符号位(算术右移)
int x = -8;
int result = x >> 1; // 结果是 -4,而不是一个很大的正数
还可以通过变量的类型,获取其取值范围。例如:对于整型 X,可以使用 type(X).min 和 type(X).max 去获取这个类型的最小值与最大值。
pragma solidity ^0.8.0;
contract TypeLimits {
function getUint8Limits() public pure returns (uint, uint) {
return (type(uint8).min, type(uint8).max); // 返回 (0, 255)
}
function getInt8Limits() public pure returns (int, int) {
return (type(int8).min, type(int8).max); // 返回 (-128, 127)
}
}
## 操练
### 练习:探索整型运算
下面的代码包含了多个测试函数,帮助你理解整型运算的各种特性。**在运行之前,先自己预测一下每个函数的返回结果!**
```SolidityEditor
pragma solidity ^0.8.0;
contract testInt {
int8 a = -1;
int16 b = 2;
uint32 c = 10;
uint8 d = 16;
function add(uint x, uint y) public pure returns (uint z) {
z = x + y;
}
function divide(uint x, uint y ) public pure returns (uint z) {
z = x / y;
}
function leftshift(int x, uint y) public pure returns (int z){
z = x << y;
}
function rightshift(int x, uint y) public pure returns (int z){
z = x >> y;
}
function testPlusPlus() public pure returns (uint ) {
uint x = 1;
uint y = ++x; // c = ++a;
return y;
}
function testMul1() public pure returns (uint8) {
unchecked {
uint8 x = 128;
uint8 y = x * 2;
return y;
}
}
function testMul2() public pure returns (uint8) {
uint8 x = 128;
uint8 y = x * 2;
return y;
}
}
任务 1:基础运算
add(10, 20),预测结果是?divide(5, 2),预测结果是?(提示:整数除法)divide(10, 0),会发生什么?任务 2:位运算
leftshift(4, 1),预测结果是?(提示:左移相当于乘以 2)rightshift(8, 2),预测结果是?(提示:右移相当于除以 2)任务 3:自增运算符
testPlusPlus(),返回值是 1 还是 2?(提示:前置自增 ++x vs 后置自增 x++)任务 4:溢出对比 ⚠️ 重点!
testMul1(),预测结果是?testMul2(),预测结果是?testMul1() 的结果是0,而不是256,这是因为发生了溢出,溢出就像时钟一样,当秒针走到59之后,下一秒又从0开始。

testMul2() 调用则会失败(revert),如图:

在 0.8.0 之前或者在 unchecked 模式下,我们都需要防止整型溢出问题,一个方法是对结果使用进行判断,防止出现异常值,例如:
pragma solidity ^0.5.0;
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a); // 做溢出判断,加法的结果肯定比任何一个元素大。
return c;
}
pragma solidity ^0.8.0;
contract testUintGas {
uint z1;
function add_high_gas(uint x, uint y) public {
z1 = x + y;
}
uint z2;
function add_less_gas(uint x, uint y) public {
unchecked {
z2 = x + y;
}
}
}
前面提到,当我们确定一个运算不会发生溢出时,使用 unchecked 模式,有更高的 GAS 效率。
以下是 add_high_gas 和 add_less_gas 使用同样参数时的 Gas 消耗对比:


Gas 消耗对比表:
| 函数 | Transaction Cost | Execution Cost | 说明 |
|---|---|---|---|
add_high_gas |
43,684 gas | 22,496 gas | 默认 checked 模式 |
add_less_gas |
43,572 gas | 22,384 gas | unchecked 模式 |
| 节省 | 112 gas | 112 gas | 约节省 0.26% |
Gas 指标说明:
gas_used优化建议: 虽然单次节省的 gas 不多,但在循环或频繁调用的场景下,累计节省会很可观。前提是你必须确保运算不会溢出!
在实际开发中,如何选择使用 uint 还是 int?
✅ 推荐使用 uint:
contract TokenExample {
mapping(address => uint) public balances; // ✅ 余额不会为负
uint public totalSupply; // ✅ 总供应量
uint[] public tokenIds; // ✅ 数组索引和长度都是 uint
}
✅ 推荐使用 int:
contract SignedExample {
int public temperature; // ✅ 温度可以为负
int public priceChange; // ✅ 价格变化(正负)
function calculateDelta(uint oldValue, uint newValue) public pure returns (int) {
return int(newValue) - int(oldValue); // ✅ 可能为负的差值
}
}
// ❌ 错误示例
uint x = 0;
x = x - 1; // 会 revert(在 checked 模式下)
// ✅ 正确做法
uint x = 0;
if (x > 0) {
x = x - 1;
}
// ❌ 错误示例
uint result = 5 / 2; // 结果是 2,不是 2.5
// ✅ 正确做法(如果需要精度)
uint result = (5 * 1e18) / 2; // 使用更大的分子保留精度
// 结果是 2.5 * 1e18
// ❌ 危险示例
uint256 big = 300;
uint8 small = uint8(big); // small = 44 (300 % 256)
// ✅ 安全做法
uint256 big = 300;
require(big <= type(uint8).max, "Value too large");
uint8 small = uint8(big);
// ❌ 容易出错
int a = -1;
uint b = 1;
// a < b 的结果可能不是你期望的!因为涉及类型转换
// ✅ 明确类型
int a = -1;
int b = 1;
bool result = a < b; // 明确使用相同类型
本节我们深入学习了 Solidity 中的整型,这是合约开发中使用最频繁的类型。让我们回顾一下重点:
uint 和 int 分别表示无符号和有符号整数uint8、uint16...uint256)uint 和 int 默认为 256 位