c++复习之继承与派生

概念

继承

类的派生指一个或多个以前定义的类产生新类的过程,原有的类称为基类,新产生的类称为派生类。
派生类不能继承基类的构造与析构函数。需要自己定义构造与析构函数。
派生类可改变基类访问成员的访问权限,重新定义已有的成员函数,增加新的成员。若定义了同名的成员,则派生类的成员优先。

定义格式:

class 派生类名:继承方式说明符 基类名
{
    类体
}

继承方式说明符指如何控制基类成员在派生类中的访问属性,通常是public

类的大小

是基类成员变量所占用的空间+派生类对象自身成员变量占用的存储空间大小。与成员函数和类中的静态成员变量无关,空类的大小是1.

友元的继承

基类的成员函数是某类的友元函数,那么这个函数将会被继承仍是某类的友元函数。

静态成员

无论继承多少次,都只是同一内存空间,也就是内存空间共享。

派生类访问成员格式:

类名::<成员名>

protected访问范围

可直接访问基类中的保护成员,还是不能访问私有成员。只能访问所作用的那个对象(即this指针指向的对象)的基类保护成员,不能访问其他基类对象的基类保护成员。

多重继承

情形1:一个派生类有多个基类

格式:

class 派生类名:: 继承方式说明符 基类名1,继承方式说明符 基类名2,...,继承方式说明符 基类名n
{
  类体;
}
例子(二义性问题解决)
#include <iostream>

using namespace std;

class B1
{
public:
    B1()
    {
        cout << "B1\n";
        b = 1;
    }
protected:
    int b;
};
class B2
{
public:
    B2()
    {
        cout << "B2\n";
        b = 2;
    }
protected:
    int b;
};
/* class C:public B2,public B1{    //:之后称为类派生表,表的顺序决定基类构造函数调用的顺序,析构函数的调用顺序正好相反
public:
    void Print(){cout<<b<<endl;}//存在二义性问题
}; */
class C: public B2, public B1   //:之后称为类派生表,表的顺序决定基类构造函数调用的顺序,析构函数的调用顺序正好相反
{
public:
    C()
    {
        cout << "C\n";
        b = 3;
    }
    void Print()
    {
        cout << "B1::b = " << B1::b << endl;
        cout << "B2::b = " << B2::b << endl; //第一种解决办法
        cout << "C::b = " << b << endl; //第二种解决办法
    }
protected:
    int b;
};
int main()
{
    C c;
    c.Print();//B2 B1 c B1::b=1 B2::b=2 C::b=3
    return 0;
}
情形2:一个基类有一个派生类,这个派生类又有一个派生类,间接基类
#include <iostream>

using namespace std;
class A
{
public:
    A(int a): m_a(a) {};
protected:
    int m_a;
};
class B1: public A
{
public:
    B1(int a): A(a)
    {
        cout << "B1\n";
    }
protected:
};
class B2: public A
{
public:
    B2(int a): A(a)
    {
        cout << "B2\n";
    }
protected:
};
class C: public B2, public B1   //:之后称为类派生表,表的顺序决定基类构造函数调用的顺序,析构函数的调用顺序正好相反
{
public://A成了C的间接基类
    C(int a1, int a2): B2(a2), B1(a1)
    {
        cout << "C\n";
    }
    void Print()
    {
        cout << "B1::m_a = " << B1::m_a << endl;
        cout << "B2::m_a = " << B2::m_a << endl;
        // cout<<"m_a = "<<m_a<<endl;   直接这样调用,编译时会报与上面类似的二义性错误。
    }
protected:
};
int main()
{
    C c(1, 2);
    c.Print();//B2 B1 C B1::m_a=1 B2::m_a=2
    return 0;
}
直接基类与间接基类规则

在声明派生类时,只需要列出它的直接基类
派生类沿着类的层次自动向上继承它的间接基类

派生类的成员

二义性问题

多个基类存在重名的成员,
解决:是在访问派生类对象中的某个变量上,加上“基类::”作为前缀

访问控制

公有继承,基类中所有公有成员将成为派生类的公有成员
在公有派生情况下,基类的公有或保护成员在派生类中访问权限不变

类兼容性规则

  • 派生类的对象可以赋值给基类的对象
  • 派生类对象可以用来初始化基类引用
  • 派生类的指针可以赋值给基类的指针,即基类指针指向派生类对象。
    反之,三条规则都不成立。

私有继承的访问

相关软考总结:面向对象的技术

第一级派生类中第二级派生类中基类与派生类外
基类的公有成员直接访问不可访问不可访问
基类的保护成员直接访问不可访问不可访问
基类的私有成员调用公有函数访问不可访问不可访问

说明:派生类私有继承时,基类中的所有公有和保护成员就成为了派生类的私有成员,所以下一级派生就不能继承了。

保护继承访问

  • 保护继承中,基类的公有成员和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可以直接访问
  • 派生类的基他成员可以直接访问从基类继承来的公有和保护成员,但在类外通过派生类的对象无法直接访问它们。

派生类构造函数和析构函数

派生类不继承基类的构造函数,那么如何完成从基类继承成员的初始化呢?
需要在派生类的构造函数中调用基类的构造函数。

构造函数格式

 派生类名::派生类函数名(参数表):基类名(基类初始化参数表),...,基类名m(初始化参数表),成员对象名(成员对象初始化参数表),...,成员对象名n(初始化参数表)
{
派生类构造函数体;
}

构造函数的执行次序

  1. 调用基类的构造函数,调用顺序按照它们被继承时声明的顺序(从左向右)
  2. 对派生类新增的成员变量,调用顺序按照它们在类中声明顺序执行
  3. 执行派生类中构造函数体中的内容
  • 如2021年4月45题
#include <iostream>
using namespace std;
class A
{
    int a;
public:
    A(int n)
    {
        a = n;
        cout << "A::a=" << a << endl;
    }
    ~A()
    {
        cout << "A的对象在消亡" << endl;
    }
};
class B
{
    int b;
public:
    B(int n1, int n2)
    {
        b = n1;
        cout << "B::b=" << b << endl;
    }
    ~B()
    {
        cout << "B的对象在消亡" << endl;
    }
};
//class C: public A, public B
class C: public B, public A//这里才是决定继承顺序的关键,而不是c对象的创建处。
{
    int c;
public:
    C(int n1, int n2, int n3, int n4): B(n3, n4), A(n2)
    {
        c = n1;
        cout << "c::c=" << c << endl;
    }
    ~C()
    {
        cout << "c的对象在消亡" << endl;
    }
};
int main(int argc, char const *argv[])
{
    C Cobj(1, 3, 5, 7); //A::a=3
                        // B::b=5
                        // C::c=1
                        // c的对象在消亡
                        // b的对象在消亡
                        // a的对象在消亡
    return 0;
}

注:析构函数在用vscode时可能会看不到,得到命令行terminal中执行才能看到。也就是点右上角的箭头来看结果才能显示出来。

复制构造函数

对于一个类,没有定义复制构造函数,则编译器会自动生成一个隐含的复制构造函数,这个隐含的构造函数会自动调用基类的复制构造函数,从而对派生类中的新增的成员对象一一进行复制。

多重继承的构造与析构函数

当创建有多个基类的派生类对象时,按照类定义中给出的基类的顺序,依次调用它们的构造函数,再调用派用类的构造函数。消亡时,按构造函数调用的逆次序进行析构。

类之间的关系

使用已有类,可以继承 1 和组合2 基类

封闭类的派生

构造函数的一般形式:

类名::函数名(形参表):内嵌对象(形参表),内嵌对象2(形参表),...
{
 类体;
}

:和{之间的部分就是初始化列表,初始化列表的“形参表”中存放的是构造函数的参数(它指明了该成员对象如何初始化)。

封闭类的派生规则
  • 生成封闭类对象的语句一定要让编译器能够弄明白其成员对象是如何初始化的,否则会报错
  • 派生类也是封闭类时,调用构造函数的顺序是:先根据派生层次从上至下依次执行所有基类的构造函数,最后执行自身的构造函数。
    如果某个类是封闭类,则构造时,先按照成员对象定义顺序执行各个成员对象所属类的构造构造函数。
  • 派生类消亡时,也是逆序执行析构函数。
实际使用例子
class CTyre  // 轮胎类
{
public:
    // 有参数构造函数
    // 初始化成员变量m_radius和m_width
    CTyre(int r, int w): m_radius(r), m_width(w) { }
private:
    int m_radius; // 半径
    int m_width;  // 宽度
};

class CEngine // 引擎类
{
    // 默认会有无参数构造函数
};

class CCar // 汽车类 -- 封闭类
{
public:
    // 有参数构造函数
    // 对成员变量m_price初始化,对成员对象tyre初始化,是用前面ccar定义的形参
    CCar(int p, int tr, int tw): m_price(p), tyre(tr, tw) {}
private:
    int m_price;    // 价格
    CTyre tyre;     // 成员对象
    CEngine engine; // 成员对象
};

int main()
{
    CCar car(10000, 20, 50);
    return 0;
}

多层次派生

间接基类 情形

类A派生类B,类B再派生类C,类C又派生类D,则A是B的直接基类,类B是类C的直接基类,类A是类C间接基类。类A也是类D的间接基类。

  • 定义这种派生类时,不需写间接基类,派生类沿着类的层次自动向上继承它所有的直接和间接基类成员。
  • 类成员之间的继承关系具有传递性
  • 所以派生类的成员包括:自已定义的成员、直接基类中定义的成员及所有间接基类中定义的全部成员。
  • 当生成派生类对象时,会从最顶层的基类开始逐层执行所有基类的构造函数,最后执行派生类自身的构造函数,消亡时会逆序执行析构。

基类与派生类指针的互相转换

  • 公有派生的情况下,3条兼容性规则3
  • 基类的指针不能直接赋值给派生类的指针,但是通过强制类型转换,可以将基类指针强制转换成派生类指针后,再赋值给派生类指针。
  • 即使基类指针指向一个派生类的对象,也不能通过基类指针访问基类中没有而仅在派生类中定义的成员函数。
  • 如果一个指针是派生类型的,则调用的是派生类中的函数,如果派生类中没有声明,则调用从基类继承的同名函数。

  1. 继承关系,是 "is a"关系或是关系。指在公有继承的赋值兼容规则下,如果类B公有继承于A,在可以使用A的对象的任何地方,则类B对象同样也能使用,即每一个类B的对象“就是一个”类A对象,反之,不成立。需要类B对象,类A对象不能代替。 ↩︎

  2. 组合关系是“has a”或有关系,表现为封闭类,考点,即一个类以另一个类的对象作为成员变量。类里有其他对象则该对象叫成员对象;有成员对象的类叫 封闭类;如student包含一个data类对象 ↩︎

  3. 详见类兼容性规则 ↩︎

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

guangod

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值