子类与父类,以及子类的继承,重载与重写。

本文详细解析了Java中的继承机制,包括子类如何继承父类的方法与变量,特别是在不同包之间的继承规则;同时深入探讨了Java中的多态概念,如方法重载与重写的区别及实现方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(一)写在前面:在java中,不支持多重继承,即一个子类只能继承一个父类。但是接口不一样,一个子类可以有多个接口。

1、子类如果和父类在同一个包中:子类自然继承了父类的除了private以外的方法,包括友好方法和友好变量。

2、如果不在同一个包中,子类只继承了父类的protected和public类,不能继承友好变量和友好方法,这也是protected类和友好类的区别。

(二)java中存在两种多态,即重载与重写。

1、方法的重载:一个类中可以有多个方法具有相同的名字,类型也可以相同,但是这些方法的参数必须不同,只有这一个要求,这就叫方法的重载。

2、方法的重写,就涉及到了继承的概念。

(1)final关键字,可以修饰类,方法,和变量,final修饰的方法,成员变量,不能被重写,只能被老老实实继承,final嘛,毕竟是最后了,没法再修改。

(2)抽象类(abstract)

    a、首先抽象类中,可以有抽象方法,也可以是非抽象的方法,不能用new创造对象。

    b、其次,被一个非抽象类继承时,那么这个非抽象类,必须重写父类也就是抽象类的抽象方法。这个也可以从另一方面说明,不能使用final和abstract同时修饰一个方法的原因。

    c、如果是抽象类,继承了一个抽象类,那么子类可以重写,也可以不重写,直接继承就行。

(3)以上都是特殊情况,一般情况下,如果子类继承了父类的某个实例方法,那么这个子类就有权利可以重写这个方法。重写方法时注意,方法的名字,类型,参数个数,参数类型,必须和父类中的实例方法一致,如果参数不同,就成了新增的方法,而不是重写的方法了。



<think>我们正在讨论C++中子类重写父类函数(包括普通函数虚函数)的区别实现机制。根据引用[1],我们可以明确几个关键概念: 重载(Overload):同一作用域中,函数名相同但参数列表不同(个数、类型、顺序),返回值类型必须一样。 重写(Override):子类继承父类父类中的函数是虚函数(用virtual修饰),子类中重新定义了这个虚函数。 隐藏(Hiding):子类中函数基类函数同名,但基类函数不是虚函数,此时基类函数会被隐藏。 用户的问题:子类重写父类普通函数虚函数的区别。 注意:重写(Override)只针对虚函数,而普通函数在子类中重新定义实际上是隐藏(Hiding)。 因此,我们需要区分两种情况: 1. 子类重写父类的虚函数(即Override)——这是多态性的核心。 2. 子类重新定义父类的普通函数(即隐藏)——这不是多态,而是隐藏了父类的同名函数。 下面我们将从多个方面进行对比: 一、基本定义 - 虚函数重写(Override): 基类中声明为virtual的成员函数,在派生类中重新定义,且函数签名(函数名、参数列表、常量性)必须相同(协变返回类型除外)。 通过基类指针或引用调用时,会根据实际对象类型动态调用对应的函数。 - 普通函数隐藏(Hiding): 基类中有一个非虚函数,派生类中定义了一个同名函数(无论参数是否相同),那么基类的同名函数在派生类作用域中被隐藏。 注意:如果参数列表不同,则构成重载(但作用域不同,所以实际上隐藏了基类同名函数,而不是重载基类函数)。此时,通过派生类对象调用该函数名时,只能访问派生类中的版本(或通过作用域运算符显式调用基类版本)。 二、实现机制 根据引用[3]: 虚函数通过虚函数表(vtable)实现动态绑定。每个包含虚函数的类都有一个虚函数表,其中存放了指向该类虚函数的指针。当子类重写虚函数时,子类的虚函数表会覆盖父类相应函数的指针(即子类的虚函数表中对应项指向子类的实现)。每个对象内部有一个指向虚函数表的指针(vptr),在运行时根据对象的实际类型来调用正确的函数。 普通函数没有虚函数表,其调用在编译时根据静态类型确定(静态绑定)。因此,当子类定义了父类普通函数同名的函数时,父类的函数被隐藏,通过子类对象调用该函数名时,只会调用子类的函数(除非使用作用域运算符指定基类)。 三、区别对比 | 特性 | 虚函数重写(Override) | 普通函数隐藏(Hiding) | |--------------|------------------------------------------------|------------------------------------------------| | **函数要求** | 基类函数必须是虚函数(virtual) | 基类函数是普通函数(非虚) | | **函数签名** | 必须相同(协变返回类型除外) | 可以不同(参数列表不同则隐藏基类所有同名函数) | | **绑定方式** | 动态绑定(运行时根据对象类型确定) | 静态绑定(编译时根据指针/引用类型确定) | | **多态支持** | 支持多态 | 不支持多态 | | **调用方式** | 通过基类指针或引用调用时,调用实际对象的函数 | 通过基类指针或引用调用时,调用基类函数;通过派生类对象调用时,调用派生类函数(基类函数被隐藏) | | **实现机制** | 通过虚函数表(vtable)实现 | 无虚函数表,编译时解析函数地址 | | **关键字** | 基类用virtual,派生类可用override(C++11) | 无特殊关键字 | 四、代码示例 下面通过代码示例来说明: ```cpp #include <iostream> using namespace std; class Base { public: // 普通函数 void show() { cout << "Base::show()" << endl; } // 虚函数 virtual void display() { cout << "Base::display()" << endl; } }; class Derived : public Base { public: // 隐藏基类的show函数(普通函数) void show() { cout << "Derived::show()" << endl; } // 重写基类的display虚函数 void display() override { cout << "Derived::display()" << endl; } }; int main() { Base* basePtr = new Derived(); // 调用普通函数:静态绑定,根据指针类型(Base*)调用Base::show() basePtr->show(); // 输出: Base::show() // 调用虚函数:动态绑定,根据实际对象类型(Derived)调用Derived::display() basePtr->display(); // 输出: Derived::display() Derived d; d.show(); // 输出: Derived::show(),因为基类的show被隐藏 // 如果想调用基类的show,可以使用作用域运算符 d.Base::show(); // 输出: Base::show() delete basePtr; return 0; } ``` 解释: - 对于普通函数show:基类Base中的show是非虚函数,Derived类中的show函数隐藏了基类的show。通过基类指针basePtr调用show时,由于是静态绑定,调用的是Base::show。通过Derived对象d调用show时,调用的是Derived::show,而基类版本被隐藏。 - 对于虚函数display:基类Base中的display是虚函数,Derived类重写了它。通过基类指针basePtr调用display时,发生动态绑定,实际调用的是Derived::display。 五、注意事项 1. 隐藏(Hiding)规则:如果派生类中定义了基类同名函数(无论参数是否相同),基类的所有同名函数(包括重载版本)都会被隐藏。例如: ```cpp class Base { public: void func(int) {} void func(double) {} }; class Derived : public Base { public: void func(const char*) {} // 隐藏了Base中所有的func(包括func(int)func(double)) }; ``` 在Derived中,只能通过作用域运算符调用基类的func,如:`d.Base::func(10);` 2. 使用override关键字(C++11)可以避免重写时的错误(如函数签名不一致),确保重写的是基类的虚函数。如果基类没有对应的虚函数,编译器会报错。 3. 纯虚函数(抽象基类):根据引用[2],基类中的纯虚函数(如`virtual void func() = 0;`)要求派生类必须重写(除非派生类也是抽象类)。这样保证了接口的一致性。 4. final关键字:引用[4]提到,可以在虚函数后面使用final禁止重写,或者在类名后使用final禁止继承。 六、总结 - 虚函数重写(Override)用于实现运行时多态,通过虚函数表实现动态绑定。 - 普通函数在子类中重新定义实际上是隐藏(Hiding),属于静态绑定,不具多态性。 - 在设计中,若希望实现多态,应使用虚函数;若希望隐藏基类实现,可以使用普通函数(但需注意隐藏带来的影响)。 通过以上分析,我们可以清晰地理解子类重写父类普通函数虚函数的区别实现机制。 相关问题: 1. 虚函数表(vtable)的具体结构是怎样的?它是如何实现动态绑定的? 2. 在多重继承中,虚函数表是如何工作的? 3. 如何避免在隐藏基类函数时不小心引入错误? 4. 纯虚函数在抽象基类中的作用是什么?</think>### C++中子类重写父类普通函数虚函数的区别及实现机制 在C++面向对象编程中,子类重写父类函数有两种本质不同的机制:**普通函数隐藏****虚函数重写**。它们的核心区别在于是否支持运行时多态,具体差异如下: #### 1. **核心区别对比** | 特性 | 重写父类普通函数(隐藏) | 重写父类虚函数(多态) | |------|--------------------------|------------------------| | **多态支持** | ❌ 静态绑定(编译时确定) | ✅ 动态绑定(运行时确定)[^1] | | **函数要求** | 函数名相同即可(参数/返回值可不同) | 必须严格匹配函数签名(协变返回类型除外)[^1] | | **实现机制** | 名称隐藏(Name Hiding) | 虚函数表(vtable)动态调度[^3] | | **调用行为** | 由指针/引用类型决定 | 由实际对象类型决定 | | **关键字** | 无特殊关键字 | 基类需`virtual`,建议用`override`(C++11)[^4] | #### 2. **实现机制详解** 1. **虚函数重写(动态多态)** - **虚函数表(vtable)**:每个含虚函数的类有独立的虚函数表,存储函数指针[^3] ```cpp class Base { public: virtual void func() { /* 基类实现 */ } // 虚函数 }; class Derived : public Base { public: void func() override { /* 子类重写 */ } // 覆盖虚函数 }; ``` - **动态绑定过程**: ```cpp Base* obj = new Derived(); obj->func(); // 运行时通过vtable调用Derived::func() ``` - 内存结构: ``` Derived对象内存布局: +---------------+ +-------------------+ | vptr (隐式) |---->| &Derived::func | // 虚函数表项被覆盖 | 其他成员数据 | | &Base::other_func | +---------------+ +-------------------+ ``` 2. **普通函数隐藏(静态绑定)** - **名称隐藏规则**:子类定义同名函数时,隐藏父类所有重载版本[^1] ```cpp class Base { public: void print(int x) { /* ... */ } // 普通函数 }; class Derived : public Base { public: void print(double x) { /* ... */ } // 隐藏Base::print(int) }; Derived d; d.print(10); // 调用Derived::print(double) d.Base::print(10); // 必须显式指定作用域 ``` - **编译时确定**:函数调用在编译期根据变量类型解析 ```cpp Base* b = new Derived(); b->print(10); // 始终调用Base::print(int) ``` #### 3. **关键差异场景分析 ```cpp #include <iostream> using namespace std; class Animal { public: // 普通函数 void eat() { cout << "Animal eats" << endl; } // 虚函数 virtual void speak() { cout << "Animal speaks" << endl; } }; class Cat : public Animal { public: // 隐藏父类普通函数 void eat() { cout << "Cat eats fish" << endl; } // 重写虚函数 void speak() override { cout << "Meow" << endl; } }; int main() { Animal* animal = new Cat(); // 普通函数:静态绑定(按指针类型) animal->eat(); // 输出: Animal eats // 虚函数:动态绑定(按对象类型) animal->speak(); // 输出: Meow delete animal; return 0; } ``` #### 4. **设计意义最佳实践** 1. **虚函数重写的价值**: - 实现运行时多态,符合开放封闭原则[^2] - 强制接口一致性(纯虚函数使类成为抽象基类)[^2] - 支持设计模式(如工厂模式、策略模式) 2. **普通函数隐藏的风险**: - 意外隐藏父类重载函数 - 破坏多态预期行为 - 解决方案:使用`using`引入父类函数 ```cpp class Cat : public Animal { public: using Animal::eat; // 引入父类重载 void eat(string food) { /*...*/ } }; ``` 3. **现代C++增强**: - `override`:显式标记重写(避免签名错误)[^4] - `final`:禁止进一步重写继承[^4] - 协变返回类型:允许返回基类返回类型的子类指针/引用 #### 5. **总结** | **场景** | **推荐方案** | |----------|--------------| | 需要运行时多态 | 使用虚函数重写(`virtual`+`override`) | | 扩展函数功能 | 普通函数重载(注意作用域`using`) | | 定义接口规范 | 纯虚函数(抽象基类)[^2] | | 防止误重写 | `final`关键字[^4] | 理解这些差异是掌握C++多态性面向对象设计的核心。虚函数通过vtable实现动态绑定,是运行时多态的基石;普通函数隐藏是静态作用域规则的结果,需谨慎使用以避免意外行为。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值