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

本专栏目的

  • 更新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;
    }
	...
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值