C++类与对象--继承(详细讲解)

C/C++
212
0
0
2024-07-15

继承

一、继承的基础介绍

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

有些类和类之间存在特殊关系,如:

我们可以发现,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的个性。这个时候我们就可用继承的技术减少重复代码。 继承的基本语法 如很多网站中都有公共的头部,公共的底部,公共的左侧列表,只有中心内容不同,接下来用普通写法和继承写法来实现网页(我以CSDN的网页为例)中的内容,看一下继承存在的意义以及好处

普通版网页和继承版网页的区别

普通版网页

//普通版网页
class ZhuYe
{
public:
    void head()
    {
        cout << "博客  下载  学习  社区  C知道    GitCode InsCode" << endl;
    }
    void foot()
    {
        cout << "用户名 关注 收藏" << endl;
    }
    void left()
    {
        cout << "原创 周排名  总排名 访问" << endl;
    }
};
​
class Czhidao
{
public:
    void head()
    {
        cout << "博客  下载  学习  社区  C知道    GitCode InsCode" << endl;
    }
    void foot()
    {
        cout << "用户名 关注 收藏" << endl;
    }
    void context()
    {
        cout << "请输入你的问题" << endl;
    }
    void left()
    {
        cout << "原创 周排名  总排名 访问" << endl;
    }
​
};
​
​
class SheQu
{
public:
    void head()
    {
        cout << "博客  下载  学习  社区  C知道    GitCode InsCode" << endl;
    }
    void foot()
    {
        cout << "用户名 关注 收藏" << endl;
    }
    void context()
    {
        cout << "与我相关 最新发布 最新回复 最热 有活动 有问题" << endl;
    }
    void left()
    {
        cout << "原创 周排名  总排名 访问" << endl;
    }
​
};
​
void test01()
{
    ZhuYe a;
    a.head();
    a.foot();
    a.left();
    cout << "-------------------------" << endl;
​
    Czhidao b;
    b.head();
    b.context();
    b.foot();
    b.left();
    cout << "-------------------------" << endl;
​
    SheQu c;
    c.head();
    c.context();
    c.foot();
    c.left();
}
​
int main()
{
    test01();
    return 0;
}

其实会发现这个代码中有大量重复代码,这样子的代码是很Low的,也不符合C++是面向对象的语言的标准,而且会使得代码量加大,这个在企业开发中是一定要杜绝

继承版网页

//继承版网页
class ZhuYe
{
public:
    void head()
    {
        cout << "博客  下载  学习  社区  C知道    GitCode InsCode" << endl;
    }
    void foot()
    {
        cout << "用户名 关注 收藏" << endl;
    }
    void left()
    {
        cout << "原创 周排名  总排名 访问" << endl;
    }
};
​
class Czhidao : public ZhuYe
{
public:
    void context()
    {
        cout << "请输入你的问题" << endl;
    }
};
​
​
class SheQu : public ZhuYe
{
public:
    void context()
    {
        cout << "与我相关 最新发布 最新回复 最热 有活动 有问题" << endl;
    }
};
​
void test01()
{
    ZhuYe a;
    a.head();
    a.foot();
    a.left();
    cout << "-------------------------" << endl;
​
    Czhidao b;
    b.head();
    b.context();
    b.foot();
    b.left();
    cout << "-------------------------" << endl;
​
    SheQu c;
    c.head();
    c.context();
    c.foot();
    c.left();
}
​
int main()
{
    test01();
    return 0;
}

以上是继承版代码,可以看出来他将重复的部分给删掉了,这里用了继承这个语法,现在我来讲讲继承的语法

语法

语法:class 子类 : 继承方式 父类

子类:又称派生类

父类:又称基类

派生类中的成员包括两大部分 一类是从基类继承过来的,一类是自己增加的成员 从基类继承过来的表现其共性,而新增的成员体现了个性

二、继承方式

三种继承方式

公共继承

保护继承

**私有继承

从图可知:

父类中的私有成员,不管子类以哪种方式继承,都不可访问

就像父亲的银行卡密码,就算你是他儿子,他也不会告诉你,因为那是他的私有财产

三、继承中的对象模型

问题,从父类继承过来的成员,哪些属于子类中?

答案:

1.父类所有非静态成员属性都会被子类继承下去 2.父类中的成员属性是被编译器给隐藏了,因此是访问不到的,但是确实被继承下去了,大家可以用以下代码检测一下

class A
{
public:
    int m_A;
protected:
    int m_B;
private:
    int m_C;
};
​
class B : public A
{
public:
    int m_D;
};
​
int main()
{
    B test;
    cout << sizeof(test) << endl;
    return 0;
}

以上代码的答案是:16,虽然B只能访问A的m_A,m_B,加上自己的m_D,只有12个字节数,但是父类的m_C只是不可访问,不等于其不存在,因此,可以看出来子类继承了父类中所有非静态成员

四、继承中构造和析构函数

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

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

大家可以通过以下代码来看看:

class A
{
public:
    A()
    {
        cout << "父类构造函数执行" << endl;
    }
    ~A()
    {
        cout << "父类析构函数函数执行" << endl;
    }
​
};
​
class B : public A
{
public:
    B()
    {
        cout << "子类构造函数执行" << endl;
    }
    ~B()
    {
        cout << "子类析构函数函数执行" << endl;
    }
​
};
​
int main()
{
    B test;
    return 0;
}

会发现继承中的构造和析构顺序如下: 先构造父类,再构造子类,析构的顺序与构造的顺序相反

五、继承同名成员的处理方式

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

访问同名成员:

访问子类同名成员 直接访问即可

访问父类类同名成员 需要加作用域

作用域写法:

对象名.父类::成员名

大家可以看看以下代码来理解

class A
{
public:
    int m_A = 10;
};
​
class B : public A
{
public:
    int m_A = 20;
};
​
​
int main()
{
    B test;
    cout << test.m_A << endl;
    cout << test.A::m_A << endl;
​
    return 0;
}

如果子类中出现与父类同名成员函数;要访问就要加作用域

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

继承同名静态成员在子类对象上如何访问? 静态成员和非静态成员出现同名,处理方式一致

访问同名成员:

访问子类同名成员 直接访问即可

访问父类类同名成员 需要加作用域

不过,静态成员有两种方式访问

通过对象

通过类名

第一种方法与非静态成员一样的方式,我就不过多赘述,我来讲讲第二种方式

为什么能用类名访问静态成员?

因为静态成员与静态成员函数在内存中都只有一份,所以所有对象都能直接访问他,因此只需要类名就能知道它具体的值

类名访问语法(以以下代码的访问为例)

class A
{
public:
    static int m_A;
};
int A::m_A = 10;
​
class B : public A
{
public:
    static int m_A;
};
int B::m_A = 20;
​
int main()
{
    cout << B::m_A << endl;
    cout << B::A::m_A << endl;
​
    return 0;
}

插入静态成员知识点:

静态成员:

类型前加static

类内声明,类外初始化,一定要初始(因为静态变量放在全局区,全局区在编译阶段就分配内存)

静态成员函数:

返回类型前加static

只可访问静态变量

总结:

同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(对象,类名)

七、多继承语法

C++中允许一个类继承多个类

语法:

class 子类 : 继承方式 父类1,继承方式 父类2, 继承方式 父类3……

多继承可能会引发父类有同名成员出现,要加作用域区分,因为容易出错,所以C++实际开发中不建议用多继承,因此不作过多介绍

八、菱形继承

概念:

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

以下例子虽然不符合事实动物的来源,但是有利于理解,大家就理解概念就好

菱形继承问题:

1.羊继承了动物的数据,驼同样继承了动物的数据,当羊驼使用数据时,就会产生二义性

class Animal
{
public:
    int m_Age;
};
​
class Sheep:public Animal
{
public:
    int m_Age;
};
​
class Tuo :public Animal
{
public:
    int m_Age;
};
​
class SheepTuo :public Sheep, public Tuo
{
public:
    int m_Age;
};
​
​
void test()
{
    SheepTuo st;
    st.Sheep::m_Age = 20;
    st.Tuo::m_Age = 10;
}
​
int main()
{
    test();
    return 0;
}

以上代码就有二义性:羊驼的年龄应该是和羊一样为20岁,还是应该和驼一样为10岁呢?

2.羊驼继承的动物的数据继承了两份,这份数据我们只需要一份就行

解决办法:

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

虚继承语法:

在继承之前加上关键字virtual变成虚继承

class Animal
{
public:
    int m_Age;
};
​
class Sheep:virtual public Animal
{
public:
    int m_Age;
};
​
class Tuo :virtual public Animal
{
public:
    int m_Age;
};
​
class SheepTuo :public Sheep, public Tuo
{
public:
    int m_Age;
};
​
​
void test()
{
    SheepTuo st;
    st.Sheep::m_Age = 20;
    st.Tuo::m_Age = 10;
}
​
int main()
{
    test();
    return 0;
}

这时你

cout << st.m_Age << endl;
cout << st.Sheep::m_Age << endl;
cout << st.Tuo::m_Age << endl;

都是等于10,因为三者在实际上时共用了一个数据,这里涉及了虚拟基类指针和虚拟基类表,这里涉及开发命令页的操作来展现,大家感兴趣的可以自行查找相关资料

总结:

菱形继承带来的主要问题是:子类继承两份相同的数据导致资源浪费以及毫无意义 可以用虚拟继承方式解决