1. 前言
接下来的几篇博客会进入C++
继承和多态的学习,在校招笔试
和面试中这一章节考察的很多!
请同学们耐心学习!
本章重点:
本篇文章着重讲解继承的概念和定义,
父类和子类的对象赋值转换,
继承中的作用域以及子类的默认成员函数
以及继承和友元,继承和静态成员的关系
最后讲解菱形继承和虚继承概念
2. 继承的基本概念
继承,其实就是一种代码的复用手段
子类继承父类就能用父类中的变量!
举一个例子:
在师生管理系统中,有学生和老师两个
角色,学生和老师的共同信息有:姓名
性别,年龄和身高等等,然而学生又有一些
专有的信息,比如学号和所属学院
老师也有专属信息如:工号和所教学科
这样就可以将师生的共同信息提取出来:
struct Person { string name; string sex; int age; int height; }
在实现student类和teacher类时
只需要继承上面的person类即可!
class Student : public Person { protected: int _stuid; // 学号 };
class Teacher : public Person { protected: int _jobid; // 工号 };
派生类也被称为子类
基类也被称为父类
父子类成员的使用:
Student st; st._stuid=123456; st.name="张三"; st.age=20;
子类对象中课直接调用父类变量!
3. 继承关系和访问限定符
继承方式和访问限定符一样有三种:
继承的方式不同,那么子类中继承
到的父类的变量的访问权限就不同
可以用下面的表格来表示它们的关系:
我简单的总结以下几点:
- 无继承体系中,protected和private没有区别
- 在继承体系中,父类的protected成员在子类
中也是protected或保护成员 - 父类的private成员在子类是不可见的!
(继承下来了但不能使用) - 实际中使用继承时一般都用public继承
- 使用关键字class时默认的继承方式是private
使用struct时默认的继承方式是public
4. 继承中的作用域
先说以下结论:
- 继承体系中基类和子类有独立的作用域
- 子类和父类中有同名成员时,子类成员将屏蔽父类对同名成员的直接访问,这种情况叫隐藏,也叫重定义(在子类成员函数中,可以使用
基类::基类成员
显示访问) - 需要注意的是如果是成员函数的隐藏
只需要函数名相同就构成隐藏 - 实际中在继承体系里面最好不要定义同名的成员
class Person { protected : int _num = 111; // 身份证号 }; class Student : public Person { protected: int _num = 999; // 学号 };
在main函数中定义student对象
后再打印_num默认为子类中的_num
若想打印父类中的_num,需要指定类域
Student st; cout<<st._num; cout<<st.Person::_num;
有同学可能会疑惑:函数名相同的话
不应该是构成函数重载吗?是的,在同一
作用域下,函数名相同确实构成函数重载但是父子类是不同作用域,这里是构成隐藏!
5. 父子类的对象赋值转换
老样子,先说结论:
- 子类对象可以赋值给基类的
-/对象/基类的指针/基类的引用 - 基类对象不能赋值给派生类对象
注意这里能够赋值不是隐式类型转换!
6. 子类中的默认成员函数
还记得类的六个默认成员函数吗?
就是不显示写系统会自动生成的:
子类的默认成员函数有哪些特殊的行为?
下面我直接给出结论:
- 子类的构造函数必须显示调用父类的构造
去初始化父类的那部分成员(拷贝构造也是) - 子类的operator=中必须调用父类的
operator=完成父类成员赋值 - 子类的析构函数不用显示调用父类的析构
编译器会自动去调用 - 子类初始化对象时,先初始化父类的成员变量
再初始化子类的成员变量 - 子类析构清理时先调用子类的析构函数
再调用父类的析构函数(与构造反过来)
可以使用以下代码区验证此结论:
class Person { public : Person(const char* name = "peter") : _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 ; } ~Person() { cout<<"~Person()" <<endl; } protected : string _name ; // 姓名 }; class Student : public Person { public : Student(const char* name, int num) : Person(name ) , _num(num ) { cout<<"Student()" <<endl; } Student(const Student& s) : Person(s) , _num(s ._num) { cout<<"Student(const Student& s)" <<endl ; } Student& operator = (const Student& s ) { cout<<"Student& operator= (const Student& s)"<< endl; if (this != &s) { Person::operator =(s); _num = s ._num; } return *this ; } ~Student() { cout<<"~Student()" <<endl; } protected : int _num ; //学号 }; void Test () { Student s1 ("jack", 18); Student s2 (s1); Student s3 ("rose", 17); s1 = s3 ; }
7. 继承与友元,继承与静态变量
继承与友元的关系很简单一句话:
友元关系不能继承
也就是说基类友元不能访问子类私有和保护成员
继承和静态成员的关系也很简单:
基类中定义的静态成员被整个继承体系共享
整个继承体系里面只有一个这样的成员
无论派生出多少个子类
都只有一个static成员实例
8. 菱形继承和虚拟继承
在使用继承时会遇见以下情况:
类B继承了类A,类C也继承了类A
然而类D继承了类B和C
此时会有一个问题,类D的实例化对象中
有类B和类C,然而B类和C类都有A类
所以说D类对象中的A类成员就重复了!
class A { int _a = 1; }; class B :public A { int _b = 2; }; class C :public A { int _c = 3; }; class D :public B, A { int _d = 4; };
我们通过内存窗口观察一下:
D对象中有两个_a,一个在B类一个在C类
这就造成了数据冗余,于是可以使用虚拟继承
来解决这一问题:
虚拟继承:在继承前加上virtual关键字
class A { int _a = 1; }; class B :virtual public A { int _b = 2; }; class C :virtual public A { int _c = 3; }; class D :public B, A { int _d = 4; };
注意,只用腰部的类加上virtual即可!
virtual这一关键字在多态还有大用处!
9. 总结以及拓展
继承是多态的基础,而笔试面试的时候
继承和多态是考察的很多的,希望同学们
把基础打扎实.当然关于继承的内容其实不止
这些,这些只是最重要的内容,关于继承问题
我们将在下一章节:多态时再展开叙述
对于is-a和has-a的拓展阅读:
🔎 下期预告:C++继多态 🔍