C++ 继承、多态与类型转换 | 函数重载 / 隐藏 / 覆盖实现与基派生类指针转换

注:本文为 "C++ 继承、多态与类型转换 " 相关合辑。

略作重排,未整理去重。

如有内容异常,请看原文。


C++ 基类指针和派生类指针之间的转换

-牧野- 原创于 2018-10-28 11:01:19 发布

本文系统解析函数重载、函数隐藏与函数覆盖的概念,重点阐述上述概念在基类与派生类间的应用机制,以及依托虚函数实现多态性的方法。同时,明确基类指针与派生类指针间的转换规则。

函数重载、函数隐藏、函数覆盖

函数重载仅发生于同一作用域内(或同一类中),要求函数名称相同,但参数类型或参数个数存在差异。函数重载无法通过返回类型进行区分,原因在于函数返回前,其返回类型无法被程序识别。

函数隐藏与函数覆盖仅发生于基类与派生类之间

函数隐藏指派生类中存在与基类同名的函数,且该函数未在基类中被声明为虚函数的情形

隐藏的具体表现为:采用常规调用方式时,派生类对象访问该函数会优先调用派生类中的同名函数,基类中的对应函数对派生类对象而言处于隐藏状态。但隐藏并不代表该函数不存在或完全不可访问,可通过 b->Base::func() 的形式访问基类中被隐藏的函数。

函数覆盖特指由基类中声明的虚函数引发的多态现象 。在某一基类中声明为 virtual 且在一个或多个派生类中被重新定义的成员函数,其声明格式为:virtual 函数返回类型 函数名(参数表) {函数体};。借助指向派生类的基类指针或引用访问派生类中同名的覆盖成员函数,可实现多态性。

函数覆盖的条件:

  1. 基类中的成员函数通过 virtual 关键字声明为虚函数;
  2. 派生类中该函数的名称、参数类型及参数个数需与基类中的对应函数完全一致;
  3. 将派生类对象赋值给基类指针或引用,以此实现多态性。

函数覆盖(多态)提供了一种基类访问不同派生类成员的方式,该特性可理解为基类对派生类成员的间接访问机制。

基类指针和派生类指针之间的转换

1. 基类指针指向基类对象、派生类指针指向派生类对象

该情形为常规使用方式,只需通过对应类的指针直接调用类的成员函数即可。

cpp 复制代码
#include<iostream>
using namespace std;
class Father{
public:
    void print()
    {
        printf("Father's function!");
    }
};
class Son:public Father
{
public:
    void print()
    {
        printf("Son's function!");
    }
};
int main()
{
    Father f1;
    Son s1;
    Father* f = &f1;
    Son* s = &s1;
    f->print();
    cout<<endl<<endl;
    s->print();
}

2. 基类指针指向派生类对象

该操作在语法层面是被允许的,通过定义基类指针与派生类对象,并将基类指针指向派生类对象即可实现,但需注意:常规情况下,该指针调用的是基类的成员函数。具体分为以下四种情形:

一、函数在基类和派生类中均存在

此时通过"指向派生类对象的基类指针"调用成员函数,实际调用的是基类的成员函数。

Father f1;

Son s1;

Father* f = &s1;

f->print(); //调用的是基类成员函数

二、函数仅存在于派生类中,基类中无该函数

由于基类指针默认调用基类的成员函数,若试图通过基类指针调用仅派生类拥有的成员函数,编译器会抛出如下错误:

error C2039: "xxx": 不是"Father"的成员

三、将基类指针强制转换为派生类指针

该操作属于向下强制类型转换,转换后"指向派生类的基类指针"可访问派生类的成员函数:

Son s1;

Father* f = &s1;

Son s = (Son )f;

s->print1(); //调用派生类成员函数

但此类强制转换操作存在潜在的运行风险。

四、基类中声明虚函数的情形

若基类中的成员函数被声明为虚函数,且派生类中对该函数进行了重定义,则通过"指向派生类的基类指针"访问该虚函数时,实际调用的是派生类中的实现版本。允许"基类指针指向派生类对象"这一操作的主要应用场景即在于此:依托虚函数与函数覆盖机制,实现多态性(指向不同派生类对象的基类指针,可调用对应派生类的成员函数)。

Father f1;

Son s1;

Father* f = &s1;

f->print(); //调用派生类成员函数

3. 派生类指针指向基类对象

该操作会触发编译错误。基类对象无法被当作派生类对象处理,原因在于派生类可能包含仅自身拥有的成员变量或成员函数。

即便通过强制转换将派生类指针转换为指向基类对象的指针,通过该指针访问的函数仍为派生类的成员函数。

Father f1;

Son s1;

Son* s=&s1;

Father* f = (Father*) s;

f->print(); //调用派生类成员函数

综上,可通过基类指针访问派生类的成员函数(方式包括强制类型转换与虚函数机制),但不存在通过派生类指针调用基类成员函数的有效方法(即使采用强制转换也无法实现)。


C++ 基类与派生类间的类型转换

公子¥小白 原创于 2021-05-31 11:24:32 发布

本文系统讲解 C++ 中隐式类型转换(基类指针与派生类指针间转换、派生类对象与基类对象间转换)和强制类型转换(static_castdynamic_cast)在继承关系中的使用规则,涉及公有继承、私有继承对类型转换的影响。同时,介绍智能指针的类型转换方法。

注:以下转换(包括隐式转换与强制转换)成功的前提为------派生类向基类的转换具备可访问性。

隐式类型转换

基类的指针(含智能指针)或引用可绑定至派生类对象,即存在派生类向基类的隐式类型转换;反之,基类向派生类的隐式类型转换不存在。

cpp 复制代码
Bulk_quote bulk; 			// 派生类对象
Quote item; 				// 基类对象
Quote *p = &bulk;			// 正确
Bulk_quote *p1 = item; 		// 无法通过编译

基类对象与派生类对象间不存在隐式类型转换。初始化或赋值类类型对象的本质是调用特定函数:初始化操作调用构造函数,赋值操作调用赋值运算符。

结论:使用派生类对象对基类对象进行初始化或赋值,可正常编译运行;反之,无法通过编译。

cpp 复制代码
Bulk_quote bulk; 			// 派生类对象
Quote item(bulk); 			// 调用 Quote::Quote(const Quote&) 拷贝构造函数
item = bulk;				// 调用 Quote::operator=(const Quote&)
Bulk_quote bulk1(item); 	// 无法通过编译
Bulk_quote bulk2 = item; 	// 无法通过编译

继承关系类间的类型转换需关注以下三点规则:

  1. 派生类向基类的类型转换仅对指针或引用类型有效;
  2. 基类向派生类不存在隐式类型转换;
  3. 与类的其他成员一致,派生类向基类的类型转换可能因访问权限限制而不可行。

派生类向基类的转换是否可访问(可行)由使用该转换的代码上下文决定,同时受派生类的派生访问说明符影响。假定类 D 继承自类 B

  1. 仅当 D 以公有方式继承 B 时,用户代码可使用派生类向基类的转换;若 D 以受保护或私有方式继承 B,则用户代码无法使用该转换。
  2. 无论 D 以何种方式继承 BD 的成员函数与友元均可使用派生类向基类的转换;派生类向其直接基类的类型转换对派生类的成员和友元始终可访问。
  3. D 以公有或受保护方式继承 B,则 D 的派生类的成员和友元可使用 DB 的类型转换;若 D 以私有方式继承 B,则该转换不可用。
cpp 复制代码
// 基类 Quote
class Quote
{
public:
	Quote();
	virtual ~Quote();
};

// 公有继承的派生类 Bulk_quote_pub
class Bulk_quote_pub : public Quote
{
public:
	Bulk_quote_pub();
	virtual ~Bulk_quote_pub();
};

// 私有继承的派生类 Bulk_quote_pri
class Bulk_quote_pri : private Quote
{
public:
	Bulk_quote_pri();
	virtual ~Bulk_quote_pri();
};

int main()
{
	Bulk_quote_pub bulk_pub; 	// 派生类对象
	Bulk_quote_pri bulk_pri; 	// 派生类对象
	Quote item; 				// 基类对象
	Quote *p = &bulk_pub;		// 正确,bulk_pub 公有继承 Quote,转换可访问
	Quote *p1 = &bulk_pri;		// 无法通过编译,bulk_pri 私有继承 Quote,转换不可访问

	return 0;
}

强制类型转换

该部分详细内容参见: static_cast 和 dynamic_cast 详解 (见下文)

本文聚焦继承关系类间的强制类型转换规则。

static_cast:

  1. 基类(父类)与派生类(子类)间指针或引用的转换:
    • 上行转换(派生类指针/引用转换为基类类型)具备安全性;
    • 下行转换(基类指针/引用转换为派生类类型)因无动态类型检查,存在安全性风险。
cpp 复制代码
Bulk_quote *bulk_p = new Bulk_quote(); 						// 派生类指针
Quote *item_p = new Quote(); 								// 基类指针

Quote *item_p1 = static_cast<Quote *>(bulk_p); 				// 上行转换,安全
Bulk_quote *bulk_p1 = static_cast<Bulk_quote *>(item_p);	// 下行转换,不安全,不建议使用
  1. 基类与派生类对象间的转换:
    • 派生类对象可转换为基类对象;
    • 基类对象不可转换为派生类对象。
cpp 复制代码
Bulk_quote bulk; 			// 派生类对象
Quote item; 				// 基类对象

Quote item1 = static_cast<Quote>(bulk);				// 可正常编译运行
Bulk_quote bulk1 = static_cast<Bulk_quote>(item); 	// 无法通过编译

dynamic_cast:

dynamic_cast 支持以下 3 种转换形式:

  • dynamic_cast< type* >(e)type 需为类类型且为有效指针类型;
  • dynamic_cast< type& >(e)type 需为类类型且 e 为左值;
  • dynamic_cast< type&& >(e)type 需为类类型且 e 为右值。

dynamic_cast 主要用于类层次结构中的上行转换下行转换

  • 上行转换时,dynamic_caststatic_cast 效果一致;
  • 下行转换时,dynamic_cast 具备类型检查功能,安全性优于 static_cast
    需注意,dynamic_cast 转换会产生显著的运行时开销。
cpp 复制代码
Bulk_quote *bulk_p = new Bulk_quote(); 						// 派生类指针
Quote *item_p = new Quote(); 								// 基类指针

Quote *item_p1 = dynamic_cast<Quote *>(bulk_p); 				// 上行转换,效果同 static_cast,运行代价更高
Bulk_quote *bulk_p1 = dynamic_cast<Bulk_quote *>(item_p);	// 下行转换,失败,bulk_p1 为空指针
Bulk_quote *bulk_p2 = dynamic_cast<Bulk_quote *>(item_p1);	// 下行转换,成功

智能指针 std::shared_ptr 的类型转换

static_castdynamic_cast 适用于原生指针的类型转换;针对智能指针的类型转换,需使用以下 4 种专用转换函数:static_pointer_castdynamic_pointer_castconst_pointer_castreinterpret_pointer_cast。上述函数的功能分别与 static_castdynamic_castconst_castreinterpret_cast 对应,区别在于操作对象为 std::shared_ptr 智能指针,且返回值同样为 std::shared_ptr 类型。仅 std::shared_ptr 提供此类专用的类型转换接口。


static_cast 和 dynamic_cast 详解

ShyHerry 原创于 2018-04-06 22:59:50 发布

本文系统介绍 C++ 中 static_castdynamic_cast 的使用方法及二者差异。static_cast 可应用于基本数据类型转换、指针转换等场景,但在类的下行转换中存在安全风险;dynamic_cast 则在类层次结构的上行与下行转换中提供类型安全检查,适用于多态场景下的类型转换。

类继承关系图

注:从继承关系图可知,派生类不仅包含自身的成员函数与成员变量,还继承了基类的成员函数与成员变量。派生类向基类的转换(上行转换),无论采用 C 语言风格还是 C++ 风格的转换方式均可成功;但基类向派生类的转换(下行转换)存在显著风险------若采用传统转换方式,派生类独有的成员函数与成员变量会丢失,调用此类成员将导致程序异常,该问题源于对类继承机制与内存分配规则的理解不足。C++ 提供 static_castdynamic_cast 运算符,专门用于继承关系类间的强制类型转换,可有效规避上述风险。

一、static_cast 和 dynamic_cast 使用方式

static_cast< new_type >(expression)
dynamic_cast< new_type >(expression)
说明new_type 为目标数据类型,expression 为原始数据类型的变量或表达式。

二、static_cast 详解:

static_cast 等价于传统 C 语言的强制类型转换,该运算符将 expression 转换为 new_type 类型,支持非 const 对象向 const 对象的隐式转换强制化,转换过程由编译器在编译阶段检查,适用于非多态场景的类型转换,可转换指针及其他类型,但无运行时类型检查机制保障转换安全性。其主要应用场景如下:

类层次结构中基类(父类)与派生类(子类)间指针或引用的转换

  • 上行转换(派生类指针/引用转换为基类类型)具备安全性;
  • 下行转换(基类指针/引用转换为派生类类型)因无动态类型检查,存在安全性风险。

基本数据类型间的转换 ,例如 int 类型转换为 char 类型、int 类型转换为枚举类型。

空指针转换为目标类型的空指针

任意类型的表达式转换为 void 类型

注意:static_cast 无法移除 expressionconstvolatile__unaligned 属性。

基本类型数据转换示例:

cpp 复制代码
char a = 'a';
int b = static_cast<char>(a);//正确,将 char 型数据转换为 int 型数据

double *c = new double;
void *d = static_cast<void*>(c);//正确,将 double 指针转换为 void 指针

int e = 10;
const int f = static_cast<const int>(e);//正确,将 int 型数据转换为 const int 型数据

const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast 无法移除 g 的 const 属性

类上行和下行转换示例:

cpp 复制代码
class Base
{};

class Derived : public Base
{};

Base* pB = new Base();
if(Derived* pD = static_cast<Derived*>(pB))
{}//下行转换存在安全风险 (不建议使用该方式)

Derived* pD = new Derived();
if(Base* pB = static_cast<Base*>(pD))
{}//上行转换具备安全性

三、dynamic_cast 详解:

转换形式:

  • dynamic_cast< type* >(e)type 需为类类型且为有效指针类型;
  • dynamic_cast< type& >(e)type 需为类类型且 e 为左值;
  • dynamic_cast< type&& >(e)type 需为类类型且 e 为右值。

e 的类型需满足以下任一条件:

  1. e 的类型是目标类型 type 的公有派生类;
  2. e 的类型是目标类型 type 的公有基类;
  3. e 的类型与目标类型 type 完全一致。

dynamic_cast 的转换目标为指针类型且转换失败,返回值为空指针;若转换目标为引用类型且转换失败,dynamic_cast 会抛出 std::bad_cast 异常(该异常定义于 typeinfo 标准库头文件)。若 e 为空指针,转换结果为目标类型的空指针。

dynamic_cast 主要用于类层次结构的上行转换、下行转换,亦可用于类之间的交叉转换(cross cast):

  • 上行转换时,dynamic_caststatic_cast 效果一致;
  • 下行转换时,dynamic_cast 具备类型检查功能,安全性优于 static_cast
    dynamic_cast 是唯一无法通过旧式转换语法实现的操作,也是唯一可能产生显著运行时开销的类型转换操作。

(1)指针类型转换示例

假设 Base 为包含至少一个虚函数的基类,DerivedBase 的公有派生类,若存在指向 Base 的指针 bp,可在运行时将其转换为指向 Derived 的指针,代码如下:

cpp 复制代码
if(Derived *dp = dynamic_cast<Derived *>(bp)){
  //使用 dp 指向的 Derived 对象
}
else{
  //使用 bp 指向的 Base 对象
}

需注意,上述代码在 if 语句中定义指针 dp,可在单个操作中完成类型转换与条件检查。

(2)引用类型转换示例

由于不存在空引用,引用类型的 dynamic_cast 转换与指针类型不同:转换失败时会抛出 std::bad_cast 异常(该异常定义于 typeinfo 头文件)。

cpp 复制代码
void f(const Base &b){
 try{
   const Derived &d = dynamic_cast<const Derived &>(b);
   //使用 b 引用的 Derived 对象
 }
 catch(std::bad_cast){
   //处理类型转换失败的场景
 }
}

四、转换注意事项:

应尽量减少类型转换操作的使用,尤其是 dynamic_cast------该转换耗时较高,易导致程序性能下降,建议优先采用其他方法替代类型转换。


函数重载、隐藏、覆盖与多态

一、函数重载的实现

函数重载的实现逻辑为同一作用域内定义名称相同、参数列表不同的函数,编译器在编译阶段根据参数的类型、个数、顺序匹配对应函数,返回值无法作为重载的判定依据。

实现示例

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

// 同一作用域(类内)实现函数重载
class Calculator {
public:
    // 重载 1:两个 int 类型相加
    int add(int a, int b) {
        cout << "int 版 add:";
        return a + b;
    }

    // 重载 2:三个 int 类型相加(参数个数不同)
    int add(int a, int b, int c) {
        cout << "3 个 int 版 add:";
        return a + b + c;
    }

    // 重载 3:两个 double 类型相加(参数类型不同)
    double add(double a, double b) {
        cout << "double 版 add:";
        return a + b;
    }

    // 错误示例:仅返回值不同,无法构成重载(编译报错)
    // double add(int a, int b) { return a + b; }
};

int main() {
    Calculator calc;
    // 编译器根据参数自动匹配重载函数
    cout << calc.add(1, 2) << endl;       // 调用 int 版 add
    cout << calc.add(1, 2, 3) << endl;    // 调用 3 个 int 版 add
    cout << calc.add(1.5, 2.5) << endl;   // 调用 double 版 add
    return 0;
}

实现要点

  1. 重载函数必须处于同一作用域(同类、同命名空间);
  2. 参数列表需满足"三不同"之一:类型不同、个数不同、顺序不同(如 add(int, double)add(double, int));
  3. 仅返回值不同无法触发重载,编译器无法通过调用语句区分目标函数;
  4. 重载属于编译期多态(静态多态),函数调用关系在编译阶段确定。

二、C++ 中函数覆盖的实现

函数覆盖(重写)是派生类对基类虚函数的重新实现,需严格满足以下条件并遵循固定实现步骤:

实现条件

  1. 基类中需将目标函数声明为虚函数(添加 virtual 关键字);
  2. 派生类中重写的函数需与基类虚函数满足"三同":函数名、参数列表、返回值完全一致(协变返回值除外);
  3. 建议使用 override 关键字显式声明(C++11+),编译器会校验覆盖的合法性,避免因参数不一致导致的隐藏问题。

实现示例

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

// 基类:声明虚函数
class Animal {
public:
    // 声明虚函数作为统一接口
    virtual void makeSound() {
        cout << "动物发出声音" << endl;
    }

    // 虚析构函数:确保派生类析构函数被正确调用
    virtual ~Animal() {}
};

// 派生类 1:覆盖基类虚函数
class Cat : public Animal {
public:
    // 严格匹配基类虚函数的名称、参数、返回值
    void makeSound() override {
        cout << "猫发出喵喵声" << endl;
    }
};

// 派生类 2:覆盖基类虚函数
class Dog : public Animal {
public:
    void makeSound() override {
        cout << "狗发出汪汪声" << endl;
    }
};

int main() {
    Animal* animal1 = new Cat();
    Animal* animal2 = new Dog();

    // 调用派生类覆盖后的函数版本
    animal1->makeSound(); // 输出:猫发出喵喵声
    animal2->makeSound(); // 输出:狗发出汪汪声

    delete animal1;
    delete animal2;
    return 0;
}

注意事项

  • 若派生类函数参数与基类虚函数不一致,覆盖会降级为函数隐藏,丧失多态特性;
  • 基类析构函数建议声明为虚函数,否则派生类对象通过基类指针销毁时,仅会调用基类析构函数,导致内存泄漏。

三、虚函数实现多态的底层逻辑

虚函数是 C++ 实现运行期多态(动态多态) 的关键机制,其依托虚函数表(vtable)虚表指针(vptr) 实现:

  1. 每个包含虚函数的类会生成一张虚函数表,存储类内所有虚函数的内存地址;
  2. 该类的每个对象会包含一个虚表指针,指向所属类的虚函数表;
  3. 当基类指针/引用指向派生类对象时,虚表指针会指向派生类的虚函数表;
  4. 程序运行时,通过虚表指针找到对应虚函数表,调用实际对象类型的虚函数版本。

四、函数重载、隐藏、覆盖在其他编程语言中的体现

不同编程语言对这三类函数特性的支持方式存在差异,具体表现如下:

特性 Python 中的体现 C# 中的体现 Java 中的体现
函数重载 无原生重载,可通过默认参数、可变参数(*args/**kwargs)模拟重载效果 支持,规则与 C++一致,还可通过 overload 特性标记重载 支持,规则与 C++一致(同一类、同名不同参),返回值不影响重载
函数隐藏 无隐藏概念,子类同名方法会直接覆盖父类方法(Python 为动态类型,无静态绑定) 与 C++类似,子类同名非虚方法会隐藏父类方法,可通过 base.方法名 调用父类方法 无"隐藏"概念,子类同名方法若不满足重写条件(如参数不同),视为新方法,不会屏蔽父类
函数覆盖 无显式重写语法,子类定义同名方法即实现重写,依托动态类型实现多态 需父类方法加 virtual,子类加 override,规则与 C++完全一致 所有非静态方法默认虚函数,子类重写需加 @Override 注解(可选但推荐),参数必须一致

说明

  • Python 作为动态类型语言,无编译期绑定,函数调用始终在运行时根据实际对象类型决定,因此无"隐藏"仅存在"重写";
  • C# 对覆盖的语法约束比 C++ 更严格,强制要求 virtual/override 关键字,减少隐式错误;
  • Java 无 virtual 关键字,除 staticfinalprivate 方法外,所有成员方法默认支持重写(覆盖)。

五、多态性的优点和应用场景

1. 多态性的优点

  • 代码扩展性强:新增派生类时,无需修改基类及调用基类的代码,仅需实现派生类的覆盖方法即可扩展功能;
  • 代码复用性高:基类定义通用接口,所有派生类可复用该接口,降低代码冗余;
  • 代码可读性好:统一的基类接口让代码逻辑更清晰,调用方无需关注具体派生类类型,仅需操作基类指针/引用;
  • 解耦性强:将"调用逻辑"与"具体实现"分离,符合开闭原则(对扩展开放,对修改关闭)。

2. 典型应用场景

  • 框架/库开发 :如图形库中定义 Shape 基类,派生 CircleRectangle 等类,框架提供统一的 draw() 接口,调用方无需关注具体图形类型;
  • 设计模式实现:如工厂模式、策略模式、模板方法模式等,均依赖多态实现"定义接口、差异化实现";
  • 接口统一化处理 :如统一处理不同类型的数据源(文件、数据库、网络),定义 DataSource 基类,派生类实现 read()/write() 方法,调用方统一操作 DataSource 指针;
  • 事件处理系统 :如 GUI 框架中,不同控件(按钮、文本框)的点击事件,通过基类 Widget 的虚函数 onClick() 统一处理。

示例:多态在策略模式中的应用

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

// 支付策略基类(统一接口)
class PaymentStrategy {
public:
    virtual void pay(double amount) = 0; // 纯虚函数,定义接口
    virtual ~PaymentStrategy() {}
};

// 支付宝支付(派生类实现具体策略)
class Alipay : public PaymentStrategy {
public:
    void pay(double amount) override {
        cout << "使用支付宝支付 " << amount << " 元" << endl;
    }
};

// 微信支付(派生类实现具体策略)
class WeChatPay : public PaymentStrategy {
public:
    void pay(double amount) override {
        cout << "使用微信支付 " << amount << " 元" << endl;
    }
};

// 订单类(调用统一接口,无需关注具体支付方式)
class Order {
private:
    PaymentStrategy* payment;
public:
    Order(PaymentStrategy* p) : payment(p) {}
    void checkout(double amount) {
        payment->pay(amount); // 多态调用具体支付方式
    }
    ~Order() { delete payment; }
};

int main() {
    // 新增支付方式仅需添加派生类,无需修改 Order 类(符合开闭原则)
    Order* order1 = new Order(new Alipay());
    order1->checkout(99.9); // 输出:使用支付宝支付 99.9 元

    Order* order2 = new Order(new WeChatPay());
    order2->checkout(199.9); // 输出:使用微信支付 199.9 元

    delete order1;
    delete order2;
    return 0;
}

六、函数覆盖与函数隐藏的联系与区别

1. 联系

  • 二者均发生于基类与派生类的不同作用域,且均表现为"派生类同名函数屏蔽基类函数"的现象;
  • 函数覆盖是函数隐藏的特殊形式------当基类同名函数被声明为虚函数时,隐藏行为升级为覆盖,触发动态多态。

2. 区别

维度 函数覆盖(重写) 函数隐藏
触发前提 基类函数必须声明为 virtual 基类函数非虚函数(若为虚函数则变为覆盖)
参数要求 函数名、参数列表、返回值完全一致(协变返回值除外) 无严格要求,参数列表可不同
调用机制 运行期动态绑定(基类指针/引用指向派生类对象时调用派生类版本) 编译期静态绑定(无论何种调用方式,优先调用派生类版本)
特性表现 支持多态 仅屏蔽基类函数,不支持多态

3. 示例对比

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

class Base {
public:
    // 非虚函数:触发函数隐藏
    void func1() { cout << "Base::func1" << endl; }
    // 虚函数:触发函数覆盖
    virtual void func2() { cout << "Base::func2" << endl; }
};

class Derived : public Base {
public:
    // 隐藏基类 func1(非虚)
    void func1() { cout << "Derived::func1" << endl; }
    // 覆盖基类 func2(虚)
    void func2() override { cout << "Derived::func2" << endl; }
};

int main() {
    Base* ptr = new Derived();
    ptr->func1(); // 调用 Base::func1(隐藏:静态绑定)
    ptr->func2(); // 调用 Derived::func2(覆盖:动态绑定)

    delete ptr;
    return 0;
}

总结

  1. 函数重载:同一作用域、同名不同参,编译期静态匹配,实现逻辑为"参数差异化";
  2. 函数覆盖:基类虚函数+派生类同名同参函数,运行期动态绑定,是实现多态的关键;
  3. 函数隐藏:派生类屏蔽基类非虚同名函数,编译期静态绑定,无多态特性;
  4. 多态优点:提升代码扩展性、复用性与解耦性,是设计模式和通用框架开发的重要基础;
  5. 跨语言特性:Java/Python/C# 对重载/覆盖/隐藏的支持规则不同,但逻辑均围绕"作用域""参数匹配""动态/静态绑定"展开。

via:

相关推荐
gfdhy2 小时前
【C++实战】多态版商品库存管理系统:从设计到实现,吃透面向对象核心
开发语言·数据库·c++·microsoft·毕业设计·毕设
清酒难咽2 小时前
算法案例之分治法
c++·经验分享·算法
小屁猪qAq2 小时前
强符号和弱符号及应用场景
c++·弱符号·链接·编译
头发还没掉光光3 小时前
HTTP协议从基础到实战全解析
linux·服务器·网络·c++·网络协议·http
jojo_zjx4 小时前
GESP 24年12月2级 数位和
c++
自由的好好干活4 小时前
PCI9x5x驱动移植支持PCI9054在win7下使用3
c++·驱动开发
WBluuue5 小时前
数据结构与算法:dp优化——优化尝试和状态设计
c++·算法·leetcode·动态规划
睡不醒的kun6 小时前
定长滑动窗口-基础篇(2)
数据结构·c++·算法·leetcode·职场和发展·滑动窗口·定长滑动窗口
小王努力学编程6 小时前
LangChain——AI应用开发框架(核心组件1)
linux·服务器·前端·数据库·c++·人工智能·langchain