C++核心编程之类和对象---C++面向对象的三大特性--继承

发布时间:2024年01月13日

目录

一、继承

1. 继承的概念

2. 继承的定义

3. 类与类之间的关系

4. 继承的两类关系

二、继承方式的基本语法

总而言之,父类的私有内容,子类是访问不到的。

三、继承中的对象模型

父类中的私有属性被编译器隐藏,访问不到,但被继承下去了

用开发人员命令提示符查看对象模型

四、继承中的构造和析构的顺序

五、继承中同名成员的处理

六、继承同名静态成员 的处理方式

七、多继承语法

八、菱形继承


一、继承

1. 继承的概念

利用已有的数据类型来定义新的数据类型通过继承机制,可以利用已有的数据类型来定义新的数据类型。所定义的新的数据类型不仅拥有新定义的成员,而且还同时拥有旧的成员。我们称已存在的用来派生新类的类为基类,又称为父类。由已存在的类派生出的新类称为派生类,又称为子类。

继承是面向对象三大特性之一。

2. 继承的定义

在C++语言中,一个派生类可以从一个基类派生,也可以从多个基类派生。从一个基类派生的继承称为单继承;从多个基类派生的继承称为多继承

3. 类与类之间的关系

我们发现,定义这些类的时候,下级别的成员除了拥有上一级的共性,还有自己的特性。

这个时候我们就可以考虑利用继承得到技术,减少重复代码。

4. 继承的两类关系

具体化

类的层次通常反映了客观世界中某种真实的模型。在这种情况下,不难看出:基类是对若干个派生类的抽象,而派生类是基类的具体化。基类抽取了它的派生类的公共特征,而派生类通过增加行为将抽象类变为某种有用的类型。

延续化

先定义一个抽象基类,该基类中有些操作并未实现。然后定义非抽象的派生类,实现抽象基类中定义的操作。例如,虚函数就属此类情况。这时,派生类是抽象的基类的实现,即可看成是基类定义的延续。这也是派生类的一种常用方法。

派生类

多继承时,一个派生类有多于一个的基类,这时派生类将是所有基类行为的组合。

派生类将其本身与基类区别开来的方法是添加数据成员和成员函数。因此,继承的机制将使得在创建新类时,只需说明新类与已有类的区别,从而大量原有的程序代码都可以复用,所以有人称类是“可复用的软件构件”。

总结:

继承的好处:可以减少重复的代码量

语法:

class 子类:继承方式? 父类

class A :? public B;

A? 类称为子类? 或者? 派生类

B? 类称为父类? 或者? 基类

派生类中的成员,包含两大部分:

一类是从基类继承过来的,一类是自己增加的成员

从基类继承过来的表现其共性,而新增的成员体现了其个性。

二、继承方式的基本语法

class 子类 : 继承方式 父类

?继承的方式一共有三种:

  1. ?公共继承 : 除了私有内容,父类的内容到子类都可以访问得到
  2. ?保护继承 : 除了私有内容,父类的公共的和保护的内容都变为保护的内容
  3. ?私有继承 : 除了私有内容,父类的公共的和保护的内容都变为私有的内容

总而言之,父类的私有内容,子类是访问不到的。

示例:

#include<iostream>
using namespace std;
// 继承方式
// 公共继承
// 父类
class Base1 {
public:

    int m_A;

protected:

    int m_B;

private:

    int m_C;

};

// 子类

class Son1 :public Base1 {

public:

    void func()

    {

        m_A = 10;? // 父类中的公共权限成员到子类依然是公共权限

        m_B = 20;? // 父类中的保护权限成员到子类依然是保护权限

        // m_C = 30;? // 父类中的私有权限成员到子类访问不到

    }

};

void test01()

{

    Son1 s1;

    s1.m_A = 100;?? // 创建一个儿子类,访问父亲类中的公共权限是可以访问到的

    // s1.m_B = 200;?? // 在Son类中,m_B是 保护权限 在类外是访问不到的

}

// 保护继承

class Base2 {

public:

    int m_A;

protected:

    int m_B;

private:

    int m_C;

};

// 子类

class Son2 :protected Base2 {

public:

    void func()

    {

        m_A = 90;? // 父类中公共成员到子类中变为保护权限,在类内才能访问

        m_B = 80;? // 原本的保护权限的成员到子类中依然是保护权限

        // m_C = 70;? // 不管是哪种方式继承,父类中的私有属性的内容都访问不到

    }
    
};

void test02()

{

    Son2 s2;

    // s2.m_A = 1000;? // 报错,父类中公共成员,继承到子类的时候已经变为保护权限,在类外访问不到

    // s2.m_B = 2000;? // 报错,保护权限在类外访问不到

}

// 私有继承

class Base3 {

public:

    int m_A;

protected:

    int m_B;

private:

    int m_C;

};

// 子类

class Son3 :private Base3 {

public:

    void func()

    {

        m_A = 100; // 父类的公共成员到子类中变为 私有成员

        m_B = 200; // 父类的保护成员到子类中变为 私有成员

        //m_C = 300; // 不管是哪种方式继承,父类中的私有属性的内容都访问不到

    }

};

class GrandSon3 :public Son3

{

public:

    void func()

    {

        // m_A = 1000;// 到了Son3中, m_A变为私有属性,即使是儿子,也访问不到

        // m_B = 2000; //与上一个相同,都是访问不到的

    }

};

void test03()

{

    Son3 s3;

    // s3.m_A = 1000;

    // s3.m_B = 2000; // 子类的成员都变为私有成员类外访问不到

}



int main()

{

    test01();

    test02();

    test03();

    return 0;

}

运行结果:

三、继承中的对象模型

??:从父类继承过来的成员,哪些属于子类对象中?

父类中所有非静态成员属性都会被子类继承下去

父类中的私有属性被编译器隐藏,访问不到,但被继承下去了

示例:

#include<iostream>

using namespace std;

// 继承中的对象模型

class Base

{

public:

    int a;

protected:

    int b;

private:

    int c;

};

?

class Son : public Base

{

public:

    int d;

};

int main()

{

    // 父类中所有非静态成员属性都会被子类继承下去

    // 父类的私有成员属性,是被编译器隐藏了,因此访问不到,但是确实被继承了

    cout<<"儿子类的大小:"<<sizeof(Son)<<endl;// 16

??? return 0;

}

运行结果:

用开发人员命令提示符查看对象模型

进入文件所在盘符,cd跳转进入文件路径下,dir查看文件中的内容

输入:cl? /d1? reportSingleClassLayout类名? cpp的文件(可以输入前几个字符后tab键自动填充)

四、继承中的构造和析构的顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题:父类和子类的构造和析构的顺序是谁先谁后?

先构造父类再构造子类,先析构子类再析构父类(没有父类直接声明子类的时候,也会先构造父类)

示例:

#include<iostream>

using namespace std;

class Base{

public:

    Base()

    {

        cout<<"Base的构造函数."<<endl;

    }

    ~Base()

    {

        cout<<"Base的析构函数."<<endl;

    }

};

class Son :public Base{

public:

    Son()

    {

        cout<<"Son的构造函数."<<endl;

    }

    ~Son()

    {

        cout<<"Son的析构函数."<<endl;

    }

};

void test01()

{

    //Base b;

    Son s; // 创建子类的时候,也会先创建父类

}

int main()

{

    test01();

    return 0;

}

运行结果:

五、继承中同名成员的处理

问题:当子类与父类出现同名成员,如何通过子类对象,访问到子类或父类中同名的数据?

  1. 访问子类同名成员,直接访问即可
  2. 访问父类的同名成员,需要加作用域

示例:

#include<iostream>

using namespace std;

class Base{

public:

    Base()

??? {

        m_A = 100;

??? }

??? void func()

??? {

        cout<<"Base - func()函数的调用 "<<endl;

??? }

??? int m_A;

};

class Son :public Base

{

public:

??? Son()

??? {

        m_A =200;

??? }

??? void func()

??? {

        cout<<"Son - func()函数的调用 "<<endl;

??? }

??? int m_A;

};

?

// 同名成员的处理方式

void test01()

{

    Son s;

??? cout<<"m_A = "<<s.m_A<<endl;

??? // 如果通过子类的对象 访问到父类的同名成员,需要加父类的作用域

??? cout<<"Base 下的 m_A = "<<s.Base::m_A<<endl;// 加父类的作用域

}

?

// 同名函数的处理方式

void test02()

{

    Son s1;

??? s1.func(); // 直接调用 调用的是子类的函数

??? s1.Base::func(); // 加作用域 调用父类的函数调用

?

    // s.func(100);

    // s.Base::func(100);

?? // 如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名函数(重载的)

    // 如果想要访问到父类中隐藏的同名成员函数,需要加作用域

}

int main()

{

    test01();

    test02();

    return 0;

}

运行结果:

总结:

  1. 子类对象可以直接访问到子类中同名成员
  2. 子类对象加作用域可以访问到父类同名成员
  3. 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类同名函数

六、继承同名静态成员 的处理方式

问题:

继承中同名的静态成员在子类对象上如何进行访问?

  • 访问子类同名成员,直接访问即可
  • 访问父类的同名成员,需要加作用域

总结:

  1. ?访问子类同名成员,直接访问即可
  2. ?访问父类的同名成员,需要加作用域
  3. 子类出现和父类同名的静态成员函数,也会隐藏父类中所有同名成员函数
  4. 如果想要访问父类中被隐藏的同名成员,也需要加作用域

示例:

#include<iostream>

using namespace std;

// 继承中同名静态成员处理

class Base

{

public:

??? static int m_A; // 类内声明,类外初始化

?

??? static void func()

??? {

        cout<<"Base - static 函数的调用 "<<endl;

??? }

};

int Base:: m_A = 100;

class Son : public Base

{

public:

??? static int m_A;

?

??? static void func()

??? {

        cout<<"Son - static 函数的调用 "<<endl;

??? }

};

int Son:: m_A = 200;

?

// 同名静态成员属性

void test01()

{

??? // 1. 通过对象访问数据

??? cout<<"通过对象访问"<<endl;

??? Son s;

    cout<<"Son 下的 m_A = "<<s.m_A<<endl;

??? cout<<"Base 下的 m_A = "<<s.Base::m_A<<endl<<endl;

???

??? // 2. 通过类名访问

??? cout<<"通过类名访问"<<endl;

??? cout<<" Son 下的 m_A = "<<Son::m_A<<endl;

??? // 第一个:: 代表通过类名方式访问 第二个::代表访问父类作用域下

??? cout<<"Base 下的 m_A = "<<Son::Base::m_A<<endl<<endl;

}

// 同名静态成员函数

void test02()

{

??? // 1. 通过对象来访问

??? cout<<"通过对象访问"<<endl;

    Son s2;

??? s2.func();

??? s2.Base::func();

???

??? cout<<endl;

??? cout<<"通过类名访问"<<endl;

??? // 2. 通过类名访问

??? Son::func();

???? // 第一个:: 代表通过类名方式访问 第二个::代表访问父类作用域下

??? Son::Base::func();

?

    // 如果子类出现和父类同名静态成员函数,也会隐藏父类中所有同名函数成员

    // 如果想访问父类中被隐藏同名成员,需要加作用域

    // Son::Base::func(100);? 即重载了func为有参构造

}

int main()

{

    test01();

    test02();

    return 0;
    
}

运行结果:

七、多继承语法

c++允许一个类继承多个类

语法:class 子类 : 继承方式 父类1,继承方式 父类2...

多继承可能会引发类中有同名成员出现,需要加作用域区分

总结:多继承中如果父类出现同名情况,子类使用的时候加作用域。

示例:

#include<iostream>

using namespace std;

// 多继承的语法

class Base1

{

public:

??? Base1()

    {

        m_A = 100;

    }

??? int m_A;

};

class Base2

{

public:

??? Base2()

??? {

        m_A = 200;

??? }

??? int m_A;

};

?

// 子类 需要继承Base1 和 Base2

class Son:public Base1,public Base2

{

public:

??? Son()

??? {

        m_C = 300;

??????? m_D = 400;

??? }

??? int m_C;

??? int m_D;

};

void test01()

{

    Son s;

??? cout<<"子类的大小(sizeof Son)"<<sizeof(s)<<endl;

??? // 当两个杜父类中有成员名相同,需要加作用域

??? cout<<"Base1 m_A = "<<s.Base1::m_A<<endl;

??? cout<<"Base2 m_A = "<<s.Base2::m_A<<endl;

}

int main()

{

    test01();

    return 0;

}

运行结果:

通过命令提示符查看类的大小

?

八、菱形继承

概念:

两个派生类继承同一个基类

又有某个类同时继承这两个派生类

这种继承称为菱形继承,或者钻石继承

案例:

问题:

  1. 羊继承了动物的数据,驼同样继承了动物的数据,当草泥马使用数据时,就会产生二义性。
  2. 草泥马继承自动物的数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。

示例:

#include<iostream>

using namespace std;

// 模拟菱形继承

// 动物类

class Animal

{

public:

??? int m_age;

};

?

// 利用虚继承? 解决菱形继承的问题

// 继承之前加关键字 virtual 变为虚继承

// Animal 变为虚基类

// 羊类

class sheep:virtual public Animal

{

?

};

?

// 驼类

class Tuo:virtual public Animal{};

?

// 羊驼类

class sheep_Tuo:public sheep,public Tuo{};

void test01()

{

    sheep_Tuo st;

?

??? st.sheep::m_age = 18;

??? st.Tuo::m_age = 28;

??? // 当出现菱形继承的时候,有两个父类中有相同的数据,需要加作用域区分

??? cout<<"st.sheep::m_age = "<<st.sheep::m_age<<endl;

??? cout<<"st.Tuo::m_age = "<<st.Tuo::m_age<<endl;

?

??? // 虚继承导致数据只有一份,不存在数据来源不明确的情况

??? cout<<"st.m_age = "<<st.m_age<<endl; // 现在就可以访问到

??? // 产生虚基类指针 vbptr

??? // virtual? base??? pointer? 指向 vbtable

??? // 这份数据有一份就可以,但是菱形继承导致数据有两份,浪费资源

}

int main()

{

    test01();

    return 0;

}

运行结果:

命令提示符查看

vbptr? 虚基类指针 ---指向 vbtable --虚基类表

v -- virtual

b -- base

ptr? -- pointer

?

文章来源:https://blog.csdn.net/hjl011006/article/details/135573067
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。