Solidity 开发环境 + 数据类型 + 变量
掌握 Remix IDE 使用、Solidity 全部基础数据类型、三种变量类型
日期: 2026-04-10 方向: Solidity 阶段: 第一阶段:基础构建 标签: #solidity #remix #data-types #variables #basics
今日目标
| 类型 | 内容 |
|---|---|
| 学习 | 掌握 Remix IDE 使用、Solidity 全部基础数据类型、三种变量类型 |
| 实操 | 在 Remix 中编写和部署合约,在 Sepolia 测试网部署第一个合约 |
| 产出 | 一个包含所有数据类型的 DataTypes 合约 + 部署到 Sepolia 的交易记录 |
一、Remix IDE 完全指南
1.1 什么是 Remix
Remix(https://remix.ethereum.org)是以太坊官方推荐的浏览器端 Solidity 开发环境。对于初学者来说,它是最快上手的工具——无需安装任何东西,打开浏览器即可编写、编译、部署和调试智能合约。
1.2 Remix 界面布局
打开 Remix 后,你会看到四个核心区域:
┌─────────────────────────────────────────────────┐
│ 📁 文件浏览器 │ 📝 代码编辑器 │
│ (左侧面板) │ (中央区域) │
│ │ │
│ contracts/ │ // SPDX-License-Identifier: MIT │
│ scripts/ │ pragma solidity ^0.8.0; │
│ tests/ │ │
│ │ contract MyFirst { ... } │
├─────────────────┼───────────────────────────────────┤
│ 🔧 插件面板 │ 📋 终端/控制台 │
│ - Compiler │ (底部区域) │
│ - Deploy │ 交易日志、编译输出 │
│ - Debugger │ │
└─────────────────┴───────────────────────────────────┘
1.3 关键操作流程
Step 1: 创建合约文件
- 在左侧 File Explorer 中,点击 contracts 文件夹
- 点击新建文件图标,命名为
DataTypes.sol
Step 2: 编写合约代码
- 每个
.sol文件开头必须有 License 标识和 pragma 版本声明
Step 3: 编译合约
- 左侧点击 "Solidity Compiler" 插件(第二个图标)
- 选择编译器版本(推荐 0.8.20+)
- 点击 "Compile" 按钮
- 绿色勾表示编译成功
Step 4: 部署合约
- 左侧点击 "Deploy & Run Transactions" 插件(第三个图标)
- Environment 选择 "Remix VM (Shanghai)" 做本地测试
- 点击 "Deploy" 按钮
1.4 部署到 Sepolia 测试网
要部署到真正的区块链(测试网),需要:
- 安装 MetaMask 浏览器扩展
- 切换到 Sepolia 测试网络
- 领取测试 ETH:访问 https://sepoliafaucet.com 或 https://faucets.chain.link/sepolia
- 在 Remix 的 Environment 中选择 "Injected Provider - MetaMask"
- MetaMask 会弹出确认框,点击确认即可部署
二、Solidity 数据类型详解
Solidity 是静态类型语言,每个变量在编译时必须明确类型。这与 JavaScript/Python 这类动态类型语言有本质区别。理解数据类型对于智能合约开发至关重要,因为不同类型的 gas 消耗不同,选错类型可能导致严重的安全漏洞。
2.1 布尔类型 (bool)
bool public isActive = true;
bool public isCompleted = false;
// 布尔运算
bool public result1 = !isActive; // 逻辑非: false
bool public result2 = isActive && isCompleted; // 逻辑与: false
bool public result3 = isActive || isCompleted; // 逻辑或: true
注意事项:
- 默认值为
false - gas 优化:Solidity 支持短路求值(short-circuit evaluation),
false && expr不会计算expr
2.2 整数类型 (uint / int)
这是 Solidity 中最常用的类型。uint 表示无符号整数(≥0),int 表示有符号整数(可以为负)。
// 无符号整数 - 最常用
uint8 public smallNum = 255; // 0 ~ 255 (2^8 - 1)
uint16 public medNum = 65535; // 0 ~ 65535
uint32 public timestamp = 1700000000; // 常用于时间戳
uint64 public biggerNum = 18446744073709551615;
uint128 public veryBig = 0;
uint256 public hugeNum = 0; // 0 ~ 2^256 - 1
// uint 等同于 uint256
uint public defaultUint = 100;
// 有符号整数
int8 public signedSmall = -128; // -128 ~ 127
int256 public signedBig = -1;
int public defaultInt = -50; // int 等同于 int256
// 整数运算
uint public sum = 10 + 20; // 加法: 30
uint public diff = 20 - 10; // 减法: 10
uint public product = 5 * 6; // 乘法: 30
uint public quotient = 10 / 3; // 除法: 3 (向下取整,没有小数!)
uint public remainder = 10 % 3; // 取模: 1
uint public power = 2 ** 10; // 幂运算: 1024
关键知识点:
-
为什么 uint256 最常用? EVM 的原生字长是 256 位,使用 uint256 最高效。使用更小的类型(如 uint8)在单独使用时反而可能消耗更多 gas,因为 EVM 需要额外操作来截断到更小的范围。但在 struct 中紧凑排列小类型可以节省存储。
-
溢出保护:Solidity 0.8.0+ 内置溢出检查。
uint8(255) + 1会自动 revert,不再需要 SafeMath 库。 -
没有浮点数! Solidity 不支持浮点数。处理小数通常使用放大倍数,例如 ERC20 代币用
decimals = 18,即 1 Token =1 * 10^18最小单位。
// 模拟小数:1.5 ETH = 1500000000000000000 wei
uint public onePointFiveEth = 1.5 ether; // Solidity 内置单位
uint public oneGwei = 1 gwei; // = 10^9 wei
uint public oneEther = 1 ether; // = 10^18 wei
2.3 地址类型 (address)
地址是 Solidity 特有的类型,表示以太坊上的 20 字节(160位)账户地址。
// 普通地址
address public owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
// 可支付地址 - 可以接收 ETH
address payable public recipient = payable(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4);
// 地址的属性和方法
// addr.balance - 获取地址余额(单位: wei)
// addr.transfer(amount) - 发送 ETH(失败则 revert)
// addr.send(amount) - 发送 ETH(失败返回 false)
// addr.call{value: amount}("") - 低级调用(推荐方式)
address vs address payable:
address: 不能直接接收 ETHaddress payable: 可以调用.transfer()和.send()发送 ETH- 从
address转为address payable需要显式转换:payable(addr)
function getBalance(address _addr) public view returns (uint) {
return _addr.balance;
}
function sendEther(address payable _to) public payable {
// 推荐使用 call 发送 ETH
(bool success, ) = _to.call{value: msg.value}("");
require(success, "Transfer failed");
}
2.4 字节类型 (bytes)
// 固定大小字节数组
bytes1 public a = 0x41; // 'A' 的 ASCII 码,1字节
bytes2 public b = 0x4142; // 'AB'
bytes4 public selector = 0xa9059cbb; // transfer 函数选择器
bytes20 public addrBytes = bytes20(owner); // 地址本质上就是 bytes20
bytes32 public hash = keccak256("hello"); // 哈希结果是 bytes32
// 动态大小字节数组
bytes public dynamicData = "hello world";
bytes public emptyBytes = "";
// bytes32 vs string
// bytes32: 固定长度,gas 便宜,适合存哈希/ID
// string: 动态长度,gas 贵,适合存可读文本
2.5 字符串类型 (string)
string public greeting = "Hello, Solidity!";
string public name = unicode"你好世界"; // 支持 Unicode
// 字符串在 Solidity 中功能有限:
// - 不能直接比较:greeting == "Hello" ❌
// - 不能直接获取长度:greeting.length ❌
// - 不能直接拼接(0.8.12+ 可以用 string.concat)
// 字符串比较的正确方式
function compareStrings(string memory _a, string memory _b) public pure returns (bool) {
return keccak256(abi.encodePacked(_a)) == keccak256(abi.encodePacked(_b));
}
// 字符串拼接 (Solidity 0.8.12+)
function concat(string memory _a, string memory _b) public pure returns (string memory) {
return string.concat(_a, _b);
}
2.6 枚举类型 (enum)
enum Status { Pending, Active, Completed, Cancelled }
// Pending = 0, Active = 1, Completed = 2, Cancelled = 3
Status public currentStatus = Status.Pending;
function activate() public {
currentStatus = Status.Active;
}
function getStatusAsUint() public view returns (uint) {
return uint(currentStatus); // 枚举转整数
}
三、三种变量类型
Solidity 有三种变量类型,它们的存储位置和 gas 成本完全不同。理解这三种变量是写好合约的基础。
3.1 状态变量 (State Variables)
状态变量永久存储在区块链上(storage)。它们是合约的"持久化数据"。
contract StateVariableExample {
// 状态变量 - 存储在区块链上
uint public count = 0; // 任何人可读
string private secretMsg = "hi"; // 仅合约内部可读(但链上数据仍公开!)
address internal admin; // 合约和子合约可读
// 每次修改状态变量都要消耗 gas(SSTORE 操作码)
// 首次写入: ~20,000 gas
// 后续修改: ~5,000 gas
// 读取: ~200 gas (SLOAD)
function increment() public {
count += 1; // 修改状态变量,消耗 gas
}
}
3.2 局部变量 (Local Variables)
局部变量只存在于函数执行期间,存储在内存(memory)或栈(stack)中。
function calculate(uint _x) public pure returns (uint) {
// 局部变量 - 函数执行完就消失,不消耗存储 gas
uint temp = _x * 2;
uint result = temp + 10;
return result;
}
3.3 全局变量 (Global Variables)
全局变量是 Solidity 预定义的特殊变量,提供区块链和交易的信息。
contract GlobalVariables {
function getInfo() public view returns (
address sender,
uint value,
uint gasPrice,
uint blockNum,
uint blockTime,
uint chainId
) {
sender = msg.sender; // 调用者地址
value = msg.value; // 发送的 ETH 数量(wei)
gasPrice = tx.gasprice; // 交易 gas 价格
blockNum = block.number; // 当前区块号
blockTime = block.timestamp; // 当前区块时间戳(秒)
chainId = block.chainid; // 链 ID (1=主网, 11155111=Sepolia)
}
}
常用全局变量一览:
| 变量 | 类型 | 说明 |
|---|---|---|
msg.sender | address | 当前调用者地址 |
msg.value | uint | 随交易发送的 ETH(wei) |
msg.data | bytes | 完整的 calldata |
msg.sig | bytes4 | calldata 前4字节(函数选择器) |
block.timestamp | uint | 当前区块时间戳 |
block.number | uint | 当前区块号 |
block.chainid | uint | 链 ID |
block.prevrandao | uint | 前一个区块的 randao 值 |
tx.gasprice | uint | 交易 gas 价格 |
tx.origin | address | 交易发起者(最初的 EOA) |
gasleft() | uint | 剩余 gas |
⚠️ 安全警告:永远不要用 tx.origin 做权限校验!用 msg.sender。因为 tx.origin 总是指向最初的外部账户,容易被中间合约利用做钓鱼攻击。
四、代码实战:完整 DataTypes 合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/// @title DataTypes - Solidity 数据类型学习合约
/// @author SC Day1 Learning
/// @notice 这个合约展示了 Solidity 中所有基础数据类型
contract DataTypes {
// ============ 布尔类型 ============
bool public isActive = true;
// ============ 整数类型 ============
uint8 public maxUint8 = type(uint8).max; // 255
uint256 public maxUint256 = type(uint256).max; // 超大数
int8 public minInt8 = type(int8).min; // -128
int8 public maxInt8 = type(int8).max; // 127
uint public tokenDecimals = 18;
uint public totalSupply = 1_000_000 * 10 ** 18; // 1M tokens, 下划线提高可读性
// ============ 地址类型 ============
address public owner;
address payable public treasury;
// ============ 字节类型 ============
bytes1 public singleByte = 0xff;
bytes4 public transferSelector = bytes4(keccak256("transfer(address,uint256)"));
bytes32 public dataHash;
bytes public dynamicBytes = hex"deadbeef";
// ============ 字符串类型 ============
string public projectName = "MomoWeb3";
string public description;
// ============ 枚举类型 ============
enum OrderStatus { Created, Paid, Shipped, Completed, Cancelled }
OrderStatus public currentOrder = OrderStatus.Created;
// ============ 状态变量: 常量和不可变量 ============
uint public constant MAX_SUPPLY = 21_000_000; // 编译时确定,不占存储槽
address public immutable deployer; // 部署时确定,之后不可改
uint public immutable deployTime;
// ============ 全局变量演示 ============
uint public lastCaller;
constructor(string memory _description) {
owner = msg.sender;
treasury = payable(msg.sender);
deployer = msg.sender;
deployTime = block.timestamp;
description = _description;
dataHash = keccak256(abi.encodePacked(_description));
}
/// @notice 展示整数运算
function mathOperations(uint _a, uint _b) public pure returns (
uint sum,
uint diff,
uint product,
uint quotient,
uint remainder
) {
require(_b > 0, "Division by zero");
require(_a >= _b, "Underflow protection");
sum = _a + _b;
diff = _a - _b;
product = _a * _b;
quotient = _a / _b;
remainder = _a % _b;
}
/// @notice 展示地址操作
function getAddressInfo(address _addr) public view returns (
uint balance,
bool isContract,
bytes32 codeHash
) {
balance = _addr.balance;
// 判断是否是合约地址
uint size;
assembly {
size := extcodesize(_addr)
}
isContract = size > 0;
codeHash = _addr.codehash;
}
/// @notice 展示全局变量
function getGlobalInfo() public view returns (
address sender,
uint blockNum,
uint blockTime,
uint gasLeft,
uint chainId
) {
sender = msg.sender;
blockNum = block.number;
blockTime = block.timestamp;
gasLeft = gasleft();
chainId = block.chainid;
}
/// @notice 更新订单状态
function updateOrderStatus(OrderStatus _newStatus) public {
require(msg.sender == owner, "Only owner");
currentOrder = _newStatus;
}
/// @notice 字符串比较
function isProjectName(string memory _name) public view returns (bool) {
return keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked(projectName));
}
/// @notice 接收 ETH
receive() external payable {}
/// @notice 查看合约余额
function getContractBalance() public view returns (uint) {
return address(this).balance;
}
}
五、Sepolia 部署实操步骤
5.1 准备工作
- 安装 MetaMask:到 https://metamask.io 下载浏览器扩展
- 切换网络:MetaMask → 网络下拉 → 显示测试网络 → 选择 Sepolia
- 领取测试 ETH:
- https://sepoliafaucet.com(需要 Alchemy 账号)
- https://faucets.chain.link/sepolia(需要连接钱包)
- 每次约可领取 0.5 ETH,部署一个简单合约约消耗 0.001-0.01 ETH
5.2 在 Remix 中部署
- 打开 Remix → 粘贴上面的合约代码
- Compiler 面板 → 选择 0.8.20 → Compile
- Deploy 面板 → Environment 选择 "Injected Provider - MetaMask"
- 确认网络显示 "Sepolia (11155111)"
- 在 Deploy 旁边的输入框中填写构造函数参数(如
"My first contract") - 点击 Deploy → MetaMask 弹出确认 → 确认交易
- 等待 15-30 秒,交易确认后合约地址会出现在 "Deployed Contracts" 下方
5.3 在 Etherscan 上验证
部署成功后,复制合约地址,到 https://sepolia.etherscan.io 搜索,可以看到:
- 部署交易的详情
- 合约的字节码
- 如果验证了源码,可以直接在 Etherscan 上读写合约
六、关键要点总结
| 要点 | 说明 |
|---|---|
| Solidity 是静态类型 | 变量必须声明类型,编译时检查 |
| uint256 是默认整数 | EVM 原生字长,gas 最优 |
| 没有浮点数 | 用放大倍数模拟小数 (10^18) |
| address vs address payable | 只有 payable 能转 ETH |
| 状态变量 = 链上存储 | 最贵的操作,SSTORE ~20000 gas |
| constant/immutable 省 gas | 不占存储槽,编译时/部署时确定 |
| msg.sender ≠ tx.origin | 永远用 msg.sender 做权限检查 |
七、常见误区
误区 1:以为 private 变量真的"私有"
uint private secret = 42;
// ❌ 链上所有数据都是公开的!
// 任何人都可以通过读取 storage slot 获取 private 变量的值
// private 只是说其他合约不能通过接口读取
误区 2:uint 减法下溢
uint a = 5;
uint b = 10;
// uint c = a - b; // 0.8.0+ 自动 revert!
// 0.7.x 及以前:c 会变成一个极大的数(下溢)
误区 3:string 和 bytes 混淆
// string 适合存储人类可读文本
// bytes32 适合存储哈希、ID 等固定长度数据
// 不要用 string 存哈希——gas 浪费严重
误区 4:以为 block.timestamp 精确
// block.timestamp 由矿工/验证者设置
// 有小范围的操纵空间(约 15 秒)
// 不要用于需要精确到秒的逻辑
八、面试关联
Q: Solidity 中 uint8 和 uint256 哪个更省 gas?
A: 在单独使用时,uint256 更省 gas,因为 EVM 原生字长是 256 位,使用更小的类型需要额外的掩码操作。但在 struct 中,将多个小类型紧凑排列可以共享一个存储槽(32 字节),此时小类型更省 gas。
Q: 为什么 Solidity 没有浮点数?
A: 浮点数有精度问题(0.1 + 0.2 ≠ 0.3),这在金融场景中不可接受。区块链上的资产是精确到 wei 的整数,不存在"约等于"的概念。所有小数都通过放大倍数来处理。
Q: msg.sender 和 tx.origin 有什么区别?
A: msg.sender 是直接调用当前函数的地址(可以是 EOA 或合约),tx.origin 始终是发起这笔交易的 EOA。用 tx.origin 做权限检查有钓鱼攻击风险——恶意合约可以诱导用户调用自己,然后转发调用到目标合约,此时 tx.origin 仍是用户。
九、参考资源
| 资源 | 链接 | 说明 |
|---|---|---|
| Solidity 官方文档 | https://docs.soliditylang.org/ | 最权威的参考 |
| Remix IDE | https://remix.ethereum.org/ | 在线开发环境 |
| Solidity by Example | https://solidity-by-example.org/ | 代码示例大全 |
| Sepolia Etherscan | https://sepolia.etherscan.io/ | 测试网区块浏览器 |
| OpenZeppelin Docs | https://docs.openzeppelin.com/ | 合约安全库 |
| CryptoZombies | https://cryptozombies.io/ | 游戏化学习 Solidity |