C++ 中的继承和多态
C++ 中的继承和多态
- 一、继承
- 二、函数重载、隐藏、覆盖、重写
- 1.函数重载(Function Overload)
- 2.函数隐藏(Function Hiding)
- 3.函数重写与函数覆盖(Function Override)
- 三、多态
- 四、纯虚函数和抽象类
- 五、多重继承的二义性(菱形继承)
一、继承
继承允许我们依据一个类来定义另一个类,这使得创建和维护一个应用程序变得更容易。这样做也达到了重用代码功能和提高执行效率的效果。
派生类的成员可以直接访问基类的保护成员(protected),但不能直接访问基类的私有成员(private)。不过需要注意的是,派生类的成员函数只能访问所作用的那个对象(即this指针指向的对象)的基类保护成员,不能访问其他基类对象的基类保护成员(比如通过函数参数传来的基类对象)。
继承分为公有继承、保护继承与私有继承,除了公有继承,剩下两个很少用到,三者区别如下:
二、函数重载、隐藏、覆盖、重写
1.函数重载(Function Overload)
C++规定在同一作用域中,函数名相同但函数特征标(即参数个数、类型、顺序)不同时,构成函数重载。
函数重载的注意事项:
- 返回值类型不能作为重载的标准。
- 参数是否为引用不能作为重载的标准,尽管某些时候能通过编译,但在调用时会产生二义性。
- 成员函数是否被static修饰也不能作为重载的标准,因为在通过实例化后的对象调用方法时无法区分是否要调用静态成员函数。
- 一个函数不能既作为重载函数,又作为有默认参数的函数,因为当调用函数时如果少写一个参数,系统无法判定是利用重载函数还是利用默认参数的函数,即
int func(int a)
和int func(int a = 0)
是不能在同一作用域中同时存在的。
这里还要特别注意一下const修饰函数或函数参数时的情况:
class A {public:/*** 不管形参有没有const修饰实参都不会被修改,二者在调用时没有区别,因此不能构成重载*/void func(int a);void func(const int a); // NO/*** 由底层const修饰的指针指向的实参不能被修改,二者在调用时存在区别,因此可以构成重载*/void func_bot_p(int *a);void func_bot_p(const int *a); // YES/*** 不管有没有顶层const修饰,该指针指向的内容都可以被修改,二者在调用时没有区别,因此不能构成重载*/void func_top_p(int *a);void func_top_p(int *const a); // NO/*** 在const修饰引用时实参不能被修改,二者在调用时存在区别,因此可以构成重载*/void func_ref(int &a);void func_ref(const int &a); // YES/*** 由const修饰的成员函数只能由const对象调用,二者在调用时存在区别,因此可以构成重载*/void func_ret(int a);void func_ret(int a) const; // YES
};
2.函数隐藏(Function Hiding)
不同作用域中定义的同名函数会构成函数隐藏(不要求函数返回值和函数参数类型相同)。
类成员函数会屏蔽全局函数,派生类成员函数会屏蔽与其同名的基类成员函数(但如果该基类成员函数为虚函数,且函数返回值和特征标相同则构成函数重写)。
#include <iostream>using namespace std;void func() {cout << "global::func()" << endl;
}class A {public:/*** 隐藏了外部的func*/void func() {cout << "A::func()" << endl;}void use_func() {func();::func(); // 使用全局函数时要加作用域}
};class B : public A {public:/*** 隐藏了基类的func*/void func() {cout << "B::func()" << endl;}void use_func() {func();A::func(); // 使用基类函数时要加作用域}
};int main() {A a;B b;a.use_func();b.use_func();
}
atreus@MacBook-Pro % g++ main.cpp -o main -std=c++11
atreus@MacBook-Pro % ./main
A::func()
global::func()
B::func()
A::func()
atreus@MacBook-Pro %
3.函数重写与函数覆盖(Function Override)
派生类中与基类同返回值类型、同名和同特征标的虚函数重定义,构成虚函数覆盖,也叫虚函数重写。
需要注意的是,在默认情况下,如果重新定义了继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针,这种新出现的特性叫做返回类型协变(covariance of return type)。
#include <iostream>using namespace std;class A {public:void func() {cout << "A::func()" << endl;}virtual void func_v() {cout << "A::func_v()" << endl;}
};class B : public A {public:/* 函数隐藏 */void func() {cout << "B::func()" << endl;}/* 函数重载 */void func_v() override {cout << "B::func_v()" << endl;}
};int main() {A *a = new B;a->func();a->func_v();delete a;
}
atreus@MacBook-Pro % g++ main.cpp -o main -std=c++11
atreus@MacBook-Pro % ./main
A::func()
B::func_v()
atreus@MacBook-Pro %
三、多态
多态是指一个方法同时具有多种形态,具体形态取决于调用该方法的具体对象。从实现的角度可以将多态分为编译时多态(主要通过函数模板、函数重载和运算符重载实现)和运行时多态(主要通过虚函数和函数重写实现)。
对于运行时多态,其实现主要有三个前提:
- 存在继承。
- 存在函数重写(覆盖)。
- 存在基类指针或者引用指向子类对象。
运行时多态的实现要借助于动态绑定,动态绑定借助于虚函数实现,虚函数的限制如下:
- 只有类的成员函数才能声明为虚函数。
- 基类的析构函数可以是虚函数且通常声明为虚函数。
- 构造函数不能为虚函数。
- 内联函数不能是虚函数。
- 静态成员函数不能是虚函数。
虚函数、虚函数表及虚函数实现多态的原理
其中,动态绑定是运行时绑定,通过地址实现,它是指基类的指针或引用有可能指向不同的派生类对象。对于非虚函数,执行时实际调用该函数的对象类型即为该指针或引用的静态类型。而对于虚函数,执行时实际调用该函数的对象类型为该指针或引用所指对象的实际类型。
四、纯虚函数和抽象类
当类声明中包含纯虚函数(定义是末尾有 = 0
的虚函数)时,则不能创建该类的对象,这个类变为抽象类,C++中的抽象类类似于Java中的接口,抽象类必须至少包含一个纯虚函数。
此外,对于抽象类还有以下注意事项:
- 抽象类只能用作其他类的基类,当然也可以作为另一个抽象类的基类。
- 抽象类不能用来定义对象,不能实例化,也不能用作参数类型、函数返回类型或显式转换的类型。
- 如果一个非抽象类从抽象类中派生,则其必须通过覆盖来实现所有的继承而来的抽象成员函数。
#include <iostream>/* 抽象类 */
class Car {public:virtual void showName() = 0; // 纯虚函数
};class Audi : public Car {public:void showName() override { std::cout << "Audi" << std::endl; }
};class Volvo : public Car {public:void showName() override { std::cout << "Volvo" << std::endl; }
};int main() {Audi audi;Volvo volvo;audi.showName(); // Audivolvo.showName(); // Volvoreturn 0;
}
五、多重继承的二义性(菱形继承)
菱形继承是指当类B和类C同时继承于基类A,类D同时继承于类B和类C,此时类A中的成员变量和成员函数继承到类D中就变成了两份,在D中调用A中的成员会导致二义性,同时一个变量分两份存储也存在内存空间浪费的问题。
通过虚基类和虚继承机制,可以在多继承中只保留一份共同成员,从而解决了多继承导致的命名冲突和数据冗余。
在继承方式前面加上 virtual
关键字就是虚继承,如果不采用虚继承,在类D中使用类A中的m_a时则需要通过 B::m_a
或 C::m_a
来指定具体使用哪个m_a。
#include <iostream>using namespace std;class A {protected:int m_a = 0;
};class B : virtual public A {protected:int m_b = 1;
};class C : virtual public A {protected:int m_c = 2;
};class D : public B, public C {protected:int m_d = 3;public:D() {cout << m_a << endl;cout << m_b << endl;cout << m_c << endl;cout << m_d << endl;}
};int main() {D d;return 0;
}
atreus@MacBook-Pro % g++ main.cpp -o main -std=c++11
atreus@MacBook-Pro % ./main
0
1
2
3
atreus@MacBook-Pro %
C++标准库中的iostream类就是一个虚继承的实际应用案例。iostream从istream和ostream直接继承而来,而istream和ostream又都继承自一个共同的名为base_ios的类,是典型的菱形继承。
参考:
https://cloud.tencent.com/developer/article/1177174
https://blog.csdn.net/weixin_39640298/article/details/88725073
http://c.biancheng.net/view/2280.html
C++ 中的继承和多态相关推荐
- Java中的继承 与 多态(中)
先导: 我们在<Java中的继承 与 多态(上)>当中讲解了如下几个问题, 1.继承是什么 2.super关键字 3.特殊考点-父子类中不同代码块的实现顺序 所以现在我们对于继承 ...
- Python中的继承和多态
本文以生活中的基础现象为切入点,主要介绍了Python基础中继承和多态,包括单继承.多继承的语法.多态常见的 "鸭子类型". 以及如何重写父类的方法都做了详细的讲解. 一.继承的介 ...
- python有什么用处案例_用实例解释Python中的继承和多态的概念
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类.父类或超类(Base class.Supe ...
- C#中的继承与多态还有接口
简单继承 多态 接口 参考 简单继承 最简单的三个类 [csharp] view plaincopy print? public class Animal { public Animal() { De ...
- java中的多态与继承_【Java学习笔记之十六】浅谈Java中的继承与多态
1. 什么是继承,继承的特点? 子类继承父类的特征和行为,使得子类具有父类的各种属性和方法.或子类从父类继承方法,使得子类具有父类相同的行为. 特点:在继承关系中,父类更通用.子类更具体.父类具有更 ...
- python中的继承与多态_Python3 与 C# 面向对象之~继承与多态
2.继承 ¶ 2.1.单继承 ¶ 在OOP中,当我们定义一个Class的时候,可以从某个现有的Class继承 新的Class称为子类,而被继承的 ...
- java中继承和多态的实验,Java中的继承和多态
我有两个类:Triangle和RightAngledTr. RightAngledTr继承自Triangle.代码如下: 三角类: class Triangle { public void draw( ...
- c语言编程继承例子,C语言模拟实现C++的继承与多态示例
一.面向过程编程与面向对象编程的区别 众所周知,C语言是一种典型的面向过程编程语言,而C++确实在它的基础上改进的一款面向对象编程语言,那么,面向过程与面向对象到底有什么样的区别呢? [从设计方法角度 ...
- 【Java】继承、多态、接口
Java中的继承.多态和接口 1.用类比引入继承概念 众所周知,Java是一门面向对象的语言.如果我们要设计多种多样的交通工具,比如汽车.火车.飞机,虽然这些工具功能不同.形态不同,但是他们很多的基本 ...
最新文章
- CSS布局——横向两列布局
- 替代方法_ASD干预:替代行为的正确使用方法和注意事项
- chrome浏览器下“多余”的2px
- Qt中的QLabel组件
- python3.4 使用pymysql 连接mysql数据库
- BZOJ 4327 [JSOI2012]玄武密码 (AC自动机)
- android gradle错误,Android studio gradle错误与顶级异常
- 队列的链式存储结构及实现
- 以下不属于时序逻辑电路的有_静态时序分析圣经翻译计划——附录B:SDF
- ES6新特性_ES6_Symbol的介绍与创建---JavaScript_ECMAScript_ES6-ES11新特性工作笔记015
- 使用Data URI Scheme优雅的实现前端导出csv
- mysql 数据库导出导入到本地文件
- 稳坐CACTI,遥知千里
- qt解决中文乱码问题。总结一下
- 仿人机器人(五连杆、七连杆)拉格朗日动力学建模
- icem搅拌器网格划分_搅拌器研究所的第六个开放电影项目
- ROS中NodeHandle nh与NodeHandle nh(“~“)区别
- 求两个数的最小公倍数和最大公因数
- MFI认证——什么是苹果MFI认证
- 男女朋友关系是这么确定的。。。【其实相爱很简单】