c++---继承

c++—目录索引(知识小渠道)


1.继承

继承概念
 继承是面向对象复用的重要手段,通过继承定义一个类,继承是类型之间的关系建模,共享共有的

访问限定符和继承关系
 private:私有,在父类里,成员为私有限定,意味着不管是什么继承,这个成员就是不让你看,就算你是从我这继承的

 protect:保护,父类成员受保护,说明访问还是可以让你访问的,但只能是子类在自己的内部访问,不可以在类外面通过类对象进行访问,不管是基类对象还是子类对象

三种继承关系

继承方式 基类public成员 基类protect成员 基类private成员 继承引起的访问限制关系
public继承 仍为public成员 仍为protect成员 不可见 基类的非私有成员在子类的访问属性都不变
protect继承 变为protect成员 仍为protect成员 不可见 基类的非私有成员都成为子类的保护成员
private继承 变为private成员 变为private成员 不可见 基类的非私有成员都成为子类的私有成员
class father
{
public:
    int fa;
protected:
    int fb;
private:
    int fc;
};
class son:public father//共有继承
{
public:
    int sa;
protected:
    int sb;
private:
    int sc;
public:
    void func()
    {
        sb = fb;//在子类的内部可以访问父类的保护成员
    }
};
void Test()
{
    son s;
    s.fb=5;//不可以通过子类对象访问父类的保护成员
}

小结:

  • 基类中的private成员,不管通过何种方式继承,在子类中都不能被访问
  • 某个成员不想被对象直接访问,但要在子类中能被访问,就定义成protected成员
  • public继承是一个接口继承,保持is-a原则,每个父类可用的成员对子类也可用,因为每个子类对象也是一个父类对象
  • protected/private继承是一个实现继承,基类的部分成员并未完全成为子类接口的一部分,是has-a的关系原则
  • 不管哪种继承,在派生类内部都可以访问基类的公有成员和保护成员,但是基类的私有成员存在只是不可见
  • 使用关键字class默认继承是private,使用struct默认是public
  • is-a原则:is-a-kind-of,指的是子类是父类的一种,
  • has-a原则:代表的是对象和它的成员的从属关系,同一种类的对象,通过它们的属性的不同值来区别

继承–赋值,public

  • 子类对象可以赋值给父类对象
  • 父类对象不能赋值给子类对象
  • 父类的指针/引用可以指向子类对象
  • 子类的指针/引用不能指向父类对象(可以强转)
class father
{
public:
     int fa;
     int fb;
};
class son:public father
{
public:
    int sa;
};
void test()
{
    son s;
    faeher f;
    f=s;//子类可以赋给父类(切片)
    s=f;//父类不可以赋给子类

    father& f1=f;//父类的引用可以指向子类
    f1=s;

    father* f2=&f;//父类的指针可以指向子类
    f2=&s;

    son& s1=s;//子类的引用不能指向父类(可强转)
    s=(son&)f;

    son* s2=&s;//子类的指针不能指向父类(可强转)
    s2=(son*)&f;
}

理解:
 子类是从父类继承的,所以父类必定包含子类的部分成员,在赋值时,父类只取自己的那部分成员,并不接受子类的成员,从而发生切片,丢弃了子类的成员;
 父类给子类赋值时,子类并不知道父类有多少成员,部分成员继承过来,但私有成员不可见,故而赋值时子类没有更多的空间来存放父类的私有成员,如果说要进行切片,那么要哪些成员,不要哪些成员,并不知道
成员函数的重载,覆盖,隐藏
重载

  • 在同一个类的作用域里,成员函数的名字不同,参数不同,两个函数构成重载
  • 参数不同可以是顺序不同,数目不同(包括const非const)
  • virtual关键字可有可无
    覆盖

  • 覆盖指派生类重新实现了基类的成员函数

  • 发生覆盖的两个成员函数分别在两个不同的类域,分别为基类和派生类
  • 发生覆盖的两个成员函数的函数名相同,参数完全相同(参数顺序,参数数目)
  • 基类的成员函数必须为虚函数(virtual)
    隐藏

  • 隐藏指派生类的成员函数遮蔽了与其他函数名相同的基类成员函数

  • 派生了的函数名和基类的函数名相同,参数不同;基类的成员函数被隐藏
  • 派生类的函数名和基类的函数名相同,参数名完全相同,但基类成员函数没有virtual关键字,基类的成员函数被隐藏
class father
{
public:
    void func(int x)
    {
        cout<<"father:func"<<end1;
    }
    void func1(int x,int y)
    {
        cout<<"father:func1"<<end1;
    }
    virtual void func2(int x)
    {
        cout<<"father:func2"<<end1;
    }
};
class son:public father
{
public:
    void func(float x)
    {
        cout<<"son:func1"<<end1;
    }
    void func1(int x,int y)
    {
        cout<<"son:func1"<<end1;
    }
    virtual void func2(int x)
    {
        cout<<"son:func2"<<end1;
    }
};
//father::func和father::func1构成重载
//son::func隐藏了father::func
//son::func1隐藏了father::func1
//son::func2覆盖了father::func2
//隐藏的想用可注明作用域调函数

小结:

  • 在继承体系中基类和派生类都有独立的作用域
  • 子类和父类中有同名成员,子类成员将屏蔽对成员的直接访问(在子类成员函数中,可以使用基类::基类成员 访问)

派生类的默认成员函数

  • 派生类继承父类时,并不会继承基类的成员函数(构造函数,析构函数,赋值函数)
  • 在继承关系里面,在派生类中如果没有显示定义六个成员函数,编译系统会默认合成这六个默认的成员函数(构造函数,拷贝构造函数,析构函数,赋值操作符重载,取地址操作符重载,const修饰的取地址操作符重载)
  • 派生类的构造函数(包括拷贝构造函数)只能在其初始化列表(不能在派生类构造函数内调用基类的构造函数)里显示的调用基类的构造函数(除非基类的构造函数不可访问)
  • 如果基类是多态类,那么必须把基类的析构函数定义为虚函数
  • 实现派生类的赋值函数时,派生类中继承自父类的成员变量可以直接调用基类的赋值函数实现
class Base
{
public:
    Base(const char* name = "")
        :_name(name)
    {
        cout << "base构造" << end1;
    }
    Base(const Base& b)
        :_name(b._name)
    {
        cout << "base拷贝构造" << end1;
    }
    Base& operator=(const Base& b)
    {
        cout << "Base赋值运算符重载" << end1;
        if (this != &b)
        {
            _name = b._name;
        }
        return *this;
    }
    ~Base()
    {
        cout << "Base析构" << end1;
    }
protected:
    string _name;
};

class Dervied :public Base
{
public:
    Dervied(const char* name,int num)
        :Base(name)//显示调基类的构造
        ,_num(num)
    {
            cout << "Dervied构造" << end1;
    }
    Dervied(const Dervied& der)
        :Base(der)//显示调基类的拷贝构造
        ,_num(num)
    {
            cout << "Dervied拷贝构造" << end1;
    }
    Dervied& operator=(const Dervied& der)
    {
        cout << "Dervied赋值运算符重载" << end1;
        if (this != &der)
        {
            Base::operator=(der);//调基类的赋值运算符重载
            _num = der._num;
        }
        return *this;
    }
    ~Dervied()
    {
        cout << "Dervied析构" << end1;
    }
protected:
    int _num;
};
void Test()
{
    Dervied d("phony", 10);
    cout << end1;
    Dervied d1(d);
    cout << end1;
    Dervied d3("cat", 11);
    cout << end1;
    d1 = d3;
    cout << end1;
}
int  main()
{
    Test();
    system("pause");
    return 0;
}

在实现默认成员函数时,从基类继承的成员就调用积累的成员函数进行构造,拷贝构造等,析构时,顺序则相反(父子子父)
派生类的析构函数隐藏了基类的析构函数,在调用完自己的析构函数时,自动调用父类的析构函数(编译器会把所有的析构函数名换成destructor,这样就构成了隐藏)

单继承和多继承

单继承
单继承指单一继承,一个子类只有一个直接父类时,称这个继承关系为单继承,一对一
这里写图片描述
多继承

  • 一个子类有两个或两个以上直接父类时称这个继承关系为多继承,这种继承方式使一个子类可以继承多个父类的特性
  • 多继承可以看作是单继承的扩展,派生类具有多个基类,派生类与每个基类之间的关系仍可看作单继承
  • 多继承的派生类的构造函数与单继承的派生类的构造函数相似,它必须同时负责该派生类所有基类构造函数的调用。同时,派生类的参数个数必须包含完成所有基类初始化所需的参数个数,在子类的内存中它们是按照生命定义的顺序存放的
    这里写图片描述

菱形继承
两个自己继承同一个父类,又来一个子类同时继承这两个子类(钻石继承)
问题存在于数据冗余和产生二义性
这里写图片描述
B和C都继承了父类A,此时父类A里的函数就存了两份,D又继承了BC,肯定会去它的父类里找,但是现在有两份,不知道选哪一个,因此产生了二义性
解决:
指明类域

void Test()
{
    D d;
    d.B::func();
    d.C::func();
}

采用虚继承

class A
{
public:
    void func()
    {
        cout<<"A::func()"<<end1;
    }
protected:
    int _a;
};
class B:virtual public A//B虚继承A
{
protectedint _b;
};
class C:virtual public A//C虚继承A
{
protectedint _c;
};
class D:public B,public C
{
protected:
    int _d;
}
void Test()
{
    D d;
    cout<<sizeod(d)<<end1;//24
    d._a=1;
    d._b=1;
    d._c=1;
}
int main()
{
    Test();
    system("pause");
    return 0;
}

虚继承

  • 虚继承就是让B C在继承A时加上virtual关键字
  • 虚继承解决了在菱形继承体系里面子类对象包含多份父类对象的数据冗余和浪费空间问题
  • 每一个继承自父类对象的实例中都存有一个指针,这个指针指向的虚基表的其中一项,里面存的是一个偏移量,对象的实例可以通过自身的地址加上这个偏移量找到存放继承自父类对象的地址

如果有什么不对的地方,可以评论告诉我,望指导!

猜你喜欢

转载自blog.csdn.net/phonycat/article/details/80298137