c++—继承、继承方式

1. c++三大核心功能

    (1)封装:提高代码的维护性,遇到问题可以准确定位;

    (2)继承:提高代码的复用性,注意不是ctrl+c,ctrl+v,而是不做任何修改或操作源码就能实现代码的复用;

    (3)多态:提高代码的扩展性;

2. 代码复用的两种方式

    (1)组合(has-a),在一个B类的内部,定义一个A类的实例化成员,这样就可以调用A类里面的成员或者方法;

        优点:方便操作,逻辑清晰,安全;

        缺点:要在B类中实例化很多其他类的成员,导致占用的内存很大;

#include <iostream>

using namespace std;

class A
{
public:
    void print()
    {
        cout<<"hello world"<<endl;
    }

    int m_num;
};

class B
{
public:
    A a;  //组合的方式实现代码复用
    void print()
    {
        a.print();
    }

    int m_len; 
};

int main(int argc, char **argv)
{
    B b;
    b.print();



    return 0;
}

    (2)继承(is-a),子类继承父类,或派生类继承基类;注意基类与派生类之间是相互独立的空间,不是共享成员;派生类的实例化对象的大小=基类对象的大小+派生类成员的大小;

        覆盖:当派生类拥有与基类相同的属性或者方法时,派生类会覆盖继承过来的属性与方法,空间还是增加的,不会不变;

        一共有三种继承方式,实际工程应用中只用public方式:

继承方式 基类中的public成员 基类中protect成员 基类中private成员
public公有继承 在派生类的内外均可见 在派生类内部可见,外部不可见 在派生类内外均不可见
protect保护继承 在派生类的内部可见,外部不可见
private私有继承 在派生类的内部可见,外部不可见 在派生类内外均不可见
#include <iostream>

using namespace std;

class A
{
public:
    void print()
    {
        cout<<"hello world"<<endl;
    }

    int m_num;
private:
    int m_index;
};

class B : private A
{
public:
    void test()
    {
        cout<<"A::m_num = "<<m_num<<endl;  //私有继承,只可以在派生类的内部查看,且只可以查看基类的公有成员;
        print();  //基类的公有函数也可以在类内查看;
    }


    int m_len; 
};

int main(int argc, char **argv)
{
    B b;
    b.test();

    return 0;
}

    (3)基类与派生类

           派生类的构造函数:派生类从基类继承的成员还是要基类的构造函数初始化完成,派生类新增的成员,就在派生类的构造函数中初始化;具体执行顺序如下:

        ①如果派生类中含有其他类的实例化对象,就是说成员对象,那么构造函数的执行顺序:基类→成员对象→派生类构造函数;析构函数则相反;

        ②如果基类中没有无参构造函数(例如,只有有参的,那么这是系统提供的默认无参构造函数就失效了,需要自己写一个基类的无参构造函数),那么派生类里所有的构造函数都要显是调用基类的构造函数(例如自定义有参构造函数等);

        ③若一个派生类继承了多个基类,那么基类中的构造函数执行顺序与其继承顺序相关(即public的前后顺序);

        ④若要调用内嵌的成员对象的构造函数是,执行顺序按照它们在类中的声明顺序执行;

     (4)函数遮蔽

        就是指派生类中有与基类重名的函数,相当于重载了基类中的函数方法,系统优先调用派生类自己的函数,如需要调用基类中的函数,需要加作用域限定符;总之就是如需要调用基类的一些函数,包括普通成员函数、拷贝构造函数、移动拷贝构造函数等,都需要显式调用(第一个加作作用域限定符,后两个加显示调用,跟在派生类的对应构造函数后面);

示例:

#include <iostream>

using namespace std;

class A
{
public:
    A()
    {
        cout<<"A"<<endl;
    }

    A(int num):m_num(num)
    {
        cout<<"A int"<<endl;
    }

    void print()
    {
        cout<<"hello world"<<endl;
    }

    ~A()
    {
        cout<<"~A"<<endl;
    }

// private:
    int m_num;
    int m_index;
};

class B : public A
{
public:
    B():A()  //派生类中的构造函数都要显式调用基类的构造函数;
    {
        cout<<"B"<<endl;
    }

    B(int len):m_len(len),A(len)  //派生类中的构造函数都要显式调用基类的构造函数;
    {
        cout<<"B int"<<endl;
    }

    void print()
    {
        cout<<"hi world"<<endl;
    }

//private:
    int m_len; 
};

int main(int argc, char **argv)
{
    B b1;
    B b2(5);

    b1.print();
    b1.A::print();  //B类中与基类中函数重名,发生了函数遮蔽/函数覆盖,若要调用基类的函数,需要作用于限定符;

    cout<<b2.m_num<<endl;

    return 0;
}

    (5)多继承:在D类访问最原始的基类A中的成员时,容易出现二义性;所以引入了虚继承,这时B和C类里面都有一个续表指针,最终D类在构造函数的时候需要,需要显式调用A类的构造函数;如果B类和C类没有引入虚继承,那么D类访问A类的成员需要作用域限定符,表明走B这边,还是走C这里;

#include <iostream>

using namespace std;

class A
{
public:
    A(int a): m_a(a)
    {
        cout<<"A"<<endl;
    }

    int m_a;
};

class B : virtual public A
{
public:
    B(int b): m_b(b),A(2)
    {
        cout<<"B"<<endl;
    }

    int m_b;
};

class C : virtual public A
{
public:
    C(int c): m_c(c),A(3)
    {
        cout<<"C"<<endl;
    }

    int m_c;
};

class D : public B, public C
{
public:
    D(int d): m_d(d),B(2),C(3),A(7)  //B和C都是虚继承,那么D这里需要显式调用A的构造函数
    {
        cout<<"D"<<endl;
    }

    int m_d;
};

int main(int argc, char **arg)
{

    D d(4);
    cout<<d.m_a<<endl;  //这里就不用作用域限定符了
    cout<<d.B::m_a<<endl;  //如果B/C没有采用虚继承,则需要作用域限定符

    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_72814368/article/details/130925052