c++入门学习⑦——继承和多态(超级详细版)

这篇具有很好参考价值的文章主要介绍了c++入门学习⑦——继承和多态(超级详细版)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

继承

继承是什么?

为什么会存在继承?

语法:

一些基本的定义:

三种继承方式:

对象模型

对于构造和析构的顺序

同名函数的处理方式

总结:

静态成员:

定义:

性质:

共享数据 

编译阶段分配内存

类内声明类外初始化

静态成员函数

静态成员函数与普通成员函数的区别:

静态成员访问:

多继承

菱形继承

菱形继承会遇到的问题:

如何解决?

原理:

多态

多态分类:

静态多态和动态多态区分:

静态多态:(静态绑定)

动态多态:(动态绑定)

动态多态实现:

引入:

那什么是虚函数?

虚函数的重写?

多态实现代码示例:

虚函数的动态绑定机制(面试经常问的)

 重载、重定义、重写的区别

纯虚函数

抽象类:

那什么是接口继承呢?

那正常的继承叫做什么?

多态原理(工具观察):

虚析构

语法:

原理:通过父类指针来释放子类空间

纯虚析构

特点:

语法:

纯虚析构和虚析构的共性与区别:

虚析构和纯虚析构共性:

虚析构和纯虚析构区别:

总结:

总结:


前言

这篇博客介绍相关c++继承以及多态的内容,在c++中属于很重要的一个部分,请认真学习(ง •_•)ง

继承

继承是什么?

简单从字面意思上来说就是继承上一个事物的基本特点

准确定义是:继承是面向对象三大特征之一,可以使得子类具有父类的属性和方法 , 还可以在子类中重新定义 ,以及追加属性和方法。

为什么会存在继承?

假如要构造两个类——斑点猫和白猫,其中包含的行为有类对象的行为习惯等,而这两个同属于猫类,他们有很多相似的地方,如果没有继承,则需要重复性写这一相似的内容,一个两个还好,如果多个类都有相似之处,继承就是比较好的方式了。

语法:

class 子类名:继承方式(public/private/protected)  父类名

一些基本的定义:

基类:被继承的类,又称为“父类”

派生类:继承其他类的类,又称为“子类”

三种继承方式:

公共继承public:继承父类的公共权限,则相应的私有权限和保护权限也继承过去,也是相应的私有和保护权限

保护继承protected:继承父类的保护权限,则父类的公共权限到子类中变为保护权限,而私有权限仍然是私有权限

私有继承private:继承父类的私有权限,父类中所有权限到子类中都是私有权限。

对象模型

这里创造两个类,一个基类,一个派生类,B类继承A类的公共权限

class A

class B

#include<iostream>
using namespace std
class A
{
public:
    int a;
};
class B:public A
{
public:    
    int b;
};

则它们在内存中的分布是:

c++入门学习⑦——继承和多态(超级详细版),c/c++,学习,c++,开发语言

也就是说,派生类会继承一份基类的成员,然后在旁边创建自己的成员

对于构造和析构的顺序

当创建一个子类对象后,如果要初始化它,则哪一个类先构造,哪一个类后构造,程序结束时,谁先析构?

谁的构造函数先调用,谁的析构函数先调用

代码示例:

#include<iostream>
using namespace std;
 
class A
{
public:
    A()
    {
        cout<<"A的构造函数"<<endl;
    }
    ~A()
    {
        cout<<"A的析构函数"<<endl;
    }
    int m_a;
};
class B:public A
{
public:
    B()
    {
        cout<<"B的构造函数"<<endl;
    }
    ~B()
    {
        cout<<"B的析构函数"<<endl;
    }
    int m_b;
};
test1(){
	B b;
}
int main()
{
    test1();
    system("pause"); 
    return 0;
}

 输出结果为:

c++入门学习⑦——继承和多态(超级详细版),c/c++,学习,c++,开发语言

由此可见刚才问题的答案是:父类先构造然后子类构造,子类析构再父类析构

这个可以用鸡生蛋来记——鸡比它生的蛋要早出生,先有这只鸡才有这个蛋,然后吃的时候,先吃蛋,再吃鸡

同名函数的处理方式

对于基类和派生类的同名函数如何处理呢?换句话说当调用这个同名函数时,真正会起作用的是哪一个函数呢?

我们来通过代码看一看:

#include<iostream>
using namespace std;
 
class A
{
public:
    void speak()
	{
    	cout<<"A会说话了"<<endl;
	}
    int m_a;
};
class B:public A
{
public:
    void speak()
	{
    	cout<<"B会说话了"<<endl;
	}
    int m_b;
};
test1(){
	B b;
	b.speak();
}
int main()
{
    test1();
    system("pause"); 
    return 0;
}

c++入门学习⑦——继承和多态(超级详细版),c/c++,学习,c++,开发语言

通过代码发现派生类调用同名函数,则调用派生类的同名函数

得出结论:

如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数

那么如何通过派生类调用基类的同名函数呢?

通过添加作用域      类名::

c++入门学习⑦——继承和多态(超级详细版),c/c++,学习,c++,开发语言

总结:

  • 当子类与父类拥有同名的成员函数,子类会隐意父类中同名成员函教,加作用域可以访问到父类中同名函数
  • 子类对象可以直接访问到子类中同名成员
  • 子类对象加作用域可以访问到父类同名成员

作用域是👉类名::

静态成员:

定义:

简单来说:普通的成员前加上一个static关键字,就被称为静态成员。

当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

性质:

共享数据 

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。

编译阶段分配内存

在编译阶段就已经分配好内存了

类内声明类外初始化

静态成员不可以在类内初始化,要在类外初始化,初始化时在成员名加上作用域即可

静态成员函数

在成员函数前加上关键字static,这样就把类的特定对象和该函数独立开

静态函数只要使用类名加范围解析运算符 :: 就可以访问

静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数

静态成员函数与普通成员函数的区别:
  • 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)
  • 普通成员函数有 this 指针,可以访问类中的任意成员

静态成员访问:

  1. 通过类名访问:类名::父类作用域::成员
  2. 通过对象访问
  3. 同名静态函数或者是变量,父类的所有同名函数包括函数重载会被隐藏,除非加上作用域才可以成功

多继承

子类是否只可以继承一个类,可以进行多个类吗?如果可以继承多个类,多继承的语法是什么?

c++允许子类可以继承多个类

语法为:class 子类  :继承权限 父类1 ,继承权限 父类2,继承权限 父类3……

但是注意:在实际开发过程中,不建议使用多继承语法

菱形继承

什么是菱形继承?

如下图:

c++入门学习⑦——继承和多态(超级详细版),c/c++,学习,c++,开发语言

这样一种几个类相互有一定的继承关系的看起来像菱形一样的,被称为菱形继承

定义:两个派生类继承同一个基类,又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石继承

菱形继承会遇到的问题:

由于类4同时继承类2和类3,而类2和类3都继承了1,那么相当于类4继承了两份类1.

如何解决?

使用虚继承的方式,用关键字virtual,可以使派生类不重复继承

在派生类继承的时候在继承权限前加上这个关键字virtual即可

示例:

c++入门学习⑦——继承和多态(超级详细版),c/c++,学习,c++,开发语言

原理:

虚继承会产生虚基类指针vbptr

该指针指向虚基表,虚基表记录的是通过指针访问公共祖先的数据的偏移量

多态

多态是c++面向对象三大特征之一

多态按字面的意思就是多种形态,具体解释是指:不同对象去完成某一个行为是而产生的不同状态

当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态

多态性提供接口与具体实现之间的隔离,将what和how这两个板块分离开

多态好处:

  1. 组织结构清晰
  2. 可读性强
  3. 对于前期和后期扩展以及维护性高

多态分类:

分为静态多态动态多态(其实静态多态之前就已经涉及了)

静态多态和动态多态区分:

静态多态的函数地址早绑定-编译阶段确定函数地址
动态多态的函数地址晚绑定-运行阶段确定函数地址

静态多态:(静态绑定)

静态多态:函数重载,重定义,运算符重载属于静态多态

静态多态的函数地址早绑定-编译阶段确定函数地址

动态多态:(动态绑定)

动态多态:派生类和虚函数实现运行时的多态

动态多态的函数地址晚绑定-运行阶段确定函数地址

动态多态满足条件:

1.有继承关系

2.子类重写父类虚函数,函数返回值类型,名称,参数列表完全相同

3.使用->父类指针或者引用执行于类对象

下面仅介绍重点——动态多态

动态多态实现:

如何实现动态多态呢?有什么作用?使用场景又是什么?

多态是当不同继承关系的类对象去调用一个函数而产生的不同的行为

实现动态多态的条件

  • 类对象要调用虚函数,且派生类中必须要包含基类虚函数的重写
  • 通过基类的引用/指针去调用虚函数⭐ 

引入:

继承会使子类都含有父类的数据,而每一个子类都对这份数据进行重写,而如果想创建一个函数,使其可以操纵父类所有派生的子类?(即使以后还有派生的子类,也可以操纵)

该怎么做呢?

其实最重要的是函数的参数

参数设定其实需要找到这几个派生类的共性——》它们都有父类的数据

因此如果要实现这样的一个函数,那么就需要其参数为父类的指针或是引用,而要操纵所有子类,这就需要用父类指针来保存子类的空间地址⭐⭐⭐

而用父类指针保存子类地址会造成问题:

代码示例:

#include<iostream>
using namespace std;
class A
{
public:
    void say()
    {
        cout << "A会说话了" << endl;
    }
};

class B : public A
{
public:
    void say()
    {
        cout << "A类里的B会说话了" << endl;
    }
};
class C :public A
{
public:
    void say()
    {
        cout << "A类里的C会说话了" << endl;
    }
};
void test1(A*a)
{
    a->say();//输出的都是 A会说话了
}
int main()
{
    A* a=new B;
    test1(a);
    A* b = new C;
    test1(b);
    return 0;
}

我们发现本身要通过父类的指针来完成子类的重定义函数,但是实际上调用的都是父类的函数,为什么呢?

因为指针指向的地址是由指针指向类型决定的,A*a,这个式子就决定了它要指向父类,而子类中继承了父类的数据,因此a指向子类中的父类,实现的也是父类的函数

如何解决这一个问题呢? 

用虚函数

那什么是虚函数?

被关键字virtual修饰的类成员函数,而且子类中要重写虚函数(可加virtual也可不加)

虚函数的重写?

虚函数的重写(又可以叫做覆盖)👉

派生类中有一个跟基类完全相同的虚函数(这里指它们的返回值类型、函数名字、参数列表完全相同),则称子类的虚函数重写了基类的虚函数,而其中的重写内容可以做适当改变,来实现多态

多态实现代码示例:

#include<iostream>
using namespace std;
class A
{
public:
    virtual void say()
    {
        cout << "A会说话了" << endl;
    }
};

class B : public A
{
public:
    void say()
    {
        cout << "A类里的B会说话了" << endl;
    }
};

void test1(A*a)
{
    a->say();//输出的是A类里的B会说话了
}
int main()
{
    A* a=new B;
    test1(a);
    return 0;
}

这样父类指针就可以调用子类的函数了,解决了父类指针指向子类地址的一个问题 

那为什么变成虚函数后,父类指针可以调用子类中的子类函数而非父类呢?

虚函数的动态绑定机制(面试经常问的)

当一个类中的函数变为虚函数之后,会产生虚函数指针(vfptr),虚函数指向虚函数表(vftable),而如果这个类没有被继承的话——》虚函数表保存的是这个虚函数的入口地址

如果被继承了,子类会把父类中的虚函数指针给继承过来,但是这时候这个虚函数表里的内容就发生变化了,它里面的是子类重写的地址

c++入门学习⑦——继承和多态(超级详细版),c/c++,学习,c++,开发语言如图本质上A*a指针还是指向父类地址,但是由于去调用时发现它是一个虚函数指针,而这个指针指向的虚函数表是重写的say函数地址,因此调用的是子类函数。

 重载、重定义、重写的区别

重载:同一作用城,同名函教,参数的顺序,个数,类型不同都可以重载。函数的返回值类型不能作为重载条件(函数重载,运算行重载)
重定义:有继承,子类重定义父亲的同名函数(非虚函数),参数顺序,个数,类型可以不同,子类的同名函数会屏蔽父类的所有同名函数(可以通过作用域解决)
重与(覆盖):有继承,子类重写父类的虚函数。返回值类型,函数名,参数顺序,个数,类型都必须一致。 

而当这样写后,代码只进行子类函数调用,此时的父类虚函数就没有什么用了,那么我们可以不可以找到一种方式去省略掉父类虚函数的内容——可以,用纯虚函数,下面就来介绍什么是纯虚函数?

纯虚函数

在多态中,通常父类中虚函数的实现是毫无意义的主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数

纯虚函数是指在虚函数上加上=0则称为虚函数

例如:

virtual void say ()=0;

而当一个类中由纯虚函数后这个类就成了——抽象类👇 

抽象类:

抽象类是指类中含有纯虚函数的类,抽象类又称为接口类,抽象类不能实例化对象(因为它没有函数体,怎么调用?)

抽象类特点:

无法实例化对象

子类必须重写抽象类中的所有纯虚函数,否则也属于抽象类⭐

其实抽象类主要目的是设计类的接口

也就是说只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,其实这里的纯虚函数存在接口继承

那什么是接口继承呢?

简单来说虚函数的继承——接口继承,派生类并没有继承基类函数,而是基类虚函数的接口,这是一个实现多态的方式,可以达到重写的目的,如果不要求多态实现,函数尽量不要定义为虚函数

那正常的继承叫做什么?

普通函数的继承为——实现继承,派生类它继承了基类函数,继承的是函数的实现,而非接口,派生类可以使用这个接口

多态原理(工具观察):

前面已经将结果多态如何通过虚函数实现的了,现在来使用工具观察多态实现的虚函数表

⭐满足多态以后的函数调用,并不是在编译时确定的,而是运行起来以后到对象中去找的

不满足多态的函数调用是在编译时确认好的。

这里借助vs下的一个工具

c++入门学习⑦——继承和多态(超级详细版),c/c++,学习,c++,开发语言

c++入门学习⑦——继承和多态(超级详细版),c/c++,学习,c++,开发语言

输入dir空格后出现目录,然后直接输入以下内容,即可看代码中的类

  • cl(空格)/d1(空格)reportSingleClassLayout(类名)(空格)(文件名)(回车)     

例如在test.cpp文件中,A类的结构:

  • cl /d1 reportSingleClassLayoutA test.cpp

最后就可以看见这个类的内部情况。

⭐回顾:我们要达到多态,有两个条件,一个是虚函数覆盖,一个是基类对象的指针或引用调用虚函数

哎?有人发现没,其实刚才多态实现里的代码有个问题——new出来的空间一直没有释放,堆区空间没释放会发生内存泄漏,其实这里就涉及到另一个知识了——虚析构

这时可能会问,为什么非要用虚析构呢?正常的析构为什么不可以释放掉代码呢?这时假如你写一个代码,就会发现——父类的析构函数正常调用,而子类的析构函数无法调用,也就是我们没办法在子类析构函数中去写代码,释放掉子类的堆区数据。

做法就是在父类的析构前加上一个很熟悉的关键字——virtual

这也就是下面要介绍的虚析构

虚析构

多态使用时,若子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,父类在析构的时候不会调用子类析构,如果子类有堆区属性,则会出现内存泄漏
则需要将父类中的析构函数改为虚析构

语法:

虚析构语法:
virtua1 ~类名()

原理:通过父类指针来释放子类空间

由于刚开始实现多态的时候,子类的空间是由父类指针储存的,因此只能通过父类指针来释放子类空间。

已知的是

构造是:父类——》成员——》子类

析构是:子类——》成员——父类

析构函数本身也是个成员函数,虚析构,产生虚函数指针,指向虚函数表:包含这个类的析构函数 

c++入门学习⑦——继承和多态(超级详细版),c/c++,学习,c++,开发语言

纯虚析构

特点:

纯虚析构的本质:是析构函数,完成各个类的回收工作。
必须为纯虚析构函数提供一个函数体
而且纯虚析构函数必须在类外实现

含有纯虚析构的类也是抽象类

语法:

virtual ~类名()=0;
在类外:类名::~类名() 

纯虚析构和虚析构的共性与区别:

虚析构和纯虚析构共性:

可以解决父类指针释放子类对象
都需要有具体的函数实现


虚析构和纯虚析构区别:

如果是纯虚析构,该类属于抽象类,且无法实例化对象,需要在类外实现

总结:

  • 1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  • 2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  • 3.拥有纯虚析构函数的类也属于抽象类

总结:

c++的入门核心内容基本介绍完毕,整理的有关c++的文件操作以及内存分区放在下面了,可以按需观看

c++文件操作-CSDN博客

c++内存的四大分区详解-CSDN博客

下一阶段是对于c++的模板,欢迎点赞收藏关注主页专栏o(* ̄▽ ̄*)ブ文章来源地址https://www.toymoban.com/news/detail-833458.html

到了这里,关于c++入门学习⑦——继承和多态(超级详细版)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • c、c++、java、python、js对比【面向对象、过程;解释、编译语言;封装、继承、多态】

    目录 内存管理、适用 区别 C 手动内存管理:C语言没有内置的安全检查机制,容易出现内存泄漏、缓冲区溢出等安全问题。 适用于系统级编程 C++ 手动内存管理:C++需要程序员手动管理内存,包括分配和释放内存,这可能导致内存泄漏和指针错误。 适用于游戏引擎和系统级编

    2024年02月08日
    浏览(76)
  • 【C++进阶】继承、多态的详解(多态篇)

    作者:爱写代码的刚子 时间:2023.8.16 前言:本篇博客主要介绍C++中多态有关的知识,是C++中的一大难点,刚子将带你深入C++多态的知识。(该博客涉及到的代码是在x86的环境下,如果是在x86_64环境下指针的大小可能需要变成8bytes) 多态的概念 多态的概念:通俗来说,就是多

    2024年02月12日
    浏览(42)
  • 【C++进阶】继承、多态的详解(继承篇)

    作者:爱写代码的刚子 时间:2023.7.28 前言:本篇博客主要介绍C++进阶部分内容——继承,C++中的继承和多态是比较复杂的,需要我们认真去深挖其中的细节。 继承的概念及定义 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序

    2024年02月13日
    浏览(39)
  • 【C++】继承和多态

    继承机制是面向对象程序设计使代码可以 复用 的最重要的手段,它允许程序员在保持原有类特性的基础上进行 扩展 ,增加功能,这样产生新的类,称 派生类/子类 。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的复用都是函数复

    2024年02月06日
    浏览(42)
  • C++ 中的继承和多态

    继承允许我们 依据一个类来定义另一个类 ,这使得创建和维护一个应用程序变得更容易。这样做也达到了重用代码功能和提高执行效率的效果。 派生类的成员可以直接访问基类的保护成员(protected),但不能直接访问基类的私有成员(private) 。不过需要注意的是,派生类

    2024年02月06日
    浏览(41)
  • C++基础篇:07 继承与多态

            当遇到问题,先看一下现有的类是否能够解决一部分问题,如果有则继承,并在此基础上进行扩展来解决所有问题,以此缩短解决问题的时间(代码复用)         当遇到一个大而复杂的问题时,可以把复杂问题拆分成若干个小问题,为每个小问题的解决设计一

    2024年02月08日
    浏览(50)
  • C++类开发第七篇(详细说说多态和编译原理)

    多态性(polymorphism)提供接口与具体实现之间的另一层隔离,从而将”what”和”how”分离开来。多态性改善了代码的可读性和组织性,同时也使创建的程序具有可扩展性,项目不仅在最初创建时期可以扩展,而且当项目在需要有新的功能时也能扩展。 c++支持编译时多态(静态多

    2024年03月09日
    浏览(53)
  • c++面向对象之封装、继承、和多态

    把客观事物封装成类,而且可以把自己的数据和方法设置为只能让可信的类或者对象操作,对不可信的信息进行隐藏(利用public,private,protected,friend)实现 has-a :描述一个类由多个部件类构成,一个类的成员属性是另一个已经定义好的类。 use-a:一个类使用另一个类,通过类之间

    2024年02月02日
    浏览(53)
  • C++ 面向对象核心(继承、权限、多态、抽象类)

    继承(Inheritance)是面向对象编程中的一个重要概念,它允许一个类(称为派生类或子类)从另一个类(称为基类或父类)继承属性和方法。继承是实现类之间的关系,通过继承,子类可以重用父类的代码,并且可以在此基础上添加新的功能或修改已有的功能。 在C++中,继承

    2024年02月08日
    浏览(49)
  • 【C++】继承和多态、共有私有和保护、重写

    继承是一种机制,通过它 一个类可以从另一个类继承属性和方法 。派生类(子类)继承基类(父类)的成员函数和数据成员,并且可以在其基础上扩展自己的成员函数和数据成员。C++支持多重继承,即一个派生类可以同时从多个基类中继承。 多态是指 同一种操作作用于不同

    2024年02月03日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包