一文讲透 C++ / Java 中方法重载(Overload)与方法重写(Override)在调用时机等方面的区别

一文讲透 C++ / Java 中方法重载(Overload)与方法重写(Override)在调用时机等方面的区别

在学习 C++ 或 Java 的面向对象时,方法重载(Overload)方法重写(Override) 都是对 同名函数 的某种二次操作,容易混淆,这篇文章就来系统梳理其区别,以及它们分别在什么时候决定调用目标。

文章目录

  • [一文讲透 C++ / Java 中方法重载(Overload)与方法重写(Override)在调用时机等方面的区别](#一文讲透 C++ / Java 中方法重载(Overload)与方法重写(Override)在调用时机等方面的区别)
    • [1 什么是方法重载(Overload)](#1 什么是方法重载(Overload))
      • [1.1 Java 示例](#1.1 Java 示例)
      • [1.2 C++ 示例](#1.2 C++ 示例)
      • [1.3 Overload 函数在编译期决定调用](#1.3 Overload 函数在编译期决定调用)
    • [2 什么是方法重写(Override)](#2 什么是方法重写(Override))
      • [2.1 Java 示例](#2.1 Java 示例)
      • [2.2 C++ 示例](#2.2 C++ 示例)
      • [2.3 Override 在运行时决定调用](#2.3 Override 在运行时决定调用)
    • [3 二者区别](#3 二者区别)
      • [3.1 重载:先根据调用处的参数列表来选签名](#3.1 重载:先根据调用处的参数列表来选签名)
      • [3.2 重写:根据对象选实现](#3.2 重写:根据对象选实现)
    • [4 例子](#4 例子)
      • [例 1](#例 1)
      • [例 2](#例 2)
      • [例 3](#例 3)
    • [5 C++ 和 Java 在这方面的异同](#5 C++ 和 Java 在这方面的异同)
      • [5.1 Java:普通实例方法默认支持重写分派](#5.1 Java:普通实例方法默认支持重写分派)
      • [5.2 C++:必须使用 virtual 才有动态绑定](#5.2 C++:必须使用 virtual 才有动态绑定)
    • [6 结语](#6 结语)

1 什么是方法重载(Overload)

方法重载(Overload) 指的是:在同一个类 中,允许存在多个方法名相同 ,但参数列表不同 的方法。

参数列表不同可以表现为参数个数/类型/顺序等任何不同。但注意,返回值不同,不能单独构成重载


1.1 Java 示例

java 复制代码
class Demo {
    void print(int a) {
        System.out.println("print(int)");
    }

    void print(double a) {
        System.out.println("print(double)");
    }

    void print(String a) {
        System.out.println("print(String)");
    }
}

调用:

java 复制代码
public class Main {
    public static void main(String[] args) {
        Demo d = new Demo();
        d.print(10);
        d.print(3.14);
        d.print("hello");
    }
}

输出:

java 复制代码
print(int)
print(double)
print(String)

1.2 C++ 示例

cpp 复制代码
#include <iostream>
using namespace std;

class Demo {
public:
    void print(int a) {
        cout << "print(int)" << endl;
    }

    void print(double a) {
        cout << "print(double)" << endl;
    }

    void print(string a) {
        cout << "print(string)" << endl;
    }
};

int main() {
    Demo d;
    d.print(10);
    d.print(3.14);
    d.print("hello");
    return 0;
}

1.3 Overload 函数在编译期决定调用

编译器在编译阶段,根据实参类型和方法签名,决定调用哪个函数。也就是说,重载属于静态绑定(Static Binding) ,也叫编译时多态

例如:

java 复制代码
d.print(10);

编译器看到参数 10int,就会在编译阶段直接匹配到:

java 复制代码
print(int)

运行时不会再重新选择。


2 什么是方法重写(Override)

方法重写(Override) 指的是 子类重新实现父类中已经存在的方法 ,要求方法名相同、参数列表相同、返回值相同(说白了就是只有函数体里面在干啥的内容不同),且访问权限不能更严格。它体现面向对象中的 继承多态

2.1 Java 示例

java 复制代码
class Parent {
    void show() {
        System.out.println("Parent show()");
    }
}

class Child extends Parent {
    @Override
    void show() {
        System.out.println("Child show()");
    }
}

调用:

java 复制代码
public class Main {
    public static void main(String[] args) {
        Parent p = new Child();
        p.show();
    }
}

输出:

java 复制代码
Child show()

2.2 C++ 示例

在 C++ 中,重写通常要配合 virtual

cpp 复制代码
#include <iostream>
using namespace std;

class Parent {
public:
    virtual void show() {
        cout << "Parent show()" << endl;
    }
};

class Child : public Parent {
public:
    void show() override {
        cout << "Child show()" << endl;
    }
};

int main() {
    Parent* p = new Child();
    p->show();
    return 0;
}

输出:

cpp 复制代码
Child show()

2.3 Override 在运行时决定调用

重写的特点是:编译器只知道"这个调用是一个虚方法调用/可重写方法调用",真正执行父类版本还是子类版本,要到运行时根据对象的实际类型决定 。因此,重写属于 动态绑定(Dynamic Binding) ,是一种 运行时多态


3 二者区别

在此之前现介绍一个名词:

函数签名(Function Signature) 是用来唯一标识一个函数的重要信息集合,由 函数名称参数类型列表(包括顺序) 组成,用于区分不同的函数实现。(注意,函数签名不包含其返回值)


3.1 重载:先根据调用处的参数列表来选签名

看下面的代码:

java 复制代码
class Demo {
    void test(int x) {
        System.out.println("int");
    }

    void test(double x) {
        System.out.println("double");
    }
}

调用:

java 复制代码
Demo d = new Demo();
d.test(10);

编译器会在编译时,根据参数列表 10 去匹配,从而选择 test(int)。这里选的这个参数列表就是函数签名


3.2 重写:根据对象选实现

看这段代码:

java 复制代码
class Parent {
    void show() {
        System.out.println("Parent");
    }
}

class Child extends Parent {
    @Override
    void show() {
        System.out.println("Child");
    }
}

调用:

java 复制代码
Parent p = new Child();
p.show();

编译时只能确定 p 的静态类型是 Parent、调用的方法签名是 show(),但真正执行 Parent.show() 还是 Child.show(),要到运行时 看实际对象类型 new Child()


4 例子

例 1

java 复制代码
class Parent {
    void show(Object o) {
        System.out.println("Parent show(Object)");
    }
}

class Child extends Parent {
    void show(String s) {
        System.out.println("Child show(String)");
    }
}

调用:

java 复制代码
Parent p = new Child();
p.show("hello");

很多人第一眼会以为输出是:

java 复制代码
Child show(String)

但实际上输出是:

java 复制代码
Parent show(Object)

为什么?

  1. 第一步:编译期选签名

变量 p静态类型(变量被定义时的类型)Parent,所以编译器只会在 Parent 中查找可调用的方法。

Parent 中只有:

java 复制代码
show(Object)

因此编译器在编译时就选定了它。

  1. 运行期看是否被重写

运行时(这个时候变量才被 new 分配内存)对象确实是 Child,但 Child 里定义的是:

java 复制代码
show(String)

这不是对 show(Object) 的重写(参数列表都不一样了),而是一个新的重载方法。所以运行时找不到 show(Object) 的重写版本,就去调用父类的:

java 复制代码
Parent show(Object)

这说明 重载看引用类型和参数类型,编译期决定;重写看实际对象类型,运行期决定。


例 2

java 复制代码
class Parent {
    void f(int x) {
        System.out.println("Parent f(int)");
    }

    void f(double x) {
        System.out.println("Parent f(double)");
    }
}

class Child extends Parent {
    @Override
    void f(int x) {
        System.out.println("Child f(int)");
    }
}

调用:

java 复制代码
Parent p = new Child();
p.f(10);
p.f(3.14);

输出:

java 复制代码
Child f(int)
Parent f(double)

分析:

  1. 对于 p.f(10)
  • 编译期:根据参数 10 选中 f(int)
  • 运行期:Child 重写了 f(int),所以执行 Child f(int)
  1. 对于 p.f(3.14)
  • 编译期:根据参数 3.14 选中 f(double)
  • 运行期:Child 没有重写 f(double),所以执行 Parent f(double)

这再次说明 先重载匹配签名,再重写分派实现。


例 3

java 复制代码
class A {
    void show(Object o) {
        System.out.println("A Object");
    }

    void show(String s) {
        System.out.println("A String");
    }
}

class B extends A {
    @Override
    void show(Object o) {
        System.out.println("B Object");
    }
}

调用:

java 复制代码
A a = new B();
a.show("hello");

输出是什么?

答案:

java 复制代码
A String
  1. 第一步:编译期选签名

变量 a 的静态类型是 A,参数是 "hello",因此编译器优先匹配:

java 复制代码
show(String)
  1. 运行期看重写

运行时对象是 B,但 B 只重写了:

java 复制代码
show(Object)

没有重写 show(String),所以最终调用的还是:

java 复制代码
A String

5 C++ 和 Java 在这方面的异同

虽然 C++ 和 Java 都有 Overload 和 Override,二者都遵循重载在编译时决定,重写在运行时决定的特点,都体现了面向对象中的多态思想,但二者在语法特点上有所不同。

5.1 Java:普通实例方法默认支持重写分派

在 Java 中,普通实例方法默认就是虚方法机制。也就是说,只要是子类重写父类实例方法,调用通常都会表现为动态绑定。

例如:

java 复制代码
Parent p = new Child();
p.show();

会自动调用子类实现。

5.2 C++:必须使用 virtual 才有动态绑定

在 C++ 中,如果父类方法没有加 virtual,即使子类写了同名同参函数,也不会产生真正意义上的动态绑定。

例如:

cpp 复制代码
#include <iostream>
using namespace std;

class Parent {
public:
    void show() {
        cout << "Parent" << endl;
    }
};

class Child : public Parent {
public:
    void show() {
        cout << "Child" << endl;
    }
};

int main() {
    Parent* p = new Child();
    p->show();
}

输出是:

cpp 复制代码
Parent

因为没有 virtual,调用 在编译期就按指针类型 Parent* 决定了

只有写成:

cpp 复制代码
virtual void show()

才会发生运行时动态绑定。


6 结语

函数(C++)/ 方法(Java)调用通常分两步:先在编译 期确定调用哪个方法签名,再在运行期 确定调用哪个方法实现。

Overload 在编译时决定,Override 在运行时决定,因为重载解决的是 调用哪个方法签名 ,重写解决的是 调用哪个方法实现

相关推荐
一叶飘零_sweeeet2 小时前
深入拆解 Fork/Join 框架:核心原理、分治模型与参数调优实战
java·并发编程
云烟成雨TD2 小时前
Spring AI Alibaba 1.x 系列【23】短期记忆
java·人工智能·spring
摇滚侠2 小时前
帮我整理一份 IDEA 开发中常用快捷键
java·ide·intellij-idea
yolo_guo3 小时前
glog单行 30000 字节限制问题
c++
疯狂成瘾者3 小时前
YAML配置介绍
java
cccccc语言我来了3 小时前
C++轻量级消息队列服务器
java·服务器·c++
better_liang3 小时前
每日Java面试场景题知识点之-MCP协议在Java开发中的应用实践
java·spring boot·ai·mcp·企业级开发
河阿里3 小时前
SpringBoot :使用 @Configuration 集中管理 Bean
java·spring boot·spring
xiaoshuaishuai83 小时前
C# Codex 脚本编写
java·服务器·数据库·c#