C++ 继承

本文详细介绍了面向对象编程中的继承概念、各种继承方式(public、protected、private)、构造函数和析构函数的调用顺序,以及如何处理多继承和环形继承问题,包括虚继承的应用。此外,还讨论了静态成员的继承和内嵌对象的构造顺序。
摘要由CSDN通过智能技术生成

为什么需要继承

一般用层次分类的方法来描述他们的关系
在这里插入图片描述

继承的定义

  • 继承是面向对象程序设计使代码可以复用的重要的手段,允许程序员在保持原有的特性基础上进行扩展,增加新功能。
  • 一个B类继承于A类,或者说 从类A派生类B  A是基类(父类),B是派生类(子类)

派生类成员包含两大部分

  • 从父类继承来的 自身固有的
  • 继承来的表现为共性,新增的成员体现个性
class 派生类名字:继承方式 基类名{
 // 派生类新增的数据成员和成员函数
}

/*
继承方式:
public
protected
private
*/

实例

class Person{
public:
    void getName(){
        std::cout<<"name:"<<_name<<std::endl;
    }
    void setName(std::string name){
        _name=name;
    }
    void getAge(){
        std::cout<<"age:"<<_age<<std::endl;
    }
    void setAge(int age){
        _age=age;
    }
protected:
    std::string _name;
    int _age;
};

class Student:public Person{
    /*
     * 继承
     * 相当于student类里面也有
     *     std::string _name;
     *     int _age;
     * */
private:
    std::string _stuId;
};
class Teacher:public Person{
private:
    std::string _teaId;
};


    Student s;
    Teacher t;

    std::cout<<sizeof(Student)<<std::endl;  //72B
    std::cout<< sizeof(Person)<<std::endl;  //40B

继承方式

c++的继承方式会影响子类的对外访问属性

public继承:父类成员在子类保持原有的访问等级
protected继承:父类中public成员变为protected(其他不变)protected依为protected,private依为private
private继承:父类成员在子类中变成private成员

速记技巧:
认为安全等级public<protected<private
继承时,比继承方式等级低(小于)的变为继承方式等级,比继承方式等级高(大于等于)的不变

protected和private继承区别

  • private成员在子类中存在但不可访问,无论何种继承方式,派生类中都不可以访问基类的private类型的成员
  • protected成员在子类中存在可以访问,但子类的对象中不可直接访问

注意:使用using关键字可以改变基类成员在派生类的访问权限,但是只能改变基类public和protected成员的访问权限,private不能被改变

class A{
public:
    int x;
protected:
    int y;
private:
    int z;
};

class B:public A{
public:
	using A::y;//将protected修改为public
    void fun(){
        x;  //和继承方式无关
        y;//public、protected修饰的成员,在派生类可以发访问
        // 报错
        z;//private修饰的成员,在派生类不可以访问
    }
private:
	using A::x;//将public修改为private
};

指针突破基类的private

class CTest{
public:
    int x = 10;
    void fun(){
        cout<<"x="<<x<<endl;
        cout<<"y="<<y<<endl;
        cout<<"z="<<z<<endl;
    }
protected:
    int y = 20;
private:
    int z = 30;
};
class STest:public CTest{
public:
    int k = 40;
    void printS(){
        cout<<"k="<<k<<endl;
    }
};



STest *s = new STest;
    s->fun();
    s->printS();
    //全0 说明可以通过指针操作private
    ::memset(s,0,sizeof (STest));
    
    //我们通过指针来修改基类私有变量的值
    //首先,因为三个数据类型都是int 我们先把p强制转化为 int* ,也就是第一个地址
    //然后,private是第三个 所以+2
    //最后,解引用
    *((int*)s + 2) = 50;
    s->fun();
    s->printS();
    delete s;

单继承构造、析构函数调用

顺序

构造函数

  • 子类对象在创建时会首先调用父类的构造函数
  • 父类构造函数执行完毕后,才会调用子类的构造函数
  • 当父类有带参数构造函数时,需要子类初始化列表中显示调用父类构造函数

先有父类后有子类

析构函数

先销毁子类后销毁父类 和构造相反

class A{
public:
    int a;
    A(){
        std::cout<<"A()"<< std::endl;
    }
    A(int a){
        std::cout<<"A(int a)"<< std::endl;
        this->a=a;
    }
    ~A(){
        std::cout<<"~A()"<< std::endl;
    }
};

class B:public A{
public:
    B(){
        std::cout<<"B()"<< std::endl;
    }
	//在派生类中手动调用基类的构造函数
    B(int b):A(b){
        std::cout<<"B(int b):A(b)"<< std::endl;
    }
    ~B(){
        std::cout<<"~B()"<< std::endl;
    }
};

int main() {
    B b;
    /*
	A()
	B()
	~B()
	~A()
	*/

    B a(50);
    /*
	A(int a)
	B(int b):A(b)
	~B()
	~A()
	*/

派生类和基类成员属性冲突

class A{
public:
    int a;
    A(){
        a = 10;
        std::cout<<"A()"<< std::endl;
    }
    ~A(){
        std::cout<<"~A()"<< std::endl;
    }
};

class B:public A{
public:
    int a;
    B(){
        a=20;
        std::cout<<"B()"<< std::endl;
    }

    ~B(){
        std::cout<<"~B()"<< std::endl;
    }
};

	B b;
    std::cout<<sizeof b<<std::endl;//8B,说明派生类可以继承基类同名属性

    // 那么访问b.a是访问的派生类的成员变量还是基类的成员变量呢?
    std::cout<<b.a<<std::endl;//20,同名的时候访问派生类的成员属性

    //怎么访问基类中与派生类中成员同名的属性?
    std::cout<<b.A::a<<std::endl;//10,同名的时候访问基类的成员属性

总结

  • 当子类和父类成员同名时,子类依然从父类继承同名成员
  • 如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则)
  • 在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)

隐藏

  • 子类函数和父类函数名称相同,参数也相同 而且父类函数没有 virtual 父类函数被隐藏起来
class D{
public:
    void fun(){
        std::cout<<"D"<<std::endl;
    }
};

class C:public D{
public:
    void fun(){
        std::cout<<"C"<<std::endl;
    }
};

    C c;
    c.fun(); //访问的是C的成员函数
    c.D::fun();//访问的是D的成员函数
  • 子类函数和父类函数名称相同,参数不相同,父类函数也被隐藏起来
class D{
public:
    void fun(){
        std::cout<<"D::fun"<<std::endl;
    }
};

class C:public D{
public:
    void fun(int x){
        std::cout<<"C::fun"<<std::endl;
    }
};


    C c;
    c.fun(); //访问不到D类的fun,这样会报错

多继承

在这里插入图片描述

定义格式

class 类名 : 继承方式1 基类名1,继承方式2 基类名2,...,{

}

class A(){
public:
	int a;
};

class B(){
public:
	int b;
};

class C:public A,public B{
public:
	int c;
}

环形继承

多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示:
在这里插入图片描述

class A{
public:
    int a_x;
};

class B:public A{
public:
    //int a_x;
    int b_x;
};

class C:public A{
public:
    //int a_x;
    int c_x;
};

class D:public B,public C{
public:
    //int a_x;
    //int b_x;
    //int c_x;
    int d_x;
};

    D d;
    std::cout<<sizeof d<<std::endl; //20

    std::cout<<d.a_x<< std::endl; //报错 派生类访问间接基类的成员属性是 不知道是为B还是C的

解决环形继承的问题

1、作用域::变量

但是这样没什么意义,还有有多份属性

    std::cout<<d.B::a_x<<std::endl;
    std::cout<<d.C::a_x<<std::endl;

2、虚继承

class A{
public:
    int a_x;
};

//virtual 虚继承,D类继承B和C中A的属性是共享的,也就是说D中只有一份 a_x
class B:virtual public A{
};

class C:virtual public A{
};

class D:public B,public C{
};


    D d;
    
    // 占用空间不同?后续解释 (virtual搞的鬼)
    std::cout<<sizeof(D)<<std::endl; //24
    std::cout<< sizeof(A)<<std::endl; //4

相比于普通基类,虚基类先构造(打破了从左到右的顺序)

#include<iostream>
using namespace std;
class Base1{
public:
	Base1(){cout<<"Base1"<<endl;}
	~Base1(){cout<<"~Base1"<<endl;}
};
class Base2{
public:
	Base2(){cout<<"Base2"<<endl;}
	~Base2(){cout<<"~Base2"<<endl;}
};

class Level1:public Base2,virtual public Base1{
public:
	Level1(){cout<<"Level1"<<endl;}
	~Level1(){cout<<"~Level1"<<endl;}
};

class Level2:public Base2,virtual public Base1{
public:
	Level2(){cout<<"Level2"<<endl;}
	~Level2(){cout<<"~Level2"<<endl;}
};

class TopLevel:public Level1,virtual public Level2{
public:
	TopLevel(){cout<<"TopLevel"<<endl;}
	~TopLevel(){cout<<"~TopLevel"<<endl;}
};
int main(){
	TopLevel top;
	return 0;
};

静态成员的继承

  • 静态成员可以被继承而且还是共享
  • 同名基类被隐藏
class AA{
public:
    static int num;
};
int AA::num=100;

class BB:public AA{

};

    BB::num=50;
    //静态成员不仅被继承而且还是共享
    std::cout<<BB::num<<std::endl;//50
    std::cout<<AA::num<<std::endl;//50
class AA{
public:
    static int num;
};
int AA::num=100;

class BB:public AA{
public:
    static int num;
};
int BB::num=100;

    BB::num=50;
    //派生类隐藏基类静态成员变量
    std::cout<<BB::num<<std::endl;//50
    std::cout<<AA::num<<std::endl;//100

继承和内嵌对象

当一个类即继承另外一个类,有内嵌一个另外类的对象时,构造函数调用顺序:

基类-》内嵌成员对象-》派生类

析构函数调用顺序和构造函数调用顺序完全相反

基类构造顺序看继承的先后,内嵌对象构造顺序看声明先后

一个例题

#include<iostream>
using namespace std;
class A{
public:
	A(int i){cout<<i;}
};
class B:virtual public A{
	public:
	B(int i,int j=0):A(j){cout<<i;}
};
class C:virtual public A{
	public:
	C(int i,int j=0):A(j){cout<<i;}
};
class D:public C,public B{
	C obj2;
	B obj1;
public:
	D(int a,int b,int c,int d):obj1(a),obj2(b),B(c),C(d),A(a){
		cout<<b;
	}
};
int main(){
	D d(1,2,3,4);
	return 0;
};

看输出结果:

先是基类

首先构造A(a) 也就是A(1)【最先调用的一定是最高级的】
然后根据继承顺序调用C(4)【应该是A(0) C(4,0)因为是虚继承 前面已经调用了后续就不调用A了】
然后B(3)【与上面同理】

然后内嵌对象
声明顺序是C、B
所以先构造C【A(0)、C(2,0)】
然后B【A(0)、B(1,0)】

最后派生类
cout<<b

所以最终的顺序应该是 14302012

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值