一.继承概念
继承的概念
继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员保持原有类特性的基础上进行扩展,增加功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构,体现了由简单到复杂的认知过程。
继承定义
下面我们看到Person是父类,也称作基类。Student是子类,也称作派生类
class Person
{
public:
//....
};
// 子类 继承方式 父类
class Student : public Person
{
public:
//...
};
继承方式有三种 public protected private
1. 基类private成员在派生类中无论以什么方式继承都是不可见的。这里的不可见是指基类的私有成员还是被继承到了派生类对象中,但无论是在类内还是类外都是不能访问的。
(可以调用父类函数间接访问)
2.派生类的成员类型要根据基类的成员类型 继承类型来判断,访问权限大小public > protected> private,我们取两个类型中权限小的那个作为派生类的成员类型。
(eg.protected继承,基类public->派生类protected protected->protected private->private)
3.protected和private区别,继承方式是protected/public的话,
protected:基类在类内可以直接使用,但类外不行。
private:基类在类内外都不可以访问,只能通过父类的函数间接访问。
4. 使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过最好显示的写出继承方式。
二.基类和派生类对象赋值转换
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片
或者切割。(可以子赋值给父,但父不能赋值给子)
注:这里的赋值转换和double b=3.14 int a=b的断流不同,断流是产生一个临时变量赋值,这个临时变量是常量引用 指针时需要缩小权限加const
而赋值转换不会产生临时对象,引用 指针都是指向派生类中基类的部分。
所以引用 指针可以改变派生类中的基类。
三.继承中的作用域
1. 基类和派生类是两个独立的作用域。
2. 子类和父类中有同名成员,在子类中能直接访问到子类的同名变量(父类的不能直接访问),这种情况叫隐藏,也叫重定义。(在子类成员函数中,可以使用 基父类::父类成员 显示访问)
3. 需要注意的是如果是成员函数的隐藏,只需要函数名相同就构成隐藏。
可以看到这里只能找到子类的fun函数。父类和子类的fun函数构成了隐藏关系,因为分别在两个不同的作用域所以不构成重载。
四.派生类的默认成员函数
构造函数
子类的构造函数对成员变量初始化时会分为3个部分,1基本类型 2自定义类型(调用该类型的构造函数) 3整个父类成员变量(调用父类的构造函数)构造顺序是按先父类成员,再子类成员。
父类没有默认构造函数,就需要传参就显示传(类似于匿名对象Person(_name) )。
注:构造顺序是按声明顺序,与初始化列表顺序无关。
拷贝构造函数
拷贝构造函数和构造函数差不多,也是分为3部分。向父类拷贝构造函数传参时,可以直接传子类对象,利用切割/切片原理把子类父类的部分传过去实现拷贝构造。
class Person
{
public:
Person(const char* name = "")
: _name(name)
{
cout << "Person()" << endl;
}
Person(const Person& p)
: _name(p._name)
{
cout << "Person(const Person& p)" << endl;
}
protected:
string _name; // 姓名
};
class Student : public Person
{
public:
// 父类构造显示调用,可以保证先父后子
Student(const char* name = "", int x = 0, const char* address = "")
:_x(x)
, _address(address)
, _name(Person::_name )
, Person(name)
{}
Student(const Student& st)
:Person(st)//切片
, _x(st._x)
, _address(st._address)
{}
protected:
int _x = 1;
string _address;
string _name;
};
赋值构造
子类赋值构造也需要调用父类的赋值构造函数,但调用时要注意函数重名会隐藏父类函数,
需要指名父类作用域。
Student& operator=(const Student& st)
{
if (this != &st)
{
Person::operator=(st);
_x = st._x;
_address = st._address;
}
return *this;
}
析构函数
调用析构函数时不需要显示调用父类的析构函数,析构完子类成员后会自动调用父类析构函数。析构顺序与构造顺序相反,先析构子类再析构父类。
(如果先析构父类成员,在子类中再访问父类成员就会出现报错)
(构造函数初始化列表是按照声明顺序初始化的,所以不需要担心顺序)
五.继承与友元
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员。
class Person
{
public:
friend void Display(const Person& p, const Student& s);
protected:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _stuNum; // 学号
};
六. 继承与静态成员
在基类中定义的静态成员变量,与派生类继承的静态成员变量是同一个。静态成员变量不存在于对象中,属于整个类。
#include"test.h"
class Person
{
public:
Person() { ++_count; }
protected:
string _name; // 姓名
public:
static int _count; // 统计人的个数。
};
int Person::_count = 0;
class Student : public Person
{
protected:
int _stuNum; // 学号
};
int main()
{
Person p;
Student s;
cout << &p._count << endl;
cout << &s._count << endl;
}
七.复杂的菱形继承及菱形虚拟继承
单继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承。
class Student : public Person
多继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
class Student : public Person, public Teacher
菱形继承
菱形继承:菱形继承是多继承的一种特殊情况。子类的任两个父类继承了相同的父类,导致子类中继承了两份相同的数据,还会引发二义性的问题。
class Person
{
public:
string _name; // 姓名
};
class Student : public Person
{
protected:
int _num; //学号
};
class Teacher : public Person
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
void Test()
{
// 这样会有二义性无法明确知道访问的是哪一个
Assistant a;
a._name = "peter";
// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
a.Student::_name = "xxx";
a.Teacher::_name = "yyy";
}
如何解决数据冗余和二义性的问题?
虚拟继承(virtual)可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。
(在继承相同类的两个父类中加 virtaul )
eg.
class Person
{
public:
string _name; // 姓名
};
class Student : virtual public Person
{
protected:
int _num; //学号
};
class Teacher : virtual public Person
{
protected:
int _id; // 职工编号
};
class Assistant : public Student, public Teacher
{
protected:
string _majorCourse; // 主修课程
};
void Test()
{
Assistant a;
a._name = "peter";
}
虚拟继承原理:把两个父类中共同的数据合成一个,偏移量便于找数据位置。
继承和组合
public继承是一种is-a的关系。也就是说每个派生类对象都是一个基类对象。
组合是一种has-a的关系。假设B组合了A,每个B对象中都有一个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; }
};
// Tire和Car构成has-a的关系
class Tire {
protected:
string _brand = "Michelin"; // 品牌
size_t _size = 17;
// 尺寸
};
class Car {
protected:
string _colour = "白色";
// 颜色
string _num = "陕ABIT00";
// 车牌号
Tire _t;
// 轮胎
};
public继承 派生类可以访问基类的public protected
组合 因为对象在类外只能访问类中public。实际尽量多去用组合。组合的耦合度低,代码维护性好。