继承的概念
继承机制是面向对象程序设计使代码可以复用的重要手段,它允许程序员在保持原有类特性的基础之上进行其他扩展,增加功能,这样产生的新类,称之为派生类。
继承呈现了面向对象程序设计的层次结构,体现了有简单到复杂的认知过程。
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;
}
- 上方代码中setNum函数中num是一个局部变量,并不是Student类中的成员变量num,所以调用setNum函数不影响成员变量num的值;setNum2函数才有改变成员变量num效果;
- Person和Stdent的id成员变量是两个不同的变量,调用showId函数,打印结果是100。
小结:
3. 同名隐藏发生在不同作用域下,同作用域下叫函数重载;
4. 父类和子类中有同名的成员,子类只能看到自己的成员,如果需要访问父类的成员,需要加上父类的作用域;
5. 成员变量隐藏:成员变量名相同;
6. 函数名相同,父类子类作用域不同构成函数隐藏,与参数个数无关;
7. 只要不同作用域下,有同名成员,当前作用域下的这个名称的成员就会覆盖其他同名成员,该机制不是继承独有的。
继承中的成员函数
子类的构造函数
- 一定会调用父类的构造函数
a. 如果不显式调用,自动调用父类的默认构造;
b. 如果显式调用,则调用显式指定的父类构造; - 继承自父类的成员变量,一定要通过父类的构造函数完成初始化,在子类的初始化列表中只能显式初始化子类新增的成员变量;
- 初始化顺序:一定是先初始化父类的成员,再初始化子类的成员;
- 创建子类对象时,首先调用子类的构造函数,在子类构造函数的初始化列表中调用父类的构造函数,先执行父类的构造逻辑,再执行子类的构造逻辑;
子类的拷贝构造函数
- 默认行为(没有显式定义子类的拷贝构造):调用父类的拷贝构造;
- 显式定义子类的拷贝构造的默认行为(没有显式调用父类的拷贝构造):调用父类的默认构造;
- 在子类的拷贝构造中可以指定调用哪一个父类的构造函数,可以是拷贝构造函数,也可以是其他构造函数;
子类的赋值运算符重载函数
- 默认行为:调用父类的赋值运算符重载函数;
- 显示定义:和父类的赋值运算符构成同名隐藏;
- 建议调用父类的赋值运算符,可以使代码复用;
子类的析构函数
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;
}
在虚拟继承中,存在虚基表指针和和虚基表,虚基表指针指向虚基表的首地址,虚基表中存放虚基表指针的地址到公共成员的偏移量。
通过用指针的大小换取重复成员的大小,重复的成员越多,越能看出虚拟继承的好处。