本章将深入讲解 Solidity 的语言结构与基本语法,让你从"能看懂"走向"能写出"。我们将探索变量、数据类型、函数、可见性、事件、修饰符等概念,用生动的比喻、清晰的表格和实战示例帮助你理解。通过构建一个简单的"留言墙(Message Wall)"智能合约,你将掌握状态与内存变量的区别、函数的 Gas 成本、事件的日志机制,以及 Solidity 编程的独特思维方式。这一章是你成为 Solidity 开发者的真正起点。
关键词: 变量作用域、数据类型、函数修饰符、事件、状态管理
3.1 语言基础概览:Solidity 的语法风格
"Solidity 看起来像 JavaScript,却更像法律文件。"
Solidity 的语法灵感来源于 JavaScript、C++ 和 Python。
它像 JS 一样使用大括号 {},但又像 C 一样严格定义类型。
如果你熟悉这些语言,你会觉得 Solidity "似曾相识",但又"处处不同"。
🧠 Solidity 与常见语言语法对比
| 特性 | JavaScript | Solidity | 备注 |
|---|---|---|---|
| 变量声明 | let a = 1; |
uint a = 1; |
Solidity 要声明类型 |
| 数组 | [1, 2, 3] |
uint[] nums = [1,2,3]; |
固定/动态长度皆可 |
| 字符串 | "hello" |
"hello" |
字符串不能直接拼接 |
| 函数定义 | function add(a,b){} |
function add(uint a, uint b) public returns(uint){} |
必须声明可见性与返回值 |
| 类 | class |
contract |
合约即类 |
Solidity 就像"严格模式的 JavaScript",
但它运行在全球分布式计算机网络上。
3.2 数据类型与变量:区块链上的存储单位
"每一个变量,都是写进账本的墨迹。"
🧩 Solidity 中的主要数据类型
| 类型分类 | 示例 | 说明 |
|---|---|---|
| 整数类型 | uint256, int8 |
uint 表示无符号整数(0或正数) |
| 布尔类型 | bool |
仅 true 或 false |
| 地址类型 | address |
表示账户地址(钱包/合约) |
| 字符串 | string |
存储文本 |
| 固定字节数组 | bytes1 ~ bytes32 |
固定长度的二进制数据 |
| 动态数组 | uint[], string[] |
可扩展集合 |
| 映射(mapping) | mapping(address => uint) |
类似字典或哈希表 |
| 结构体(struct) | 自定义类型 | 类似类的简单形式 |
📦 状态变量与局部变量
- 状态变量(State Variable) :
保存在区块链上,修改它需要交易与 Gas。 - 局部变量(Local Variable) :
保存在内存中,只在函数执行时存在,不上链。
solidity
pragma solidity ^0.8.0;
contract VariableDemo {
uint public counter = 0; // 状态变量
function increase() public {
uint temp = counter; // 局部变量
temp += 1;
counter = temp; // 写回区块链(消耗 Gas)
}
}
| 操作类型 | 存储位置 | 是否消耗 Gas | 生命周期 |
|---|---|---|---|
| 状态变量 | 区块链存储(storage) | ✅ 是 | 永久保存 |
| 局部变量 | 内存(memory) | ❌ 否 | 函数执行期间 |
| 常量 | 编译时写死 | ❌ 否 | 永久不变 |
💡 "Storage、Memory、Calldata"的区别图解
+----------------------------------------------------------+
| 区块链永久存储 (Storage) → 状态变量 |
| 函数临时存储 (Memory) → 临时对象 |
| 只读外部数据 (Calldata) → 外部函数参数 |
+----------------------------------------------------------+
可简单理解为:
storage:存到硬盘;memory:存到内存;calldata:传参快递箱。
3.3 函数与可见性修饰符
"在 Solidity 世界中,函数不仅定义行为,也定义信任边界。"
🧠 函数的定义格式
solidity
function functionName(type parameter)
visibility
stateMutability
returns(type)
{
// 函数体
}
例如:
solidity
function add(uint a, uint b) public pure returns(uint) {
return a + b;
}
📜 可见性(Visibility)关键字
| 修饰符 | 说明 | 谁能调用 | 类比 |
|---|---|---|---|
public |
任何人可调用 | 所有用户与合约 | 公共服务 |
external |
仅外部调用 | 外部账户 | API 接口 |
internal |
合约内部可见 | 继承合约 | 保护函数 |
private |
当前合约内部 | 无法继承 | 私有函数 |
💧 状态修饰符(State Mutability)
| 关键字 | 说明 | 是否修改状态 | 是否消耗 Gas |
|---|---|---|---|
pure |
不读不写 | ❌ | ❌ |
view |
只读 | ❌ | ❌ |
| (无修饰) | 可写 | ✅ | ✅ |
payable |
可接收 ETH | ✅ | ✅ |
示例:
solidity
function getCounter() public view returns(uint) {
return counter;
}
function add(uint a, uint b) public pure returns(uint) {
return a + b;
}
function receiveEther() public payable {
// 可接收 ETH
}
3.4 修饰符(Modifier):智能合约的"守门员"
"Modifier 是 Solidity 的防线,用来设定进入规则。"
修饰符(modifier)允许你在函数执行前后添加条件逻辑。
比如:只有管理员能执行某个函数。
solidity
modifier onlyOwner() {
require(msg.sender == owner, "Not authorized");
_;
}
_ 表示函数主体在此处执行。
在函数中使用:
solidity
function withdraw() public onlyOwner {
payable(owner).transfer(address(this).balance);
}
执行流程图:
调用函数 → 检查 onlyOwner → 如果通过 → 执行函数体
(图3-1:Modifier 执行流程)
3.5 事件(Event):区块链的日志系统
"区块链没有数据库,但它有事件日志。"
事件是 Solidity 用来在区块链上记录活动的机制。
类似于后端日志(log),但会被写入链上。
solidity
event NewMessage(address indexed sender, string content);
function postMessage(string memory content) public {
emit NewMessage(msg.sender, content);
}
事件会在区块中被永久记录,可被 DApp 前端监听。
前端可通过 ethers.js 或 web3.js 读取事件日志。
| 事件元素 | 说明 |
|---|---|
event |
声明事件 |
emit |
触发事件 |
indexed |
可检索字段 |
| 区块日志 | 可供前端监听 |
3.6 实战项目:构建一个"留言墙(Message Wall)"智能合约
"从一句留言,开始你的链上交流。"
让我们结合本章所学,写一个完整可运行的项目。
这个合约允许用户:
- 留下留言;
- 记录留言者地址;
- 触发事件;
- 读取所有留言。
💬 合约代码
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract MessageWall {
struct Message {
address sender;
string text;
uint timestamp;
}
Message[] public messages;
address public owner;
event NewMessage(address indexed sender, string text, uint timestamp);
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
constructor() {
owner = msg.sender;
}
function postMessage(string memory _text) public {
messages.push(Message(msg.sender, _text, block.timestamp));
emit NewMessage(msg.sender, _text, block.timestamp);
}
function getMessagesCount() public view returns (uint) {
return messages.length;
}
function clearMessages() public onlyOwner {
delete messages;
}
}
🧩 功能说明表
| 函数 | 功能 | 修饰符 | Gas 成本 |
|---|---|---|---|
postMessage() |
发布新留言 | 无 | 高 |
getMessagesCount() |
查询留言数 | view | 无 |
clearMessages() |
删除所有留言 | onlyOwner | 高 |
🧠 运行逻辑示意图
用户 → postMessage() → 写入 messages[] → emit NewMessage() → 区块链事件日志
(图3-2:MessageWall 执行流程)
3.7 区块链思维:为什么写入成本高?
"区块链上的每一个字节,都是全球共识的成本。"
当你写入一条留言时:
- 所有节点都要保存这条信息;
- 共识算法要验证这条交易;
- 因此写入成本(Gas)远高于传统数据库。
阅读(view)是免费的,写入是昂贵的。
这也引出了区块链的设计哲学:
计算要轻,存储要少,逻辑要明。
3.8 小结与思考
本章你学会了:
- Solidity 的基本语法与类型;
- 函数可见性与状态修饰符;
- 使用修饰符定义权限;
- 事件机制;
- 构建完整留言墙项目。
📘 思考练习
- 给
MessageWall添加一个like功能,每条留言可被点赞。 - 实现留言分页查询函数。
- 使用事件记录点赞行为。
- 通过 MetaMask 将合约部署到测试网。
下一章预告:
第4章《Solidity 进阶特性与合约间交互》
我们将探索继承、多态、接口(Interface)、抽象合约、库(Library),以及合约之间如何互相调用。
这将是从"代码逻辑"迈向"智能生态"的关键一步。