一、本篇介绍
1、继承基础介绍
2、基类与派生类对象赋值转换
3、隐藏
4、派生类的默认成员函数
5、继承与友元
6、继承与静态成员
7、菱形继承
8、继承与组合
二、继承基础
继承概念:继承属于面向对象三大特性之一,是代码复用的重要手段,它允许程序员在原有类特性的基础上进行拓展,增加功能,这样产生的新类叫做派生类,继承呈现了面向对象设计的层次结构,体现了由简单到复杂的认知过程,继承是类设计层次的复用。
继承的方式有三种:
公有继承、保护继承、私有继承,一般常用的是公有继承。
~提问~
1、父类的私有成员在子类不可见,是不是没有继承下来?
答:继承下来了,不可见的意思是在B的类内和类外都不能访问A的私有成员。
2、需要知道继承下来的不只有成员变量,还有成员函数,但访问都收到访问限定符与继承方式的限制。
3、class和struct默认的继承方式分别是什么?
答:class默认是私有继承,struct默认是公有继承。
三、基类与派生类对象赋值转换
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。反之一般的父类对象不能赋值给派生类对象。这种做法就像切割、切片一样,因此基类与派生类对象赋值转换也称作切片、切割。
~提问~
一、以下代码输出什么?
struct A { public: void Print(int x) { _a = x; cout << _a << endl; } public: int _a = 10; }; class B : public A { private: int _b; }; int main() { A a; a._a = 30; B b; b._a = 10; a = b; cout << a._a << endl; cout << b._a << endl; return 0; }
答:输出10,b对象切片给a,即把b中父类部分赋值给a。引用、指针同理。
试验一
四、隐藏
子类和父类中有相同的函数名或者变量名,子类会屏蔽父类同名成员的直接访问,这种现象称作隐藏,也叫重定义。
~提问~
一、以下代码输出什么?
struct A { public: void Print(int x) { cout << "Print(int x)" << endl; } public: int _a = 10; }; class B : public A { public: void Print() { cout << "Print()" << endl; } public: int _a = 3; }; int main() { B b; cout << b._a << endl; cout << b.A::_a << endl; b.Print(); b.A::Print(2); return 0; }
总结:成员名相同就构成隐藏,导致子类就不能直接访问父类的同名成员,如要访问需要加作用域。
五、派生类的默认成员函数
之前我们介绍过一个类有6个默认成员函数,分别为:
1、编译器默认生成无参的构造函数
2、编译器会默认生成一个浅拷贝的拷贝构造
3、编译器会默认重载一个浅拷贝的赋值构造运算符
4、编译器默认生成的析构函数
5、编译器默认重载的&操作符。//一般忽略它的存在
6、编译器默认重载的+const版本的&操作符。//一般忽略它的存在
我们一般知道前4个就行,5和6基本不怎么会去用。
对于派生类来说,前四个默认成员函数要做的事情多了一点。
1、无参构造函数:对于自定义类型调用它的默认构造函数,对于继承过来的成员变量也会调用父类的默认构造函数进行初始化。
2、浅拷贝的拷贝构造:对于自定义类型调用它的拷贝构造,对于内置类型完成浅拷贝,于继承过来的成员变量调用父类的拷贝构造完成拷贝。
3、浅拷贝的赋值构造运算符:对于自定义类型调用它的赋值拷贝,对于内置类型完成浅拷贝,于继承过来的成员变量调用父类的赋值拷贝完成拷贝。
4、析构函数:对于自定义类型调用它的析构函数,内置类型不处理,调用完毕后会自动调用父类的析构函数。
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) { if (this != &p) { _name = p._name; } cout << "Person operator=(const Person& p)" << endl; 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) { if (this != &s) { Person::operator =(s); _num = s._num; } cout << "Student& operator= (const Student& s)" << endl; return *this; } ~Student() { cout << "~Student()" << endl; } protected: int _num; //学号 }; int main() { Student s1("张三",20203292); Student s2("李四", 20207532); Student s3 = s1; s3 = s2; return 0; }
六、继承与友元
友元关系不能继承 ,也就是说基类友元不能访问子类私有和保护成员。
七、继承与静态成员
基类定义的静态成员整个继承体系共享,只有一份。
class Person { public: Person() { ++_count; } protected: string _name; // 姓名 public: static int _count; // 统计人的个数。 }; int Person::_count = 0; class Student : public Person { protected: int _stuNum; // 学号 }; class PostGraduate : public Student { protected: string _seminarCourse; // 研究科目 }; int main() { Person p1; Person p2; Person p3; Student s1; Student s2; Student s3; PostGraduate s4; PostGraduate s5; cout << Person::_count << endl; cout << Student::_count << endl; cout << PostGraduate::_count << endl; PostGraduate::_count = 10; cout << Person::_count << endl; cout << Student::_count << endl; cout << PostGraduate::_count << endl; return 0; }
八、菱形继承
单继承:
多继承:
菱形继承:
class A { public: int _a; }; class B : public A { public: int _b; }; class C : public A { public: int _c; }; class D : public B, public C { public: int _d; }; int main() { D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5; return 0; }
菱形继承带来的问题:
1、二义性
2、数据冗余
如何解决?
答:使用虚继承。
class A { public: int _a; }; class B :virtual public A { public: int _b; }; class C : virtual public A { public: int _c; }; class D : public B, public C { public: int _d; }; int main() { D d; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5; return 0; }
九、继承与组合
继承:
组合:
如果是has-a的关系就用组合,如果是is-a的关系就用继承。
如果关系不是很明确,建议用组合。