目录
继承
1.继承定义
2.继承方式和访问限定符
3.继承基类成员后,派生类相应的访问方式的变化
4.基类和派生类的对象赋值转换
1.切片或切割
2.赋值兼容
5.作用域
6.隐藏/重定义
派生类的默认成员函数
1.构造函数
2.拷贝构造函数
3.赋值运算符重载
4.析构函数
5.友元关系
6.静态成员
继承方式
1.单继承
2.多继承
3.菱形继承
4.菱形虚拟继承
1.菱形继承对象模型
2.菱形虚拟继承对象模型
5.虚基表指针
6.虚基表
继承和对象组合
小结
继承
1.继承定义
继承是使代码可以复用的重要手段,允许在原有类特性的基础上进行拓展,增加功能,这样产生的类叫做派生类。
Teacher:派生类
public:继承方式
Person: 基类
class Teacher:public Person
{
public:
int _workid;
}
2.继承方式和访问限定符
3.继承基类成员后,派生类相应的访问方式的变化
总的来说:
1.对于基类的private成员:派生类中不可见。
2.对于public继承方式:基类是什么继承方式,子类就是什么访问方式(除开基类private成员)
3对于protected继承方式:子类都是protected访问方式(除开基类private的成员)
4对于private继承方式:子类都是private访问方式(除开基类private)
5.所以protected的出现就是为了让基类中的除private成员,在派生类中可以访问,但是在类内不能访问。
注意
1.class可以缺省继承方式,默认为private。struct默认为public
2.实际运用一般都是public继承,因为protected、private继承下,继承的成员只能在派生类中使用。
class Student:Person
{
//.......
}
4.基类和派生类的对象赋值转换
1.切片或切割
派生类对象可以赋值给基类的对象,基类的指针,基类的引用。
即派生类可以赋值给基类,因为派生类有的成员基类也有
基类不能赋值给派生类(先暂时这样认为),因为派生类有的成员基类不一定有
在public继承下
相当于:每一个子类对象都是一个特殊的父类对象
class Person
{
public:
void Pint()
{
cout << "name: " << _name << endl;
cout << "age: " << _age << endl;
}
string _name;
int _age;
};
class Student :public Person
{
int _stdid;
};
class Teacher : public Person
{
int _jobid;
};
void test1()
{
Student st;
Person p = st;
//切割/切片赋值兼容
Person& ref = st;//注意:这里可以不用加const,叫做赋值兼容
Person* ptr = &st;
ref._name += "xiao";
ptr->_name += "ming";//可以改变子类
st.Pint();
}
1.对于派生类赋值给基类的指针,指针指向的就是派生类的切片
2.派生类赋值给基类的引用,引用即派生类的切片
2.赋值兼容
注意:这里的引用不用const 叫做赋值兼容 (本来隐式类型转换会产生临时变量,临时变量具有常性)
5.作用域
同一个域中不能同名(除了函数重载)
函数重载:在同一个域才存在
1局部域
2全局域
1.2会影响生命周期
3命名空间域
4类域(衍生出:基类,派生类)是两个独立作用域
6.隐藏/重定义
1.当子类和父类有同名成员,子类成员将隐藏对父类的同名成员的直接访问
2.虽然直接访问不行,但是还是可以用 基类::基类成员 来间接访问
3.对于成员函数的隐藏,只要函数名字不同就构成隐藏(要区分:函数重载)
因为子类和父类是两个不同的作用域,就算函数名相同也不会构成重载
4.实际中一般不在继承体系中定义同名成员
class A
{
public:
void fun()
{
cout << "func()" << endl;
}
};
class B : public A
{
public:
void fun(int i)
{
A::fun();
cout << "func(int i)->" << i << endl;
}
};
void test()
{
B b;
//b.fun();//调用参数过少
b.fun(10);//调用B的
b.A::fun();//调用A的
}
派生类的默认成员函数
把父类成员当作一个自定义类型(不能单独初始化父类的某个成员变量)
复习:
1.对于自定义类型在初始化时是在初始化列表初始化
2.不在初始化列表初始化:调用自定义类型的默认构造函数(没有就报错)
在初始化列表初始化:调用父类默认构造
1.构造函数
就像匿名对象,但不是匿名对象
本质是复用
Student2(int stdid, const string& name, int age)
:_stdid(stdid)
,Person2(name,age)
{}
2.拷贝构造函数
这里直接传s就行,相当于切片、切割
本质是复用
Student2(const Student2&s)
:Person2(s)//这里直接传s就行,相当于切片、切割
,_stdid(s._stdid)
{}
3.赋值运算符重载
这里直接传s就行,相当于切片、切割
注意要类域
Student2& operator=(const Student2& s)
{
if (&s != this)
{
Person2::operator=(s);
_stdid = s._stdid;
}
return *this;
}
4.析构函数
因为后续析构的需要 析构函数名会统一处理成 ~destructor
子类析构会隐藏父类
~Student2()
{
//~Person2();报错
Person2::~Person2();
//Person2::~Person2();
}
相当于下面 (注意下面会报错)
~destructor()
{
Person2::~desturctor();//相当于构成隐藏
~desturctor();
}
5.友元关系
友元关系没有继承
基类的友元可以访问基类的私有和保护成员,但是不能访问派生类的私有和保护成员
6.静态成员
如果在基类定义的静态成员变量,那么这个继承体系只有一个静态成员,其所有派生类都只有一个static成员实例
继承方式
1.单继承
一个子类只有一个父类
2.多继承
一个子类有多个父类
3.菱形继承
是多继承的一种特殊情况
存在数据冗余和二义性的问题
4.菱形虚拟继承
在继承关系前加上virtual
注意是对于指向同一个基类的派生类
class Person
{
public:
string _name;
};
class Student : virtual public Person
{
protected:
int _num; };
class Teacher : virtual public Person
{
protected:
int _id;
1.菱形继承对象模型
这里的BC都有_a ,存在数据冗余
2.菱形虚拟继承对象模型
虚拟继承解决数据冗余和二义性原理
5.虚基表指针
A为虚基类,虚基类放在最下面,变为公共的,派生类都有一个指针(叫做虚基表指针),指向的内容是偏移量,用偏移量计算虚基类的位置。
6.虚基表
这两个表就叫做虚基表
切片时指针偏移,指到自己对象的位置
继承和对象组合
public继承是is-a的关系,例如学生是人
对象组合是一种has-a的关系,例如汽车有轮胎
两者本质都是复用,但又不同点
注意要点:
1.优先使用对象组合,而非类继承
因为对于pubic继承,这种通过派生类的复用被称为:白箱复用(white-box reuse)
。因为在继承关系中,基类的内部细节对子类可见,基类和派生类有很强的依赖关系。
即低内聚,高耦合。
而对于对象组合,这种通过组合对象的复用一般叫做:黑箱复用(black-box reuse)
。因为在组合关系中,对象的内部细节是不可见的,组合类之间没有很强的依赖关系。
即低耦合,高内聚。
2.要实现多态也要继承。
继承:is-a例子
// Car和BMW Car和Benz构成is-a的关系
class Car{
protected:
string _colour = "白色"; // 颜色
string _num = "陕ABIT00"; // 车牌号
};
class BMW : public Car{
public:
void Drive() {cout << "好开-操控" << endl;}
};
class Benz : public Car{
public:
void Drive() {cout << "好坐-舒适" << endl;}
};
组合:has-a关系
// Tire和Car构成has-a的关系
class Tire{
protected:
string _brand = "Michelin"; // 品牌
size_t _size = 17; // 尺寸
};
class Car{
protected:
string _colour = "白色"; // 颜色
string _num = "陕ABIT00"; // 车牌号
Tire _t; // 轮胎
};
小结
1.什么是菱形继承?问题是?
菱形继承是多继承的一种特殊情况,两个子类继承同一个父类。
由于两个子类会有两个父类的成员,因此会造成数据冗余和二义性。
2.什么是菱形虚拟继承?如何解决数据冗余和二义性?
菱形虚拟继承是指在继承同一个父类的子类中用虚拟继承(virtua)的继承方式。
从而使基类的成员只储存一份,然后通过派生类的虚基表中的虚基表指针找到对应派生类与基类的偏移量,然后通过偏移量找到基类。