📚 第六章 | Solidity 函数与可见性修饰符全面讲解
——写对函数,合约安全又高效!
✅ 本章导读
智能合约中,函数 是一切交互的入口。
不管你是发币、发 NFT、做 DeFi,所有的链上操作都要通过函数来完成。
而 Solidity 的函数有非常细的权限管理和执行逻辑,如果不懂这些规则,你可能会:
- 把外部接口暴露成公开函数 → 被黑客利用
- 没理解
view
和pure
,导致 Gas 浪费 - 不小心写出可重入函数,引发安全漏洞
fallback
和receive
函数没写好,收不到 ETH- 弄错
this.
内外部调用,Gas 费用直接翻倍
这一章,从基础写法到进阶用法,从安全优化到实战应用,一步步来。
✅ 本章你将掌握
- Solidity 函数基础用法
- 可见性修饰符(public / private / internal / external)
- 状态修饰符(pure / view / payable)
- 特殊函数(constructor / fallback / receive)
- 函数重载
- 实战案例
- 最佳实践和常见坑
1️⃣ Solidity 函数基础
Solidity 的函数就是对外暴露的接口,跟区块链用户交互、控制资产转移、调用合约的关键。
✅ 函数基本结构
function 函数名(参数) 可见性 状态修饰符 返回值类型 {
// 函数逻辑
}
示例
function add(uint a, uint b) public pure returns (uint) {
return a + b;
}
2️⃣ 函数的可见性修饰符(Visibility)
修饰符 | 谁能调用? | 应用场景 |
---|---|---|
public |
所有人/合约都能调用 | DApp 接口、合约逻辑外调 |
private |
仅当前合约内部可见 | 内部逻辑封装,防止外部访问 |
internal |
当前合约 + 继承合约访问 | 可继承的内部工具函数、逻辑封装 |
external |
只能外部调用(合约 or 用户) | 面向外部用户,不能内部 函数() 调用(需 this. ) |
✅ public
任何人都能调,合约对外接口默认选择
function getBalance() public view returns (uint) {
return address(this).balance;
}
✅ private
只能本合约内部访问,不能被继承合约访问
function _calcFee(uint amount) private pure returns (uint) {
return amount * 2 / 100;
}
✅ internal
本合约 + 子合约能访问,继承模式下常用
function _mint(address to, uint amount) internal {
balances[to] += amount;
}
✅ external
只能外部访问,不能内部直接调用(但可以用 this.函数()
)
function deposit() external payable {
balances[msg.sender] += msg.value;
}
⚠️ 常见坑
external
比public
更省 Gas(只读参数不复制)external
不支持内部函数直接调,除非this.
,但这样调用贵!- 大多数项目
external
用于对外交易入口、跨合约调用接口
3️⃣ 状态修饰符(State Mutability)
修饰符 | 能干嘛? | 场景 |
---|---|---|
pure |
不能读写任何状态 | 纯函数,数学运算等 |
view |
只能读状态,不改状态 | 查询类函数(查余额、查状态) |
payable |
能接受 ETH | 打钱、收款接口 |
✅ pure
- 无链上状态交互
- 仅纯运算
function add(uint a, uint b) public pure returns (uint) {
return a + b;
}
✅ view
- 可读链上状态
- 不改变状态
function balanceOf(address user) public view returns (uint) {
return balances[user];
}
✅ payable
- 函数可以收 ETH
- 不加
payable
,用户打钱也进不来
function deposit() public payable {
balances[msg.sender] += msg.value;
}
⚠️ 常见坑
- 收 ETH 必须
payable
,不然交易直接失败 payable
通常配合receive()
或fallback()
- 推荐给
constructor
加payable
,支持初始化打款
4️⃣ 特殊函数(constructor / receive / fallback)
👉 constructor(构造函数)
- 合约部署时自动执行一次
- 一般用于初始化 owner、配置参数
constructor(uint _initSupply) {
owner = msg.sender;
totalSupply = _initSupply;
}
⚠️ 注意
- 只能执行一次
- 不可被外部再次调用
payable constructor
支持部署时转 ETH
👉 receive
- 专门收 ETH,必须
payable
- 无参数、无返回值
receive() external payable {
emit Received(msg.sender, msg.value);
}
⚠️ 规则
- 用户
transfer()
或send()
时自动触发 - 优先级高于
fallback
- 不写
receive()
,收不到直接打进来的 ETH
👉 fallback
- 处理未定义函数调用
- 可以
payable
接收 ETH
fallback() external payable {
emit Fallback(msg.sender, msg.value);
}
场景
- 升级代理合约
- 实现
call
路由 - 收未知函数调用
- 捕获错误转账
5️⃣ 函数重载(Overloading)
Solidity 支持多个重名函数,只要参数不同即可。
function register(string memory name) public { ... }
function register(string memory name, uint age) public { ... }
⚠️ 注意
- 只能在编译时决定,前端调用 ABI 必须明确区分
- 建议使用函数签名:
functionName(type1,type2)
6️⃣ 实战案例 | 钱包合约函数设计
✅ 合约目标
- 用户打钱到合约
- owner 提现 ETH
- 记录充值、提现事件
✅ 合约代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract SimpleWallet {
address public owner;
event Deposit(address indexed user, uint amount);
event Withdraw(address indexed user, uint amount);
error NotOwner(address caller);
constructor() payable {
owner = msg.sender;
}
receive() external payable {
emit Deposit(msg.sender, msg.value);
}
fallback() external payable {
emit Deposit(msg.sender, msg.value);
}
function deposit() external payable {
emit Deposit(msg.sender, msg.value);
}
function withdraw(uint amount) external {
if (msg.sender != owner) {
revert NotOwner(msg.sender);
}
(bool success, ) = payable(owner).call{value: amount}("");
require(success, "Withdraw failed");
emit Withdraw(owner, amount);
}
function balance() external view returns (uint) {
return address(this).balance;
}
}
✅ 讲解
receive()
/fallback()
处理所有转账入口withdraw()
权限控制只允许 ownerexternal
+view
节省 Gas- 自定义错误
NotOwner
节省报错 Gas call{value:}
安全转账,避免transfer
限制constructor payable
支持部署时初始化充值
7️⃣ 最佳实践 & 常见坑
✅ 最佳实践
- 外部调用用
external
,内部调用public
- 纯计算函数用
pure
,读状态view
payable
明确收 ETH 的函数receive()
+fallback()
双保险收 ETH- 错误处理推荐用
revert
+ 自定义错误 onlyOwner
权限控制必须有- 事件必须 emit,前端数据靠事件同步
⚠️ 常见坑
- 函数没加
payable
,导致 ETH 打不进来 fallback()
没写payable
,ETH 被拒绝external
函数内部调用爆 Gasconstructor
内部初始化没传值- 函数权限设计不严密 → 被黑客攻击!
✅ 小结
这一章我们系统讲解了 Solidity 函数:
✔️ 函数的声明与结构
✔️ 可见性修饰符
✔️ 状态修饰符
✔️ 特殊函数(constructor、receive、fallback)
✔️ 函数重载
✔️ 实战设计安全函数体系
🎯 课后挑战
- 编写一个 “白名单” DApp
- 注册白名单用户
- owner 批量添加/删除白名单
- 查询白名单状态
- 事件触发白名单变化
- fallback 接收 ETH 并记录日志
- 用 Hardhat 部署本地测试
- 写自动化测试覆盖各种调用
✅ 下一章预告|第七章
👉 合约继承与接口
🚀 多合约协作开发
🚀 继承重写 / 函数重载 / 抽象合约
🚀 接口 interface 设计模式
🚀 实战 DAO / NFT 工厂合约
🚀 多继承冲突与钻石标准