news 2026/5/16 19:43:17

Solidity入门(7)- 合约继承

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Solidity入门(7)- 合约继承

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 1. 为什么需要继承
    • 1.1 代码复用的问题
    • 1.2 继承的解决方案
    • 1.3 继承的实际应用场景
  • 2. 单继承
    • 2.1 单继承基础语法
    • 2.2 访问权限
  • 3. 多重继承
    • 3.1 多重继承基础
    • 3.2 C3线性化算法
  • 4. super关键字
    • 4.1 super的作用
    • 4.2 单继承中的super
    • 4.3 多重继承中的super
  • 5. 构造函数继承
    • 5.1 构造函数执行顺序
    • 5.2 构造函数参数传递
    • 5.3 多重继承的构造函数
  • 6. 函数重写
    • 6.1 virtual和override关键字
    • 6.2 函数签名必须匹配
    • 6.3 多重继承中的override
    • 6.4 使用super在重写中调用父函数
  • 7. 抽象合约
    • 7.1 什么是抽象合约
  • 8. 接口
    • 8.1 什么是接口
    • 8.3 ERC20接口标准
  • 8.4 接口用于合约交互

1. 为什么需要继承

1.1 代码复用的问题

在没有继承机制的情况下,开发者会遇到严重的代码复用问题。

问题场景:

假设你要创建三个代币合约:稳定币、治理代币、奖励代币。它们都需要:

  • ERC20基本功能(transfer、approve等)
  • 权限控制(只有owner可以执行某些操作)
  • 暂停功能(紧急情况下暂停转账)

没有继承的做法:

// 稳定币合约 contract StableCoin{// 复制粘贴ERC20代码 mapping(address=>uint256)public balanceOf;functiontransfer(address to, uint256 amount)public{}// 复制粘贴权限控制代码 address public owner;modifieronlyOwner(){}// 复制粘贴暂停功能代码 bool public paused;modifierwhenNotPaused(){}functionpause()public{}}// 治理代币合约 contract GovernanceToken{// 又复制粘贴一遍所有代码... mapping(address=>uint256)public balanceOf;functiontransfer(address to, uint256 amount)public{}address public owner;//... 完全重复的代码}// 奖励代币合约 contract RewardToken{// 再次复制粘贴...}

这种做法的问题:

代码冗余:

  • 三个合约有90%的代码相同

    • 浪费存储空间
    • 增加部署成本
  • 维护困难:

    • 发现bug需要修改三个地方
    • 容易遗漏
    • 一致性难以保证
  • 升级麻烦:

    • 添加新功能需要修改所有合约
    • 无法批量更新
    • 测试成本高
  • 容易出错:

    • 复制粘贴可能出错
    • 某个合约可能用旧版本代码
    • 难以追踪哪个版本最新

1.2 继承的解决方案

继承(Inheritance)是面向对象编程的核心特性,它允许一个合约(子合约)继承另一个合约(父合约)的属性和方法。

使用继承的做法:

// 基础合约1:ERC20功能 contract BaseERC20{mapping(address=>uint256)public balanceOf;functiontransfer(address to, uint256 amount)public virtual returns(bool){require(balanceOf[msg.sender]>=amount);balanceOf[msg.sender]-=amount;balanceOf[to]+=amount;returntrue;}}// 基础合约2:权限控制 contract Ownable{address public owner;constructor(){owner=msg.sender;}modifieronlyOwner(){require(msg.sender==owner,"Not owner");_;}}// 基础合约3:暂停功能 contract Pausable{bool public paused;modifierwhenNotPaused(){require(!paused,"Paused");_;}functionpause()internal{paused=true;}}// 子合约:继承所有功能 contract StableCoin is BaseERC20, Ownable, Pausable{// 自动获得所有父合约的功能 // 只需要添加特有功能functionemergencyPause()public onlyOwner{pause();}}contract GovernanceToken is BaseERC20, Ownable, Pausable{// 同样继承所有功能}contract RewardToken is BaseERC20, Ownable, Pausable{// 同样继承所有功能}

继承的优势:

  • 代码复用:

    • 公共功能只写一次
    • 多个子合约共享
    • 减少90%以上的重复代码
  • 易于维护:

    • bug修复只改一处
    • 所有子合约自动受益
    • 保证一致性
  • 模块化设计:

    • 功能分离清晰
    • 每个合约职责单一
    • 易于理解和测试
  • 灵活扩展:

    • 子合约可以添加新功能
    • 可以重写父合约函数
    • 组合不同功能模块

1.3 继承的实际应用场景

场景1:代币项目

// 基础代币 → 标准代币 → 项目代币
ERC20 → ERC20Burnable → MyToken

场景2:权限管理

// 基础权限 → 角色管理 → 具体合约
Ownable → AccessControl → ProjectContract

场景3:安全功能

// 基础合约 → 安全增强 → 最终合约
BaseContract → ReentrancyGuard + Pausable → SecureContract

场景4:可升级合约

// 存储合约 → 逻辑合约 → 代理合约
Storage → Logic → Proxy

2. 单继承

2.1 单继承基础语法

单继承是最简单的继承形式,一个子合约只继承一个父合约。

基本语法:

contract Parent{// 父合约代码}contract Child is Parent{// 子合约代码 // 自动继承Parent的所有内容}

关键字说明:

  • is:表示继承关系
  • Child:子合约(派生合约)
  • Parent:父合约(基础合约)

简单示例:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.19;contract Parent{uint256 public value;functiongetValue()public view returns(uint256){returnvalue;}functionsetValue(uint256 _value)public{value=_value;}}contract Child is Parent{// 自动继承: // - value状态变量 // - getValue()函数 // - setValue()函数 // 添加新功能functiondoubleValue()public view returns(uint256){returnvalue *2;// 可以直接访问父合约的value}}

子合约获得了什么:

// 部署Child合约后,可以调用: Child child=new Child();child.value();// 继承自Parent child.getValue();// 继承自Parent child.setValue(100);// 继承自Parent child.doubleValue();// Child自己的函数

2.2 访问权限

子合约对父合约的访问权限取决于可见性修饰符。

  • public:最开放,子合约和外部都能访问
  • internal:只有子合约能访问,外部不能
  • private:最严格,连子合约都不能访问

3. 多重继承

3.1 多重继承基础

Solidity支持多重继承,一个合约可以同时继承多个父合约。

基本语法:

contract Child is Parent1, Parent2, Parent3{// 同时继承多个父合约}

继承顺序:

  • 从左到右列出父合约
  • 顺序很重要
  • 影响super调用和函数解析
    基础示例:
contract Parent1{functionfoo()public virtual returns(string memory){return"Parent1";}}contract Parent2{functionbar()public virtual returns(string memory){return"Parent2";}}// 多重继承 contract Child is Parent1, Parent2{// 自动获得foo()和bar()functiontest()public view returns(string memory, string memory){return(foo(), bar());}}

3.2 C3线性化算法

Solidity使用C3线性化算法(C3 Linearization)确定继承顺序。

基本规则:

  • 从左到右:Child is A, B, C,A最先,C最后
  • 深度优先:先处理父合约的父合约
  • 保持单调性:不能打乱已有的顺序

简单示例:

继承声明:contract C is A, B
继承顺序:C → A → B
调用链:
C.foo()
→ A.foo()
→ B.foo()

4. super关键字

4.1 super的作用

super关键字用于调用父合约的函数,即使子合约已经重写了该函数。

基本概念:

contract Parent{functiongreet()public virtual returns(string memory){return"Hello from Parent";}}contract Child is Parent{functiongreet()public override returns(string memory){// 调用父合约的greet()string memory parentGreeting=super.greet();returnstring.concat("Hello from Child, ", parentGreeting);}}

调用过程:

Child.greet()

获取super.greet()的返回值:“Hello from Parent”

拼接:“Hello from Child, Hello from Parent”

返回最终结果

4.2 单继承中的super

在单继承中,super指向唯一的父合约。

contract Counter{uint256 public count;functionincrement()public virtual{count+=1;}}contract DoubleCounter is Counter{functionincrement()public override{// 先调用父合约的increment(+1) super.increment();// 再自己加一次(再+1) count+=1;// 最终效果:每次+2}}

4.3 多重继承中的super

在多重继承中,super不是指向单个父合约,而是按照继承顺序调用下一个合约。

关键理解:

contract A{functionfoo()public virtual returns(string memory){return"A";}}contract B is A{functionfoo()public virtual override returns(string memory){returnstring.concat("B->", super.foo());}}contract C is A{functionfoo()public virtual override returns(string memory){returnstring.concat("C->", super.foo());}}contract D is B, C{functionfoo()public override(B, C)returns(string memory){returnstring.concat("D->", super.foo());}}

调用链分析:

1)、Solidity 从右到左处理继承列表。
2)、C 在继承列表中更靠右,所以在 D 之后首先出现。
3)、B 在 C 之后。
4)、A 是共同的基类,放在最后。

D.foo()执行 ↓ super.foo()→ 指向 C(线性化顺序的下一个) ↓ C.foo()执行 ↓ C 中的 super.foo()→ 指向 B(不是 A!) ↓ B.foo()执行 ↓ B 中的 super.foo()→ 指向 A ↓ A.foo()返回"A"↓ B.foo()返回"B->A"↓ C.foo()返回"C->B->A"↓ D.foo()返回"D->C->B->A"

重要提示: super 的含义:在多重继承中,super 不是指"直接父合约",而是指C3 线性化顺序中的下一个合约。 在 D 的上下文中,C 虽然直接继承自 A,但 C 的 super 会指向 B 这是因为在整个继承链中,C 的下一个是 B,而不是直接跳到 A

5. 构造函数继承

5.1 构造函数执行顺序

构造函数总是按照继承顺序执行,从父到子。

执行规则:

  • 父合约优先:所有父合约构造函数先执行
  • 按继承顺序:从左到右
  • 最后是子合约:子合约构造函数最后执行
    示例:
contract A{uint256 public valueA;constructor(){valueA=1;// 第1个执行}}contract B{uint256 public valueB;constructor(){valueB=2;// 第2个执行}}contract C is A, B{uint256 public valueC;constructor(){valueC=3;// 第3个执行}}

执行顺序:

部署C合约

  1. A的构造函数执行(valueA = 1)
  2. B的构造函数执行(valueB = 2)
  3. C的构造函数执行(valueC = 3)

    完成

5.2 构造函数参数传递

当父合约的构造函数需要参数时,有两种传递方式。

方式1:在继承声明时传递(固定值)

contract Parent{uint256 public value;constructor(uint256 _value){value=_value;}}// 在继承声明时传递固定值 contract Child is Parent(100){constructor(){// Parent构造函数接收100}}

特点:

  • 适合固定值
  • 代码简洁
  • 不够灵活

方式2:在子构造函数中传递(动态值)

contract Parent{uint256 public value;constructor(uint256 _value){value=_value;}}// 在子构造函数中传递动态值 contract Child is Parent{constructor(uint256 _value)Parent(_value){// 通过参数传递给Parent}}

特点:

  • 更灵活
  • 可以传递动态值
  • 推荐使用

5.3 多重继承的构造函数

多个父合约都需要参数时,必须全部初始化。

contract A{uint256 public valueA;constructor(uint256 _a){valueA=_a;}}contract B{uint256 public valueB;constructor(uint256 _b){valueB=_b;}}contract C is A, B{uint256 public valueC;// 方式1:混合传递 constructor(uint256 _a, uint256 _c)A(_a)// 动态传递给A B(200)// 固定值传递给B{valueC=_c;}// 方式2:全部动态传递(推荐) constructor(uint256 _a, uint256 _b, uint256 _c)A(_a)B(_b){valueC=_c;}}

执行顺序保持不变:

无论如何传递参数,执行顺序始终是:
A() → B() → C()

6. 函数重写

6.1 virtual和override关键字

函数重写(Function Overriding)允许子合约修改父合约函数的行为。

基本规则:

  • 父合约:函数必须标记为virtual
  • 子合约:重写函数必须标记为override
  • 两者必须配对:缺一不可
    基础示例:
contract Parent{// virtual:表示这个函数可以被重写functiongetValue()public virtual returns(uint256){return100;}}contract Child is Parent{// override:表示重写父合约的函数functiongetValue()public override returns(uint256){return200;// 修改返回值}}

6.2 函数签名必须匹配

重写函数必须与父合约函数签名完全一致。

必须相同的部分:

  • 函数名
  • 参数类型
  • 参数顺序
  • 返回类型

可以不同的部分:

  • 可见性(可以更开放,不能更严格)
  • 状态修饰符(可以更严格,不能更宽松)
    示例:
contract Parent{functionfoo(uint256 a)public virtual returns(uint256){returna;}}contract Child is Parent{// 正确:签名完全相同functionfoo(uint256 a)public override returns(uint256){returna *2;}// 错误:参数不同 //functionfoo(uint256 a, uint256 b)public override returns(uint256){//returna + b;//}// 错误:返回类型不同 //functionfoo(uint256 a)public override returns(string memory){//return"hello";//}// 正确:internal → public(更开放)functionfoo()public override returns(uint256){return2;}// 错误:internal → private(更严格) //functionfoo()private override returns(uint256){//return2;//}}

6.3 多重继承中的override

当多个父合约有同名函数时,必须明确指定重写哪些。

contract A{functionfoo()public virtual returns(string memory){return"A";}}contract B{functionfoo()public virtual returns(string memory){return"B";}}contract C is A, B{// 必须明确指定:override(A, B)functionfoo()public override(A, B)returns(string memory){return"C";}// 错误:不明确 //functionfoo()public override returns(string memory){//return"C";//}}

语法规则:

// 单继承:简单overridefunctionfoo()public override returns(string memory){}// 多重继承:明确指定functionfoo()public override(Parent1, Parent2)returns(string memory){}// 如果继续被继承,还要加virtualfunctionfoo()public virtual override(Parent1, Parent2)returns(string memory){}

6.4 使用super在重写中调用父函数

contract Logger{event Log(string message);functionlog(string memory message)public virtual{emit Log(message);}}contract TimestampLogger is Logger{functionlog(string memory message)public override{// 先调用父合约的log super.log(message);// 再添加时间戳日志 emit Log(string.concat("Timestamp: ", uint2str(block.timestamp)));}functionuint2str(uint _i)internal pure returns(string memory){if(_i==0)return"0";uint j=_i;uint len;while(j!=0){len++;j /=10;}bytes memory bstr=new bytes(len);uint k=len;while(_i!=0){k=k-1;uint8 temp=(48+ uint8(_i - _i /10*10));bytes1 b1=bytes1(temp);bstr[k]=b1;_i /=10;}returnstring(bstr);}}

7. 抽象合约

7.1 什么是抽象合约

抽象合约(Abstract Contract)是包含至少一个未实现函数的合约。

定义语法:

abstract contract 合约名{// 至少一个未实现的函数}

基本示例:

abstract contract Animal{// 抽象函数:只有声明,没有实现functionmakeSound()public virtual returns(string memory);// 普通函数:可以有实现functionsleep()public pure returns(string memory){return"Zzz...";}// 可以有状态变量 uint256 public age;}// 实现抽象合约 contract Dog is Animal{// 必须实现makeSoundfunctionmakeSound()public pure override returns(string memory){return"Woof!";}}contract Cat is Animal{functionmakeSound()public pure override returns(string memory){return"Meow!";}}

抽象合约的特点:

  • 不能直接部署:必须被继承
  • 可以有实现:部分函数可以有实现
  • 可以有状态变量:可以定义状态变量
  • 可以有构造函数:可以有构造函数
  • 强制实现:子合约必须实现所有抽象函数

8. 接口

8.1 什么是接口

接口(Interface)是纯粹的接口定义,只声明函数签名,不包含任何实现。

定义语法:

interface 接口名{// 只有函数声明}

基本示例:

interface ICounter{// 所有函数必须是externalfunctiongetCount()external view returns(uint256);functionincrement()external;functiondecrement()external;// 可以定义事件 event CountChanged(uint256 newCount);}// 实现接口 contract Counter is ICounter{uint256 private count;event CountChanged(uint256 newCount);functiongetCount()external view override returns(uint256){returncount;}functionincrement()external override{count++;emit CountChanged(count);}functiondecrement()external override{count--;emit CountChanged(count);}}

8.2 接口的特点和限制
接口的特点:

使用interface关键字

  • 不能有实现:所有函数都是声明
  • 不能有状态变量:不能定义storage变量
  • 不能有构造函数
  • 所有函数必须external
  • 可以继承其他接口
  • 可以定义事件

接口vs合约对比:

8.3 ERC20接口标准

ERC20是最经典的接口定义示例。

interface IERC20{// 查询函数functiontotalSupply()external view returns(uint256);functionbalanceOf(address account)external view returns(uint256);functionallowance(address owner, address spender)external view returns(uint256);// 操作函数functiontransfer(address to, uint256 amount)external returns(bool);functionapprove(address spender, uint256 amount)external returns(bool);functiontransferFrom(address from, address to, uint256 amount)external returns(bool);// 事件 event Transfer(address indexed from, address indexed to, uint256 value);event Approval(address indexed owner, address indexed spender, uint256 value);}

8.4 接口用于合约交互

接口最重要的应用是合约间交互。

场景:合约A调用合约B

// 定义接口 interface IToken{functiontransfer(address to, uint256 amount)external returns(bool);functionbalanceOf(address account)external view returns(uint256);}// 使用接口与其他合约交互 contract Exchanger{functionswapTokens(address tokenAddress, address recipient, uint256 amount)public{// 通过接口与代币合约交互 IToken token=IToken(tokenAddress);// 检查余额 uint256 balance=token.balanceOf(address(this));require(balance>=amount,"Insufficient balance");// 执行转账 bool success=token.transfer(recipient, amount);require(success,"Transfer failed");}}

接口的优势:

  • 解耦:不需要知道合约的完整代码
  • 标准化:统一的接口规范
  • 互操作性:不同合约可以互相调用
  • 节省gas:不需要导入完整合约代码
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/12 8:52:14

CTF比赛含金量高吗?(非常详细),零基础入门CTF,看这一篇就够了

文章目录 前言 关于我一、基础环境二、常用工具三、Web 安全四、加密解密五、密码爆破六、文件工具七、隐写图片八、隐写音频九、隐写取证十、逆向工具十一、Java 反编译十二、Python反编译十三、PWN二进制 前言 CTF(Capture The Flag)比赛在网络安全…

作者头像 李华
网站建设 2026/5/15 15:59:40

7.2 深度研究:利用大模型高级检索与分析能力

7.2 深度研究:利用大模型高级检索与分析能力 在上一节课中,我们学习了如何整合NotebookLM、大语言模型和Cursor等工具,构建完整的AI辅助开发工作流。本节课我们将深入探讨如何利用大模型的高级检索与分析能力,提升技术研究和方案设计的效率与质量。 大模型检索能力概述 …

作者头像 李华
网站建设 2026/5/13 7:47:15

restTemplate发送POST

HttpHeaders headers new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);// 2. 构建请求参数&#xff08;与文档示例完全一致&#xff09;Map<String, Object> requestBody new HashMap<>();requestBody.put("grant_type", "…

作者头像 李华
网站建设 2026/5/15 4:25:36

4、深入现实世界:包过滤网关配置指南

深入现实世界:包过滤网关配置指南 1. 从单机到网关 在之前的基础上,我们现在要进入更常规的领域——包过滤网关的设置。虽然本章的很多内容在单机设置中也可能有用,但我们现在的主要重点是搭建一个能处理常见网络服务的网关。 2. 简单网关与NAT 我们开始构建通常所说的防…

作者头像 李华
网站建设 2026/5/13 13:57:21

springboot宠物用品商城领养系统之家小程序_dsc9dqa7

文章目录具体实现截图主要技术与实现手段关于我本系统开发思路java类核心代码部分展示结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;具体实现截图 同行可拿货,招校园代理 springboot_dsc9dqa7 宠物用品商城领养系统之家小程序…

作者头像 李华