C++继承总结

继承的概念
继承机制是面向对象程序设计使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础之上进行其他扩展,增加功能,这样产生的新类,称之为派生类。
继承呈现了面向对象程序设计的层次结构,体现了有简单到复杂的认知过程。

class Person{
public:
    void Print(){
        cout << "name" << _name << endl;
        cout << "age" << _age << endl;
    }
protected:
    string _name = "kaiser";
    int _age = 21;        
}

//继承父类Person的成员(包括函数和变量),都变成了子类Student的一部分,并且子类还有自己新增加的变量或者函数
class Student : public Person{
protected:
    int _stdid;//学号
}

在这里插入图片描述
三种继承方式的区别
在这里插入图片描述

**1.**基类的私有成员在派生类中,不可访问,但是基类的私有成员是派生类的一部分。所以在继承的部分,很少在基类中定义私有成员。

**2.**基类的保护成员在派生类中可见,但是其他类外的地方不可见。

**3.**无论哪一种继承方式,都不会影响基类的正常使用。

**4.**如果继承方式没写,那么class的默认继承方式是private继承,struct的默认继承方式是public继承

切片操作

派生类对象、指针、引用可以直接赋值给基类对象、指针。引用

Person p;
Student s;

p = s;
Person& p1 = s;
Person* p2 = &s; 

注意:
**1.**基类对象不能赋值给派生类对象
**2.**基类指针、引用可以用强制类型转换赋值给派生类指针、引用(但是存在不安全情况,要看赋值的基类的指针、引用的具体指向是否是派生类)

下图中红色是强制转换不安全情况
绿色是强制转换安全情况
蓝色是切片操作
在这里插入图片描述
同名隐藏

class Person{
protected:
    int num;
    int id = 1;
}
class Student : public Person{
public:
    void setNum(int num){
        num = num;
    }
    void setNum2(int num){
        this -> num = num;
    }
    void showId(){
        cout << id << endl;
    }
    
protected:
    int num;
    int id = 100;
}
  1. 上方代码中setNum函数中num是一个局部变量,并不是Student类中的成员变量num,所以调用setNum函数不影响成员变量num的值;setNum2函数才有改变成员变量num效果;
  2. Person和Stdent的id成员变量是两个不同的变量,调用showId函数,打印结果是100。

小结:
3. 同名隐藏发生在不同作用域下,同作用域下叫函数重载;
4. 父类和子类中有同名的成员,子类只能看到自己的成员,如果需要访问父类的成员,需要加上父类的作用域;
5. 成员变量隐藏:成员变量名相同;
6. 函数名相同,父类子类作用域不同构成函数隐藏,与参数个数无关;
7. 只要不同作用域下,有同名成员,当前作用域下的这个名称的成员就会覆盖其他同名成员,该机制不是继承独有的。

继承中的成员函数
子类的构造函数

  1. 一定会调用父类的构造函数
    a. 如果不显式调用,自动调用父类的默认构造;
    b. 如果显式调用,则调用显式指定的父类构造;
  2. 继承自父类的成员变量,一定要通过父类的构造函数完成初始化,在子类的初始化列表中只能显式初始化子类新增的成员变量;
  3. 初始化顺序:一定是先初始化父类的成员,再初始化子类的成员;
  4. 创建子类对象时,首先调用子类的构造函数,在子类构造函数的初始化列表中调用父类的构造函数,先执行父类的构造逻辑,再执行子类的构造逻辑;

子类的拷贝构造函数

  1. 默认行为(没有显式定义子类的拷贝构造):调用父类的拷贝构造;
  2. 显式定义子类的拷贝构造的默认行为(没有显式调用父类的拷贝构造):调用父类的默认构造;
  3. 在子类的拷贝构造中可以指定调用哪一个父类的构造函数,可以是拷贝构造函数,也可以是其他构造函数;

子类的赋值运算符重载函数

  1. 默认行为:调用父类的赋值运算符重载函数;
  2. 显示定义:和父类的赋值运算符构成同名隐藏;
  3. 建议调用父类的赋值运算符,可以使代码复用;

子类的析构函数
1.编译器自动生成的析构函数自动调用父类析构函数;
2.显式定义的子类析构函数自动调用父类析构函数;
3.无论子类析构函数是否显式调用父类析构函数,编译器都会自动调用一次父类的析构函数;
4.子类析构函数和父类析构函数底层函数名相同,构成函数隐藏;
注意:父类析构函数不需要在子类中显式调用,防止资源二次释放问题。

友元关系不能继承
基类静态成员无论经过多少次继承,都只有一个实例对象

菱形继承问题
在这里插入图片描述
菱形继承存在数据冗余和二义性的问题,如上图,Assistant类中有Person类成员两份

解决方法:以上图为例,让Student和Teacher虚拟继承Person,class Student : virtual public Person,class Teacher : virtual public Person。这样的话Assistant中就只有一份Person类的成员了

虚基表和虚基表指针

class A{
public:
    int a;
}
class B:virtual public A{
public:
    int b;
}
class C:virtual public A{
public:
    int c;
}
class D:public B, public C{
public:
    int d;
}            

在这里插入图片描述

在虚拟继承中,存在虚基表指针和和虚基表,虚基表指针指向虚基表的首地址,虚基表中存放虚基表指针的地址到公共成员的偏移量。
通过用指针的大小换取重复成员的大小,重复的成员越多,越能看出虚拟继承的好处。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值