文章目录
- 一、继承的概念和定义
- 1、继承的概念
- 2、继承的定义
- 3、继承基类成员访问方式的变化
- 二、基类和派生类之间的转换
- 三、继承中的作用域
- 1、隐藏规则
- 四、派生类的默认成员函数
- 1、常见默认成员函数
- 2、实现一个不能被继承的类
- 五、继承与友元
- 六、继承与静态成员变量
- 七、多继承和菱形继承
- 1、继承模型
- 3、虚继承(关键词virtual)
- 八、继承和组合
一、继承的概念和定义
1、继承的概念
继承(inheritance)机制是⾯向对象程序设计使代码可以复⽤的最重要的⼿段,它允许我们在保持原有类特性的基础上进⾏扩展,增加⽅法(成员函数)和属性(成员变量),这样产⽣新的类,称派⽣类。继承呈现了⾯向对象程序设计的层次结构,体现了由简单到复杂的认知过程。以前我们接触的函数层次的复⽤,继承是类设计层次的复⽤。
2、继承的定义
class Person
{
public:void identity(){cout << "void identity()" << _name << endl;}
protected:string _name;string _address;string _tel;int _age = 18;
};class Student : public Person
{
public:void study(){}
protected:int _stuid;
};class Teacher : public Person
{
public:void teaching(){}
protected:string title;
};
Person类称为父类也叫基类,Teacher和Student类称为子类也叫派生类。
3、继承基类成员访问方式的变化
类成员/继承方式 | public | protected继承 | private继承 |
---|---|---|---|
基类的publice成员 | 派生类的public成员 | 派生类的protected成员 | 派生类的private成员 |
基类的protected成员 | 派生类的protected成员 | 派生类的protected成员 | 派生类的private成员 |
基类的private成员 | 在派生类不可见 | 在派生类中不可见 | 在派生类中不可见 |
二、基类和派生类之间的转换
class Person
{
protected:string _name; // 姓名string _sex; // 性别
public:int _age = 18; // 年龄
};class Student : public Person
{
public:int _No; // 学号
};int main()
{Student sobj;// 赋值兼容转换,特殊处理// 1.派生类对象可以赋值给基类的指针/引用Person* pp = &sobj;Person& rp = sobj;return 0;
}
- 赋值兼容转换:就相当于把派生类直接赋值给基类中间不会产生临时对象
三、继承中的作用域
1、隐藏规则
class Person
{
protected:string _name = "张三";//姓名int _num = 4209999;//身份证
};class Student : public Person
{
public:void Print(){cout << "姓名:" << _name << endl;cout << "学号:" << _num << endl;cout << "身份证号:" << Person::_num << endl;}
protected:int _num = 16;//学号
};
基类和派生类成员同名,优先访问派生类中的成员,如果想访问基类成员需要指定类域显示调用
四、派生类的默认成员函数
1、常见默认成员函数
- 构造函数、析构函数、拷贝构造、赋值运算符重载、取地址运算符重载(const对象和普通对象)
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;}Person& operator=(const Person& p){cout << "Person operator=(const Person& p)" << endl;if (this != &p)_name = p._name;return *this;}// destructor()~Person(){cout << "~Person()" << endl;}protected:string _name; // 姓名
};class Student : public Person
{
public:Student(int num = 18, const char* name = "张三", const char* address = "武汉"):_num(num), _address(address), Person(name)//把父类当成整体初始化{cout << "Student()" << endl;}Student(const Student& s):Person(s), _num(s._num),_address(s._address){cout << "Student(const Student& s)" << endl;}Student& operator = (const Student& s){if (this != &s){_num = s._num;_address = s._address;Person::operator=(s);cout << "Student& operator = (const Student& s)" << endl;}return *this;}~Student(){cout << "~Student()" << endl; }protected:int _num; //学号string _address;};
- 初始列表中先定义的先声明,在派生类父类是最先声明的也是最先初始化的。
- 析构后定义的先析构
2、实现一个不能被继承的类
⽅法1:基类的构造函数私有,派⽣类的构成必须调⽤基类的构造函数,但是基类的构成函数私有化以后,派⽣类看不⻅就不能调⽤了,那么派⽣类就⽆法实例化出对象。
⽅法2:C++11新增了⼀个final关键字,final修饰的基类,派⽣类就不能继承了。
class Base final//C++ 11的方法
{
public:void func5() { cout << "Base::func5" << endl; }
protected:int a = 1;
private:// C++98的方法/*Base(){}*/
};
五、继承与友元
友元关系不能继承,也就是说基类友元不能访问派⽣类私有和保护成员 。
六、继承与静态成员变量
基类定义了static静态成员,则整个继承体系⾥⾯只有⼀个这样的成员。⽆论派⽣出多少个派⽣类,都只有⼀个static成员实例。
七、多继承和菱形继承
1、继承模型
- 单继承:⼀个派⽣类只有⼀个直接基类时称这个继承关系为单继承。
- 多继承:⼀个派⽣类有两个或以上直接基类时称这个继承关系为多继承,多继承对象在内存中的模型是,先继承的基类在前⾯,后⾯继承的基类在后⾯,派⽣类成员在放到最后⾯。
- 菱形继承:菱形继承是多继承的⼀种特殊情况。菱形继承的问题,从下⾯的对象成员模型构造,可以看出菱形继承有数据冗余和⼆义性的问题,在Assistant的对象中Person成员会有两份。⽀持多继承就⼀定会有菱形继承,像Java就直接不⽀持多继承,规避掉了这⾥的问题,所以实践中我们也是不建议设计出菱形继承这样的模型的。
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; // 主修课程
};int main()
{// 编译报错:error C2385: 对“_name”的访问不明确Assistant a;//a._name = "peter";// 需要显示指定访问哪个基类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}
3、虚继承(关键词virtual)
很多⼈说C++语法复杂,其实多继承就是⼀个体现。有了多继承,就存在菱形继承,有了菱形继承就有菱形虚拟继承,底层实现就很复杂,性能也会有⼀些损失,所以最好不要设计出菱形继承。多继承可以认为是C++的缺陷之⼀,后来的⼀些编程语⾔都没有多继承,如Java。
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; // 主修课程
};int main()
{// 编译报错:error C2385: 对“_name”的访问不明确Assistant a;a._name = "peter";// 需要显示指定访问哪个基类的成员可以解决二义性问题,但是数据冗余问题无法解决a.Student::_name = "xxx";a.Teacher::_name = "yyy";return 0;
}
在存在二义性的继承前面加virtual
八、继承和组合
-
public继承是⼀种is-a的关系。也就是说每个派⽣类对象都是⼀个基类对象。
-
组合是⼀种has-a的关系。假设B组合了A,每个B对象中都有⼀个A对象。
-
继承允许你根据基类的实现来定义派⽣类的实现。这种通过⽣成派⽣类的复⽤通常被称为⽩箱复⽤(white-box reuse)。术语“⽩箱”是相对可视性⽽⾔:在继承⽅式中,基类的内部细节对派⽣类可⻅。继承⼀定程度破坏了基类的封装,基类的改变,对派⽣类有很⼤的影响。派⽣类和基类间的依赖关系很强,耦合度⾼。
-
对象组合是类继承之外的另⼀种复⽤选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接⼝。这种复⽤⻛格被称为⿊箱复⽤(black-boxreuse),因为对象的内部细节是不可⻅的。对象只以“⿊箱”的形式出现。组合类之间没有很强的依赖关系,耦合度低。优先使⽤对象组合有助于你保持每个类被封装。
-
优先使⽤组合,⽽不是继承。实际尽量多去⽤组合,组合的耦合度低,代码维护性好。不过也不太那么绝对,类之间的关系就适合继承(is-a)那就⽤继承,另外要实现多态,也必须要继承。类之间的关系既适合⽤继承(is-a)也适合组合(has-a),就⽤组合。