一、继承的定义
class Person
{
public:void Print(){cout << "name:" << _name << endl;cout << "age:" << _age << endl;}
protected:string _name = "peter"; // 姓名int _age = 18; // 年龄
};class Student : public Person
{
protected:int _stuid; // 学号
};
以上就是一段继承的演示代码,定义了一个person类,student类继承了person类。
继承的格式:
继承关系和访问限定符:
继承基类成员访问方式的变化:
总结:
1、基类的private成员在派生类中无论以什么方式继承都不可见(私有成员被继承到了派生类,但是语法上限制派生类对象不管在类里面还是类外面都不能去访问它)。
2、基类private成员在派生类中是不能被访问,如果基类成员不想在类外直接被访问,但需要在
派生类中能访问,就定义为protected。
3、使用关键字class时默认的继承方式是private,使用struct时默认的继承方式是public,不过
最好显示的写出继承方式。
4、我们常用的也就是红色框住的这两个。
二、基类和派生类对象赋值转换
派生类对象 可以赋值给 基类的对象 / 基类的指针 / 基类的引用。这里有个形象的说法叫切片或者切割。基类对象不能赋值给派生类对象。
基类的指针或者引用可以通过强制类型转换赋值给派生类的指针或者引用。但是必须是基类
的指针是指向派生类对象时才是安全的。
class Person
{
protected :string _name; // 姓名string _sex; // 性别int _age; // 年龄
};class Student : public Person
{
public :int _No ; // 学号
};void Test ()
{Student sobj ;// 1.子类对象可以赋值给父类对象/指针/引用Person pobj = sobj ;Person* pp = &sobj;Person& rp = sobj;//2.基类对象不能赋值给派生类对象sobj = pobj;// 3.基类的指针可以通过强制类型转换赋值给派生类的指针pp = &sobjStudent* ps1 = (Student*)pp; // 这种情况转换时可以的。ps1->_No = 10;pp = &pobj;Student* ps2 = (Student*)pp; // 这种情况转换时虽然可以,但是会存在越界访问的问题ps2->_No = 10;
}
三、继承中的作用域
1、在继承体系中基类和派生类都有独立的作用域;
2、如果子类和父类有同名的成员(如果是函数,只需要函数名相同就构成了隐藏),子类的成员隐藏了父类的成员,在子类中可以使用基类::基类成员显示访问;
3、要注意重载和隐藏的区别:重载是在用一个作用域内,隐藏是在父子作用域内;
四、派生类的默认成员函数
1、派生类的构造函数必须调用基类的构造函数初始化基类的那一部分成员。如果基类没有默认
的构造函数,则必须在派生类构造函数的初始化列表阶段显示调用。
2、派生类的拷贝构造函数必须调用基类的拷贝构造完成基类的拷贝初始化。
3、派生类的operator=必须要调用基类的operator=完成基类的复制。
4、派生类的析构函数会在被调用完成后自动调用基类的析构函数清理基类成员。因为这样才能
保证派生类对象先清理派生类成员再清理基类成员的顺序。
5、派生类对象初始化先调用基类构造再调派生类构造。
6、派生类对象析构清理先调用派生类析构再调基类的析构。
7、因为后续一些场景析构函数需要构成重写,重写的条件之一是函数名相同那么编译器会对析构函数名进行特殊处理,处理成destrutor(),所以父类析构函数不加virtual的情况下,子类析构函数和父类析构函数构成隐藏关系。
class Person
{
public:Person(const char* name)//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 = "zhangsan", int num = 1)//Person(name)//若父类没有默认构造时,就要显示调用,类似于创建匿名对象:Person(name),_num(num){}Student(const Student& stu):Person(stu),_num(stu._num){}Student& operator=(const Student& stu){Person::operator=(stu);_num = stu._num;return *this;}~Student(){cout << "~Student()" << endl;//先调用子类的析构,再调用父类的析构}protected:int _num;//学号
};int main()
{Student s1;Student s2(s1);Student s3("lisi", 10);s1 = s3;return 0;
}
运行结果:
五、继承与友元
友元关系不能被继承,基类友元不能访问子类私有和保护成员。
class Student;
class Person
{
public:friend void Display(const Person& p, const Student s);protected:string _name = "klay"; // 姓名
};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;
}
六、继承与静态成员
基类定义了static静态成员,则整个继承体系里面只有一个这样的成员。无论派生出多少个子类,都只有一个static成员实例。
七、复杂的菱形继承及菱形虚拟继承
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是多继承的一种特殊情况。。
菱形继承的问题:从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性问题。
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";
}
int main()
{Test();return 0;
}
对象模型:
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。如上面的继承关系,在Student和Teacher的继承Person时使用虚拟继承,即可解决问题。需要注意的是,虚拟继承不要在其他地方去使用。
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; // 主修课程
};
我们来看一下菱形虚拟继承的原理,用以下代码来演示:
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;
}
菱形继承:
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;
}
菱形虚拟继承:
下图是菱形虚拟继承的内存对象成员模型:这里可以分析出D对象中将A放到的了对象组成的最下
面,这个A同时属于B和C,那么B和C如何去找到公共的A呢?这里是通过了B和C的两个指针,指
向的一张表。这两个指针叫虚基表指针,这两个表叫虚基表。虚基表中存的偏移量。通过偏移量
可以找到下面的A。
14转化成十进制是20,向下找20个字节就可以找到_a;c转换成十进制是12, 向下找12个字节就可以找到_a。
菱形虚拟继承的对象模型: