第六章 | Solidity 函数与可见性修饰符全面讲解

发布于:2025-03-25 ⋅ 阅读:(19) ⋅ 点赞:(0)

📚 第六章 | Solidity 函数与可见性修饰符全面讲解

——写对函数,合约安全又高效!


✅ 本章导读

智能合约中,函数 是一切交互的入口。
不管你是发币、发 NFT、做 DeFi,所有的链上操作都要通过函数来完成。

而 Solidity 的函数有非常细的权限管理执行逻辑,如果不懂这些规则,你可能会:

  • 把外部接口暴露成公开函数 → 被黑客利用
  • 没理解 viewpure,导致 Gas 浪费
  • 不小心写出可重入函数,引发安全漏洞
  • fallbackreceive 函数没写好,收不到 ETH
  • 弄错 this. 内外部调用,Gas 费用直接翻倍

这一章,从基础写法到进阶用法,从安全优化到实战应用,一步步来。


✅ 本章你将掌握

  1. Solidity 函数基础用法
  2. 可见性修饰符(public / private / internal / external)
  3. 状态修饰符(pure / view / payable)
  4. 特殊函数(constructor / fallback / receive)
  5. 函数重载
  6. 实战案例
  7. 最佳实践和常见坑

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;
}

⚠️ 常见坑

  • externalpublic 更省 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()
  • 推荐给 constructorpayable,支持初始化打款

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() 权限控制只允许 owner
  • external + view 节省 Gas
  • 自定义错误 NotOwner 节省报错 Gas
  • call{value:} 安全转账,避免 transfer 限制
  • constructor payable 支持部署时初始化充值

7️⃣ 最佳实践 & 常见坑


✅ 最佳实践

  1. 外部调用用 external,内部调用 public
  2. 纯计算函数用 pure,读状态 view
  3. payable 明确收 ETH 的函数
  4. receive() + fallback() 双保险收 ETH
  5. 错误处理推荐用 revert + 自定义错误
  6. onlyOwner 权限控制必须有
  7. 事件必须 emit,前端数据靠事件同步

⚠️ 常见坑

  • 函数没加 payable,导致 ETH 打不进来
  • fallback() 没写 payable,ETH 被拒绝
  • external 函数内部调用爆 Gas
  • constructor 内部初始化没传值
  • 函数权限设计不严密 → 被黑客攻击!

✅ 小结

这一章我们系统讲解了 Solidity 函数:
✔️ 函数的声明与结构
✔️ 可见性修饰符
✔️ 状态修饰符
✔️ 特殊函数(constructor、receive、fallback)
✔️ 函数重载
✔️ 实战设计安全函数体系


🎯 课后挑战

  1. 编写一个 “白名单” DApp
  • 注册白名单用户
  • owner 批量添加/删除白名单
  • 查询白名单状态
  • 事件触发白名单变化
  • fallback 接收 ETH 并记录日志
  1. 用 Hardhat 部署本地测试
  2. 写自动化测试覆盖各种调用

✅ 下一章预告|第七章

👉 合约继承与接口
🚀 多合约协作开发
🚀 继承重写 / 函数重载 / 抽象合约
🚀 接口 interface 设计模式
🚀 实战 DAO / NFT 工厂合约
🚀 多继承冲突与钻石标准

 


网站公告

今日签到

点亮在社区的每一天
去签到