1、继承的概念
1.2、继承定义
1.2.1、定义格式
class Student(派生类) : public(继承方式)Person(基类){}
1.2.2、继承方式
继承方式总共有public、protected、private这三种继承方式
继承基类成员访问方式的变化如下:
总结:
1、private修饰的成员无论以什么方式继承在子类中都不可见(并不是没有继承private修饰的成员,是语法上限制派生类对象无论在类内还是类外都不能去访问它。)
2、private修饰的成员在派生类中不可访问,如果派生类仍想访问基类的某些成员,要将基类中的这些成员定义为protected。
3、基类中其他成员的在派生类中的访问方式是继承方式和在基类中的访问方式的最小值,这里public > protected > private。
4、class的默认继承方式是private,struct的默认继承方式是public
class Person{ private:string _name;};
class Student : Person { };//没有显式写出继承方式,这里采用默认继承方式private
class Person{ private:string _name;};
struct Student : Person { };//没有显式写出继承方式,这里采用默认继承方式public
2、 基类对象与派生类对象的赋值转换
1、派生类对象可以赋值给基类的对象/基类的指针/基类的引用,这里成为切片。
2、基类对象不能赋值给派生类对象。
3、基类对象的指针也可以强制转换赋值给派生类的指针,但基类指针必须指向派生类对象才行。
Student sobj;
// 1.子类对象可以赋值给父类对象/指针/引用
Person pobj = sobj;
Person* pp = &sobj;
Person& rp = sobj;
//2.基类对象不能赋值给派生类对象
//sobj = pobj;
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
pp = &sobj;
Student * ps1 = (Student*)pp; // 这种情况转换时可以的。
ps1->_No = 10;
3、继承中的作用域
1、子类和父类有各自的作用域。
2、子类和父类有同名函数时,子类会屏蔽父类的同名函数(隐藏和重定义,这里要和后面多态中的重写(覆盖)区分,并且可以通过类名::的方式访问父类的同名成员)。
3、对于成员函数,只要函数名相同就构成隐藏。
class Person
{
protected:
string _name; // 姓名
string _sex; // 性别
int _age; // 年龄
};
class Student : public Person
{
Student(int age)
{
_age = age; //这里访问的是Student的_age,因为会优先访问最近的
Person::_age = 19;
}
protected:
int _age; // 年龄
};
4、派生类默认成员函数
1、构造函数:子类的构造函数必须先调用父类的构造函数,对父类部分进行初始化,如果子类没有默认构造函数,必须在子类构造函数的初始化列表显式调用。
2、析构函数:子类析构函数调用完成后,会自动调用父类析构函数对父类部分进行清理,这样可以保证先构造后析构的顺序。
3、拷贝构造函数:子类的拷贝构造函数必须调用父类的拷贝构造函数,完成父类部分拷贝初始化。
Person(const Person& p)
: _name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
Student(const Student& s)
: Person(s)
, _num(s._num)
{
cout << "Student(const Student& s)" << endl;
}
4、子类的operator=必须调用父类的operator完成父类部分的赋值。
Person& operator=(const Person& p)
{
cout << "Person operator=(const Person& p)" << endl;
if (this != &p)
_name = p._name;
return *this;
}
Student& operator = (const Student& s)
{
cout << "Student& operator= (const Student& s)" << endl;
if (this != &s)
{
Person::operator =(s);
_num = s._num;
}
return *this;
}
5、菱形继承和菱形虚拟继承
图示:
菱形继承带来的问题:
1、数据冗余:如下图D继承了B和C,而B、C都继承了A,因此在普通继承方式下D中有两个_a造成了数据的冗余。
2、数据二义性:如下图由于二义性,d不能直接访问_a,可以通过限定作用域来进行访问。
解决方式:采用virtual进行虚拟继承
6、继承的其他知识点:
1、友元函数不可以继承,父类的友元函数不能自由访问子类成员
2、父类定义了静态变量,整个继承体系就只有唯一一个这样的成员,无论派生多少类,都只有一个静态成员实例。
3、继承和组合:继承是一种is-a的关系,每个派生类只有一个父类对象,就比如车和宝马车,宝马车is车。组合是has-a关系,就比如轮胎和车的关系一样。对于继承和组合,我们得结合实际情况分析是is-a还是has-a关系,如果都可以,优先使用组合,因为继承会破坏父类的封装性,并且耦合性较高。