赞
踩
目录:
继承的定义
基类和派生类对象赋值转换
继承中的作用域
派生类的默认成员函数
继承与友元
继承与静态成员变量
复杂的菱形继承及菱形虚拟继承
继承和组合
继承的总结和反思
首先我们来看一个现象:假如在学校中有学生、教师、领导三个群体,他们都有着共同的姓名、性别、学工号信息,每个群体又分别有自己的专属信息:学生专业信息、教师任课信息、领导部门信息等;在管理每个群体信息的时候,如果给每个群体都去创建一个类,每个类中又分别定义对应的成员变量会很麻烦;那有没有什么办法解决呢?C++继承很好的解决了这个问题。
在person类中,有着student、teacher、leader共同拥有的姓名、性别、学工号信息,然后让它们分别去继承person类;此时person类称为父类或基类,student类、teacher类、leader类称为子类或派生类;子类可以继承父类的成员信息,并且可以在保持原有类特性的基础上进行扩展,增加功能。
#include <iostream> #include <string> using namespace std; class Person { public: void Print() { cout << _name << " " << _num << endl; } protected: string _name = "胖胖"; //姓名 int _num = 18060210; //学号 }; class Student : public Person { public: string _major = "软件工程"; //专业 }; int main() { Student s; s.Print(); return 0; }
继承关系和访问限定符
继承基类成员访问方式的变化
总结:
Student s;
// 1.子类对象可以赋值给父类对象/指针/引用
Person p1 = s;
Person* p2 = &s;
Person& p3 = s;
// 2.基类对象不能赋值给派生类对象
//s = p1;
// 3.基类的指针可以通过强制类型转换赋值给派生类的指针
Student* sp = (Student*)p2; //需要强制类型转换
sp->_major = "计算机科学与技术";
如下代码:Student的_num和Person的_num构成隐藏关系,可以看出这样代码虽然能跑,但是非常容易混淆
#include <iostream> #include <string> using namespace std; class Person { protected: string _name = "小李子"; // 姓名 int _num = 111; // 身份证号 }; class Student : public Person { public: void Print() { cout << " 姓名:" << _name << endl; cout << " 身份证号:" << Person::_num << endl; cout << " 学号:" << _num << endl; } protected: int _num = 999; // 学号 }; void Test() { Student s1; s1.Print(); }; int main() { Test(); return 0; }
如下代码:
B中的fun和A中的fun不是构成重载,因为不是在同一作用域
B中的fun和A中的fun构成隐藏,成员函数满足函数名相同就构成隐藏
#include <iostream> #include <string> using namespace std; class A { public: void fun() { cout << "func()" << endl; } }; class B : public A { public: void fun(int i) { A::fun(); cout << "func(int i)->" << i << endl; } }; void Test() { B b; b.fun(10); }; int main() { Test(); return 0; }
#include <iostream> #include <string> using namespace std; 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) { 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; //学号 }; void Test() { Student s1("jack", 18); Student s2(s1); Student s3("rose", 17); s1 = s3; } int main() { Test(); return 0; }
小问题:实现一个不能被继承的类
C++98:
把基类的构造函数私有化,派生类中调不到基类的构造函数,则无法继承。但是会导致一个问题:基类无法创建对象。则需要写一个获取对象的函数(通过拷贝给临时对象且返回临时对象)来完成基类对象的创建。可是该函数需要基类的对象来调用,这时候需要把该函数设置为静态成员函数,则在类外便可以调用。
class A
{
public:
static A GetA()
{
return A();
}
private:
A()
{}
};
C++11:
给出了新的关键字final禁止继承。
class A final
{
public:
A()
{}
};
友元关系不能继承,也就是说基类友元不能访问子类私有和保护成员
#include <iostream> #include <string> using namespace std; class Student; class Person { public: friend void Display(const Person& p, const Student& s); protected: string _name; //姓名 }; class Student : public Person { protected: int _stuNum; //学号 }; void Display(const Person& p, const Student& s) { cout << p._name << endl; cout << s._stuNum << endl; } int main() { Person p; Student s; Display(p, s); return 0; }
子类可以继承基类的静态成员变量,但整个继承体系里面只有一个这样的成员(即静态成员变量的地址不会改变)。
#include <iostream> #include <string> using namespace std; class Person { public: Person() { ++_count; } protected: string _name; //姓名 public: static int _count; //统计人的个数 }; int Person::_count = 0; class Student : public Person { protected: int _stuNum; //学号 }; class Graduate : public Student { protected: string _seminarCourse; //研究科目 }; void TestPerson() { Student s1; Student s2; Student s3; Graduate s4; cout << " 人数 :" << Person::_count << endl; Student::_count = 0; cout << " 人数 :" << Person::_count << endl; } int main() { TestPerson(); return 0; }
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Assistant
的对象中Person成员会有两份。
解决二义性问题:
#include <iostream> #include <string> using namespace std; 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"; }
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"; }
虚拟继承解决数据冗余和二义性的原理
首先我们来看一段菱形继承的代码:
#include <iostream> #include <string> using namespace std; 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; cout << sizeof(d) << endl; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5; return 0; }
可以看到在没有用虚拟继承的时候,对象d有20个字节。
再借助内存窗口观察对象成员的模型:
虽然显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决,在B和C中都分别存储着_a的值。
我们再来看菱形虚拟继承:
#include <iostream> #include <string> using namespace std; 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; cout << sizeof(d) << endl; d.B::_a = 1; d.C::_a = 2; d._b = 3; d._c = 4; d._d = 5; d._a = 6; return 0; }
可以发现在使用了虚拟继承后,对象d有24个字节
再借助内存窗口观察对象成员的模型:
这里可以分析出D对象中将A放到的了对象组成的最下面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?
这里是通过了B和C的两个指针,指向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的是偏移量。通过偏移量可以找到下面的A。
那么为什么D中的B和C部分要去找属于自己的A?
当下面的赋值发生时,d必须要去找出B/C成员中的A才能赋值过去。
D d;
B b = d;
C c = d;
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。