首页 > 编程语言 >C/C++语言基础--C++面向对象之继承、继承限制、多继承、拷贝继承等知识讲解

C/C++语言基础--C++面向对象之继承、继承限制、多继承、拷贝继承等知识讲解

时间:2024-09-28 23:47:35浏览次数:9  
标签:继承 子类 成员 C++ -- 派生类 父类 public

本专栏目的

  • 更新C/C++的基础语法,包括C++的一些新特性

前言

  • 通过前面几节课,我们学习了抽象、封装相关的概念,接下来我们将讲解继承;
  • C语言后面也会继续更新知识点,如内联汇编;
  • 本人现在正在写一个C语言的图书管理系统,1000多行代码,包含之前所学的所有知识点,包括链表和顺序表等数据结构,请大家耐心等待!!预计国庆前写完更新,现在基本功能已经完成。

文章目录

面向对象程序设计有4个主要特点:抽象、封装、继承和多态性。我们已经讲解了类和对象,了解了面向对象程序设计的两个重要特征一数据抽象与封装,接下来我们将讲解继承和多态,多态在下一节课讲解。

继承关系举例

在生活中,万事万物中皆有继承,是重要的现象,一层一层嵌套,就想我们常说的**”子从父,子子孙孙无穷尽也“**

这里本文找了一张植物继承图:

在这里插入图片描述

继承概念

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

说直白一点,假设A继承B,那么B就可以使用A的一些API和变量。

例如:我们设计一个学生管理系统,学校中有老师,学生,工作人员等,我们要记录他们的信息比如学生有电话,姓名,地址,学号,各科成绩等信息。

struct Student
{
    int _number;
    string _tel;
    string _name;
   	string _addr;
    float _chineseScore;
    float _mathScore;
    float _englistScore;
};

比如教师有电话,姓名,地址,工号,工资等信息。

struct Teacher
{
    int _number;
    string _tel;
    string _name;
   	string _addr;
    float _sal;
};

这样设计后我们会发现会有很多的重复信息,那么我们可以把重复的信息提取出来,重新建立一个Person类。

struct Person
{
    uint32_t _number;
    string _tel;
    string _name;
   	string _addr;
};

那么我们的学生类和教师类就可以复用Person类。

struct Student 
{
    Person _super;			//复用Person类的成员(相当于父类)
    float _chineseScore;
    float _mathScore;
    float _englistScore;
};
 
struct Teacher 
{
    Person _super;  // 继承Person类
    float _sal;
};
  • 注意:这在C语言中可以这样实现继承,但是,这样在一个类里面组合一个类访问其成员非常的不方便,(扩展go语言采用了以上的思想,这个我们后面介绍GO语言时候会再次详细解释)。

  • C++给我们在语法层面上直接提供了支持,上面的代码可修改如下:

struct Person
{
    uint32_t _number;
    std::string _tel;
    std::string _name;
   	std::string _addr;
};
//Student继承自Person类,复用Person类的成员
//Person类可以称为**  基类或父类   ** 
//Student类可以称为**  派生类或子类  **
struct Student : public Person	
{	
    float _chineseScore;
    float _mathScore;
    float _englistScore;
};
 
struct Teacher  : public Person
{
    float _sal;
};

在继承后父类Person的成员(成员函数与成员变量)都会变成子类的一部分,这里就体现出Student和Teacher复用了Person类,在vs2022中我们可以通过调试的监视窗口看到继承关系和调用父类成员

在这里插入图片描述

继承使用

继承语法

一个类继承自另一个类语法:

class Derived(派生类名): AccessQualifier(访问限定符) Base(基类)
{
	//成员变量和成员函数声明...
}
访问限定符(Access qualifier)

访问限定符指定了从基类继承的方式,继承方式不同,成员在派生类中的权限也不同

类成员/继承方式public继承protected继承private继承
基类的public成员派生类的public成员派生类的protected成员派生类的private成员
基类的protected成员派生类的protecte成员派生类的protected成员派生类的private成员
基类的private成员在派生类不可见在派生类不可见在派生类不可见

C++的继承方式有三种,实际上最常使用的为public继承方式,而基类的成员访问限定符设置最多的为public和protected。

那如果我们想要在A中的某一个东西,禁止他继承呢?C++就提供了林外一个关键字,如下:

  • 使用final关键字可以禁止继承
总结
  • 基类private成员在派生类中无论以什么方式继承都是不可见的;
  • 如果基类成员不想在类外直接被访问,但需要在派生类中能访问,就定义为protected。可以看出保护成员限定符是因继承才出现的;
  • class的默认继承方式为private而struct的默认继承方式为public
  • 派生类可以拥有基类没有的方法和属性,派生类可以对基类进行扩充。

以上条例,理解性记忆即可。

赋值兼容原则

我们来看这一段代码:

class Animal
{
public:
    std::string category{"Animal"};	//所属类别(如:狗 猫 蛇...)
};

class Dog :public Animal
{
public:
    std::string name{"Dog"};		//名字(如:大黄 小黑 发财 旺财...)
};

int main()
{
    Animal animal;
    Dog spike;		//(spike:猫和老鼠中大狗的名字 它的儿子叫tyke哟~)
    animal = spike;	//1、子类对象赋值给父类对象
    return 0;
}

以上代码并没有报错,为什么呢?子类赋值给父类?

因为我们可以把派生类的对象赋值给基类,派生类赋值给基类的过程我们称其为切片,但注意的是基类对象不能赋值给派生类对象,也就是子类可以赋值给父类,但是父类不能赋值给子类。

在这里插入图片描述

我们还可以用基类的指针指向子对象、用基类引用子对象

// 2、父类的指针可以指向子类的对象
Animal* pa = &dog;     //注意这个是传递地址
// 3、父类可以引用子类对象
Animal & pra = dog;     
//1中:子类赋值给父类,调用父类依然还是只能用父类
//2,3中:经常用作函数传参,可以用子类当作父类作为函数的参数
void print(Animal* pa){
    pa->tiger();
}
//调用的时候可以传递子类作为参数
print(&cat)       //指针,所以要加取地址符

总结:子类可以当作父类

赋值兼容原因:

  • 从代码中来看,子类继承了父类,拥有了父类的全部成员,所以父类能干的事,子类也能干;反之,子类对父类进行了扩充,子类能干的,父类不一定干的了。

继承中的成员

父子类同名成员变量

如果子类A继承父类B,那如果父类B和子类A都有相同名字的成员呢?

为了解决这个问题,C++提出了重定义的概念,因为无论是父类和子类,他们都有自己的一块内存空间,重定义本质就是在不同的内存空间中定义不同成员

来个代码辅助分析:

class Father
{
public:
    int age = 45;
};

class Child : public Father
{
public:
    int age = 18;
};

int main()
{
    Child self;
    std::cout <<"self age is:" << self.age << std::endl;
    return 0;
}

运行结果:

self age is: 18

那么我们如果想要打印父类的成员num应该怎么办呢?那么我们应该指定类域:

std::cout <<"self age is:" << self.Father::age << std::endl;  //很重要

输出:

self age is: 45

父子类同名成员函数

上面我们测试的是成员变量,那么成员函数呢?给父子类加个同名的成员函数,测试一下:

class Father
{
public:
    void foo()
    {
        std::cout << __FUNCSIG__ << std::endl;
    }
};

class Child : public Father
{
public:
    void foo(int i)
    {
        std::cout << __FUNCSIG__ << " " << i << std::endl;
    }
};
int main()
{
    Child self;
    self.foo(1);
    //self.foo();             //访问不到
    self.Father::foo();

    return 0;
}

结果:

void __cdecl Child::foo(int) 1
void __cdecl Father::foo(void)

可以看出,想要调用父类的API,就需要指明范围。

继承和静态成员

继承和static关键字在一起会产生什么现象?

class A
{
public:
    static int count;
};
int A::count = 222;      //在外面赋值

class B :public A
{
public:

};

int main()
{
    A::count++;
    std::cout << A::count << " " << B::count << std::endl;

    return 0;
}

结果:

223 223

小结:

  • 基类定义的静态成员,将被派生类共享,因为静态成员定义在全局区,是共享的。
  • 派生类中访问静态成员,用以下形式显式说明:
    • 通过类名直接访问:类名 :: 成员
    • 通过对象访问: 对象名 . 成员

继承和友元

  • 友元是不能够继承的,也说明父类的友元无法访问子类的私有和保护成员。

多继承

C++中是支持多继承的,但是一直存在争议,及其不推荐使用,Java直接禁止多继承,有兴趣的可以多查找C++ Prime这本书籍。
下面是本人学习中的一段演示代码截图(vs2022):
在这里插入图片描述

拷贝继承

我们知道在C++中有浅拷贝和深拷贝之分,有指针,必须深拷贝,,想要实现深拷贝就必须自己自定义实现,因为C++默认是浅拷贝的,但是如果子类在进行赋值、钱拷贝等操作的时候,那父类应该怎么操作呢???,下面是一段代码演示,解决浅拷贝问题,深拷贝其实也一样道理,只是需要重新分配内存:
拷贝构造:

class DNode
{
public:
    DNode(const DNode& other)
    {
        x = other.x;
        y = other.y;
    }
	...
};

class DSprite : public DNode
{
public:
	...
    DSprite(const DSprite& other)
        :DNode(other)    // 父类拷贝构造
        ,texture(other.texture)
    {
    }
	...
};

赋值构造:

class DNode
{
public:
    DNode& operator=(const DNode& other)
    {
        if(other==*this){
            return *this;
		}
        x = other.x;
        y = other.y;
        return *this;
    }
	...
};

class DSprite : public DNode
{
public:
    DSprite& operator=(const DSprite& other)
    {
        DNode::operator=(other);              //注意注意注意注意 :; 重点, 子类实现拷贝
        texture = other.texture;
        return *this;
    }
	...
};

标签:继承,子类,成员,C++,--,派生类,父类,public
From: https://blog.csdn.net/weixin_74085818/article/details/142624138

相关文章

  • 结对项目
    这个作业属于哪个课程计科12班这个作业要求在哪里作业要求这个作业的目标要求实现一个自动生成小学四则运算题目的命令行程序成员及仓库学号成员代码仓库3122004657林诗淇https://github.com/shiqi323/examinationPSP表格PSP2.1Personal......
  • 2024-2025-1 20241328《计算机基础与程序设计》第壹周学习总结
    2024-2025-120241328《计算机基础与程序设计》第壹周学习总结作业信息计算机基础与程序设计2024-2025-1-计算机基础与程序设计作业要求2024-2025-1计算机基础与程序设计第一周作业作业目标1、参考教程安装Linux系统;2、快速浏览一遍教材计算机科学概论(第七版),课本......
  • Java实现随机抽奖的方法有哪些
    在Java中实现随机抽奖的方法,通常我们会使用java.util.Random类来生成随机数,然后基于这些随机数来选择中奖者。以下将给出几种常见的随机抽奖实现方式,包括从数组中抽取、从列表中抽取以及基于权重的抽奖方式。1.从数组中抽取importjava.util.Random;publicclassLottery......
  • 链表高频题
    链表高频题160.相交链表#include<vector>#include<iostream>#include<algorithm>structListNode{intval;ListNode*next;ListNode(intx):val(x),next(NULL){}};classSolution{public:ListNode*getIntersectionNode(Li......
  • 2024-2025-1 20241310 《计算机基础与程序设计》第一周学习总结
    2024-2025-120241300《计算机基础与程序设计》第一周学习总结作业信息这个作业属于哪个课程2024-2025-1-计算机基础与程序设计这个作业要求在哪里2024-2025-1计算机基础与程序设计第一周作业这个作业的目标1.基于VirtualBox虚拟机安装Ubuntu图文教程安装Linux系......
  • 手机为什么能够打电话上网,一文带你搞懂其原理
    我们的手机为什么能够打电话能够上网,这个问题相信很多人都思考过,为什么在千里之外,他可以借到我拨出的电话,我说话的声音可以清晰地传到他的耳中,在这小小的手机里,我们可以浏览天下所有的资讯。这究竟是怎么样做到的?这其实是得益于蜂窝移动通信网络的发展。今天我们就来讲讲我们......
  • R机械设计V4.2(2024.09.28)
    下载:https://pan.baidu.com/s/1Dphz0m8BQWcg-T-AaeoaYA提取码:0520R机械设计V4.2(2024.09.28)更新:1、新增齿轮计算模块2、新增同步带计算模块3、新增耗气量计算模块4、全新自定义模块,(可导入旧版本数据)5、更新螺钉数据6、修正“一般设计资料-过程”速比参数  ......
  • 2024.9.28 bisect 模块
    bisect模块是Python标准库中的一个模块,主要用于维护已排序的列表。它提供了一些函数,帮助你在一个有序序列中查找元素的插入位置,以便保持序列的有序性。以下是bisect模块的一些常用功能:常用函数bisect.bisect_left(a,x,lo=0,hi=len(a)):返回元素x应该插入到列表a......
  • 20241308《计算机基础与程序设计》第一周学习总结
    班级:https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP作业要求:https://edu.cnblogs.com/campus/besti/2024-2025-1-CFAP/homework/13276作业目标:快速浏览教材作业正文:第一章1.我们目前使用的第四代计算机是否可以继续改进,功能更加强大?第二章1.为什么计算机中的每个......
  • 结对项目
    这个作业属于哪个课程https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/这个作业要求在哪里https://edu.cnblogs.com/campus/gdgy/CSGrade22-34/homework/13230这个作业的目标实现一个自动生成小学四则运算题目的命令行程序一、成员个人信息姓名学号......